feat(phase4): add gateway:startup hook for auto-starting watcher daemon
- hooks/status-watcher-hook/HOOK.md — events: ["gateway:startup"], required env vars - hooks/status-watcher-hook/handler.js — checks PID file, spawns watcher-manager.js detached - Deployed hook to /home/node/.openclaw/workspace/hooks/status-watcher-hook/ - make check passes
This commit is contained in:
53
hooks/status-watcher-hook/HOOK.md
Normal file
53
hooks/status-watcher-hook/HOOK.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# status-watcher-hook
|
||||
|
||||
Auto-starts the Live Status v4 daemon when the OpenClaw gateway starts.
|
||||
|
||||
## Events
|
||||
|
||||
```json
|
||||
["gateway:startup"]
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
On gateway startup, this hook checks whether the status-watcher daemon is already
|
||||
running (via PID file). If not, it spawns `watcher-manager.js start` as a detached
|
||||
background process, then exits immediately. The daemon continues running independently
|
||||
of this hook handler.
|
||||
|
||||
## Required Environment Variables
|
||||
|
||||
The following environment variables must be set for the watcher to function:
|
||||
|
||||
```
|
||||
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 (defaults shown):
|
||||
|
||||
```
|
||||
THROTTLE_MS 500
|
||||
IDLE_TIMEOUT_S 60
|
||||
MAX_STATUS_LINES 15
|
||||
MAX_ACTIVE_SESSIONS 20
|
||||
MAX_MESSAGE_CHARS 15000
|
||||
HEALTH_PORT 9090
|
||||
LOG_LEVEL info
|
||||
PID_FILE /tmp/status-watcher.pid
|
||||
CIRCUIT_BREAKER_THRESHOLD 5
|
||||
CIRCUIT_BREAKER_COOLDOWN_S 30
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
This hook is deployed automatically by `install.sh` or `deploy-to-agents.sh`.
|
||||
To deploy manually:
|
||||
|
||||
```sh
|
||||
cp -r hooks/status-watcher-hook /home/node/.openclaw/workspace/hooks/
|
||||
```
|
||||
|
||||
The hook activates on the next gateway startup.
|
||||
102
hooks/status-watcher-hook/handler.js
Normal file
102
hooks/status-watcher-hook/handler.js
Normal file
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* status-watcher-hook/handler.js
|
||||
*
|
||||
* Spawns the Live Status v4 watcher-manager daemon on gateway startup.
|
||||
*
|
||||
* Events: ["gateway:startup"]
|
||||
*
|
||||
* Behavior:
|
||||
* 1. Check PID file — if watcher is already running, do nothing.
|
||||
* 2. If not running, spawn watcher-manager.js as a detached background process.
|
||||
* 3. The hook handler returns immediately; the daemon runs independently.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
// PID file location (must match watcher-manager.js default)
|
||||
const PID_FILE = process.env.PID_FILE || '/tmp/status-watcher.pid';
|
||||
|
||||
// Path to watcher-manager.js (relative to this hook file's location)
|
||||
// Hook is in: workspace/hooks/status-watcher-hook/handler.js
|
||||
// Watcher is in: workspace/projects/openclaw-live-status/src/watcher-manager.js
|
||||
const WATCHER_PATH = path.resolve(
|
||||
__dirname,
|
||||
'../../projects/openclaw-live-status/src/watcher-manager.js',
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if a process is alive given its PID.
|
||||
* Returns true if process exists and is running.
|
||||
*/
|
||||
function isProcessRunning(pid) {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the watcher daemon is already running via PID file.
|
||||
* Returns true if running, false if not (or PID file stale/missing).
|
||||
*/
|
||||
function isWatcherRunning() {
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
const pidStr = fs.readFileSync(PID_FILE, 'utf8').trim();
|
||||
const pid = parseInt(pidStr, 10);
|
||||
if (isNaN(pid) || pid <= 0) return false;
|
||||
return isProcessRunning(pid);
|
||||
} catch (_err) {
|
||||
// PID file missing or unreadable — watcher is not running
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn the watcher daemon as a detached background process.
|
||||
* The parent (this hook handler) does not wait for it.
|
||||
*/
|
||||
function spawnWatcher() {
|
||||
if (!fs.existsSync(WATCHER_PATH)) {
|
||||
console.error('[status-watcher-hook] watcher-manager.js not found at:', WATCHER_PATH);
|
||||
console.error('[status-watcher-hook] Deploy the live-status project first: see install.sh');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[status-watcher-hook] Starting Live Status v4 watcher daemon...');
|
||||
|
||||
const child = spawn(process.execPath, [WATCHER_PATH, 'start'], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
child.unref();
|
||||
|
||||
console.log(
|
||||
'[status-watcher-hook] Watcher daemon spawned (PID will be written to',
|
||||
PID_FILE + ')',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook entry point — called by OpenClaw on gateway:startup event.
|
||||
*/
|
||||
async function handle(_event) {
|
||||
if (isWatcherRunning()) {
|
||||
console.log('[status-watcher-hook] Watcher already running, skipping spawn.');
|
||||
return;
|
||||
}
|
||||
|
||||
spawnWatcher();
|
||||
}
|
||||
|
||||
module.exports = { handle };
|
||||
Reference in New Issue
Block a user