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.
This commit is contained in:
114
README.md
114
README.md
@@ -1,10 +1,17 @@
|
|||||||
# Live Status v4
|
# Live Status v4.1
|
||||||
|
|
||||||
Real-time Mattermost progress updates for OpenClaw agent sessions.
|
Real-time Mattermost progress updates for OpenClaw agent sessions.
|
||||||
|
|
||||||
Version 4 replaces the manual v1 live-status CLI with a transparent infrastructure daemon.
|
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.
|
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
|
## Architecture
|
||||||
|
|
||||||
Two rendering modes (auto-detected):
|
Two rendering modes (auto-detected):
|
||||||
@@ -188,6 +195,42 @@ docker run -d \
|
|||||||
status-watcher
|
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
|
## Upgrade from v1
|
||||||
|
|
||||||
v1 required agents to call `live-status create/update/complete` manually.
|
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
|
4. After 1+ hours of verified operation, remove the v1 AGENTS.md sections
|
||||||
(see `docs/v1-removal-checklist.md` for exact sections to remove).
|
(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
|
## Troubleshooting
|
||||||
|
|
||||||
**Daemon not starting:**
|
**Daemon not starting:**
|
||||||
|
|||||||
101
plugin/Makefile
Normal file
101
plugin/Makefile
Normal file
@@ -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."
|
||||||
Reference in New Issue
Block a user