Skip to content
Merged
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
2 changes: 1 addition & 1 deletion backend/domain/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 9 additions & 1 deletion backend/infra/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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


Expand Down
8 changes: 8 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
19 changes: 12 additions & 7 deletions backend/tests/test_coding_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand All @@ -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(
Expand All @@ -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")

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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"))
Expand Down
Loading