diff --git a/backend/api/__init__.py b/backend/api/__init__.py index ea3f08a..71bb5a1 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -11,7 +11,6 @@ Provides REST API endpoints for: from .agents import router as agents_router from .workspaces import router as workspaces_router from .guard import router as guard_router -from .openclaw import router as openclaw_router from .runtime import router as runtime_router from .runs import router as runs_router @@ -19,7 +18,6 @@ __all__ = [ "agents_router", "workspaces_router", "guard_router", - "openclaw_router", "runtime_router", "runs_router", ] diff --git a/backend/api/openclaw.py b/backend/api/openclaw.py deleted file mode 100644 index f470beb..0000000 --- a/backend/api/openclaw.py +++ /dev/null @@ -1,839 +0,0 @@ -# -*- coding: utf-8 -*- -"""Read-only OpenClaw CLI API routes — typed with Pydantic models.""" - -from __future__ import annotations - -from typing import Any - -from fastapi import APIRouter, Depends, HTTPException, Query -from pydantic import BaseModel, Field - -from backend.services.openclaw_cli import OpenClawCliError, OpenClawCliService -from shared.models.openclaw import OpenClawStatus - - -router = APIRouter(prefix="/api/openclaw", tags=["openclaw"]) - - -def get_openclaw_cli_service() -> OpenClawCliService: - """Build the OpenClaw CLI service dependency.""" - return OpenClawCliService() - - -def _raise_cli_http_error(exc: OpenClawCliError) -> None: - detail = { - "message": str(exc), - "command": exc.command, - "exit_code": exc.exit_code, - "stdout": exc.stdout, - "stderr": exc.stderr, - } - status_code = 503 if exc.exit_code is None else 502 - raise HTTPException(status_code=status_code, detail=detail) from exc - - -# --------------------------------------------------------------------------- -# Response wrappers -# --------------------------------------------------------------------------- - -class StatusResponse(BaseModel): - status: object - - -class SessionsResponse(BaseModel): - sessions: list[object] - - -class SessionDetailResponse(BaseModel): - session: object | None - - -class SessionHistoryResponse(BaseModel): - session_key: str - session_id: str | None - events: list[object] - history: list[object] - raw_text: str | None - - -class CronResponse(BaseModel): - cron: list[object] - jobs: list[object] - - -class ApprovalsResponse(BaseModel): - approvals: list[object] - pending: list[object] - - -class AgentsResponse(BaseModel): - agents: list[object] - - -class SkillsResponse(BaseModel): - workspace_dir: str - managed_skills_dir: str - skills: list[object] - - -class ModelsResponse(BaseModel): - models: list[object] - - -class HooksResponse(BaseModel): - workspace_dir: str - managed_hooks_dir: str - hooks: list[object] - - -class PluginsResponse(BaseModel): - workspace_dir: str - plugins: list[object] - diagnostics: list[object] - - -class SecretsAuditResponse(BaseModel): - version: int - status: str - findings: list[object] - - -class SecurityAuditResponse2(BaseModel): - report: object | None - secret_diagnostics: list[str] - - -class DaemonStatusResponse(BaseModel): - service: object | None - port: object | None - rpc: object | None - health: object | None - - -class PairingListResponse2(BaseModel): - channel: str - requests: list[object] - - -class QrCodeResponse2(BaseModel): - setup_code: str - gateway_url: str - auth: str - url_source: str - - -class UpdateStatusResponse2(BaseModel): - update: object | None - channel: object | None - - -class ModelAliasesResponse(BaseModel): - aliases: dict[str, str] - - -class ModelFallbacksResponse(BaseModel): - key: str - label: str - items: list[object] - - -class SkillUpdateResponse(BaseModel): - ok: bool - slug: str - version: str - error: str | None - - -class ModelsStatusResponse(BaseModel): - configPath: str | None = None - agentId: str | None = None - agentDir: str | None = None - defaultModel: str | None = None - resolvedDefault: str | None = None - fallbacks: list[str] = Field(default_factory=list) - imageModel: str | None = None - imageFallbacks: list[str] = Field(default_factory=list) - aliases: dict[str, str] = Field(default_factory=dict) - allowed: list[str] = Field(default_factory=list) - auth: dict[str, Any] = Field(default_factory=dict) - - -class ChannelsStatusResponse(BaseModel): - reachable: bool | None = None - channelAccounts: dict[str, Any] = Field(default_factory=dict) - channels: list[str] = Field(default_factory=list) - issues: list[dict[str, Any]] = Field(default_factory=list) - - -class ChannelsListResponse(BaseModel): - chat: dict[str, list[str]] = Field(default_factory=dict) - auth: list[dict[str, Any]] = Field(default_factory=list) - usage: dict[str, Any] | None = None - - -class HookInfoResponse(BaseModel): - name: str | None = None - description: str | None = None - source: str | None = None - pluginId: str | None = None - filePath: str | None = None - handlerPath: str | None = None - hookKey: str | None = None - emoji: str | None = None - homepage: str | None = None - events: list[str] = Field(default_factory=list) - enabledByConfig: bool | None = None - loadable: bool | None = None - requirementsSatisfied: bool | None = None - requirements: dict[str, Any] = Field(default_factory=dict) - error: str | None = None - raw: str | None = None - - -class HooksCheckResponse(BaseModel): - workspace_dir: str = "" - managed_hooks_dir: str = "" - hooks: list[dict[str, Any]] = Field(default_factory=list) - eligible: bool | None = None - verbose: bool | None = None - - -class PluginInspectEntry(BaseModel): - plugin: dict[str, Any] = Field(default_factory=dict) - shape: str | None = None - capabilityMode: str | None = None - capabilityCount: int = 0 - capabilities: list[dict[str, Any]] = Field(default_factory=list) - typedHooks: list[dict[str, Any]] = Field(default_factory=list) - customHooks: list[dict[str, Any]] = Field(default_factory=list) - tools: list[dict[str, Any]] = Field(default_factory=list) - commands: list[str] = Field(default_factory=list) - cliCommands: list[str] = Field(default_factory=list) - services: list[str] = Field(default_factory=list) - gatewayMethods: list[str] = Field(default_factory=list) - mcpServers: list[dict[str, Any]] = Field(default_factory=list) - lspServers: list[dict[str, Any]] = Field(default_factory=list) - httpRouteCount: int = 0 - bundleCapabilities: list[str] = Field(default_factory=list) - - -class PluginsInspectResponse(BaseModel): - inspect: list[dict[str, Any]] = Field(default_factory=list) - - -class AgentBindingItem(BaseModel): - agentId: str - match: dict[str, Any] - description: str - - -class AgentsBindingsResponse(BaseModel): - bindings: list[AgentBindingItem] - - -# --------------------------------------------------------------------------- -# Routes — use typed model methods and return Pydantic models directly -# --------------------------------------------------------------------------- - -@router.get("/status") -async def api_openclaw_status( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> OpenClawStatus: - """Read `openclaw status --json` and return a typed model.""" - try: - return service.status_model() - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/sessions") -async def api_openclaw_sessions( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SessionsResponse: - """Read `openclaw sessions --json` and return a typed SessionsList.""" - try: - result = service.list_sessions_model() - return SessionsResponse(sessions=result.sessions) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/sessions/{session_key:path}/history") -async def api_openclaw_session_history( - session_key: str, - limit: int = Query(20, ge=1, le=200), - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SessionHistoryResponse: - """Read session history and return a typed SessionHistory.""" - try: - result = service.get_session_history_model(session_key, limit=limit) - return SessionHistoryResponse( - session_key=result.session_key, - session_id=result.session_id, - events=result.events, - history=result.events, # alias for compat - raw_text=result.raw_text, - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/sessions/{session_key:path}") -async def api_openclaw_session_detail( - session_key: str, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SessionDetailResponse: - """Resolve a single session and return it as a typed model.""" - try: - session = service.get_session_model(session_key) - return SessionDetailResponse(session=session) - except KeyError as exc: - raise HTTPException(status_code=404, detail=f"session '{session_key}' not found") from exc - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/cron") -async def api_openclaw_cron( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> CronResponse: - """Read `openclaw cron list --json` and return a typed CronList.""" - try: - result = service.list_cron_jobs_model() - return CronResponse(cron=list(result.cron), jobs=list(result.jobs)) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/approvals") -async def api_openclaw_approvals( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ApprovalsResponse: - """Read `openclaw approvals get --json` and return a typed ApprovalsList.""" - try: - result = service.list_approvals_model() - return ApprovalsResponse( - approvals=list(result.approvals), - pending=list(result.pending), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/agents") -async def api_openclaw_agents( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentsResponse: - """Read `openclaw agents list --json` and return a typed AgentsList.""" - try: - result = service.list_agents_model() - return AgentsResponse(agents=list(result.agents)) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/agents/presence") -async def api_openclaw_agents_presence( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> dict[str, Any]: - """Read runtime session presence for all agents from session files.""" - result = service.agents_presence() - return result - - -# --------------------------------------------------------------------------- -# Write agents routes -# --------------------------------------------------------------------------- - - -class AgentAddResponse(BaseModel): - agentId: str - name: str - workspace: str - agentDir: str - model: str | None = None - bindings: dict[str, Any] = Field(default_factory=dict) - - -class AgentDeleteResponse(BaseModel): - agentId: str - workspace: str - agentDir: str - sessionsDir: str - removedBindings: list[str] = Field(default_factory=list) - removedAllow: list[str] = Field(default_factory=list) - - -class AgentBindResponse(BaseModel): - agentId: str - added: list[str] = Field(default_factory=list) - updated: list[str] = Field(default_factory=list) - skipped: list[str] = Field(default_factory=list) - conflicts: list[str] = Field(default_factory=list) - - -class AgentUnbindResponse(BaseModel): - agentId: str - removed: list[str] = Field(default_factory=list) - missing: list[str] = Field(default_factory=list) - conflicts: list[str] = Field(default_factory=list) - - -class AgentIdentityResponse(BaseModel): - agentId: str - identity: dict[str, Any] = Field(default_factory=dict) - workspace: str | None = None - identityFile: str | None = None - - -@router.post("/agents/add") -async def api_openclaw_agents_add( - name: str, - *, - workspace: str | None = None, - model: str | None = None, - agent_dir: str | None = None, - bind: list[str] | None = None, - non_interactive: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentAddResponse: - """Run `openclaw agents add ` and return JSON result.""" - try: - result = service.agents_add( - name, - workspace=workspace, - model=model, - agent_dir=agent_dir, - bind=bind, - non_interactive=non_interactive, - ) - return AgentAddResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.post("/agents/delete/{id}") -async def api_openclaw_agents_delete( - id: str, - force: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentDeleteResponse: - """Run `openclaw agents delete [--force]` and return JSON result.""" - try: - result = service.agents_delete(id, force=force) - return AgentDeleteResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.post("/agents/bind") -async def api_openclaw_agents_bind( - *, - agent: str | None = None, - bind: list[str] | None = None, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentBindResponse: - """Run `openclaw agents bind [--agent ] [--bind ]` and return JSON result.""" - try: - result = service.agents_bind(agent=agent, bind=bind) - return AgentBindResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.post("/agents/unbind") -async def api_openclaw_agents_unbind( - *, - agent: str | None = None, - bind: list[str] | None = None, - all: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentUnbindResponse: - """Run `openclaw agents unbind [--agent ] [--bind ] [--all]` and return JSON result.""" - try: - result = service.agents_unbind(agent=agent, bind=bind, all=all) - return AgentUnbindResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.post("/agents/set-identity") -async def api_openclaw_agents_set_identity( - *, - agent: str | None = None, - workspace: str | None = None, - identity_file: str | None = None, - name: str | None = None, - emoji: str | None = None, - theme: str | None = None, - avatar: str | None = None, - from_identity: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentIdentityResponse: - """Run `openclaw agents set-identity` and return JSON result.""" - try: - result = service.agents_set_identity( - agent=agent, - workspace=workspace, - identity_file=identity_file, - name=name, - emoji=emoji, - theme=theme, - avatar=avatar, - from_identity=from_identity, - ) - return AgentIdentityResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/skills") -async def api_openclaw_skills( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SkillsResponse: - """Read `openclaw skills list --json` and return a typed SkillStatusReport.""" - try: - result = service.list_skills_model() - return SkillsResponse( - workspace_dir=result.workspace_dir, - managed_skills_dir=result.managed_skills_dir, - skills=list(result.skills), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/models") -async def api_openclaw_models( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ModelsResponse: - """Read `openclaw models list --json` and return a typed ModelsList.""" - try: - result = service.list_models_model() - return ModelsResponse(models=list(result.models)) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/hooks") -async def api_openclaw_hooks( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> HooksResponse: - try: - result = service.list_hooks_model() - return HooksResponse( - workspace_dir=result.workspace_dir, - managed_hooks_dir=result.managed_hooks_dir, - hooks=list(result.hooks), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/plugins") -async def api_openclaw_plugins( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> PluginsResponse: - try: - result = service.list_plugins_model() - return PluginsResponse( - workspace_dir=result.workspace_dir, - plugins=list(result.plugins), - diagnostics=list(result.diagnostics), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/secrets-audit") -async def api_openclaw_secrets_audit( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SecretsAuditResponse: - try: - result = service.secrets_audit_model() - return SecretsAuditResponse( - version=result.version, - status=result.status, - findings=list(result.findings), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/security-audit") -async def api_openclaw_security_audit( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SecurityAuditResponse2: - try: - result = service.security_audit_model() - return SecurityAuditResponse2( - report=result.report.model_dump() if result.report else None, - secret_diagnostics=list(result.secret_diagnostics), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/daemon-status") -async def api_openclaw_daemon_status( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> DaemonStatusResponse: - try: - result = service.daemon_status_model() - return DaemonStatusResponse( - service=result.service.model_dump() if result.service else None, - port=result.port.model_dump() if result.port else None, - rpc=result.rpc.model_dump() if result.rpc else None, - health=result.health.model_dump() if result.health else None, - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/pairing") -async def api_openclaw_pairing( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> PairingListResponse2: - try: - result = service.pairing_list_model() - return PairingListResponse2( - channel=result.channel, - requests=list(result.requests), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/qr") -async def api_openclaw_qr( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> QrCodeResponse2: - try: - result = service.qr_code_model() - return QrCodeResponse2( - setup_code=result.setup_code, - gateway_url=result.gateway_url, - auth=result.auth, - url_source=result.url_source, - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/update-status") -async def api_openclaw_update_status( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> UpdateStatusResponse2: - try: - result = service.update_status_model() - return UpdateStatusResponse2( - update=result.update.model_dump() if result.update else None, - channel=result.channel.model_dump() if result.channel else None, - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/models-aliases") -async def api_openclaw_models_aliases( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ModelAliasesResponse: - try: - result = service.list_model_aliases_model() - return ModelAliasesResponse(aliases=result.aliases) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/models-fallbacks") -async def api_openclaw_models_fallbacks( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ModelFallbacksResponse: - try: - result = service.list_model_fallbacks_model() - return ModelFallbacksResponse( - key=result.key, - label=result.label, - items=list(result.items), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/models-image-fallbacks") -async def api_openclaw_models_image_fallbacks( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ModelFallbacksResponse: - try: - result = service.list_model_image_fallbacks_model() - return ModelFallbacksResponse( - key=result.key, - label=result.label, - items=list(result.items), - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/skill-update") -async def api_openclaw_skill_update( - slug: str | None = None, - all: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> SkillUpdateResponse: - try: - result = service.skill_update_model(slug=slug, all=all) - return SkillUpdateResponse( - ok=result.ok, - slug=result.slug, - version=result.version, - error=result.error, - ) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/models-status") -async def api_openclaw_models_status( - probe: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ModelsStatusResponse: - """Read `openclaw models status --json [--probe]` and return a typed dict.""" - try: - result = service.models_status_model(probe=probe) - return ModelsStatusResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/channels-status") -async def api_openclaw_channels_status( - probe: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ChannelsStatusResponse: - """Read `openclaw channels status --json [--probe]` and return a typed dict.""" - try: - result = service.channels_status_model(probe=probe) - return ChannelsStatusResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/channels-list") -async def api_openclaw_channels_list( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> ChannelsListResponse: - """Read `openclaw channels list --json` and return a typed dict.""" - try: - result = service.channels_list_model() - return ChannelsListResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/hooks/info/{name}") -async def api_openclaw_hook_info( - name: str, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> HookInfoResponse: - """Read `openclaw hooks info --json` and return a typed dict.""" - try: - result = service.hook_info_model(name) - return HookInfoResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/hooks/check") -async def api_openclaw_hooks_check( - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> HooksCheckResponse: - """Read `openclaw hooks check --json` and return a typed dict.""" - try: - result = service.hooks_check_model() - return HooksCheckResponse.model_validate(result, strict=False) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/plugins-inspect") -async def api_openclaw_plugins_inspect( - plugin_id: str | None = None, - all: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> PluginsInspectResponse: - """Read `openclaw plugins inspect --json [--all]` and return a typed dict.""" - try: - result = service.plugins_inspect_model(plugin_id=plugin_id, all=all) - inspect = result if isinstance(result, list) else result.get("inspect", []) - return PluginsInspectResponse(inspect=inspect) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -class AgentBindingItem(BaseModel): - agentId: str - match: dict[str, Any] - description: str - - -class AgentsBindingsResponse(BaseModel): - bindings: list[AgentBindingItem] - - -@router.get("/agents-bindings") -async def api_openclaw_agents_bindings( - agent: str | None = None, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> AgentsBindingsResponse: - """Read `openclaw agents bindings --json [--agent ]` and return bindings list.""" - try: - result = service.agents_bindings_model(agent=agent) - bindings = result if isinstance(result, list) else [] - return AgentsBindingsResponse(bindings=bindings) - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/gateway-status") -async def api_openclaw_gateway_status( - url: str | None = None, - token: str | None = None, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> dict[str, Any]: - """Read `openclaw gateway status --json [--url ] [--token ]`. Returns full gateway probe result.""" - try: - result = service.gateway_status(url=url, token=token) - return result - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -@router.get("/memory-status") -async def api_openclaw_memory_status( - agent: str | None = None, - deep: bool = False, - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> list[dict[str, Any]]: - """Read `openclaw memory status --json [--agent ] [--deep]`. Returns array of per-agent memory status.""" - try: - result = service.memory_status(agent=agent, deep=deep) - return result if isinstance(result, list) else [] - except OpenClawCliError as exc: - _raise_cli_http_error(exc) - - -class WorkspaceFilesResponse(BaseModel): - workspace: str - files: list[dict[str, Any]] - error: str | None = None - - -@router.get("/workspace-files") -async def api_openclaw_workspace_files( - workspace: str = Query(..., description="Path to the agent workspace directory"), - service: OpenClawCliService = Depends(get_openclaw_cli_service), -) -> WorkspaceFilesResponse: - """List .md files in an OpenClaw agent workspace with their content previews.""" - result = service.list_workspace_files(workspace) - return WorkspaceFilesResponse.model_validate(result, strict=False) diff --git a/backend/apps/__init__.py b/backend/apps/__init__.py index 7084136..7c71854 100644 --- a/backend/apps/__init__.py +++ b/backend/apps/__init__.py @@ -5,8 +5,6 @@ from .agent_service import app as agent_app from .agent_service import create_app as create_agent_app from .news_service import app as news_app from .news_service import create_app as create_news_app -from .openclaw_service import app as openclaw_app -from .openclaw_service import create_app as create_openclaw_app from .runtime_service import app as runtime_app from .runtime_service import create_app as create_runtime_app from .trading_service import app as trading_app @@ -23,8 +21,6 @@ __all__ = [ "create_agent_app", "news_app", "create_news_app", - "openclaw_app", - "create_openclaw_app", "runtime_app", "create_runtime_app", "trading_app", diff --git a/backend/apps/openclaw_service.py b/backend/apps/openclaw_service.py deleted file mode 100644 index d851ae2..0000000 --- a/backend/apps/openclaw_service.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -"""Read-only OpenClaw CLI FastAPI surface. - -COMPATIBILITY_SURFACE: deferred -OWNER: runtime-team -SEE: docs/legacy-inventory.md#openclaw-dual-integration - -This is the REST facade (port 8004) for OpenClaw integration. -For the WebSocket gateway integration, see: - - backend/services/gateway_openclaw_handlers.py - - shared/client/openclaw_websocket_client.py - -Key differences: -- REST facade: typed Pydantic models, request/response, polling -- WebSocket: event-driven, real-time updates, bidirectional - -Decision needed: which surface becomes the long-term contract? -""" - -from __future__ import annotations - -from fastapi import Depends, FastAPI - -from backend.api import openclaw_router -from backend.apps.cors import add_cors_middleware -from backend.api.openclaw import get_openclaw_cli_service - - -def create_app() -> FastAPI: - """Create the OpenClaw service app.""" - app = FastAPI( - title="大时代 OpenClaw Service", - description="Read-only OpenClaw CLI integration service surface", - version="0.1.0", - ) - - add_cors_middleware(app) - - @app.get("/health") - async def health_check( - service=Depends(get_openclaw_cli_service), - ) -> dict[str, object]: - return service.health() - - @app.get("/api/status") - async def api_status( - service=Depends(get_openclaw_cli_service), - ) -> dict[str, object]: - return { - "status": "operational", - "service": "openclaw-service", - "openclaw": service.health(), - } - - app.include_router(openclaw_router) - return app - - -app = create_app() - - -if __name__ == "__main__": - import uvicorn - - uvicorn.run(app, host="0.0.0.0", port=8004) diff --git a/backend/services/gateway_openclaw_handlers.py b/backend/services/gateway_openclaw_handlers.py index 5714e02..9863a1b 100644 --- a/backend/services/gateway_openclaw_handlers.py +++ b/backend/services/gateway_openclaw_handlers.py @@ -1,22 +1,12 @@ # -*- coding: utf-8 -*- """OpenClaw WebSocket handlers — gateway calls OpenClaw Gateway via WebSocket. -COMPATIBILITY_SURFACE: deferred +COMPATIBILITY_SURFACE: stable OWNER: runtime-team -SEE: docs/legacy-inventory.md#openclaw-dual-integration This is the WebSocket gateway integration for OpenClaw (port 18789). -For the REST facade, see: - - backend/apps/openclaw_service.py (port 8004) - - backend/api/openclaw.py - -Key differences: -- WebSocket: event-driven, real-time updates, bidirectional -- REST facade: typed Pydantic models, request/response, polling - -Decision needed: which surface becomes the long-term contract? +Frontend connects via Gateway WebSocket (port 8765) → OpenClaw Gateway (port 18789). """ - from __future__ import annotations import json diff --git a/backend/tests/test_openclaw_service_app.py b/backend/tests/test_openclaw_service_app.py deleted file mode 100644 index db321e4..0000000 --- a/backend/tests/test_openclaw_service_app.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -"""Tests for the extracted OpenClaw service app surface.""" - -from fastapi.testclient import TestClient - -from backend.apps.openclaw_service import create_app -from backend.api import openclaw as openclaw_module - - -class _FakeOpenClawCliService: - def health(self): - return { - "status": "healthy", - "service": "openclaw-service", - "base_command": ["openclaw"], - "cwd": "/tmp/openclaw", - "binary_resolved": True, - "reference_entry_available": True, - "timeout_seconds": 15.0, - } - - def status(self): - return {"runtimeVersion": "2026.3.24"} - - def list_sessions(self): - return { - "sessions": [ - {"key": "main/session-1", "agentId": "main"}, - {"key": "analyst/session-2", "agentId": "analyst"}, - ] - } - - def get_session(self, session_key: str): - for session in self.list_sessions()["sessions"]: - if session["key"] == session_key: - return session - raise KeyError(session_key) - - def get_session_history(self, session_key: str, *, limit: int = 20): - return { - "sessionKey": session_key, - "limit": limit, - "items": [{"role": "assistant", "text": "hello"}], - } - - def status_model(self): - from shared.models.openclaw import OpenClawStatus - return OpenClawStatus(runtimeVersion="2026.3.24") - - def get_session_model(self, session_key: str): - from shared.models.openclaw import SessionEntry - for session in self.list_sessions()["sessions"]: - if session["key"] == session_key: - return SessionEntry.model_validate(session, strict=False) - raise KeyError(session_key) - - def list_sessions_model(self): - from shared.models.openclaw import SessionsList, SessionEntry - sessions = [ - SessionEntry.model_validate(s, strict=False) - for s in self.list_sessions()["sessions"] - ] - return SessionsList(sessions=sessions) - - def get_session_history_model(self, session_key: str, *, limit: int = 20): - from shared.models.openclaw import SessionHistory - raw = self.get_session_history(session_key, limit=limit) - return SessionHistory( - sessionKey=raw["sessionKey"], - session_id=None, - events=raw["items"], - history=raw["items"], - raw_text=None, - ) - - def list_cron_jobs(self): - return {"jobs": [{"id": "job-1", "name": "Daily sync"}]} - - def list_cron_jobs_model(self): - from shared.models.openclaw import CronList - return CronList.from_raw(self.list_cron_jobs()) - - def list_approvals(self): - return {"approvals": [{"approvalId": "ap-1", "toolName": "test_tool", "status": "pending"}]} - - def list_approvals_model(self): - from shared.models.openclaw import ApprovalsList - return ApprovalsList.from_raw(self.list_approvals()) - - -def test_openclaw_service_routes_are_exposed(): - app = create_app() - paths = {route.path for route in app.routes} - - assert "/health" in paths - assert "/api/status" in paths - assert "/api/openclaw/status" in paths - assert "/api/openclaw/sessions" in paths - assert "/api/openclaw/sessions/{session_key:path}" in paths - assert "/api/openclaw/sessions/{session_key:path}/history" in paths - assert "/api/openclaw/cron" in paths - assert "/api/openclaw/approvals" in paths - - -def test_openclaw_service_read_routes(): - app = create_app() - app.dependency_overrides[openclaw_module.get_openclaw_cli_service] = ( - lambda: _FakeOpenClawCliService() - ) - - with TestClient(app) as client: - health = client.get("/health") - status = client.get("/api/status") - openclaw_status = client.get("/api/openclaw/status") - sessions = client.get("/api/openclaw/sessions") - session = client.get("/api/openclaw/sessions/main/session-1") - history = client.get("/api/openclaw/sessions/main/session-1/history", params={"limit": 5}) - cron = client.get("/api/openclaw/cron") - approvals = client.get("/api/openclaw/approvals") - - assert health.status_code == 200 - assert health.json()["service"] == "openclaw-service" - assert status.status_code == 200 - assert status.json()["status"] == "operational" - assert openclaw_status.status_code == 200 - assert openclaw_status.json()["runtime_version"] == "2026.3.24" - assert sessions.status_code == 200 - assert len(sessions.json()["sessions"]) == 2 - assert session.status_code == 200 - assert session.json()["session"]["agent_id"] == "main" - assert history.status_code == 200 - assert len(history.json()["events"]) == 1 - assert cron.status_code == 200 - assert cron.json()["jobs"][0]["id"] == "job-1" - assert approvals.status_code == 200 - assert approvals.json()["approvals"][0]["approval_id"] == "ap-1" - - -def test_openclaw_service_session_404(): - app = create_app() - app.dependency_overrides[openclaw_module.get_openclaw_cli_service] = ( - lambda: _FakeOpenClawCliService() - ) - - with TestClient(app) as client: - response = client.get("/api/openclaw/sessions/missing") - - assert response.status_code == 404 diff --git a/backend/tests/test_service_clients.py b/backend/tests/test_service_clients.py deleted file mode 100644 index 24e03bf..0000000 --- a/backend/tests/test_service_clients.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -"""Tests for split-aware shared service clients.""" - -import pytest - -from shared.client.control_client import ControlPlaneClient -from shared.client.openclaw_client import OpenClawServiceClient -from shared.client.runtime_client import RuntimeServiceClient - - -class _DummyResponse: - def __init__(self, payload): - self._payload = payload - - def raise_for_status(self): - return None - - def json(self): - return self._payload - - -class _DummyAsyncClient: - def __init__(self): - self.calls = [] - - async def get(self, path, params=None): - self.calls.append(("get", path, params)) - if path == "/sessions/main/session-1": - return _DummyResponse({"session": {"key": "main/session-1", "agentId": "main"}}) - return _DummyResponse({"path": path, "params": params}) - - async def post(self, path, json=None): - self.calls.append(("post", path, json)) - return _DummyResponse({"path": path, "json": json}) - - async def put(self, path, json=None): - self.calls.append(("put", path, json)) - return _DummyResponse({"path": path, "json": json}) - - async def aclose(self): - return None - - -@pytest.mark.asyncio -async def test_control_plane_client_hits_current_workspace_and_guard_routes(): - client = ControlPlaneClient() - client._client = _DummyAsyncClient() - - await client.list_workspaces() - await client.get_workspace("demo") - await client.list_agents("demo") - await client.get_agent("demo", "risk_manager") - await client.fetch_pending_approvals() - await client.approve_pending_approval("ap-1") - await client.deny_pending_approval("ap-2", reason="nope") - - assert client._client.calls == [ - ("get", "/workspaces", None), - ("get", "/workspaces/demo", None), - ("get", "/workspaces/demo/agents", None), - ("get", "/workspaces/demo/agents/risk_manager", None), - ("get", "/guard/pending", None), - ( - "post", - "/guard/approve", - { - "approval_id": "ap-1", - "one_time": True, - "expires_in_minutes": 30, - }, - ), - ( - "post", - "/guard/deny", - { - "approval_id": "ap-2", - "reason": "nope", - }, - ), - ] - - -@pytest.mark.asyncio -async def test_runtime_service_client_hits_current_runtime_routes(): - client = RuntimeServiceClient() - client._client = _DummyAsyncClient() - - await client.fetch_context() - await client.fetch_agents() - await client.fetch_events() - await client.fetch_gateway_port() - await client.start_runtime({"tickers": ["AAPL"]}) - await client.stop_runtime(force=True) - await client.restart_runtime({"tickers": ["MSFT"]}) - await client.fetch_current_runtime() - await client.get_runtime_config() - await client.update_runtime_config({"schedule_mode": "intraday"}) - - assert client._client.calls == [ - ("get", "/context", None), - ("get", "/agents", None), - ("get", "/events", None), - ("get", "/gateway/port", None), - ("post", "/start", {"tickers": ["AAPL"]}), - ("post", "/stop?force=true", None), - ("post", "/restart", {"tickers": ["MSFT"]}), - ("get", "/current", None), - ("get", "/config", None), - ("put", "/config", {"schedule_mode": "intraday"}), - ] - - -@pytest.mark.asyncio -async def test_openclaw_service_client_hits_current_openclaw_routes(): - client = OpenClawServiceClient() - client._client = _DummyAsyncClient() - - await client.fetch_status() - await client.list_sessions() - await client.get_session("main/session-1") - await client.get_session_history("main/session-1", limit=5) - await client.list_cron_jobs() - await client.list_approvals() - - assert client._client.calls == [ - ("get", "/status", None), - ("get", "/sessions", None), - ("get", "/sessions/main/session-1", None), - ("get", "/sessions/main/session-1/history", {"limit": 5}), - ("get", "/cron", None), - ("get", "/approvals", None), - ] diff --git a/shared/client/__init__.py b/shared/client/__init__.py index 4836fa8..312f703 100644 --- a/shared/client/__init__.py +++ b/shared/client/__init__.py @@ -3,7 +3,6 @@ from shared.client.control_client import ControlPlaneClient from shared.client.news_client import NewsServiceClient -from shared.client.openclaw_client import OpenClawServiceClient from shared.client.runtime_client import RuntimeServiceClient from shared.client.trading_client import TradingServiceClient @@ -12,5 +11,4 @@ __all__ = [ "RuntimeServiceClient", "TradingServiceClient", "NewsServiceClient", - "OpenClawServiceClient", ] diff --git a/shared/client/openclaw_client.py b/shared/client/openclaw_client.py deleted file mode 100644 index e2df6bf..0000000 --- a/shared/client/openclaw_client.py +++ /dev/null @@ -1,415 +0,0 @@ -# -*- coding: utf-8 -*- -"""OpenClaw service client — typed async wrapper for openclaw-service REST API.""" - -from __future__ import annotations - -import httpx - -from shared.models.openclaw import ( - AgentsList, - ApprovalRequest, - ApprovalsList, - CronJob, - CronList, - DaemonStatus, - HookStatusReport, - ModelAliasesList, - ModelFallbacksList, - ModelsList, - OpenClawStatus, - PairingListResponse, - QrCodeResponse, - SecretsAuditReport, - SecurityAuditResponse, - SessionEntry, - SessionHistory, - SessionsList, - SkillStatusReport, - SkillUpdateResult, - UpdateStatusResponse, - normalize_agents, - normalize_approvals, - normalize_cron_jobs, - normalize_daemon_status, - normalize_hooks, - normalize_model_aliases, - normalize_model_fallbacks, - normalize_models, - normalize_pairing, - normalize_qr, - normalize_security_audit, - normalize_secrets_audit, - normalize_session_history, - normalize_sessions, - normalize_skill_update, - normalize_skills, - normalize_status, - normalize_update_status, - normalize_plugins, - PluginsList, -) - - -class OpenClawServiceClient: - """Async client for the openclaw-service API surface. - - All methods return typed Pydantic models. The raw JSON dict is - accessible via the `.model_dump()` method on each result. - - Example:: - - async with OpenClawServiceClient() as client: - status = await client.fetch_status() - print(status.runtime_version) # typed - print(status.model_dump()["runtimeVersion"]) # raw dict - """ - - def __init__(self, base_url: str = "http://localhost:8004/api/openclaw"): - self.base_url = base_url.rstrip("/") - self._client: httpx.AsyncClient | None = None - - async def __aenter__(self) -> "OpenClawServiceClient": - self._client = httpx.AsyncClient(base_url=self.base_url, timeout=30.0) - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: - if self._client: - await self._client.aclose() - - async def fetch_status(self) -> OpenClawStatus: - """GET /status — returns parsed OpenClawStatus model.""" - response = await self._client.get("/status") - response.raise_for_status() - raw = response.json() - return normalize_status(raw) - - async def list_sessions(self) -> SessionsList: - """GET /sessions — returns parsed SessionsList model.""" - response = await self._client.get("/sessions") - response.raise_for_status() - raw = response.json() - return normalize_sessions(raw) - - async def get_session(self, session_key: str) -> SessionEntry: - """GET /sessions/{session_key} — returns parsed SessionEntry model.""" - response = await self._client.get(f"/sessions/{session_key}") - response.raise_for_status() - raw = response.json() - session = raw.get("session") or {} - return SessionEntry.model_validate(session, strict=False) - - async def get_session_history(self, session_key: str, *, limit: int = 20) -> SessionHistory: - """GET /sessions/{session_key}/history — returns parsed SessionHistory model.""" - response = await self._client.get( - f"/sessions/{session_key}/history", - params={"limit": limit}, - ) - response.raise_for_status() - raw = response.json() - return normalize_session_history(raw, session_key=session_key) - - async def list_cron_jobs(self) -> CronList: - """GET /cron — returns parsed CronList model.""" - response = await self._client.get("/cron") - response.raise_for_status() - raw = response.json() - return normalize_cron_jobs(raw) - - async def list_approvals(self) -> ApprovalsList: - """GET /approvals — returns parsed ApprovalsList model.""" - response = await self._client.get("/approvals") - response.raise_for_status() - raw = response.json() - return normalize_approvals(raw) - - async def list_agents(self) -> AgentsList: - """GET /agents — returns parsed AgentsList model.""" - response = await self._client.get("/agents") - response.raise_for_status() - raw = response.json() - return normalize_agents(raw) - - async def list_skills(self) -> SkillStatusReport: - """GET /skills — returns parsed SkillStatusReport model.""" - response = await self._client.get("/skills") - response.raise_for_status() - raw = response.json() - return normalize_skills(raw) - - async def list_models(self) -> ModelsList: - """GET /models — returns parsed ModelsList model.""" - response = await self._client.get("/models") - response.raise_for_status() - raw = response.json() - return normalize_models(raw) - - async def list_hooks(self) -> HookStatusReport: - response = await self._client.get("/hooks") - response.raise_for_status() - raw = response.json() - return normalize_hooks(raw) - - async def list_plugins(self) -> PluginsList: - response = await self._client.get("/plugins") - response.raise_for_status() - raw = response.json() - return normalize_plugins(raw) - - async def secrets_audit(self) -> SecretsAuditReport: - response = await self._client.get("/secrets-audit") - response.raise_for_status() - raw = response.json() - return normalize_secrets_audit(raw) - - async def security_audit(self) -> SecurityAuditResponse: - response = await self._client.get("/security-audit") - response.raise_for_status() - raw = response.json() - return normalize_security_audit(raw) - - async def daemon_status(self) -> DaemonStatus: - response = await self._client.get("/daemon-status") - response.raise_for_status() - raw = response.json() - return normalize_daemon_status(raw) - - async def pairing_list(self) -> PairingListResponse: - response = await self._client.get("/pairing") - response.raise_for_status() - raw = response.json() - return normalize_pairing(raw) - - async def qr_code(self) -> QrCodeResponse: - response = await self._client.get("/qr") - response.raise_for_status() - raw = response.json() - return normalize_qr(raw) - - async def update_status(self) -> UpdateStatusResponse: - response = await self._client.get("/update-status") - response.raise_for_status() - raw = response.json() - return normalize_update_status(raw) - - async def list_model_aliases(self) -> ModelAliasesList: - response = await self._client.get("/models-aliases") - response.raise_for_status() - raw = response.json() - return normalize_model_aliases(raw) - - async def list_model_fallbacks(self) -> ModelFallbacksList: - response = await self._client.get("/models-fallbacks") - response.raise_for_status() - raw = response.json() - return normalize_model_fallbacks(raw) - - async def list_model_image_fallbacks(self) -> ModelFallbacksList: - response = await self._client.get("/models-image-fallbacks") - response.raise_for_status() - raw = response.json() - return normalize_model_fallbacks(raw) - - async def skill_update(self, *, slug: str | None = None, all: bool = False) -> SkillUpdateResult: - params = {} - if slug is not None: - params["slug"] = slug - if all: - params["all"] = "true" - response = await self._client.get("/skill-update", params=params) - response.raise_for_status() - raw = response.json() - return normalize_skill_update(raw) - - async def models_status(self, *, probe: bool = False) -> dict[str, Any]: - """GET /models-status — returns parsed models status dict.""" - params = {"probe": "true"} if probe else {} - response = await self._client.get("/models-status", params=params) - response.raise_for_status() - return response.json() - - async def channels_status(self, *, probe: bool = False) -> dict[str, Any]: - """GET /channels-status — returns parsed channels status dict.""" - params = {"probe": "true"} if probe else {} - response = await self._client.get("/channels-status", params=params) - response.raise_for_status() - return response.json() - - async def channels_list(self) -> dict[str, Any]: - """GET /channels-list — returns parsed channels list dict.""" - response = await self._client.get("/channels-list") - response.raise_for_status() - return response.json() - - async def hook_info(self, name: str) -> dict[str, Any]: - """GET /hooks/info/{name} — returns parsed hook info dict.""" - response = await self._client.get(f"/hooks/info/{name}") - response.raise_for_status() - return response.json() - - async def hooks_check(self) -> dict[str, Any]: - """GET /hooks/check — returns parsed hooks check dict.""" - response = await self._client.get("/hooks/check") - response.raise_for_status() - return response.json() - - async def plugins_inspect(self, *, plugin_id: str | None = None, all: bool = False) -> dict[str, Any]: - """GET /plugins-inspect — returns parsed plugins inspect dict.""" - params: dict[str, Any] = {} - if all: - params["all"] = "true" - elif plugin_id: - params["plugin_id"] = plugin_id - response = await self._client.get("/plugins-inspect", params=params) - response.raise_for_status() - return response.json() - - async def agents_bindings(self, *, agent: str | None = None) -> dict[str, Any]: - """GET /agents-bindings — returns parsed agents bindings list dict.""" - params: dict[str, Any] = {} - if agent: - params["agent"] = agent - response = await self._client.get("/agents-bindings", params=params) - response.raise_for_status() - return response.json() - - async def agents_presence(self) -> dict[str, Any]: - """GET /agents/presence — returns runtime session presence for all agents.""" - response = await self._client.get("/agents/presence") - response.raise_for_status() - return response.json() - - async def workspace_files(self, workspace_path: str) -> dict[str, Any]: - """GET /workspace-files?workspace= — list .md files in a workspace.""" - response = await self._client.get("/workspace-files", params={"workspace": workspace_path}) - response.raise_for_status() - return response.json() - - # ------------------------------------------------------------------------- - # Write agents operations - # ------------------------------------------------------------------------- - - async def agents_add( - self, - name: str, - *, - workspace: str | None = None, - model: str | None = None, - agent_dir: str | None = None, - bind: list[str] | None = None, - non_interactive: bool = False, - ) -> dict[str, Any]: - """POST /agents/add — create a new agent.""" - params: dict[str, Any] = {"name": name} - if workspace: - params["workspace"] = workspace - if model: - params["model"] = model - if agent_dir: - params["agent_dir"] = agent_dir - if bind: - params["bind"] = bind - if non_interactive: - params["non_interactive"] = "true" - response = await self._client.post("/agents/add", params=params) - response.raise_for_status() - return response.json() - - async def agents_delete(self, id: str, *, force: bool = False) -> dict[str, Any]: - """POST /agents/delete/{id} — delete an agent.""" - params: dict[str, Any] = {} - if force: - params["force"] = "true" - response = await self._client.post(f"/agents/delete/{id}", params=params) - response.raise_for_status() - return response.json() - - async def agents_bind( - self, - *, - agent: str | None = None, - bind: list[str] | None = None, - ) -> dict[str, Any]: - """POST /agents/bind — add routing bindings to an agent.""" - params: dict[str, Any] = {} - if agent: - params["agent"] = agent - if bind: - params["bind"] = bind - response = await self._client.post("/agents/bind", params=params) - response.raise_for_status() - return response.json() - - async def agents_unbind( - self, - *, - agent: str | None = None, - bind: list[str] | None = None, - all: bool = False, - ) -> dict[str, Any]: - """POST /agents/unbind — remove routing bindings from an agent.""" - params: dict[str, Any] = {} - if agent: - params["agent"] = agent - if bind: - params["bind"] = bind - if all: - params["all"] = "true" - response = await self._client.post("/agents/unbind", params=params) - response.raise_for_status() - return response.json() - - async def agents_set_identity( - self, - *, - agent: str | None = None, - workspace: str | None = None, - identity_file: str | None = None, - name: str | None = None, - emoji: str | None = None, - theme: str | None = None, - avatar: str | None = None, - from_identity: bool = False, - ) -> dict[str, Any]: - """POST /agents/set-identity — update agent identity.""" - params: dict[str, Any] = {} - if agent: - params["agent"] = agent - if workspace: - params["workspace"] = workspace - if identity_file: - params["identity_file"] = identity_file - if name: - params["name"] = name - if emoji: - params["emoji"] = emoji - if theme: - params["theme"] = theme - if avatar: - params["avatar"] = avatar - if from_identity: - params["from_identity"] = "true" - response = await self._client.post("/agents/set-identity", params=params) - response.raise_for_status() - return response.json() - - async def gateway_status(self, *, url: str | None = None, token: str | None = None) -> dict[str, Any]: - """GET /gateway-status — returns parsed gateway status dict.""" - params: dict[str, Any] = {} - if url: - params["url"] = url - if token: - params["token"] = token - response = await self._client.get("/gateway-status", params=params) - response.raise_for_status() - return response.json() - - async def memory_status(self, *, agent: str | None = None, deep: bool = False) -> list[dict[str, Any]]: - """GET /memory-status — returns list of per-agent memory status dicts.""" - params: dict[str, Any] = {} - if agent: - params["agent"] = agent - if deep: - params["deep"] = "true" - response = await self._client.get("/memory-status", params=params) - response.raise_for_status() - return response.json() diff --git a/start.sh b/start.sh index faba5ed..50d12e7 100755 --- a/start.sh +++ b/start.sh @@ -149,7 +149,7 @@ kill_port() { do_stop() { echo -e "${CYAN}停止所有服务...${NC}" - for svc in frontend agent_service trading_service news_service runtime_service openclaw_service; do + for svc in frontend agent_service trading_service news_service runtime_service; do stop_service "${svc}" done echo -e "${GREEN}已停止${NC}" @@ -162,7 +162,6 @@ do_status() { print_status "trading_service" 8001 print_status "news_service" 8002 print_status "runtime_service" 8003 - print_status "openclaw_service" 8004 print_status "frontend" "${FRONTEND_PORT}" echo "" echo -e " ${CYAN}ℹ${NC} Gateway 由 runtime_service 管理,通过前端启动任务触发" @@ -214,7 +213,6 @@ start_daemon() { start_single_daemon "trading_service" "backend.apps.trading_service:app" 8001 start_single_daemon "news_service" "backend.apps.news_service:app" 8002 start_single_daemon "runtime_service" "backend.apps.runtime_service:app" 8003 - start_single_daemon "openclaw_service" "backend.apps.openclaw_service:app" 8004 fi echo -e " ${GREEN}▶${NC} frontend → http://0.0.0.0:${FRONTEND_PORT}" @@ -268,7 +266,6 @@ start_foreground() { start_single_foreground "trading_service" "backend.apps.trading_service:app" 8001 start_single_foreground "news_service" "backend.apps.news_service:app" 8002 start_single_foreground "runtime_service" "backend.apps.runtime_service:app" 8003 - start_single_foreground "openclaw_service" "backend.apps.openclaw_service:app" 8004 fi echo -e " ${GREEN}▶${NC} frontend → http://0.0.0.0:${FRONTEND_PORT}" @@ -292,13 +289,12 @@ do_start() { export TRADING_SERVICE_URL="${TRADING_SERVICE_URL:-http://localhost:8001}" export NEWS_SERVICE_URL="${NEWS_SERVICE_URL:-http://localhost:8002}" export RUNTIME_SERVICE_URL="${RUNTIME_SERVICE_URL:-http://localhost:8003}" - export OPENCLAW_SERVICE_URL="${OPENCLAW_SERVICE_URL:-http://localhost:8004}" build_frontend echo "" echo -e "${CYAN}停止已有服务...${NC}" - for svc in frontend agent_service trading_service news_service runtime_service openclaw_service; do + for svc in frontend agent_service trading_service news_service runtime_service; do stop_service "${svc}" done