Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions coworker/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,14 +445,21 @@ def install_persona(body: dict) -> dict[str, Any]:
"error": "gallery requires cloud sign-in (or the cloud is unreachable)",
}
markdown = manifest.get("manifest_markdown", "")
digest = "sha256:" + hashlib.sha256(markdown.encode()).hexdigest()
digest = (
"sha256:" + hashlib.sha256(markdown.encode("utf-8")).hexdigest()
)
if (
manifest.get("manifest_hash")
and manifest["manifest_hash"] != digest
):
return {"ok": False, "error": "manifest hash mismatch"}
with tempfile.TemporaryDirectory() as td:
(Path(td) / f"{slug}.md").write_text(markdown)
# UTF-8 explicitly: the hash above and `load_manifest_file` both
# treat this markdown as UTF-8, so the file between them must match.
# Defaulting to the locale encoding breaks on Windows (cp1252),
# where CJK/emoji fail to encode and an em-dash writes as a byte the
# UTF-8 read then rejects.
(Path(td) / f"{slug}.md").write_text(markdown, encoding="utf-8")
summaries = reg.install_from_dir(td)
cloud.gallery_install_event(manager.secrets, load_config(), slug)
else:
Expand Down
30 changes: 30 additions & 0 deletions tests/test_cloud_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,36 @@ def test_gallery_install_runs_consent_flow(client, monkeypatch):
assert events == ["sales"] # install event fired


NON_ASCII_MANIFEST = """---
id: sales
name: Sales Coworker — 销售
icon: chart
tagline: It’s a “quality” pitch — 你好
family: knowledge
workspace: deliverable
tools: [files, search, todo]
description: d
---
You are the Sales Coworker. \U0001f680"""


def test_gallery_install_handles_non_ascii_manifest(client, monkeypatch):
"""Gallery copy carries em-dashes, curly quotes, CJK and emoji.

The install path hashes the markdown as UTF-8 and `personas/manifest.py` reads it
back as UTF-8, so the temp file in between must be written as UTF-8 too. Writing it
with the locale encoding works on Linux/macOS but not on Windows, where the default
is cp1252: CJK and emoji fail to encode outright, while an em-dash silently writes
as a single cp1252 byte that the UTF-8 read then rejects.
"""
_stub_gallery(monkeypatch, markdown=NON_ASCII_MANIFEST)
body = client.post("/v1/personas/install", json={"gallery_slug": "sales"}).json()
assert body["ok"], body
installed = {p["id"]: p for p in body["personas"]}
assert installed["sales"]["name"] == "Sales Coworker — 销售"
assert installed["sales"]["tagline"] == "It’s a “quality” pitch — 你好"


def test_gallery_install_rejects_hash_mismatch(client, monkeypatch):
_stub_gallery(monkeypatch, hash_ok=False)
body = client.post("/v1/personas/install", json={"gallery_slug": "sales"}).json()
Expand Down