Initial commit

This commit is contained in:
LZH-YS1998
2026-07-01 17:56:31 +08:00
commit d78931979d
731 changed files with 311088 additions and 0 deletions
+262
View File
@@ -0,0 +1,262 @@
"""OPC Market service."""
from __future__ import annotations
from pathlib import Path
from typing import Any
from .context import OfficeServiceContext
from .models import ServiceError, ServiceEvent, ServiceResult
class MarketService:
def __init__(self, context: OfficeServiceContext) -> None:
self.context = context
def _ensure_custom_org_editable(self) -> None:
if not self.context.is_custom_org_editable():
raise ServiceError(
"org_read_only",
"Corporate organization is read-only. Select or create a saved custom org before editing.",
)
async def browse(self) -> ServiceResult:
from opc.market.architecture_registry import get_all_presets
return ServiceResult({"presets": [
item.to_display_card() if hasattr(item, "to_display_card") else self._preset_payload(item)
for item in get_all_presets()
]})
async def preview(self, preset_id: str) -> ServiceResult:
from opc.market.architecture_registry import get_preset
preset = get_preset(preset_id)
if not preset:
raise ServiceError("preset_not_found", "Preset not found", {"preset_id": preset_id})
return ServiceResult(preset.to_detail() if hasattr(preset, "to_detail") else {"preset": self._preset_payload(preset)})
async def apply_preset(self, *, preset_id: str, strategy: str = "overwrite") -> ServiceResult:
from opc.market.architecture_registry import apply_architecture_preset_to_config, get_preset
self._ensure_custom_org_editable()
preset = get_preset(preset_id)
if not preset:
raise ServiceError("preset_not_found", f"Preset '{preset_id}' not found", {"preset_id": preset_id})
if strategy not in {"namespace", "overwrite"}:
raise ServiceError("invalid_strategy", "Strategy must be namespace or overwrite")
async with self.context.config_lock:
info = apply_architecture_preset_to_config(self.context.engine.config, preset_id, strategy=strategy, clear_existing=True)
role_ids = list(info.role_ids)
work_item_template_ids = list(info.work_item_template_ids or info.template_ids)
employee_ids = self._ensure_default_employees(role_ids)
self._persist_config()
events = await self._reload_custom_agents_for_roles(role_ids)
events.extend(await self._org_events())
return ServiceResult({
"ok": True,
"action": "market_preset_applied",
"package_id": preset_id,
"name": getattr(preset, "name", preset_id),
"roles": len(role_ids),
"work_item_templates": len(work_item_template_ids),
"employees": len(employee_ids),
}, events)
async def list_installed(self) -> ServiceResult:
packages = []
for package in list(getattr(self.context.engine.config.org, "installed_packages", []) or []):
if hasattr(package, "model_dump"):
packages.append(package.model_dump())
elif isinstance(package, dict):
packages.append(package)
return ServiceResult({"packages": packages})
async def export(self, *, package_id: str, name: str, description: str = "", version: str = "1.0.0", output_dir: str = ".") -> ServiceResult:
from opc.market import PackageExporter
if not package_id or not name:
raise ServiceError("missing_package_fields", "package_id and name required")
exporter = PackageExporter(self.context.engine.config, self.context.opc_home)
package = exporter.export_current(package_id=package_id, name=name, description=description, version=version)
out_path = exporter.write_to_path(package, Path(output_dir or self.context.opc_home / "exports"))
return ServiceResult({
"ok": True,
"action": "market_exported",
"path": str(out_path),
"package_id": package_id,
"roles": len(package.roles),
"templates": len(package.talent_templates),
})
async def install(self, *, path: str, strategy: str = "namespace") -> ServiceResult:
from opc.market import PackageLoader, SandboxChecker
self._ensure_custom_org_editable()
if not path:
raise ServiceError("missing_path", "path required")
loader = PackageLoader(self.context.engine.config, self.context.opc_home)
package = loader.load_from_path(Path(path))
report = SandboxChecker().validate(package)
if not report.passed:
raise ServiceError("package_security_failed", "Security check failed", {
"sandbox_errors": list(report.errors),
"sandbox_warnings": list(report.warnings),
})
async with self.context.config_lock:
info = loader.install(package, strategy=strategy)
employee_ids = self._ensure_default_employees(list(info.role_ids))
self._persist_config()
events = await self._reload_custom_agents_for_roles(list(info.role_ids))
events.extend(await self._org_events())
return ServiceResult({
"ok": True,
"action": "market_installed",
"package_id": info.package_id,
"name": info.name,
"roles": len(info.role_ids),
"templates": len(info.template_ids),
"employees": len(employee_ids),
"warnings": list(report.warnings),
}, events)
async def uninstall(self, package_id: str) -> ServiceResult:
from opc.market import PackageLoader
from opc.market.package_format import InstalledPackageInfo
self._ensure_custom_org_editable()
if not package_id:
raise ServiceError("missing_package_id", "package_id required")
removed_role_ids: set[str] = set()
for package in self.context.engine.config.org.installed_packages:
pid = package.package_id if isinstance(package, InstalledPackageInfo) else package.get("package_id", "")
if pid == package_id:
removed_role_ids = set(package.role_ids if isinstance(package, InstalledPackageInfo) else package.get("role_ids", []))
break
async with self.context.config_lock:
success = PackageLoader(self.context.engine.config, self.context.opc_home).uninstall(package_id)
if success:
org = getattr(self.context.engine, "org_engine", None)
if org:
org.reload_from_config()
await self._clean_orphaned_assignments(removed_role_ids)
self._persist_config()
if not success:
raise ServiceError("package_not_found", f"Package '{package_id}' not found", {"package_id": package_id})
events: list[ServiceEvent] = []
if removed_role_ids and self.context.agent_store is not None:
try:
agents = await self.context.agent_store.get_all()
to_remove = [agent for agent in agents if agent.get("opc_role_id") in removed_role_ids]
for agent in to_remove:
await self.context.agent_store.remove_agent(agent["agent_id"])
if to_remove:
if self.context.sync_role_map is not None:
await self.context.sync_role_map()
sync = getattr(self.context.agent_store, "sync_custom_shadow", None)
if callable(sync):
await sync()
events.append(ServiceEvent("ack", {
"ok": True,
"action": "agents_spawned",
"agents": await self.context.agent_store.get_all(),
}))
except Exception:
pass
events.extend(await self._org_events())
return ServiceResult({"ok": True, "action": "market_uninstalled", "package_id": package_id}, events)
def _persist_config(self) -> None:
org = getattr(self.context.engine, "org_engine", None)
if org:
org.reload_from_config()
if self.context.persist_runtime_config is not None:
self.context.persist_runtime_config()
else:
self.context.engine.config.save()
def _ensure_default_employees(self, role_ids: list[str]) -> list[str]:
org = getattr(self.context.engine, "org_engine", None)
if not org:
return []
employee_ids: list[str] = []
for role_id in role_ids:
try:
employee = org.ensure_default_employee_for_role(role_id, persist=False)
if employee:
employee_ids.append(employee.employee_id)
except Exception:
pass
return employee_ids
async def _reload_custom_agents_for_roles(self, role_ids: list[str]) -> list[ServiceEvent]:
if self.context.mode_state.exec_mode not in {"org", "custom"}:
return []
org = getattr(self.context.engine, "org_engine", None)
store = self.context.agent_store
if org is None or store is None:
return []
try:
db = getattr(store, "_db", None)
if db is not None:
await db.execute("DELETE FROM agents")
await db.execute("DELETE FROM custom_agents_shadow")
await db.commit()
loader = getattr(store, "_load_from_role_infos", None)
if callable(loader):
installed = set(role_ids)
roles_info = [role for role in org.list_agents() if role.role_id in installed]
if roles_info:
await loader(roles_info, "custom")
sync = getattr(store, "sync_custom_shadow", None)
if callable(sync):
await sync()
if self.context.sync_role_map is not None:
await self.context.sync_role_map()
return [ServiceEvent("ack", {"ok": True, "action": "agents_spawned", "agents": await store.get_all()})]
except Exception:
return []
async def _clean_orphaned_assignments(self, removed_role_ids: set[str]) -> None:
if not removed_role_ids:
return
store = getattr(self.context.engine, "store", None)
if not store:
return
try:
tasks = await store.get_tasks(project_id=getattr(self.context.engine, "project_id", None) or "default")
for task in tasks:
if getattr(task, "assigned_to", "") in removed_role_ids:
task.assigned_to = ""
await store.save_task(task)
except Exception:
pass
async def _org_events(self) -> list[ServiceEvent]:
from .org import OrgService
return (await OrgService(self.context).info(include_events=True)).events
@staticmethod
def _preset_payload(preset: Any) -> dict[str, Any]:
return {
"id": getattr(preset, "id", ""),
"name": getattr(preset, "name", ""),
"description": getattr(preset, "description", ""),
"category": getattr(preset, "category", ""),
"roles": len(getattr(preset, "roles", []) or []),
"templates": len(getattr(preset, "work_item_templates", []) or []),
"collaboration_pattern": getattr(preset, "collaboration_pattern", ""),
}
@staticmethod
def _installed_payload(package: Any) -> dict[str, Any]:
if hasattr(package, "package_id"):
return {
"package_id": package.package_id,
"name": package.name,
"version": package.version,
"role_ids": list(package.role_ids),
"template_ids": list(package.template_ids),
}
return dict(package or {})