feat(openclaw): add live ops space scene state
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m7s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
Your Name
2026-06-29 21:17:13 +08:00
parent 1b184f9150
commit 165afd24bc
8 changed files with 1056 additions and 3 deletions

View File

@@ -384,6 +384,9 @@ from src.services.observability_contract_matrix import (
from src.services.offsite_escrow_readiness_status import (
load_latest_offsite_escrow_readiness_status,
)
from src.services.openclaw_live_ops_scene_state import (
load_openclaw_live_ops_scene_state,
)
from src.services.p0_cicd_baseline_source_readiness import (
load_latest_p0_cicd_baseline_source_readiness,
)
@@ -889,6 +892,29 @@ async def get_agent_autonomous_runtime_control() -> dict[str, Any]:
) from exc
@router.get(
"/openclaw-live-ops-scene-state",
response_model=dict[str, Any],
summary="取得 OpenClaw Live Ops Space 場景狀態",
description=(
"從 AI Agent autonomous runtime control 的 public-safe trace ledger / "
"work item progress 產生 OpenClaw 持續動畫工作室 scene state。"
"此端點不讀 raw session / SQLite、不讀 secret、不暴露 Telegram 原始 payload、"
"不執行 runtime action、不寫 host/K8s。"
),
)
async def get_openclaw_live_ops_scene_state() -> dict[str, Any]:
"""Return public-safe OpenClaw live ops animation scene state."""
try:
return await load_openclaw_live_ops_scene_state()
except ValueError as exc:
logger.error("openclaw_live_ops_scene_state_invalid", error=str(exc))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="OpenClaw Live Ops Space 場景狀態無效",
) from exc
@router.get(
"/automation-backlog-snapshot",
response_model=dict[str, Any],

View File

@@ -0,0 +1,323 @@
"""OpenClaw Live Ops Space scene state.
This service maps the public-safe AI Agent autonomous runtime control readback
into a small animation contract. It never reads raw sessions, SQLite, secrets,
or runtime logs, and it never performs runtime actions.
"""
from __future__ import annotations
from datetime import UTC, datetime
from typing import Any
from src.services.ai_agent_autonomous_runtime_control import (
build_ai_agent_autonomous_runtime_control_with_live_readback,
)
_SCHEMA_VERSION = "openclaw_live_ops_scene_state_v1"
_SOURCE_SCHEMA_VERSION = "ai_agent_autonomous_runtime_control_v1"
_ZONES = [
{
"zone_id": "control",
"label": "OpenClaw Control",
"kind": "control_plane",
"position": {"x": 48, "y": 38},
},
{
"zone_id": "mcp",
"label": "MCP",
"kind": "tool_context",
"position": {"x": 18, "y": 30},
},
{
"zone_id": "rag",
"label": "RAG",
"kind": "knowledge_retrieval",
"position": {"x": 24, "y": 62},
},
{
"zone_id": "playbook",
"label": "PlayBook",
"kind": "procedure_trust",
"position": {"x": 54, "y": 70},
},
{
"zone_id": "verifier",
"label": "Verifier",
"kind": "post_apply_verification",
"position": {"x": 78, "y": 34},
},
{
"zone_id": "telegram",
"label": "Telegram Receipt",
"kind": "receipt_delivery",
"position": {"x": 82, "y": 66},
},
{
"zone_id": "deploy",
"label": "Deploy Readback",
"kind": "release_truth",
"position": {"x": 48, "y": 18},
},
]
_ZONE_BY_STAGE = {
"mcp_context": "mcp",
"service_log_evidence": "rag",
"executor_log_projection": "rag",
"playbook_trust": "playbook",
"post_apply_verifier": "verifier",
"telegram_receipt": "telegram",
"trace_ledger": "control",
"work_item_progress": "control",
"deploy_readback": "deploy",
"km_writeback": "playbook",
}
_ZONE_POSITIONS = {zone["zone_id"]: zone["position"] for zone in _ZONES}
async def load_openclaw_live_ops_scene_state() -> dict[str, Any]:
"""Build scene state from the live autonomous runtime control readback."""
runtime_control = await build_ai_agent_autonomous_runtime_control_with_live_readback()
return build_openclaw_live_ops_scene_state(runtime_control)
def build_openclaw_live_ops_scene_state(
runtime_control: dict[str, Any],
) -> dict[str, Any]:
"""Map autonomous runtime control into the OpenClaw scene contract."""
if runtime_control.get("schema_version") != _SOURCE_SCHEMA_VERSION:
raise ValueError(f"runtime_control.schema_version must be {_SOURCE_SCHEMA_VERSION}")
readback = _dict(runtime_control.get("runtime_receipt_readback"))
trace = _dict(readback.get("trace_ledger"))
progress = _dict(readback.get("work_item_progress"))
work_items = _work_items(progress)
agents = _agents(trace, work_items)
source_connected = bool(trace) or bool(work_items)
safety = _dict(trace.get("public_safety"))
payload = {
"schema_version": _SCHEMA_VERSION,
"generated_at": datetime.now(UTC).isoformat(timespec="seconds"),
"status": "live_readback_connected" if source_connected else "waiting_for_live_readback",
"experience": {
"name": "OpenClaw Live Ops Space",
"mode": "continuous_animated_operations_room",
"route": "/zh-TW/openclaw/live-ops-space",
},
"source": {
"source_endpoint": "/api/v1/agents/agent-autonomous-runtime-control",
"source_schema_version": runtime_control.get("schema_version"),
"deploy_readback_marker": _dict(runtime_control.get("program_status")).get(
"deploy_readback_marker"
),
"trace_ledger_schema_version": trace.get("schema_version"),
"work_item_progress_schema_version": progress.get("schema_version"),
"live_source_connected": source_connected,
},
"room": {
"room_id": "openclaw-live-ops",
"layout": "isometric_ops_room",
"zones": _ZONES,
"animation_loop": {
"enabled": True,
"tick_ms": 4200,
"motion_model": "idle_walk_work_wait_verify",
},
},
"agents": agents,
"work_items": work_items,
"rollups": {
"zone_count": len(_ZONES),
"agent_count": len(agents),
"work_item_count": len(work_items),
"animated_entity_count": len(agents) + len(work_items),
"completed_work_item_count": sum(
1 for item in work_items if item.get("status") == "completed"
),
"blocked_work_item_count": sum(
1 for item in work_items if item.get("status") == "blocked"
),
"source_stage_count": _int(trace.get("stage_count")),
"recorded_stage_count": _int(trace.get("recorded_stage_count")),
},
"boundaries": {
"raw_session_read_allowed": False,
"sqlite_read_allowed": False,
"secret_value_display_allowed": False,
"internal_reasoning_display_allowed": False,
"telegram_unredacted_payload_display_allowed": False,
"runtime_action_performed": False,
"host_or_k8s_write_performed": False,
"uses_public_safe_trace_ledger": safety.get("reads_raw_sessions") is not True,
},
}
_validate_scene_state(payload)
return payload
def _agents(trace: dict[str, Any], work_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
stages = [
item
for item in _list(trace.get("stages"))
if isinstance(item, dict) and str(item.get("stage_id") or "")
]
if not stages:
stages = [
{
"stage_id": "work_item_progress",
"display_name": "Work Item Progress",
"recorded": bool(work_items),
"total": len(work_items),
"recent": 0,
}
]
agents: list[dict[str, Any]] = []
for index, stage in enumerate(stages[:8]):
stage_id = str(stage.get("stage_id") or f"stage_{index}")
zone_id = _ZONE_BY_STAGE.get(stage_id, "control")
position = _position_for(zone_id, index)
state = _agent_state(stage)
agents.append(
{
"agent_id": f"openclaw-{stage_id.replace('_', '-')}",
"label": str(stage.get("display_name") or stage_id).strip(),
"zone_id": zone_id,
"state": state,
"animation": _animation_for_state(state, index),
"position": position,
"current_task": {
"stage_id": stage_id,
"recorded": stage.get("recorded") is True,
"total": _int(stage.get("total")),
"recent": _int(stage.get("recent")),
"feeds_learning": stage.get("feeds_learning") is True,
"required_for_closed_loop": (
stage.get("required_for_closed_loop") is True
),
},
}
)
return agents
def _work_items(progress: dict[str, Any]) -> list[dict[str, Any]]:
ordered = [
item
for item in _list(progress.get("ordered_items"))
if isinstance(item, dict) and str(item.get("work_item_id") or "")
]
items: list[dict[str, Any]] = []
for index, item in enumerate(ordered[:12]):
status = str(item.get("status") or "pending")
zone_id = _zone_for_work_item(item)
items.append(
{
"work_item_id": str(item.get("work_item_id") or ""),
"title": str(item.get("title") or item.get("work_item_id") or ""),
"priority": str(item.get("priority") or ""),
"status": status,
"zone_id": zone_id,
"animation": _animation_for_state(_state_from_status(status), index),
"position": _position_for(zone_id, index + 2),
"next_controlled_action": str(
item.get("next_controlled_action") or ""
),
"exit_criteria": str(item.get("exit_criteria") or ""),
}
)
return items
def _zone_for_work_item(item: dict[str, Any]) -> str:
text = " ".join(
str(item.get(key) or "")
for key in ("work_item_id", "title", "next_controlled_action", "exit_criteria")
).lower()
if "telegram" in text:
return "telegram"
if "verifier" in text or "verify" in text:
return "verifier"
if "km" in text or "knowledge" in text:
return "playbook"
if "deploy" in text:
return "deploy"
if "mcp" in text:
return "mcp"
if "rag" in text or "log" in text:
return "rag"
return "control"
def _position_for(zone_id: str, index: int) -> dict[str, int]:
base = _dict(_ZONE_POSITIONS.get(zone_id))
x = _int(base.get("x")) + ((index % 3) - 1) * 4
y = _int(base.get("y")) + ((index % 2) * 4)
return {"x": max(6, min(92, x)), "y": max(8, min(86, y))}
def _agent_state(stage: dict[str, Any]) -> str:
if stage.get("recorded") is not True:
return "waiting"
if _int(stage.get("recent")) > 0:
return "working"
if stage.get("feeds_learning") is True:
return "verified"
return "idle"
def _state_from_status(status: str) -> str:
if status == "completed":
return "verified"
if status == "blocked":
return "blocked"
if status == "in_progress":
return "working"
return "waiting"
def _animation_for_state(state: str, index: int) -> str:
if state == "verified":
return "pulse_verified"
if state == "blocked":
return "waiting_blink"
if state == "working":
return "typing_loop" if index % 2 else "walk_and_work_loop"
if state == "waiting":
return "idle_scan_loop"
return "idle_breathing_loop"
def _validate_scene_state(payload: dict[str, Any]) -> None:
if payload.get("schema_version") != _SCHEMA_VERSION:
raise ValueError(f"schema_version must be {_SCHEMA_VERSION}")
if not _list(_dict(payload.get("room")).get("zones")):
raise ValueError("room.zones must be present")
if not _list(payload.get("agents")):
raise ValueError("agents must be present")
boundaries = _dict(payload.get("boundaries"))
for key in (
"raw_session_read_allowed",
"sqlite_read_allowed",
"secret_value_display_allowed",
"internal_reasoning_display_allowed",
"telegram_unredacted_payload_display_allowed",
"runtime_action_performed",
"host_or_k8s_write_performed",
):
if boundaries.get(key) is not False:
raise ValueError(f"boundaries.{key} must remain false")
def _dict(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}
def _list(value: Any) -> list[Any]:
return value if isinstance(value, list) else []
def _int(value: Any) -> int:
try:
return int(value)
except (TypeError, ValueError):
return 0

View File

@@ -9,8 +9,8 @@ from fastapi.testclient import TestClient
from src.api.v1.agents import router
from src.services.credential_escrow_evidence_intake_readiness import (
load_latest_credential_escrow_evidence_intake_readiness,
validate_credential_escrow_evidence_refs,
validate_credential_escrow_evidence_owner_response,
validate_credential_escrow_evidence_refs,
)
ESCROW_ITEMS = [

View File

@@ -0,0 +1,144 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
import src.api.v1.agents as agents_api
from src.api.v1.agents import router
from src.services.openclaw_live_ops_scene_state import (
build_openclaw_live_ops_scene_state,
)
def test_openclaw_live_ops_scene_state_maps_runtime_control_to_animation_contract():
payload = build_openclaw_live_ops_scene_state(_runtime_control_payload())
assert payload["schema_version"] == "openclaw_live_ops_scene_state_v1"
assert payload["status"] == "live_readback_connected"
assert payload["experience"]["name"] == "OpenClaw Live Ops Space"
assert payload["room"]["layout"] == "isometric_ops_room"
assert payload["room"]["animation_loop"]["enabled"] is True
assert payload["source"]["source_endpoint"] == (
"/api/v1/agents/agent-autonomous-runtime-control"
)
assert payload["source"]["live_source_connected"] is True
assert payload["rollups"]["zone_count"] >= 6
assert payload["rollups"]["agent_count"] >= 2
assert payload["rollups"]["work_item_count"] == 3
assert payload["rollups"]["animated_entity_count"] >= 5
assert any(agent["zone_id"] == "mcp" for agent in payload["agents"])
assert any(agent["zone_id"] == "verifier" for agent in payload["agents"])
assert any(item["status"] == "blocked" for item in payload["work_items"])
assert payload["boundaries"]["raw_session_read_allowed"] is False
assert payload["boundaries"]["sqlite_read_allowed"] is False
assert payload["boundaries"]["secret_value_display_allowed"] is False
assert payload["boundaries"]["runtime_action_performed"] is False
def test_openclaw_live_ops_scene_state_endpoint_returns_public_safe_scene(
monkeypatch,
):
async def fake_scene_state():
return build_openclaw_live_ops_scene_state(_runtime_control_payload())
monkeypatch.setattr(
agents_api,
"load_openclaw_live_ops_scene_state",
fake_scene_state,
)
app = FastAPI()
app.include_router(router, prefix="/api/v1")
client = TestClient(app)
response = client.get("/api/v1/agents/openclaw-live-ops-scene-state")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "openclaw_live_ops_scene_state_v1"
assert data["room"]["animation_loop"]["enabled"] is True
assert data["rollups"]["agent_count"] >= 2
assert data["boundaries"]["telegram_unredacted_payload_display_allowed"] is False
assert data["boundaries"]["host_or_k8s_write_performed"] is False
def _runtime_control_payload() -> dict:
return {
"schema_version": "ai_agent_autonomous_runtime_control_v1",
"program_status": {
"deploy_readback_marker": (
"p2_416_d1n_autonomous_runtime_control_prod_readback_v2"
)
},
"runtime_receipt_readback": {
"trace_ledger": {
"schema_version": "ai_agent_autonomous_trace_ledger_v1",
"stage_count": 3,
"recorded_stage_count": 2,
"missing_required_stage_ids": ["telegram_receipt"],
"public_safety": {
"reads_raw_sessions": False,
"stores_secret_values": False,
"stores_unredacted_telegram_payload": False,
"stores_internal_reasoning": False,
},
"stages": [
{
"stage_id": "mcp_context",
"display_name": "MCP context",
"recorded": True,
"total": 42,
"recent": 5,
"feeds_learning": True,
"required_for_closed_loop": True,
},
{
"stage_id": "post_apply_verifier",
"display_name": "Post apply verifier",
"recorded": True,
"total": 18,
"recent": 0,
"feeds_learning": True,
"required_for_closed_loop": True,
},
{
"stage_id": "telegram_receipt",
"display_name": "Telegram receipt",
"recorded": False,
"total": 0,
"recent": 0,
"feeds_learning": False,
"required_for_closed_loop": True,
},
],
},
"work_item_progress": {
"schema_version": "ai_agent_automation_work_item_progress_v1",
"ordered_items": [
{
"work_item_id": "P0-006",
"priority": "P0",
"title": "StockPlatform freshness retry readback",
"status": "blocked",
"next_controlled_action": "wait retry then verify",
"exit_criteria": "STOCK_FRESHNESS_STATUS=ok",
},
{
"work_item_id": "P0-005",
"priority": "P0",
"title": "Credential escrow evidence refs",
"status": "in_progress",
"next_controlled_action": "validate evidence refs",
"exit_criteria": "owner_response_accepted_count=1",
},
{
"work_item_id": "P0-003",
"priority": "P0",
"title": "Gitea private inventory closeout",
"status": "completed",
"next_controlled_action": "",
"exit_criteria": "active_blocker_count=0",
},
],
},
},
}

View File

@@ -1247,7 +1247,45 @@
"statusOk": "正常",
"statusWarning": "警告",
"messageOk": "所有系統運作正常,無需處理。",
"messageWarning": "{host} 狀態異常,建議檢查相關服務。"
"messageWarning": "{host} 狀態異常,建議檢查相關服務。",
"liveOpsSpace": {
"title": "Live Ops Space",
"status": {
"loading": "Loading scene",
"ready": "Scene connected",
"degraded": "Waiting for readback"
},
"actions": {
"refresh": "Refresh"
},
"source": {
"marker": "marker",
"updated": "updated"
},
"panels": {
"rollups": "Scene metrics",
"boundaries": "Safety boundaries",
"workItems": "Work items"
},
"metrics": {
"agents": "Agents",
"workItems": "Work items",
"animated": "Animated",
"blocked": "Blocked"
},
"boundary": {
"closed": "closed",
"open": "open"
},
"states": {
"working": "working",
"verified": "verified",
"blocked": "blocked",
"waiting": "waiting",
"idle": "idle"
},
"empty": "Scene state is not available yet."
}
},
"ai": {
"title": "AI 決策引擎",

View File

@@ -1247,7 +1247,45 @@
"statusOk": "正常",
"statusWarning": "警告",
"messageOk": "所有系統運作正常,無需處理。",
"messageWarning": "{host} 狀態異常,建議檢查相關服務。"
"messageWarning": "{host} 狀態異常,建議檢查相關服務。",
"liveOpsSpace": {
"title": "OpenClaw 持續工作室",
"status": {
"loading": "讀取場景中",
"ready": "場景已連線",
"degraded": "等待讀回"
},
"actions": {
"refresh": "重新讀取"
},
"source": {
"marker": "部署 marker",
"updated": "更新"
},
"panels": {
"rollups": "場景指標",
"boundaries": "安全邊界",
"workItems": "工作項目"
},
"metrics": {
"agents": "Agent",
"workItems": "工作項",
"animated": "動畫物件",
"blocked": "阻擋"
},
"boundary": {
"closed": "關閉",
"open": "開啟"
},
"states": {
"working": "工作中",
"verified": "已驗證",
"blocked": "阻擋",
"waiting": "等待",
"idle": "待命"
},
"empty": "尚未取得場景狀態。"
}
},
"ai": {
"title": "AI 決策引擎",

View File

@@ -0,0 +1,466 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useLocale, useTranslations } from "next-intl";
import {
Activity,
AlertTriangle,
Bot,
BrainCircuit,
CheckCircle2,
MessageSquareText,
RefreshCw,
Rocket,
SearchCheck,
ShieldCheck,
Wrench,
} from "lucide-react";
import { AppLayout } from "@/components/layout";
import { getRuntimeApiBaseUrl } from "@/lib/runtime-api-base";
import { cn } from "@/lib/utils";
type SceneStatus = "loading" | "ready" | "degraded";
type SceneZone = {
zone_id: string;
label: string;
kind: string;
position: { x: number; y: number };
};
type SceneAgent = {
agent_id: string;
label: string;
zone_id: string;
state: string;
animation: string;
position: { x: number; y: number };
current_task?: {
stage_id?: string | null;
recorded?: boolean | null;
total?: number | null;
recent?: number | null;
feeds_learning?: boolean | null;
required_for_closed_loop?: boolean | null;
};
};
type SceneWorkItem = {
work_item_id: string;
title: string;
priority: string;
status: string;
zone_id: string;
animation: string;
position: { x: number; y: number };
next_controlled_action?: string | null;
exit_criteria?: string | null;
};
type SceneState = {
schema_version: "openclaw_live_ops_scene_state_v1";
generated_at: string;
status: string;
source: {
deploy_readback_marker?: string | null;
live_source_connected?: boolean | null;
};
room: {
zones: SceneZone[];
animation_loop: {
enabled: boolean;
tick_ms: number;
motion_model: string;
};
};
agents: SceneAgent[];
work_items: SceneWorkItem[];
rollups: {
zone_count: number;
agent_count: number;
work_item_count: number;
animated_entity_count: number;
completed_work_item_count: number;
blocked_work_item_count: number;
source_stage_count: number;
recorded_stage_count: number;
};
boundaries: Record<string, boolean>;
};
const API_BASE = getRuntimeApiBaseUrl();
const zoneIconByKind: Record<string, typeof Activity> = {
control_plane: BrainCircuit,
tool_context: Wrench,
knowledge_retrieval: SearchCheck,
procedure_trust: ShieldCheck,
post_apply_verification: CheckCircle2,
receipt_delivery: MessageSquareText,
release_truth: Rocket,
};
function stateLabel(t: ReturnType<typeof useTranslations>, state: string): string {
if (state === "working") return t("states.working");
if (state === "verified") return t("states.verified");
if (state === "blocked") return t("states.blocked");
if (state === "waiting") return t("states.waiting");
return t("states.idle");
}
function stateClass(state: string): string {
if (state === "working") return "border-[#4a90d9] bg-[#eef5ff] text-[#1f5b9b]";
if (state === "verified") return "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]";
if (state === "blocked") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
if (state === "waiting") return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
return "border-[#d8d3c7] bg-white text-[#5f5b52]";
}
function stateFromWorkItemStatus(status: string): string {
if (status === "completed") return "verified";
if (status === "in_progress") return "working";
if (status === "blocked") return "blocked";
return "waiting";
}
function shortValue(value?: string | null): string {
if (!value) return "--";
return value.length > 18 ? `${value.slice(0, 14)}...` : value;
}
function formatNumber(value: number | null | undefined): string {
return new Intl.NumberFormat("zh-TW").format(Number(value ?? 0));
}
export default function OpenClawLiveOpsSpacePage({
params,
}: {
params: { locale: string };
}) {
const t = useTranslations("openclaw.liveOpsSpace");
const locale = useLocale();
const [scene, setScene] = useState<SceneState | null>(null);
const [status, setStatus] = useState<SceneStatus>("loading");
const [updatedAt, setUpdatedAt] = useState<Date | null>(null);
const fetchSceneState = useCallback(async () => {
if (!API_BASE) {
setStatus("degraded");
return;
}
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), 12_000);
try {
const response = await fetch(
`${API_BASE}/api/v1/agents/openclaw-live-ops-scene-state`,
{ cache: "no-store", signal: controller.signal },
);
if (!response.ok) {
setStatus("degraded");
return;
}
const payload = (await response.json()) as SceneState;
setScene(payload);
setUpdatedAt(new Date());
setStatus("ready");
} catch {
setStatus("degraded");
} finally {
window.clearTimeout(timeout);
}
}, []);
useEffect(() => {
void fetchSceneState();
const timer = window.setInterval(fetchSceneState, 15_000);
return () => window.clearInterval(timer);
}, [fetchSceneState]);
const zones = scene?.room.zones ?? [];
const agents = scene?.agents ?? [];
const workItems = scene?.work_items ?? [];
const boundaryOk = useMemo(() => {
if (!scene) return false;
return [
"raw_session_read_allowed",
"sqlite_read_allowed",
"secret_value_display_allowed",
"runtime_action_performed",
"host_or_k8s_write_performed",
].every((key) => scene.boundaries[key] === false);
}, [scene]);
return (
<AppLayout locale={params.locale} fullBleed>
<main className="min-h-screen overflow-x-hidden bg-[#f7f8f7] text-[#141413]">
<div className="mx-auto flex w-full max-w-[1520px] flex-col gap-4 px-3 py-4 lg:px-5">
<section className="grid min-w-0 items-start gap-4 lg:grid-cols-[minmax(0,1fr)_360px]">
<div className="min-w-0 border border-[#d8d3c7] bg-white">
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3 border-b border-[#e5e1d8] px-4 py-3">
<div className="min-w-0">
<p className="text-xs font-semibold uppercase text-[#77736a]">
OpenClaw
</p>
<h1 className="mt-1 break-words text-2xl font-semibold leading-tight text-[#141413] sm:text-3xl">
{t("title")}
</h1>
</div>
<div className="flex items-center gap-2">
<span
className={cn(
"inline-flex items-center gap-2 border px-3 py-1.5 text-xs font-semibold",
status === "ready"
? "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]"
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
)}
>
{status === "ready" ? (
<CheckCircle2 className="h-4 w-4" aria-hidden="true" />
) : (
<AlertTriangle className="h-4 w-4" aria-hidden="true" />
)}
{status === "ready"
? t("status.ready")
: status === "loading"
? t("status.loading")
: t("status.degraded")}
</span>
<button
type="button"
onClick={() => void fetchSceneState()}
className="inline-flex h-9 w-9 items-center justify-center border border-[#d8d3c7] bg-white text-[#4f4b44] transition hover:border-[#4a90d9] hover:text-[#1f5b9b]"
title={t("actions.refresh")}
aria-label={t("actions.refresh")}
>
<RefreshCw className="h-4 w-4" aria-hidden="true" />
</button>
</div>
</div>
<div className="relative aspect-[16/10] min-h-[360px] overflow-hidden bg-[#eef3f8] sm:min-h-[500px]">
<div className="absolute inset-x-[5%] bottom-[7%] top-[8%] rotate-[-2deg] skew-y-[-7deg] border border-[#c8d6df] bg-[#fdfefe] shadow-[0_28px_70px_rgba(54,72,88,0.16)]" />
<div className="absolute inset-x-[7%] bottom-[11%] top-[12%] rotate-[-2deg] skew-y-[-7deg] bg-[linear-gradient(90deg,rgba(74,144,217,0.12)_1px,transparent_1px),linear-gradient(0deg,rgba(74,144,217,0.1)_1px,transparent_1px)] bg-[size:42px_42px]" />
{zones.map((zone) => {
const Icon = zoneIconByKind[zone.kind] ?? Activity;
return (
<div
key={zone.zone_id}
className="absolute flex h-14 w-28 -translate-x-1/2 -translate-y-1/2 rotate-[-2deg] items-center gap-2 border border-[#cbd4d8] bg-white/90 px-2 shadow-[0_10px_24px_rgba(54,72,88,0.12)] backdrop-blur"
style={{ left: `${zone.position.x}%`, top: `${zone.position.y}%` }}
>
<span className="flex h-8 w-8 shrink-0 items-center justify-center border border-[#d8d3c7] bg-[#f7f8f7] text-[#4a90d9]">
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
<span className="min-w-0 truncate text-[11px] font-semibold text-[#34302a]">
{zone.label}
</span>
</div>
);
})}
{workItems.map((item, index) => (
<div
key={item.work_item_id}
className={cn(
"openclaw-work-token absolute h-8 w-20 -translate-x-1/2 -translate-y-1/2 border px-2 py-1 text-[10px] font-semibold shadow-[0_8px_20px_rgba(54,72,88,0.16)]",
stateClass(stateFromWorkItemStatus(item.status)),
)}
style={{
left: `${item.position.x}%`,
top: `${item.position.y}%`,
animationDelay: `${index * 0.35}s`,
}}
>
<span className="block truncate">{item.work_item_id}</span>
</div>
))}
{agents.map((agent, index) => (
<div
key={agent.agent_id}
className="openclaw-agent absolute -translate-x-1/2 -translate-y-1/2"
style={{
left: `${agent.position.x}%`,
top: `${agent.position.y}%`,
animationDelay: `${index * 0.28}s`,
}}
>
<div
className={cn(
"flex h-14 w-14 items-center justify-center border-2 bg-white shadow-[0_16px_30px_rgba(54,72,88,0.2)]",
stateClass(agent.state),
)}
>
<Bot className="h-6 w-6" aria-hidden="true" />
</div>
<div className="mt-1 max-w-28 truncate border border-[#d8d3c7] bg-white px-1.5 py-0.5 text-center text-[10px] font-semibold text-[#34302a] shadow-sm">
{agent.label}
</div>
</div>
))}
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(145deg,rgba(255,255,255,0.18),transparent_45%,rgba(20,20,19,0.04))]" />
<div className="absolute bottom-3 left-3 flex flex-wrap gap-2">
<span className="border border-[#d8d3c7] bg-white/90 px-2 py-1 font-mono text-[11px] text-[#5f5b52]">
{t("source.marker")} {shortValue(scene?.source.deploy_readback_marker)}
</span>
<span className="border border-[#d8d3c7] bg-white/90 px-2 py-1 font-mono text-[11px] text-[#5f5b52]">
{t("source.updated")}{" "}
{updatedAt
? updatedAt.toLocaleTimeString(locale, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
: "--"}
</span>
</div>
</div>
</div>
<aside className="grid min-w-0 gap-3">
<section className="border border-[#d8d3c7] bg-white p-4">
<div className="flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-[#141413]">
{t("panels.rollups")}
</h2>
<Activity className="h-4 w-4 text-[#4a90d9]" aria-hidden="true" />
</div>
<div className="mt-4 grid grid-cols-2 gap-2">
{[
["agents", scene?.rollups.agent_count],
["workItems", scene?.rollups.work_item_count],
["animated", scene?.rollups.animated_entity_count],
["blocked", scene?.rollups.blocked_work_item_count],
].map(([key, value]) => (
<div key={String(key)} className="border border-[#e5e1d8] bg-[#fafafa] p-3">
<p className="text-[11px] font-semibold text-[#77736a]">
{t(`metrics.${key}`)}
</p>
<p className="mt-1 font-mono text-2xl font-semibold text-[#141413]">
{formatNumber(Number(value ?? 0))}
</p>
</div>
))}
</div>
</section>
<section className="border border-[#d8d3c7] bg-white p-4">
<div className="flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-[#141413]">
{t("panels.boundaries")}
</h2>
<ShieldCheck
className={cn("h-4 w-4", boundaryOk ? "text-[#17602a]" : "text-[#8a5a08]")}
aria-hidden="true"
/>
</div>
<div className="mt-3 grid gap-2">
{[
"raw_session_read_allowed",
"sqlite_read_allowed",
"secret_value_display_allowed",
"runtime_action_performed",
"host_or_k8s_write_performed",
].map((key) => (
<div
key={key}
className="flex min-w-0 items-center justify-between gap-3 border border-[#e5e1d8] px-3 py-2"
>
<span className="truncate font-mono text-[11px] text-[#5f5b52]">
{key}
</span>
<span
className={cn(
"border px-2 py-0.5 text-[10px] font-semibold",
scene?.boundaries[key] === false
? "border-[#8fc29a] bg-[#f0faf2] text-[#17602a]"
: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
)}
>
{scene?.boundaries[key] === false ? t("boundary.closed") : t("boundary.open")}
</span>
</div>
))}
</div>
</section>
<section className="min-h-[260px] border border-[#d8d3c7] bg-white p-4">
<div className="flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-[#141413]">
{t("panels.workItems")}
</h2>
<SearchCheck className="h-4 w-4 text-[#4a90d9]" aria-hidden="true" />
</div>
<div className="mt-3 grid gap-2">
{workItems.length === 0 ? (
<div className="border border-[#e5e1d8] bg-[#fafafa] p-4 text-sm text-[#77736a]">
{t("empty")}
</div>
) : (
workItems.slice(0, 6).map((item) => (
<div key={item.work_item_id} className="border border-[#e5e1d8] p-3">
<div className="flex min-w-0 items-center justify-between gap-2">
<span className="min-w-0 truncate text-sm font-semibold text-[#141413]">
{item.title || item.work_item_id}
</span>
<span
className={cn(
"shrink-0 border px-2 py-0.5 text-[10px] font-semibold",
stateClass(stateFromWorkItemStatus(item.status)),
)}
>
{stateLabel(t, stateFromWorkItemStatus(item.status))}
</span>
</div>
<p className="mt-2 truncate font-mono text-[11px] text-[#77736a]">
{item.next_controlled_action || item.exit_criteria || "--"}
</p>
</div>
))
)}
</div>
</section>
</aside>
</section>
</div>
</main>
<style jsx global>{`
@keyframes openclaw-agent-drift {
0%,
100% {
transform: translate(-50%, -50%) translate3d(0, 0, 0);
}
50% {
transform: translate(-50%, -50%) translate3d(5px, -7px, 0);
}
}
@keyframes openclaw-work-token-flow {
0%,
100% {
transform: translate(-50%, -50%) translate3d(0, 0, 0);
}
50% {
transform: translate(-50%, -50%) translate3d(9px, 4px, 0);
}
}
.openclaw-agent {
animation: openclaw-agent-drift 4.8s ease-in-out infinite;
will-change: transform;
}
.openclaw-work-token {
animation: openclaw-work-token-flow 6.2s ease-in-out infinite;
will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
.openclaw-agent,
.openclaw-work-token {
animation: none;
}
}
`}</style>
</AppLayout>
);
}

View File

@@ -15,6 +15,24 @@
- 沒有讀 secret / token / `.env` / raw sessions / SQLite / auth沒有寫 credential marker沒有寫 StockPlatform DB。
- 沒有使用 GitHub / `gh` / GitHub API / GitHub Actions。
## 2026-06-29 — 21:18 P1 OpenClaw Live Ops Space scene state 與持續動畫工作室
**照優先順序推進策略**
- P0-006 已由上游讀回推進到 StockPlatform freshness / ingestion recovered目前只剩 fresh all-host reboot window 證明,不應讓下一個可交付 P1 空轉。
- P1-OPENCLAW-LIVE-OPS-SPACE 依既有 priority work-order 接續實作,不取代 P0 runtime truth也不切換 OpenClaw 核心。
**完成內容**
- 新增 `openclaw_live_ops_scene_state` 服務與 GET `/api/v1/agents/openclaw-live-ops-scene-state`,把 `agent-autonomous-runtime-control` 的 public-safe `trace_ledger` / `work_item_progress` 投影成 2D isometric scene contract。
- 新增 `/zh-TW/openclaw/live-ops-space`,顯示 OpenClaw / AI Agent 持續工作室、MCP / RAG / PlayBook / verifier / Telegram receipt / deploy readback zones、Agent avatar、Work Item token 與持續動畫 loop。
- scene state 明確關閉 raw session、SQLite、secret、Telegram unredacted payload、runtime action、host/K8s write 邊界。
**驗證**
- `pytest apps/api/tests/test_openclaw_live_ops_scene_state_api.py apps/api/tests/test_ai_agent_autonomous_runtime_control.py apps/api/tests/test_credential_escrow_evidence_intake_readiness_api.py apps/api/tests/test_delivery_closure_workbench_api.py apps/api/tests/test_reboot_auto_recovery_slo_scorecard_api.py`30 passed。
- `pnpm --dir apps/web typecheck``NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web build`:通過。
- Playwright smoke本機 Chrome + API route interceptiondesktop 1440 / mobile 390 都有動畫、無水平 overflow、截圖非空。
**邊界**:未讀 raw sessions / SQLite / auth / `.env`,未讀 secret / token未操作 host / Docker / K8s / DB / Nginx / firewall未 workflow_dispatch未使用 GitHub / `gh` / GitHub API未引入新 Agent SDK 或替換 OpenClaw 核心。
## 2026-06-29 — 20:36 P0-005 non-blocking evidence refs validator
**照優先順序修正執行策略**