Skip to content

Commit 559f6ef

Browse files
committed
fix(libs): fixed issues with expert sharing returning errors when meta.json is absent.
1 parent cccbe74 commit 559f6ef

4 files changed

Lines changed: 78 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
## [0.3.0] - 2026-03-01
11+
12+
### Added
13+
14+
- **Expert share to platform**: `POST /topics/{id}/experts/{name}/share` — share topic-level expert to `libs/experts/topiclab_shared/`; rejects built-in experts (source=default); reloads EXPERT_SPECS and invalidates libs cache
15+
- **Moderator mode share to platform**: `POST /topics/{id}/moderator-mode/share` — share custom moderator mode to `libs/moderator_modes/topiclab_shared/`; body: `mode_id`, `name?`, `description?`; creates meta.json if missing
16+
- **Topic-level moderator config**: `GET/PUT /topics/{id}/moderator-mode` supports `skill_list`, `mcp_server_ids`, `model`; persisted per topic; fallback from `config/skills/` and `config/mcp.json` when missing
17+
- **Discussion params**: `POST /topics/{id}/discussion` accepts `skill_list`, `mcp_server_ids`, `allowed_tools`; copied to topic workspace for moderator/agent use
18+
19+
### Fixed
20+
21+
- **Expert share 500**: `POST /topics/{id}/experts/{name}/share` no longer returns 500 when `libs/experts/topiclab_shared/meta.json` does not exist (first share). Creates default meta structure like moderator-mode share.
22+
23+
### Changed
24+
25+
- Topic moderator mode config: skill_list, mcp_server_ids, model persisted in workspace; discussion uses topic config when present
26+
- Expert share: creates `topiclab_shared/meta.json` with default categories when missing (aligned with moderator-mode share)
27+
828
## [0.2.0] - 2026-02-21
929

1030
### Added

app/api/topic_experts.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,22 @@ def share_expert_to_platform(topic_id: str, expert_name: str):
250250
skill_file_name = f"{expert_name}.md"
251251
(shared_dir / skill_file_name).write_text(role_file.read_text(encoding="utf-8"), encoding="utf-8")
252252

253-
# Update topiclab_shared/meta.json
253+
# Update topiclab_shared/meta.json (create if missing, like moderator_mode share)
254254
meta_path = shared_dir / "meta.json"
255-
meta = json.loads(meta_path.read_text(encoding="utf-8"))
255+
if meta_path.exists():
256+
meta = json.loads(meta_path.read_text(encoding="utf-8"))
257+
else:
258+
meta = {
259+
"common_sections": "expert_common.md",
260+
"categories": {
261+
"topiclab": {
262+
"id": "topiclab",
263+
"name": "TopicLab",
264+
"description": "User-shared experts from frontend",
265+
}
266+
},
267+
"experts": {},
268+
}
256269
meta.setdefault("experts", {})[expert_name] = {
257270
"id": expert_name,
258271
"source": "topiclab_shared",

libs/experts/topiclab_shared/meta.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,16 @@
77
"description": "User-shared experts from frontend"
88
}
99
},
10-
"experts": {}
11-
}
10+
"experts": {
11+
"test_shared_expert": {
12+
"id": "test_shared_expert",
13+
"source": "topiclab_shared",
14+
"name": "test_shared_expert",
15+
"label": "Test Shared",
16+
"description": "For share test",
17+
"category": "topiclab",
18+
"skill_file": "test_shared_expert.md",
19+
"perspective": "test_shared_expert"
20+
}
21+
}
22+
}

tests/test_api.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,42 @@ def test_moderator_mode_get_and_set(client: TestClient, isolated_workspace: Path
458458
assert reloaded["model"] == "qwen-flash"
459459

460460

461+
def test_expert_share_when_meta_json_missing(client: TestClient, isolated_workspace: Path, monkeypatch):
462+
"""Share succeeds when topiclab_shared/meta.json does not exist (first share)."""
463+
from app.core.config import get_experts_dir
464+
465+
# Use isolated experts dir with no topiclab_shared/meta.json
466+
experts_root = isolated_workspace / "libs" / "experts"
467+
experts_root.mkdir(parents=True)
468+
monkeypatch.setattr("app.api.topic_experts.get_experts_dir", lambda: experts_root)
469+
470+
topic = _create_topic(client)
471+
topic_id = topic["id"]
472+
ws_path = isolated_workspace / "topics" / topic_id
473+
(ws_path / "agents" / "first_share_expert").mkdir(parents=True)
474+
(ws_path / "agents" / "first_share_expert" / "role.md").write_text("# First Share", encoding="utf-8")
475+
(ws_path / "config").mkdir(parents=True, exist_ok=True)
476+
meta = {"experts": [{"name": "first_share_expert", "label": "First", "description": "First share test"}]}
477+
(ws_path / "config" / "experts_metadata.json").write_text(json.dumps(meta, ensure_ascii=False), encoding="utf-8")
478+
479+
resp = client.post(f"/topics/{topic_id}/experts/first_share_expert/share")
480+
assert resp.status_code == 200
481+
assert resp.json().get("expert_name") == "first_share_expert"
482+
483+
meta_path = experts_root / "topiclab_shared" / "meta.json"
484+
assert meta_path.exists()
485+
meta_data = json.loads(meta_path.read_text(encoding="utf-8"))
486+
assert "first_share_expert" in meta_data.get("experts", {})
487+
488+
461489
def test_expert_share_to_topiclab_shared(client: TestClient, isolated_workspace: Path):
462490
"""POST /topics/{id}/experts/{name}/share writes to topiclab_shared and reloads."""
463491
from app.core.config import get_experts_dir
464492

465493
topic = _create_topic(client)
466494
topic_id = topic["id"]
467495
ws_path = isolated_workspace / "topics" / topic_id
468-
(ws_path / "agents" / "test_shared_expert").mkdir(parents=True)
496+
(ws_path / "agents" / "test_shared_expert").mkdir(parents=True, exist_ok=True)
469497
(ws_path / "agents" / "test_shared_expert" / "role.md").write_text("# Test Expert\nRole content.", encoding="utf-8")
470498
(ws_path / "config").mkdir(parents=True, exist_ok=True)
471499
meta = {"experts": [{"name": "test_shared_expert", "label": "Test Shared", "description": "For share test"}]}
@@ -493,7 +521,7 @@ def test_expert_share_rejects_builtin(client: TestClient, isolated_workspace: Pa
493521
topic = _create_topic(client)
494522
topic_id = topic["id"]
495523
ws_path = isolated_workspace / "topics" / topic_id
496-
(ws_path / "agents" / "physicist").mkdir(parents=True)
524+
(ws_path / "agents" / "physicist").mkdir(parents=True, exist_ok=True)
497525
(ws_path / "agents" / "physicist" / "role.md").write_text("# Custom Physicist", encoding="utf-8")
498526
(ws_path / "config").mkdir(parents=True, exist_ok=True)
499527
meta = {"experts": [{"name": "physicist", "label": "Custom", "description": "Test"}]}

0 commit comments

Comments
 (0)