bookworm-smart-assistant/scripts/session-trace.js

112 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* 跨 Hook 会话追踪 (Phase 3)
*
* 提供共享 traceId使 3 个 hooks 的事件可以关联分析。
* 所有消费者必须 try/catch 包裹fail-open。
*/
const fs = require('fs');
const { safeAppendJsonl } = require('../hooks/lib/safe-append.js');
const path = require('path');
// ─── 路径解析 ────────────────────────────────────────
let debugDir;
try {
const { PATHS } = require('./paths.config.js');
debugDir = PATHS.debugDir;
} catch {
debugDir = path.resolve(__dirname, '..', 'debug');
}
const SESSION_TRACE_FILE = path.join(debugDir, 'session-trace.json');
const SESSION_LOCK_FILE = path.join(debugDir, 'session-active.lock');
/**
* 获取当前会话的 traceId同 session 复用)
* @returns {{ sessionId: string, traceId: string, startedAt: string, eventCount: number }}
*/
function getSessionTrace() {
if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
// 读取当前 sessionId
let sessionId = 'unknown';
try {
if (fs.existsSync(SESSION_LOCK_FILE)) {
const lock = JSON.parse(fs.readFileSync(SESSION_LOCK_FILE, 'utf8'));
sessionId = lock.sessionId || lock.id || 'unknown';
}
} catch {}
// 读取或创建 trace 状态
let trace = null;
try {
if (fs.existsSync(SESSION_TRACE_FILE)) {
trace = JSON.parse(fs.readFileSync(SESSION_TRACE_FILE, 'utf8'));
}
} catch {}
// 同 session 复用 traceId否则创建新的
if (trace && trace.sessionId === sessionId && sessionId !== 'unknown') {
return trace;
}
// 新 trace
const traceId = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
const newTrace = {
sessionId,
traceId,
startedAt: new Date().toISOString(),
eventCount: 0,
};
try {
// W5: 原子写入 (temp+rename)
var tmp = SESSION_TRACE_FILE + '.tmp.' + process.pid;
fs.writeFileSync(tmp, JSON.stringify(newTrace, null, 2) + '\n');
fs.renameSync(tmp, SESSION_TRACE_FILE);
} catch {}
return newTrace;
}
/**
* 追加事件到日期分片的 trace 日志
* @param {string} hookName - 来源 hook 名称
* @param {string} eventType - 事件类型 (detection|block-warn|pass|outcome)
* @param {Object} data - 附加数据
*/
function appendTraceEvent(hookName, eventType, data) {
try {
if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
const trace = getSessionTrace();
const dateStr = new Date().toISOString().slice(0, 10);
const logFile = path.join(debugDir, `trace-${dateStr}.jsonl`);
const event = {
ts: new Date().toISOString(),
traceId: trace.traceId,
sessionId: trace.sessionId,
hook: hookName,
event: eventType,
data: data || {},
};
safeAppendJsonl(logFile, event);
// 更新 eventCount (原子写入)
trace.eventCount = (trace.eventCount || 0) + 1;
try {
var tmp2 = SESSION_TRACE_FILE + '.tmp.' + process.pid;
fs.writeFileSync(tmp2, JSON.stringify(trace, null, 2) + '\n');
fs.renameSync(tmp2, SESSION_TRACE_FILE);
} catch {}
} catch {}
}
// ─── 导出 ─────────────────────────────────────────────
if (typeof module !== 'undefined') {
module.exports = { getSessionTrace, appendTraceEvent };
}