Skip to content

Latest commit

 

History

History
172 lines (136 loc) · 6.77 KB

File metadata and controls

172 lines (136 loc) · 6.77 KB

Dashboard

The dashboard is a single-page HTML app served by the orchestrator's embedded HTTP server. It lets you launch and observe Council sessions, Autoloop runs, and Forge (Ultraapp) builds from a browser — no CLI, no webchat, no plugin tool calls needed.

URL: http://127.0.0.1:18796/dash (local) or whatever public hostname you front the embedded server with (the recommended setup uses a path-based reverse proxy, e.g. https://<your-host>/dash).

Tabs

Tab Backed by Launch endpoint
Autoloop SessionManager.autoloopStart() POST /autoloop/new
Council SessionManager.councilStart() POST /council/new
Forge UltraappManager.createRun() POST /ultraapp/new

Each tab has a + New button in the sidebar. Council and Autoloop open a modal form (because they need workspace/task input); Forge POSTs an empty body and drops you into an interview (the spec is built conversationally).

Standalone deployment

The recommended way to run the dashboard 24/7 is a separate clawo serve process under launchd — completely decoupled from the OpenClaw gateway. The gateway's plugin-side embedded server still works (lazy init on first tool call); when both processes try to bind the default port, the loser gracefully skips, so the two coexist without conflict.

Example ~/Library/LaunchAgents/com.clawo.serve.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key><string>com.clawo.serve</string>
    <key>RunAtLoad</key><true/>
    <key>KeepAlive</key><true/>
    <key>ThrottleInterval</key><integer>5</integer>
    <key>ProgramArguments</key>
    <array>
      <string>/opt/homebrew/bin/node</string>
      <string>/opt/homebrew/bin/clawo</string>
      <string>serve</string>
      <string>--port</string><string>18796</string>
      <string>--host</string><string>127.0.0.1</string>
    </array>
    <key>StandardOutPath</key>
    <string>/Users/USER/.openclaw/logs/clawo-serve.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/USER/.openclaw/logs/clawo-serve.log</string>
  </dict>
</plist>

Bootstrap:

launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.clawo.serve.plist
launchctl print "gui/$(id -u)/com.clawo.serve" | grep state

Auth

The embedded server self-generates a 32-byte token at startup and writes it to ~/.openclaw/server-token (mode 0600). Same-user processes on the box read it and present it as Authorization: Bearer <token> (or ?token=<v> query / clawo_auth cookie).

Local access

http://127.0.0.1:18796/dash?token=$(cat ~/.openclaw/server-token)

The server sets a clawo_auth cookie on the first query-token request, so the bookmark /dash works on subsequent visits.

Hosted access via reverse proxy (recommended)

Don't expose the token to the public internet. Instead, gate the public hostname with whatever auth layer you already trust (CF Access passkey, Tailscale, mTLS, etc.) and have the reverse proxy inject the Bearer token on behalf of the user when forwarding to port 18796. The browser authenticates only against your edge auth; the dashboard's own token stays inside the box.

Example sasha-doctor pattern (matches the user-side setup):

// after the edge auth check passes:
if (!req.headers.authorization) {
  req.headers.authorization =
    "Bearer " + fs.readFileSync("~/.openclaw/server-token", "utf-8").trim();
}
proxyHTTP(req, res, 18796);

The /login?token=...&redirect=/dash endpoint exists as a fallback for quick one-shot setups (works locally and through proxies that DON'T inject the Bearer for you), but the proxy-injects-Bearer pattern is preferred because users never see or paste the token.

Token-file write is deferred to the listen()-success callback so a second process that loses the EADDRINUSE race does NOT clobber the winner's token. The token is also re-read from disk on every request so that if a different clawo instance (test runner, nohup launch, etc.) writes a new value mid-life, the proxy and the server stay in agreement on the next request — no restart required.

Resuming a terminated autoloop run

Opening a run whose status is terminated (because its process has exited, or because you're viewing it cross-process) no longer hangs on "Waiting…". The dashboard fetches /autoloop/<id>/chat_history, replays the conversation into the Planner pane, and surfaces a green Resume run button in the topbar. Clicking it POSTs /autoloop/<id>/resume; the orchestrator re-attaches the Planner (reusing the persisted Claude session ID when available, so Claude's context picks up where it left off) and the dashboard reconnects to /events for live updates.

Runs that pre-date this feature have no chat.jsonl and no persisted session — they still resume cleanly, but with a blank Planner pane and a fresh Claude context. New runs going forward retain both.

Cross-process visibility

When the dashboard runs in a different process from where you spawn runs (e.g. you started a council via the OpenClaw plugin tool from webchat, but the dashboard is in clawo serve), the run state is invisible across in-memory boundaries. The dashboard fixes this by unioning in-memory state with on-disk records on every list call:

  • Councils: ~/.openclaw/council-logs/council-*.md — parsed for - **ID**:, - **Time**:, - **Task**:, - **Status**: headers. Legacy transcripts (pre-v4.0) fall back to a filename-derived id.
  • Autoloops: ~/.claw-orchestrator/autoloop-registry.jsonl — an append-only JSONL index written by autoloopStart(). Stale entries whose ledger directory no longer exists are filtered out at read time.
  • Forge: UltraappStore.listRuns() already reads from disk (~/.claw-orchestrator/ultraapps/).

Result: any run you've ever started — from any process — shows up in the sidebar, sorted newest-first, until the underlying files are deleted.

Reverse-proxy integration

If you front the embedded server with sasha-doctor (or another reverse proxy), route these paths to 127.0.0.1:18796:

  • /dashboard, /dash, /login
  • /autoloop/*, /council/*, /ultraapp/*

The dashboard's relative fetch() calls expect the proxy to preserve the path verbatim — no prefix stripping. /v1/openclaw/* should keep routing to the OpenClaw gateway, not the embedded server.

Reset

To wipe dashboard state without touching real run data:

# Forget all known autoloops (council/forge unchanged).
rm ~/.claw-orchestrator/autoloop-registry.jsonl

# Force the standalone server to mint a fresh auth token.
launchctl kickstart -k "gui/$(id -u)/com.clawo.serve"
# Then visit /login?token=$(cat ~/.openclaw/server-token)&redirect=/dash once
# to refresh the cookie.