diff --git a/backend/domain/models.py b/backend/domain/models.py index 7464840..c729484 100644 --- a/backend/domain/models.py +++ b/backend/domain/models.py @@ -115,7 +115,7 @@ class WorkspaceCreate(BaseModel): owner_account_id: str = Field(default="", max_length=128) tenant_id: str = Field(default="", max_length=128) team_id: str = Field(default="", max_length=128) - model_policy: Literal["all", "routes_only"] = "routes_only" + model_policy: Literal["all", "routes_only"] = "all" category: Literal["employee", "department", "dedicated"] = "employee" template_id: str = Field(default="", max_length=128) diff --git a/backend/infra/workspaces.py b/backend/infra/workspaces.py index 10eb729..649923c 100644 --- a/backend/infra/workspaces.py +++ b/backend/infra/workspaces.py @@ -132,7 +132,7 @@ def create_workspace( name: str, tenant_id: str = "", team_id: str = "", - model_policy: str = "routes_only", + model_policy: str = "all", category: str = "employee", template_id: str = "", ) -> dict[str, Any]: @@ -204,6 +204,10 @@ def create_workspace( "开发目录。Agent 在此目录下创建 MCP/Skill 等代码文件。\n", encoding="utf-8", ) + if workspace: + from infra import hosted_workspace_engines + + hosted_workspace_engines.register_workspace_engine(workspace) return workspace @@ -356,6 +360,10 @@ def update_workspace( (workspace_id, new_owner), ) workspace = get_workspace(workspace_id) + if workspace is not None: + from infra import hosted_workspace_engines + + hosted_workspace_engines.sync_workspace_engine(workspace) return workspace diff --git a/backend/main.py b/backend/main.py index 4e29793..060cc4c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -230,8 +230,16 @@ async def _bcast(data: dict) -> None: except asyncio.CancelledError: pass + from infra import hosted_workspace_engines from services.hosted_dispatch_worker import hosted_dispatch_loop + try: + synced = hosted_workspace_engines.sync_all_active_workspaces() + if synced: + logger.info("[hosted-dispatch] synced %d active workspace fleet engines", synced) + except Exception as exc: + logger.warning("[hosted-dispatch] workspace fleet sync failed: %s", exc) + _timeout_task = asyncio.create_task(_timeout_loop()) _checkpoint_task = asyncio.create_task(_checkpoint_loop()) _chronicle_task = asyncio.create_task(_chronicle_loop()) diff --git a/backend/tests/test_coding_agent.py b/backend/tests/test_coding_agent.py index 3642cd9..f391c1f 100644 --- a/backend/tests/test_coding_agent.py +++ b/backend/tests/test_coding_agent.py @@ -92,6 +92,7 @@ def test_workspace_owner_isolation_and_path_guard(self) -> None: workspaces.resolve_workspace_path(workspace, "../escape.txt") def test_workspace_profile_crud_and_run_injection(self) -> None: + from infra import workspaces from services import claude_code_runner client = self._client() @@ -142,8 +143,9 @@ def test_workspace_profile_crud_and_run_injection(self) -> None: updated = asyncio.run(claude_code_runner.run_claude_agent(run_id)) self.assertEqual(updated["status"], "succeeded") - context = Path(workspace["root_path"]) / ".evotown" / "AGENT_CONTEXT.md" - profile_md = Path(workspace["root_path"]) / ".evotown" / "AGENT_PROFILE.md" + ws_root = workspaces.resolve_workspace_path(workspace) + context = ws_root / ".evotown" / "AGENT_CONTEXT.md" + profile_md = ws_root / ".evotown" / "AGENT_PROFILE.md" self.assertTrue(context.is_file()) self.assertTrue(profile_md.is_file()) text = context.read_text(encoding="utf-8") @@ -152,6 +154,8 @@ def test_workspace_profile_crud_and_run_injection(self) -> None: self.assertIn("conventional commits", text) def test_workspace_file_index_lists_relative_paths_only(self) -> None: + from infra import workspaces + client = self._client() _alice, alice_key = self._account_key("Alice") create_ws = client.post( @@ -161,7 +165,7 @@ def test_workspace_file_index_lists_relative_paths_only(self) -> None: ) workspace = create_ws.json()["workspace"] ws_id = workspace["workspace_id"] - root = Path(workspace["root_path"]) + root = workspaces.resolve_workspace_path(workspace) (root / "notes.md").write_text("# hello", encoding="utf-8") (root / ".evotown" / "hidden.json").write_text("{}", encoding="utf-8") @@ -213,9 +217,10 @@ def test_create_run_and_runner_writes_shared_context(self) -> None: stored_workspace = workspaces.get_workspace(workspace["workspace_id"]) assert stored_workspace is not None - context_path = Path(stored_workspace["root_path"]) / ".evotown" / "AGENT_CONTEXT.md" - skills_path = Path(stored_workspace["root_path"]) / ".evotown" / "skills_manifest.json" - knowledge_path = Path(stored_workspace["root_path"]) / ".evotown" / "knowledge_context.json" + ws_root = workspaces.resolve_workspace_path(stored_workspace) + context_path = ws_root / ".evotown" / "AGENT_CONTEXT.md" + skills_path = ws_root / ".evotown" / "skills_manifest.json" + knowledge_path = ws_root / ".evotown" / "knowledge_context.json" self.assertTrue(context_path.is_file()) self.assertTrue(skills_path.is_file()) self.assertTrue(knowledge_path.is_file()) @@ -282,7 +287,7 @@ def test_runner_injects_selected_skills_and_mcp(self) -> None: stored_workspace = workspaces.get_workspace(workspace["workspace_id"]) assert stored_workspace is not None - root = Path(stored_workspace["root_path"]) + root = workspaces.resolve_workspace_path(stored_workspace) self.assertTrue((root / ".evotown" / "mcp_context.json").is_file()) self.assertTrue((root / ".evotown" / "skills" / "http-request" / "SKILL.md").is_file()) mcp_payload = json.loads((root / ".evotown" / "mcp_context.json").read_text(encoding="utf-8"))