From 3e93e40109af5578c7ffc3ccb13d289f2ebd812f Mon Sep 17 00:00:00 2001 From: sol Date: Mon, 9 Mar 2026 14:41:33 +0000 Subject: [PATCH] docs: add v4.1.0 changelog, plugin deploy guide, and plugin Makefile - README: document all 5 fixes from Issue #5 (floating widget, RHS panel refresh bug, browser auth fix, session cleanup goroutine, KV scan optimization) - README: add full Mattermost Plugin section with build/deploy instructions, manual deploy path for servers with plugin uploads disabled, auth model docs - plugin/Makefile: build/package/deploy/health targets for production deployment on any new OpenClaw+Mattermost server Closes the documentation gap so any developer can deploy this from scratch. --- README.md | 114 +++++++++++++++++++++++++++++++++++++++++++++++- plugin/Makefile | 101 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 plugin/Makefile diff --git a/README.md b/README.md index f865cf5..48c32a2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,17 @@ -# Live Status v4 +# Live Status v4.1 Real-time Mattermost progress updates for OpenClaw agent sessions. Version 4 replaces the manual v1 live-status CLI with a transparent infrastructure daemon. Agents no longer need to call `live-status`. The watcher auto-updates Mattermost as they work. +## What's New in v4.1 + +- **Floating widget** — PiP-style overlay using `registerRootComponent`. Auto-shows when a session starts, auto-hides when idle. Draggable, collapsible, position persisted to localStorage. Solves the "status box buried in long threads" problem without touching post ordering. +- **RHS panel fix** — Panel now loads existing sessions on mount (previously empty after page refresh). Added dual auth path so browser JS can fetch sessions without the daemon shared secret. +- **Session cleanup** — Orphaned sessions (daemon crash, etc.) now auto-expire: stale after 30 min inactivity, deleted after 1 hour. +- **KV prefix filter** — `ListActiveSessions` now filters at the KV level instead of scanning all plugin keys. + ## Architecture Two rendering modes (auto-detected): @@ -188,6 +195,42 @@ docker run -d \ status-watcher ``` +## Changelog + +### v4.1.0 (2026-03-09) + +**New: Floating Widget** (`plugin/webapp/src/components/floating_widget.tsx`) +- Registers as a root component via `registerRootComponent` — always visible, not tied to any post position +- Auto-shows when an agent session becomes active (WebSocket event), auto-hides 5 seconds after completion +- Draggable — drag to any screen position, position persisted in `localStorage` +- Collapsible to a pulsing dot with session count badge +- Shows agent name, elapsed time, and last 5 status lines of the most active session +- Click to expand or jump to the thread + +**Fix: RHS Panel blank after page refresh** (`plugin/webapp/src/components/rhs_panel.tsx`) +- Previously the panel was empty after a browser refresh because it only showed sessions received via WebSocket during the current page load +- Now fetches `GET /api/v1/sessions` on mount to pre-populate with existing active sessions +- WebSocket updates continue to keep it live after initial hydration + +**Fix: Plugin auth for browser requests** (`plugin/server/api.go`) +- Previously all plugin API requests required the shared secret (daemon token) +- Browser-side `fetch()` calls from the webapp can't include the shared secret +- Added dual auth: `GET` endpoints now also accept Mattermost session auth (`Mattermost-User-Id` header, auto-injected by the Mattermost server) +- Write operations (`POST`/`PUT`/`DELETE`) still require the shared secret — daemon-only + +**New: Session cleanup goroutine** (`plugin/server/plugin.go`, `plugin/server/store.go`) +- Added `LastUpdateMs` timestamp field to `SessionData` +- Cleanup goroutine runs every 5 minutes in `OnActivate` +- Sessions active >30 minutes with no update are marked `interrupted` (daemon likely crashed) +- Non-active sessions older than 1 hour are deleted from the KV store +- Prevents indefinite accumulation of orphaned sessions from daemon crashes + +**Fix: KV store scan optimization** (`plugin/server/store.go`) +- `ListActiveSessions` now filters by `ls_session_` key prefix before deserializing +- Avoids scanning unrelated KV entries from other plugins + +--- + ## Upgrade from v1 v1 required agents to call `live-status create/update/complete` manually. @@ -208,6 +251,75 @@ AGENTS.md contained a large "Live Status Protocol (MANDATORY)" section. 4. After 1+ hours of verified operation, remove the v1 AGENTS.md sections (see `docs/v1-removal-checklist.md` for exact sections to remove). +## Mattermost Plugin + +The plugin (`plugin/`) provides WebSocket-based live rendering — no "(edited)" labels, full terminal UI with theme support. + +### Requirements + +- Mattermost 7.0+ +- Go 1.21+ (for building server binary) +- Node.js 18+ (for building React webapp) +- A bot token with System Admin or plugin management rights (for deployment) + +### Build and Deploy + +```sh +# Build server + webapp +cd plugin +make all + +# Deploy to Mattermost (uploads + enables plugin) +MM_URL=https://your-mattermost.example.com \ +MM_TOKEN=your_system_admin_token \ +make deploy + +# Verify plugin is healthy +PLUGIN_SECRET=your_shared_secret \ +MM_URL=https://your-mattermost.example.com \ +make health +``` + +### Plugin Configuration (Admin Console) + +After deploying, configure in **System Console > Plugins > OpenClaw Live Status**: + +| Setting | Description | Default | +|---------|-------------|---------| +| `SharedSecret` | Shared secret between plugin and daemon. Must match `PLUGIN_SECRET` in `.env.daemon`. | _(empty — set this)_ | +| `MaxActiveSessions` | Max simultaneous tracked sessions | 20 | +| `MaxStatusLines` | Max status lines per session | 30 | + +### Manual Deploy (when plugin uploads are disabled) + +If your Mattermost server has plugin uploads disabled (common in self-hosted setups), deploy directly to the host filesystem: + +```sh +# Build the package +cd plugin && make package +# Outputs: plugin/dist/com.openclaw.livestatus.tar.gz + +# Extract to Mattermost plugins volume (adjust path to match your setup) +tar xzf plugin/dist/com.openclaw.livestatus.tar.gz \ + -C /opt/mattermost/volumes/app/mattermost/plugins/ + +# Restart or reload plugin via API +curl -X POST \ + -H "Authorization: Bearer $MM_TOKEN" \ + "$MM_URL/api/v4/plugins/com.openclaw.livestatus/enable" +``` + +### Plugin Auth Model + +The plugin uses dual authentication: + +- **Shared secret** (Bearer token in `Authorization` header) — used by the daemon for all write operations (POST/PUT/DELETE sessions) +- **Mattermost session** (`Mattermost-User-Id` header, auto-injected by the Mattermost server) — used by the browser webapp for read-only operations (GET sessions, GET health) + +This means the RHS panel and floating widget can fetch existing sessions on page load without needing the shared secret in the frontend. + +--- + ## Troubleshooting **Daemon not starting:** diff --git a/plugin/Makefile b/plugin/Makefile new file mode 100644 index 0000000..8face3d --- /dev/null +++ b/plugin/Makefile @@ -0,0 +1,101 @@ +## Makefile — OpenClaw Live Status Mattermost Plugin +## Builds, packages, and deploys the plugin to a running Mattermost instance. +## +## Requirements: +## - Go 1.21+ +## - Node.js 18+ +## - curl + tar +## +## Usage: +## make all Build server + webapp +## make deploy Build and deploy to Mattermost (requires MM_URL + MM_TOKEN) +## make package Create distributable tar.gz +## make clean Remove build artifacts + +PLUGIN_ID := com.openclaw.livestatus +PLUGIN_DIR := $(shell pwd) +SERVER_DIR := $(PLUGIN_DIR)/server +WEBAPP_DIR := $(PLUGIN_DIR)/webapp + +# Build outputs +SERVER_BIN := $(SERVER_DIR)/dist/plugin-linux-amd64 +WEBAPP_BUNDLE := $(WEBAPP_DIR)/dist/main.js +PACKAGE_FILE := $(PLUGIN_DIR)/dist/$(PLUGIN_ID).tar.gz + +# Deployment (override via env or command line) +MM_URL ?= http://localhost:8065 +MM_TOKEN ?= + +.PHONY: all server webapp package deploy clean check-env + +all: server webapp + +server: + @echo ">> Building Go server (linux/amd64)..." + @mkdir -p $(SERVER_DIR)/dist + cd $(SERVER_DIR) && GOOS=linux GOARCH=amd64 go build -o dist/plugin-linux-amd64 . + @echo " Built: $(SERVER_BIN)" + +webapp: + @echo ">> Building React webapp..." + cd $(WEBAPP_DIR) && npm install --legacy-peer-deps + cd $(WEBAPP_DIR) && npx webpack --mode production + @echo " Built: $(WEBAPP_BUNDLE)" + +package: all + @echo ">> Packaging plugin..." + @mkdir -p $(PLUGIN_DIR)/dist + @rm -rf /tmp/$(PLUGIN_ID) + @mkdir -p /tmp/$(PLUGIN_ID)/server/dist /tmp/$(PLUGIN_ID)/webapp/dist /tmp/$(PLUGIN_ID)/assets + @cp $(PLUGIN_DIR)/plugin.json /tmp/$(PLUGIN_ID)/ + @cp $(SERVER_BIN) /tmp/$(PLUGIN_ID)/server/dist/ + @cp $(WEBAPP_BUNDLE) /tmp/$(PLUGIN_ID)/webapp/dist/ + @[ -f $(PLUGIN_DIR)/assets/icon.svg ] && cp $(PLUGIN_DIR)/assets/icon.svg /tmp/$(PLUGIN_ID)/assets/ || true + @cd /tmp && tar czf $(PACKAGE_FILE) $(PLUGIN_ID)/ + @rm -rf /tmp/$(PLUGIN_ID) + @echo " Package: $(PACKAGE_FILE)" + +deploy: check-env package + @echo ">> Deploying plugin to $(MM_URL)..." + @# Disable existing plugin + @curl -sf -X POST \ + -H "Authorization: Bearer $(MM_TOKEN)" \ + "$(MM_URL)/api/v4/plugins/$(PLUGIN_ID)/disable" > /dev/null 2>&1 || true + @# Delete existing plugin + @curl -sf -X DELETE \ + -H "Authorization: Bearer $(MM_TOKEN)" \ + "$(MM_URL)/api/v4/plugins/$(PLUGIN_ID)" > /dev/null 2>&1 || true + @# Upload new plugin + @curl -sf -X POST \ + -H "Authorization: Bearer $(MM_TOKEN)" \ + -F "plugin=@$(PACKAGE_FILE)" \ + -F "force=true" \ + "$(MM_URL)/api/v4/plugins" | grep -q "id" && echo " Uploaded." || (echo " Upload failed (plugin uploads may be disabled)." && exit 1) + @# Enable plugin + @curl -sf -X POST \ + -H "Authorization: Bearer $(MM_TOKEN)" \ + "$(MM_URL)/api/v4/plugins/$(PLUGIN_ID)/enable" > /dev/null + @echo " Plugin enabled. Verifying health..." + @sleep 2 + @echo " Done. Run 'make health' to verify." + +health: + @PLUGIN_SECRET=$${PLUGIN_SECRET:-}; \ + if [ -n "$$PLUGIN_SECRET" ]; then \ + curl -sf -H "Authorization: Bearer $$PLUGIN_SECRET" \ + "$(MM_URL)/plugins/$(PLUGIN_ID)/api/v1/health" | python3 -m json.tool 2>/dev/null || \ + curl -sf -H "Authorization: Bearer $$PLUGIN_SECRET" \ + "$(MM_URL)/plugins/$(PLUGIN_ID)/api/v1/health"; \ + else \ + echo "Set PLUGIN_SECRET to check health"; \ + fi + +check-env: + @if [ -z "$(MM_TOKEN)" ]; then \ + echo "ERROR: MM_TOKEN is required. Set MM_TOKEN=your_bot_token"; \ + exit 1; \ + fi + +clean: + @rm -rf $(SERVER_DIR)/dist $(WEBAPP_DIR)/dist $(PLUGIN_DIR)/dist + @echo ">> Cleaned build artifacts."