fix: production deployment issues
1. session-monitor: handle timestamp-prefixed transcript filenames
OpenClaw uses {ISO}_{sessionId}.jsonl — glob for *_{sessionId}.jsonl
when direct path doesn't exist.
2. session-monitor: skip stale sessions (>5min since last transcript write)
Prevents creating status boxes for every old session in sessions.json.
3. status-watcher: parse actual OpenClaw JSONL transcript format
Records are {type:'message', message:{role,content:[{type,name,...}]}}
not {type:'tool_call', name}. Now shows live tool calls with arguments
and assistant thinking text.
4. handler.js: fix module.exports for OpenClaw hook loader
Expects default export (function), not {handle: function}.
5. HOOK.md: add YAML frontmatter metadata for hook discovery.
This commit is contained in:
@@ -113,7 +113,30 @@ class SessionMonitor extends EventEmitter {
|
||||
* @returns {string}
|
||||
*/
|
||||
_transcriptPath(agentId, sessionId) {
|
||||
return path.join(this.transcriptDir, agentId, 'sessions', `${sessionId}.jsonl`);
|
||||
const sessionsDir = path.join(this.transcriptDir, agentId, 'sessions');
|
||||
const directPath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
||||
|
||||
// OpenClaw may use timestamp-prefixed filenames: {ISO}_{sessionId}.jsonl
|
||||
// Check direct path first, then glob for *_{sessionId}.jsonl
|
||||
if (fs.existsSync(directPath)) {
|
||||
return directPath;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(sessionsDir);
|
||||
const suffix = `${sessionId}.jsonl`;
|
||||
const match = files.find(
|
||||
(f) => f.endsWith(suffix) && f !== suffix && !f.endsWith('.deleted'),
|
||||
);
|
||||
if (match) {
|
||||
return path.join(sessionsDir, match);
|
||||
}
|
||||
} catch (_e) {
|
||||
// Directory doesn't exist or unreadable
|
||||
}
|
||||
|
||||
// Fallback to direct path (will fail with ENOENT, which is handled upstream)
|
||||
return directPath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,6 +249,29 @@ class SessionMonitor extends EventEmitter {
|
||||
|
||||
const transcriptFile = this._transcriptPath(agentId, sessionId);
|
||||
|
||||
// Skip stale sessions — only track if transcript was modified in last 5 minutes
|
||||
// This prevents creating status boxes for every old session in sessions.json
|
||||
try {
|
||||
const stat = fs.statSync(transcriptFile);
|
||||
const ageMs = Date.now() - stat.mtimeMs;
|
||||
const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
||||
if (ageMs > STALE_THRESHOLD_MS) {
|
||||
if (this.logger) {
|
||||
this.logger.debug(
|
||||
{ sessionKey, ageS: Math.floor(ageMs / 1000) },
|
||||
'Skipping stale session (transcript not recently modified)',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (_e) {
|
||||
// File doesn't exist — skip silently
|
||||
if (this.logger) {
|
||||
this.logger.debug({ sessionKey, transcriptFile }, 'Skipping session (transcript not found)');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Sub-agents always pass through — they inherit parent channel via watcher-manager
|
||||
const isSubAgent = !!spawnedBy;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user