From 835faa0eab4a56034b21f4bd3822037756fcd111 Mon Sep 17 00:00:00 2001 From: sol Date: Sat, 7 Mar 2026 17:45:22 +0000 Subject: [PATCH] feat(phase5): polish + deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - skill/SKILL.md: rewritten to 9 lines — 'status is automatic' - deploy-to-agents.sh: no AGENTS.md injection; deploys hook + npm install - install.sh: clean install flow; prints required env vars - deploy/status-watcher.service: systemd unit file - deploy/Dockerfile: containerized deployment (node:22-alpine) - src/live-status.js: deprecation warning + start-watcher/stop-watcher pass-through - README.md: full docs (architecture, install, config, upgrade guide, troubleshooting) - make check: 0 errors, 0 format issues - npm test: 59 unit + 36 integration = 95 tests passing --- README.md | 262 +++++++++++++++++++++++++++++++--- deploy-to-agents.sh | 53 +++++++ deploy/Dockerfile | 36 +++++ deploy/status-watcher.service | 36 +++++ install.sh | 134 +++++++++-------- skill/SKILL.md | 86 +---------- src/live-status.js | 29 +++- 7 files changed, 476 insertions(+), 160 deletions(-) create mode 100755 deploy-to-agents.sh create mode 100644 deploy/Dockerfile create mode 100644 deploy/status-watcher.service diff --git a/README.md b/README.md index f35225d..07cc21c 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,256 @@ -# OpenClaw Live Status Tool +# Live Status v4 -A lightweight CLI tool for OpenClaw agents to provide "Antigravity-style" live status updates in Mattermost channels (and others) without spamming. +Real-time Mattermost progress updates for OpenClaw agent sessions. -## Features +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. -- **Live Updates:** Create a single message and update it repeatedly. -- **Sub-Agent Support:** Works in clean environments via embedded config or CLI flags. -- **Cross-Channel:** Supports dynamic channel targeting via `--channel`. -- **One-Click Install:** Updates binaries, skills, and agent protocols automatically. +## Architecture -## Installation +``` +OpenClaw Gateway + Agent Sessions + -> writes {uuid}.jsonl as they run -Run the interactive installer wizard: + status-watcher daemon (SINGLE PROCESS) + -> fs.watch recursive on transcript directory (inotify, Node 22) + -> Multiplexes all active sessions + -> SessionState map: sessionKey -> { postId, pendingToolCalls, lines[] } + -> Shared HTTP connection pool (keep-alive, maxSockets=4) + -> Throttled Mattermost updates (leading edge + trailing flush, 500ms) + -> Circuit breaker for API failure resilience + -> Graceful shutdown (SIGTERM -> mark all boxes "interrupted") + -> Sub-agent nesting (child sessions under parent status box) -```bash -./install.sh + gateway:startup hook + -> hooks/status-watcher-hook/handler.js + -> Checks PID file; spawns daemon if not running + -> Daemon starts automatically with gateway + + Mattermost API + -> PUT /api/v4/posts/{id} (in-place edits, unlimited) + -> Shared http.Agent (keepAlive, maxSockets=4) + -> Circuit breaker: open after 5 failures, 30s cooldown ``` -## Usage +## Install -```bash -# Create a new status box -ID=$(live-status create "Initializing...") +### Prerequisites -# Update the status box -live-status update $ID "Working..." +- Node.js 22.x +- OpenClaw gateway running +- Mattermost bot token + +### One-command install + +```sh +cd /path/to/MATTERMOST_OPENCLAW_LIVESTATUS +bash install.sh +``` + +This installs npm dependencies and deploys the `gateway:startup` hook. +The daemon starts automatically on the next gateway restart. + +### Manual start (without gateway restart) + +Set required env vars, then: + +```sh +node src/watcher-manager.js start +``` + +## Configuration + +All config via environment variables. No hardcoded values. + +### Required + +| Variable | Description | +| ---------------- | ----------------------------------------------------- | +| `MM_TOKEN` | Mattermost bot token | +| `MM_URL` | Mattermost base URL (e.g. `https://slack.solio.tech`) | +| `TRANSCRIPT_DIR` | Path to agent sessions directory | +| `SESSIONS_JSON` | Path to sessions.json | + +### Optional + +| Variable | Default | Description | +| ---------------------------- | ------------------------- | ------------------------------------------ | +| `THROTTLE_MS` | `500` | Min interval between Mattermost updates | +| `IDLE_TIMEOUT_S` | `60` | Inactivity before marking session complete | +| `MAX_SESSION_DURATION_S` | `1800` | Hard timeout per session (30 min) | +| `MAX_STATUS_LINES` | `15` | Max lines in status box (oldest dropped) | +| `MAX_ACTIVE_SESSIONS` | `20` | Concurrent status box limit | +| `MAX_MESSAGE_CHARS` | `15000` | Mattermost post truncation limit | +| `HEALTH_PORT` | `9090` | Health endpoint port (0 = disabled) | +| `LOG_LEVEL` | `info` | Logging level (pino) | +| `PID_FILE` | `/tmp/status-watcher.pid` | PID file location | +| `CIRCUIT_BREAKER_THRESHOLD` | `5` | Failures before circuit opens | +| `CIRCUIT_BREAKER_COOLDOWN_S` | `30` | Cooldown before half-open probe | +| `TOOL_LABELS_FILE` | _(built-in)_ | External tool labels JSON override | +| `DEFAULT_CHANNEL` | _none_ | Fallback channel for non-MM sessions | + +## Status Box Format + +``` +[ACTIVE] main | 38s +Reading live-status source code... + exec: ls /agents/sessions [OK] +Analyzing agent configurations... + exec: grep -r live-status [OK] +Writing new implementation... + Sub-agent: proj035-planner + Reading protocol... + Analyzing JSONL format... + [DONE] 28s +Plan ready. Awaiting approval. +[DONE] 53s | 12.4k tokens +``` + +## Daemon Management + +```sh +# Start +node src/watcher-manager.js start + +# Stop (graceful shutdown) +node src/watcher-manager.js stop + +# Status +node src/watcher-manager.js status + +# Pass-through via legacy CLI +live-status start-watcher +live-status stop-watcher + +# Health check +curl http://localhost:9090/health +``` + +## Deployment Options + +### Hook (default) + +The `gateway:startup` hook in `hooks/status-watcher-hook/` auto-starts the daemon. +No configuration needed beyond deploying the hook. + +### systemd + +```sh +# Copy service file +cp deploy/status-watcher.service /etc/systemd/system/ + +# Create env file +cat > /etc/status-watcher.env < label resolver + tool-labels.json Built-in tool label defaults + live-status.js Legacy CLI (deprecated; backward compat) + +hooks/ + status-watcher-hook/ gateway:startup hook (auto-start daemon) + +deploy/ + status-watcher.service systemd unit file + Dockerfile Container deployment + +test/ + unit/ Unit tests (59 tests) + integration/ Integration tests (36 tests) ``` diff --git a/deploy-to-agents.sh b/deploy-to-agents.sh new file mode 100755 index 0000000..dee2f70 --- /dev/null +++ b/deploy-to-agents.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# deploy-to-agents.sh — Deploy Live Status v4 to OpenClaw workspace +# +# Deploys the gateway:startup hook and installs npm dependencies. +# Does NOT inject anything into AGENTS.md. +# The watcher daemon auto-starts on next gateway restart. +# +# Usage: bash deploy-to-agents.sh [--workspace DIR] + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE="${WORKSPACE:-/home/node/.openclaw/workspace}" + +# Parse flags +while [[ "$#" -gt 0 ]]; do + case "$1" in + --workspace) WORKSPACE="$2"; shift 2 ;; + *) shift ;; + esac +done + +echo "===================================" +echo " Live Status v4 Deploy" +echo "===================================" +echo "Workspace: $WORKSPACE" +echo "" + +# 1. npm install +echo "[1/2] Installing npm dependencies..." +cd "$SCRIPT_DIR" +npm install --production +echo " Done." + +# 2. Deploy hook +echo "[2/2] Deploying gateway:startup hook..." +HOOKS_DIR="$WORKSPACE/hooks" +mkdir -p "$HOOKS_DIR/status-watcher-hook" +cp -r "$SCRIPT_DIR/hooks/status-watcher-hook/." "$HOOKS_DIR/status-watcher-hook/" +echo " Hook deployed to: $HOOKS_DIR/status-watcher-hook/" + +echo "" +echo "===================================" +echo " Deployment Complete" +echo "===================================" +echo "" +echo "The watcher will auto-start on next gateway startup." +echo "" +echo "To activate immediately, ensure these env vars are set, then run:" +echo " node $SCRIPT_DIR/src/watcher-manager.js start" +echo "" +echo "Required env vars: MM_TOKEN, MM_URL, TRANSCRIPT_DIR, SESSIONS_JSON" +echo "See install.sh for full config reference." diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..84211b7 --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,36 @@ +FROM node:22-alpine + +LABEL description="Live Status v4 - OpenClaw session watcher daemon" +LABEL source="https://git.eeqj.de/ROOH/MATTERMOST_OPENCLAW_LIVESTATUS" + +WORKDIR /app + +# Copy package files and install production dependencies only +COPY package.json package-lock.json ./ +RUN npm ci --production + +# Copy source and supporting files +COPY src/ ./src/ +COPY skill/ ./skill/ + +# Environment variables (required — set at runtime) +# MM_TOKEN, MM_URL, TRANSCRIPT_DIR, SESSIONS_JSON must be provided +ENV NODE_ENV=production \ + LOG_LEVEL=info \ + HEALTH_PORT=9090 \ + THROTTLE_MS=500 \ + IDLE_TIMEOUT_S=60 \ + MAX_STATUS_LINES=15 \ + MAX_ACTIVE_SESSIONS=20 \ + PID_FILE=/tmp/status-watcher.pid + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -qO- http://localhost:${HEALTH_PORT}/health || exit 1 + +# Run as non-root +USER node + +EXPOSE ${HEALTH_PORT} + +CMD ["node", "src/watcher-manager.js", "start"] diff --git a/deploy/status-watcher.service b/deploy/status-watcher.service new file mode 100644 index 0000000..1bdf87a --- /dev/null +++ b/deploy/status-watcher.service @@ -0,0 +1,36 @@ +[Unit] +Description=Live Status v4 - OpenClaw session watcher daemon +Documentation=https://git.eeqj.de/ROOH/MATTERMOST_OPENCLAW_LIVESTATUS +After=network.target + +[Service] +Type=simple +User=node +WorkingDirectory=/opt/openclaw-live-status + +# Load environment variables from file +EnvironmentFile=/etc/status-watcher.env + +# Start the watcher daemon directly (not via CLI wrapper) +ExecStart=/usr/bin/node /opt/openclaw-live-status/src/watcher-manager.js start + +# Graceful shutdown — watcher handles SIGTERM (marks boxes interrupted, flushes) +ExecStop=/bin/kill -TERM $MAINPID + +# Restart policy +Restart=on-failure +RestartSec=5s +StartLimitBurst=3 +StartLimitIntervalSec=60s + +# Logging — output goes to journald +StandardOutput=journal +StandardError=journal +SyslogIdentifier=status-watcher + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/install.sh b/install.sh index 16ee7ef..f2d656e 100755 --- a/install.sh +++ b/install.sh @@ -1,69 +1,81 @@ #!/bin/bash +# install.sh — Live Status v4 installer +# +# Installs npm dependencies and deploys the gateway:startup hook. +# The watcher daemon starts automatically on next gateway restart. +# +# Usage: bash install.sh [--workspace DIR] -# OpenClaw Live Status Installer (Wizard Mode) +set -e -echo "==========================================" -echo " OpenClaw Live Status Tool - Installer " -echo "==========================================" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE="${1:-/home/node/.openclaw/workspace}" -# 1. Install Binary -echo "[+] Installing binary to /usr/local/bin/live-status..." -cp src/live-status.js /usr/local/bin/live-status -chmod +x /usr/local/bin/live-status +# Parse flags +while [[ "$#" -gt 0 ]]; do + case "$1" in + --workspace) WORKSPACE="$2"; shift 2 ;; + *) shift ;; + esac +done -# 2. Install Skill -echo "[+] Installing skill to /app/skills/live-status..." -mkdir -p /app/skills/live-status -cp skill/SKILL.md /app/skills/live-status/SKILL.md - -# 3. Global Agent Configuration (Injection) -echo "[+] Configuring global agent defaults..." -AGENTS_FILE="/home/node/.openclaw/workspace/AGENTS.md" - -if [ -f "$AGENTS_FILE" ]; then - if grep -q "Live Status Protocol" "$AGENTS_FILE"; then - echo " > AGENTS.md already configured." - else - echo " > Injecting mandatory protocol into AGENTS.md..." - cat >> "$AGENTS_FILE" <\` to stream progress. -5. **Complete:** Mark as \`[COMPLETED]\` when done. - -**Why:** This provides "Antigravity-style" visibility without spamming channels. Sub-agents MUST use this tool instead of \`message\` for progress reports. -EOF - fi -else - echo " > Warning: AGENTS.md not found. Skipping injection." -fi - -# 4. Mattermost Configuration (Wizard) +echo "===================================" +echo " Live Status v4 Installer" +echo "===================================" +echo "Project: $SCRIPT_DIR" +echo "Workspace: $WORKSPACE" echo "" -echo "[?] Mattermost Configuration Check:" -# Check if the installed tool still has the default placeholder -if grep -q "DEFAULT_TOKEN_PLACEHOLDER" /usr/local/bin/live-status; then - echo " > Default token detected." - read -p " > Enter your Mattermost Bot Token: " NEW_TOKEN - if [[ -n "$NEW_TOKEN" ]]; then - # Replace the placeholder in the INSTALLED binary (not the source) - sed -i "s/DEFAULT_TOKEN_PLACEHOLDER/$NEW_TOKEN/g" /usr/local/bin/live-status - echo " > Token configured successfully." - else - echo " > No token entered. Tool may not function." - fi -else - echo " > Custom token already configured." -fi +# 1. Install npm dependencies +echo "[1/3] Installing npm dependencies..." +cd "$SCRIPT_DIR" +npm install --production +echo " Done." + +# 2. Deploy hook +echo "[2/3] Deploying gateway:startup hook..." +HOOKS_DIR="$WORKSPACE/hooks" +mkdir -p "$HOOKS_DIR/status-watcher-hook" +cp -r "$SCRIPT_DIR/hooks/status-watcher-hook/." "$HOOKS_DIR/status-watcher-hook/" +echo " Hook deployed to: $HOOKS_DIR/status-watcher-hook/" + +# 3. Print required environment variables +echo "[3/3] Post-install configuration" echo "" -echo "==========================================" -echo " Installation Complete!" -echo "==========================================" -echo "Usage: live-status create \"...\"" +echo "===================================" +echo " Required Environment Variables" +echo "===================================" +echo "" +echo "Set these before the watcher will function:" +echo "" +echo " MM_TOKEN Mattermost bot token" +echo " (find in openclaw.json -> mattermost.accounts)" +echo "" +echo " MM_URL Mattermost base URL" +echo " e.g. https://slack.solio.tech" +echo "" +echo " TRANSCRIPT_DIR Path to agent sessions directory" +echo " e.g. /home/node/.openclaw/agents/main/sessions" +echo "" +echo " SESSIONS_JSON Path to sessions.json" +echo " e.g. /home/node/.openclaw/agents/main/sessions/sessions.json" +echo "" +echo "Optional (shown with defaults):" +echo " THROTTLE_MS=500 Update interval (ms)" +echo " IDLE_TIMEOUT_S=60 Idle before marking session done" +echo " MAX_STATUS_LINES=15 Lines shown in status box" +echo " MAX_ACTIVE_SESSIONS=20 Concurrent session limit" +echo " HEALTH_PORT=9090 Health endpoint port (0=disabled)" +echo " LOG_LEVEL=info Logging level" +echo " PID_FILE=/tmp/status-watcher.pid" +echo "" +echo "===================================" +echo " Installation Complete" +echo "===================================" +echo "" +echo "The watcher starts automatically on next gateway startup." +echo "To start immediately (with env vars set):" +echo " node $SCRIPT_DIR/src/watcher-manager.js start" +echo "" +echo "Health check (once running):" +echo " curl http://localhost:9090/health" diff --git a/skill/SKILL.md b/skill/SKILL.md index ecbd76a..9c01357 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,85 +1,11 @@ # Live Status Skill -**Real-time progress updates in Mattermost via in-place post editing.** -Creates a single "status box" post and updates it repeatedly — no chat spam. +Status updates are automatic. You do not need to call live-status manually. -## Usage +The Live Status v4 daemon watches your transcript in real-time and posts +progress updates to Mattermost automatically. Focus on your task. -### Create a status box +If you receive `live-status CLI is deprecated` warnings, ignore them. +The underlying infrastructure is handling visibility for you. -```bash -live-status --channel create "🚀 **Task Started:** Initializing..." -``` - -Returns the `POST_ID` (26-char string). **Capture it.** - -### Create in a thread - -```bash -live-status --channel --reply-to create "🚀 Starting..." -``` - -### Update the status box - -```bash -live-status update "🚀 **Task Running** -\`\`\` -[10:00] Step 1... OK -[10:01] Step 2... Working -\`\`\`" -``` - -### Mark complete - -```bash -live-status update "✅ **Task Complete** -\`\`\` -[10:00] Step 1... OK -[10:01] Step 2... OK -[10:05] Done. -\`\`\`" -``` - -### Delete a status box - -```bash -live-status delete -``` - -## Multi-Agent Support - -When multiple agents share a channel, each creates its **own** status box: - -```bash -# Agent A -BOX_A=$(live-status --channel $CH --agent god-agent create "🤖 God Agent working...") -# Agent B -BOX_B=$(live-status --channel $CH --agent nutrition-agent create "🥗 Nutrition Agent working...") -``` - -Each agent updates only its own box by ID. No conflicts. - -## Options - -| Flag | Purpose | -| --------------- | --------------------------------------------------- | -| `--channel ID` | Target channel (or set `MM_CHANNEL_ID`) | -| `--reply-to ID` | Post as thread reply (sets `root_id`) | -| `--agent NAME` | Use bot token mapped to this agent in openclaw.json | -| `--token TOKEN` | Explicit bot token (overrides everything) | -| `--host HOST` | Mattermost hostname | - -## Auto-Detection - -The tool reads `openclaw.json` automatically for: - -- **Host** — from `mattermost.baseUrl` -- **Token** — from `mattermost.accounts` (mapped via `--agent` or defaults) -- No env vars or manual config needed in most cases. - -## Protocol - -1. **Always** capture the `POST_ID` from `create`. -2. **Always** append to previous log (maintain full history in the message). -3. **Use code blocks** for technical logs. -4. Each new task gets a **new** status box — never reuse old IDs across tasks. +For advanced use (manual status boxes), see README.md in the live-status project. diff --git a/src/live-status.js b/src/live-status.js index 211d222..6ad0938 100755 --- a/src/live-status.js +++ b/src/live-status.js @@ -7,6 +7,14 @@ const http = require('http'); const fs = require('fs'); const path = require('path'); +// --- DEPRECATION WARNING --- +// In v4, live-status CLI is deprecated. The status-watcher daemon handles +// all updates automatically by tailing JSONL transcripts. You do not need +// to call this tool manually. It remains available for backward compatibility. +if (process.stderr.isTTY) { + console.error('NOTE: live-status CLI is deprecated as of v4. Status updates are now automatic.'); +} + // --- PARSE ARGS --- const args = process.argv.slice(2); let command = null; @@ -33,7 +41,12 @@ for (let i = 0; i < args.length; i++) { i++; } else if (arg === '--rich') { options.rich = true; - } else if (!command && ['create', 'update', 'complete', 'error', 'delete'].includes(arg)) { + } else if ( + !command && + ['create', 'update', 'complete', 'error', 'delete', 'start-watcher', 'stop-watcher'].includes( + arg, + ) + ) { command = arg; } else { otherArgs.push(arg); @@ -294,7 +307,17 @@ async function deletePost(postId) { } // --- CLI ROUTER --- -if (command === 'create') { +if (command === 'start-watcher' || command === 'stop-watcher') { + // Pass-through to watcher-manager.js + const { spawnSync } = require('child_process'); + const watcherPath = path.join(__dirname, 'watcher-manager.js'); + const subCmd = command === 'start-watcher' ? 'start' : 'stop'; + const result = spawnSync(process.execPath, [watcherPath, subCmd], { + stdio: 'inherit', + env: process.env, + }); + process.exit(result.status || 0); +} else if (command === 'create') { createPost(otherArgs.join(' '), 'create'); } else if (command === 'update') { updatePost(otherArgs[0], otherArgs.slice(1).join(' '), 'update'); @@ -311,6 +334,8 @@ if (command === 'create') { console.log(' live-status [options] complete '); console.log(' live-status [options] error '); console.log(' live-status [options] delete '); + console.log(' live-status start-watcher (pass-through to watcher-manager start)'); + console.log(' live-status stop-watcher (pass-through to watcher-manager stop)'); console.log(''); console.log('Options:'); console.log(' --rich Use rich message attachments (colored cards)');