mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] apps: export drops the stale inline files snapshot for workspace apps so edits don't revert on import (disk is source of truth)
This commit is contained in:
@@ -40,7 +40,8 @@ class AppExportable:
|
||||
"description": self.output.description,
|
||||
"icon": self.output.icon,
|
||||
"input_schema": self.output.input_schema,
|
||||
"files": self.output.files, # flat-app inline source; webapp apps leave this empty
|
||||
# With a workspace, disk is the source and files() ships it; carrying output.files too snaps every edited file back to its creation-time v1 on import (new files survive, edits look reverted). Only true flat apps (no workspace) still need the inline copy.
|
||||
"files": self.output.files if not self.output.workspace_id else {},
|
||||
}
|
||||
|
||||
def files(self) -> dict[str, bytes]:
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Export must not carry the stale inline output.files snapshot when a workspace
|
||||
exists. output.files is frozen at creation (v1); the agent edits land on disk
|
||||
(v2). serialize() used to ship the v1 snapshot alongside the v2 disk copy, so an
|
||||
imported app reverted every edited file (new files survived since they were never
|
||||
in the snapshot). With a workspace, disk is the single source of truth; only true
|
||||
flat apps (no workspace) still carry the inline copy."""
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.apps.outputs import workspace_io
|
||||
from backend.apps.outputs.models import Output
|
||||
from backend.apps.swarm.entities import apps as apps_mod
|
||||
from backend.apps.swarm.entities.apps import AppExportable
|
||||
|
||||
|
||||
class P_Ctx:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def p_ws_root(tmp_path, monkeypatch):
|
||||
root = tmp_path / "ws"
|
||||
root.mkdir()
|
||||
out = tmp_path / "out"
|
||||
out.mkdir()
|
||||
monkeypatch.setattr(apps_mod, "OUTPUTS_WORKSPACE_DIR", str(root))
|
||||
monkeypatch.setattr(workspace_io, "DATA_DIR", str(out))
|
||||
return root
|
||||
|
||||
|
||||
def p_make_app(ws_root, *, workspace: bool) -> str:
|
||||
wsid = uuid.uuid4().hex if workspace else None
|
||||
if wsid:
|
||||
folder = ws_root / wsid
|
||||
folder.mkdir()
|
||||
(folder / "app.py").write_text("print('v1')\n")
|
||||
o = Output(
|
||||
name="Demo", description="", icon="view_quilt",
|
||||
input_schema={"type": "object", "properties": {}, "required": []},
|
||||
files={"app.py": "print('v1')\n"},
|
||||
workspace_id=wsid, session_id=None,
|
||||
)
|
||||
workspace_io.save(o)
|
||||
return o.id
|
||||
|
||||
|
||||
def test_workspace_app_export_omits_stale_inline_files(p_ws_root):
|
||||
oid = p_make_app(p_ws_root, workspace=True)
|
||||
o = workspace_io.load_output(oid)
|
||||
folder = p_ws_root / o.workspace_id
|
||||
# agent edit -> v2 on disk: modify app.py + add new.py (output.files stays v1)
|
||||
(folder / "app.py").write_text("print('v2 EDITED')\n")
|
||||
(folder / "new.py").write_text("print('v2 NEW')\n")
|
||||
|
||||
exp = AppExportable.load(oid)
|
||||
payload = exp.serialize(P_Ctx())
|
||||
disk = exp.files()
|
||||
|
||||
# the stale v1 snapshot must NOT ride along
|
||||
assert payload["files"] == {}, "workspace app must not export the frozen output.files snapshot"
|
||||
# disk (v2) is what travels, both the edit and the new file
|
||||
assert disk["workspace/app.py"] == b"print('v2 EDITED')\n"
|
||||
assert disk["workspace/new.py"] == b"print('v2 NEW')\n"
|
||||
|
||||
|
||||
def test_flat_app_without_workspace_still_carries_inline_files(p_ws_root):
|
||||
oid = p_make_app(p_ws_root, workspace=False)
|
||||
exp = AppExportable.load(oid)
|
||||
payload = exp.serialize(P_Ctx())
|
||||
# no workspace -> inline files ARE the source, must survive export
|
||||
assert payload["files"] == {"app.py": "print('v1')\n"}
|
||||
Reference in New Issue
Block a user