- openclaw-control-center
- Skill by
- ara.so
- — Daily 2026 Skills collection
- OpenClaw Control Center transforms OpenClaw from a black box into a local, auditable control center. It provides visibility into agent activity, token spend, task execution chains, cross-session collaboration, memory state, and document sources — with security-first defaults that keep all mutations off by default.
- What It Does
- Overview
-
- System health, pending items, risk signals, and operational summary
- Usage
-
- Daily/7d/30d token spend, quota, context pressure, subscription window
- Staff
-
- Who is actively executing vs. queued — not just "has tasks"
- Collaboration
-
- Parent-child session handoffs and verified cross-session messages (e.g.
- Main ⇄ Pandas
- )
- Tasks
-
- Task board, approvals, execution chains, run evidence
- Memory
-
- Per-agent memory health, searchability, and source file editing
- Documents
-
- Shared and agent-core documents opened from actual source files
- Settings
-
- Connector wiring status, security risk summary, update status
- Installation
- git
- clone https://github.com/TianyiDataScience/openclaw-control-center.git
- cd
- openclaw-control-center
- npm
- install
- cp
- .env.example .env
- npm
- run build
- npm
- test
- npm
- run smoke:ui
- npm
- run dev:ui
- Open:
- http://127.0.0.1:4310/?section=overview&lang=zh
- http://127.0.0.1:4310/?section=overview&lang=en
- Use
- npm run dev:ui
- over
- UI_MODE=true npm run dev
- — more stable, especially on Windows shells.
- Project Structure
- openclaw-control-center/
- ├── control-center/ # All modifications must stay within this directory
- │ ├── src/
- │ │ ├── runtime/ # Core runtime, connectors, monitors
- │ │ └── ui/ # Frontend UI components
- │ ├── .env.example
- │ └── package.json
- ├── docs/
- │ └── assets/ # Screenshots and documentation images
- ├── README.md
- └── README.en.md
- Critical constraint
- Only modify files inside control-center/ . Never modify ~/.openclaw/openclaw.json . Environment Configuration Copy .env.example to .env and configure:
Security defaults — do NOT change without understanding implications
READONLY_MODE=true LOCAL_TOKEN_AUTH_REQUIRED=true IMPORT_MUTATION_ENABLED=false IMPORT_MUTATION_DRY_RUN=false APPROVAL_ACTIONS_ENABLED=false APPROVAL_ACTIONS_DRY_RUN=true
Connection
OPENCLAW_GATEWAY_URL=http://127.0.0.1:PORT OPENCLAW_HOME=~/.openclaw
UI
PORT=4310 DEFAULT_LANG=zh Security Flag Meanings Flag Default Effect READONLY_MODE true All state-changing endpoints disabled LOCAL_TOKEN_AUTH_REQUIRED true Import/export and write APIs require local token IMPORT_MUTATION_ENABLED false Import mutations blocked entirely IMPORT_MUTATION_DRY_RUN false Dry-run mode for imports when enabled APPROVAL_ACTIONS_ENABLED false Approval actions hard-disabled APPROVAL_ACTIONS_DRY_RUN true Approval actions run as dry-run when enabled Key Commands
Development
npm run dev:ui
Start UI server (recommended)
npm run dev
One-shot monitor run, no HTTP UI
Build & Test
npm run build
TypeScript compile
npm test
Run test suite
npm run smoke:ui
Smoke test the UI endpoints
Lint
npm run lint
ESLint check
npm run lint:fix
Auto-fix lint issues
TypeScript Code Examples
Connecting to the Runtime Monitor
import
{
createMonitor
}
from
'./src/runtime/monitor'
;
const
monitor
=
createMonitor
(
{
gatewayUrl
:
process
.
env
.
OPENCLAW_GATEWAY_URL
??
'http://127.0.0.1:4310'
,
readonlyMode
:
process
.
env
.
READONLY_MODE
!==
'false'
,
localTokenAuthRequired
:
process
.
env
.
LOCAL_TOKEN_AUTH_REQUIRED
!==
'false'
,
}
)
;
// Fetch current system overview
const
overview
=
await
monitor
.
getOverview
(
)
;
console
.
log
(
overview
.
systemStatus
)
;
// 'healthy' | 'degraded' | 'critical'
console
.
log
(
overview
.
pendingItems
)
;
// number
console
.
log
(
overview
.
activeAgents
)
;
// Agent[]
Reading Agent Staff Status
import
{
StaffConnector
}
from
'./src/runtime/connectors/staff'
;
const
staff
=
new
StaffConnector
(
{
gatewayUrl
:
process
.
env
.
OPENCLAW_GATEWAY_URL
}
)
;
// Get agents actively executing (not just queued)
const
activeAgents
=
await
staff
.
getActiveAgents
(
)
;
activeAgents
.
forEach
(
agent
=>
{
console
.
log
(
${
agent
.
name
}
:
${
agent
.
status
}
)
;
// 'executing' | 'queued' | 'idle' | 'blocked'
console
.
log
(
Current task:
${
agent
.
currentTask
?.
title
??
'none'
}
)
;
console
.
log
(
Last output:
${
agent
.
lastOutput
}
)
;
}
)
;
// Get the full staff roster including queue depth
const
roster
=
await
staff
.
getRoster
(
)
;
Tracing Cross-Session Collaboration
import
{
CollaborationTracer
}
from
'./src/runtime/connectors/collaboration'
;
const
tracer
=
new
CollaborationTracer
(
{
gatewayUrl
:
process
.
env
.
OPENCLAW_GATEWAY_URL
}
)
;
// Get parent-child session handoffs
const
handoffs
=
await
tracer
.
getSessionHandoffs
(
)
;
handoffs
.
forEach
(
handoff
=>
{
console
.
log
(
${
handoff
.
parentSession
}
→
${
handoff
.
childSession
}
)
;
console
.
log
(
Delegated task:
${
handoff
.
taskTitle
}
)
;
console
.
log
(
Status:
${
handoff
.
status
}
)
;
}
)
;
// Get verified cross-session messages (e.g. Main ⇄ Pandas)
const
crossSessionMessages
=
await
tracer
.
getCrossSessionMessages
(
)
;
crossSessionMessages
.
forEach
(
msg
=>
{
console
.
log
(
${
msg
.
fromAgent
}
⇄
${
msg
.
toAgent
}
:
${
msg
.
messageType
}
)
;
// messageType: 'sessions_send' | 'inter-session message'
}
)
;
Fetching Token Usage and Spend
import
{
UsageConnector
}
from
'./src/runtime/connectors/usage'
;
const
usage
=
new
UsageConnector
(
{
gatewayUrl
:
process
.
env
.
OPENCLAW_GATEWAY_URL
}
)
;
// Today's usage
const
today
=
await
usage
.
getUsageSummary
(
'today'
)
;
console
.
log
(
Tokens used:
${
today
.
tokensUsed
}
)
;
console
.
log
(
Cost: $
${
today
.
costUsd
.
toFixed
(
4
)
}
)
;
console
.
log
(
Context pressure:
${
today
.
contextPressure
}
)
;
// contextPressure: 'low' | 'medium' | 'high' | 'critical'
// Usage trend over 7 days
const
trend
=
await
usage
.
getUsageTrend
(
7
)
;
trend
.
forEach
(
day
=>
{
console
.
log
(
${
day
.
date
}
:
${
day
.
tokensUsed
}
tokens, $
${
day
.
costUsd
.
toFixed
(
4
)
}
)
;
}
)
;
// Token attribution by task (who ate the scheduled task tokens)
const
attribution
=
await
usage
.
getTokenAttribution
(
)
;
attribution
.
tasks
.
forEach
(
task
=>
{
console
.
log
(
${
task
.
title
}
:
${
task
.
tokensUsed
}
(
${
task
.
percentOfTotal
}
%)
)
;
}
)
;
Reading Memory State
import
{
MemoryConnector
}
from
'./src/runtime/connectors/memory'
;
const
memory
=
new
MemoryConnector
(
{
openclawHome
:
process
.
env
.
OPENCLAW_HOME
??
'~/.openclaw'
,
}
)
;
// Get memory health per active agent (scoped to openclaw.json)
const
memoryState
=
await
memory
.
getMemoryState
(
)
;
memoryState
.
agents
.
forEach
(
agent
=>
{
console
.
log
(
${
agent
.
name
}
:
)
;
console
.
log
(
Available:
${
agent
.
memoryAvailable
}
)
;
console
.
log
(
Searchable:
${
agent
.
memorySearchable
}
)
;
console
.
log
(
Needs review:
${
agent
.
needsReview
}
)
;
}
)
;
// Read daily memory for an agent
const
dailyMemory
=
await
memory
.
readDailyMemory
(
'main-agent'
)
;
console
.
log
(
dailyMemory
.
content
)
;
// Edit memory (requires READONLY_MODE=false and valid local token)
await
memory
.
writeDailyMemory
(
'main-agent'
,
updatedContent
,
{
token
:
localToken
}
)
;
Checking Wiring Status
import
{
WiringChecker
}
from
'./src/runtime/connectors/wiring'
;
const
wiring
=
new
WiringChecker
(
{
gatewayUrl
:
process
.
env
.
OPENCLAW_GATEWAY_URL
}
)
;
const
status
=
await
wiring
.
getWiringStatus
(
)
;
status
.
connectors
.
forEach
(
connector
=>
{
console
.
log
(
${
connector
.
name
}
:
${
connector
.
status
}
)
;
// status: 'connected' | 'partial' | 'disconnected'
if
(
connector
.
status
!==
'connected'
)
{
console
.
log
(
Fix:
${
connector
.
nextStep
}
)
;
}
}
)
;
Approving Tasks (Gated Endpoint)
import
{
TaskConnector
}
from
'./src/runtime/connectors/tasks'
;
const
tasks
=
new
TaskConnector
(
{
gatewayUrl
:
process
.
env
.
OPENCLAW_GATEWAY_URL
,
approvalActionsEnabled
:
process
.
env
.
APPROVAL_ACTIONS_ENABLED
===
'true'
,
approvalActionsDryRun
:
process
.
env
.
APPROVAL_ACTIONS_DRY_RUN
!==
'false'
,
}
)
;
// This throws if APPROVAL_ACTIONS_ENABLED=false (default)
try
{
const
result
=
await
tasks
.
approveTask
(
'task-id-123'
,
{
token
:
localToken
}
)
;
if
(
result
.
dryRun
)
{
console
.
log
(
'Dry run — no actual state change'
)
;
}
}
catch
(
err
)
{
if
(
err
.
code
===
'APPROVAL_ACTIONS_DISABLED'
)
{
console
.
log
(
'Set APPROVAL_ACTIONS_ENABLED=true to enable approvals'
)
;
}
}
UI Section Navigation
Navigate via query params:
http://127.0.0.1:4310/?section=overview&lang=zh
http://127.0.0.1:4310/?section=usage&lang=en
http://127.0.0.1:4310/?section=staff&lang=zh
http://127.0.0.1:4310/?section=collaboration&lang=en
http://127.0.0.1:4310/?section=tasks&lang=zh
http://127.0.0.1:4310/?section=memory&lang=en
http://127.0.0.1:4310/?section=documents&lang=zh
http://127.0.0.1:4310/?section=settings&lang=en
Sections:
overview
|
usage
|
staff
|
collaboration
|
tasks
|
memory
|
documents
|
settings
Languages:
zh
(Chinese, default) |
en
(English)
Integration Patterns
Embedding in an Existing OpenClaw Workflow
If your OpenClaw agent needs to hand off instructions to the control center for setup, use the documented install block:
// In your OpenClaw agent task
const
installInstructions
=
`
cd openclaw-control-center
npm install
cp .env.example .env
Edit .env: set OPENCLAW_GATEWAY_URL and OPENCLAW_HOME
npm run build && npm test && npm run dev:ui ` ; Adding a Custom Connector All connectors live in control-center/src/runtime/connectors/ . Follow this pattern: // control-center/src/runtime/connectors/my-connector.ts import { BaseConnector , ConnectorOptions } from './base' ; export interface MyData { id : string ; value : string ; } export class MyConnector extends BaseConnector { constructor ( options : ConnectorOptions ) { super ( options ) ; } async getData ( ) : Promise < MyData [ ]
{ // Always check readonly mode for any write operation this . assertNotReadonly ( 'getData is readonly-safe' ) ; const response = await this . fetch ( '/api/my-endpoint' ) ; return response . json ( ) as Promise < MyData [ ]
; } } Custom UI Section // control-center/src/ui/sections/MySection.tsx import React from 'react' ; import { useConnector } from '../hooks/useConnector' ; import { MyConnector } from '../../runtime/connectors/my-connector' ; export const MySection : React . FC = ( ) => { const { data , loading , error } = useConnector ( MyConnector , 'getData' ) ; if ( loading ) return < div
Loading ... < / div
; if ( error ) return < div
Error : { error . message } < / div
; return ( < ul
{ data ?. map ( item => ( < li key = { item . id }
{ item . value } < / li
) ) } < / ul
) ; } ; Troubleshooting "Missing src/runtime" or "Missing core source" This almost always means the working directory is wrong:
Ensure you're in the repo root
pwd
should end in /openclaw-control-center
ls control-center/src/runtime
should exist
If cloned correctly but still missing: the clone was incomplete. Re-clone: git clone https://github.com/TianyiDataScience/openclaw-control-center.git UI Doesn't Start / Port Conflicts
Check if port 4310 is in use
lsof -i :4310
or on Windows
netstat -ano | findstr :4310
Change port in .env
PORT
4311 Data Not Showing (Partial or Empty Sections) Open Settings → 接线状态 (Wiring Status) — it lists exactly which connectors are connected, partial, or missing. Common causes: OPENCLAW_GATEWAY_URL not set or wrong port OPENCLAW_HOME doesn't point to actual ~/.openclaw OpenClaw subscription snapshot not at default path Token Auth Failures
Generate a local token (see openclaw docs for token location)
cat ~/.openclaw/local-token
Pass via header in API calls
curl
-H
"X-Local-Token:
Check active agents
cat ~/.openclaw/openclaw.json | grep -A5 '"agents"' Remove stale entries from openclaw.json — the memory section will update on next load. Windows Shell Issues Prefer npm run dev:ui over UI_MODE=true npm run dev . Cross-env variable setting behaves differently in PowerShell/CMD. The dev:ui script handles this internally. Prerequisites Node.js + npm A running OpenClaw installation with accessible Gateway Read access to ~/.openclaw on the local machine (Optional) ~/.codex and OpenClaw subscription snapshot for full usage data