bookworm-smart-assistant/hooks/prompt-dispatcher.js

107 lines
3.3 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* UserPromptSubmit Hook: 统一提交派遣器 (v6.2 M3)
*
* 合并 2 UserPromptSubmit 钩子为单进程减少 ~50ms 串行开销:
* 1. security-startup-guard (同步, <5ms, fail-open)
* 2. route-interceptor-bundle (异步, 路由引擎, fail-open)
*
* 读取 stdin 一次:
* - security-startup-guard runGuard() 不需要 stdin 数据 (仅检查 lockfile + settings)
* - route-interceptor-bundle 作为子进程接收 stdin 数据 (保留其 timeout 逻辑)
*
* 退出码: 0 (始终放行, UserPromptSubmit 不阻断)
*/
'use strict';
const { spawn } = require('child_process');
const path = require('path');
const HOOKS_DIR = __dirname;
function main() {
let rawInput = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
rawInput += chunk;
if (rawInput.length > 256 * 1024) {
// 输入过大,直接退出
process.exit(0);
}
});
process.stdin.on('end', () => {
// --- 阶段 1: security-startup-guard (同步, fail-open) ---
try {
const guard = require('./security-startup-guard.js');
if (guard && guard.runGuard) {
guard.runGuard();
}
} catch {}
// --- 阶段 2: route-interceptor-bundle (子进程, 保留独立 timeout) ---
const bundlePath = path.join(HOOKS_DIR, 'route-interceptor-bundle.js');
const hookTimingMs = Date.now(); // [P3-6] 计时起点
try {
const child = spawn('node', [bundlePath], {
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 3000,
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (d) => { stdout += d; });
child.stderr.on('data', (d) => { stderr += d; });
child.on('close', () => {
// [P3-6] 记录路由耗时
try {
const elapsed = Date.now() - hookTimingMs;
const timingPath = path.join(HOOKS_DIR, '..', 'debug', 'hook-timing.jsonl');
const entry = JSON.stringify({ ts: new Date().toISOString(), hook: 'route-interceptor-bundle', elapsed }) + '\n';
require('fs').appendFileSync(timingPath, entry);
// [P0-1] METRICS_EMIT_v1
try {
const metrics = require('./lib/metrics.js');
let ri = {};
try {
const parsed = JSON.parse(stdout || '{}');
const ctx = (parsed.hookSpecificOutput && parsed.hookSpecificOutput.additionalContext) || '';
const cm = ctx.match(/\u7f6e\u4fe1\u5ea6\s+(\d+)%/);
const sm = ctx.match(/\u4e3b\u8def\u7531:\s+(\S+)/);
ri = { confidence: cm ? +cm[1] : null, skill: sm ? sm[1] : null };
} catch {}
metrics.emit('route', Object.assign({ hook: 'prompt-dispatcher', elapsed_ms: elapsed }, ri));
} catch {}
} catch {}
// 转发 route-interceptor-bundle 的 stdout (含 additionalContext)
if (stdout) {
process.stdout.write(stdout);
}
if (stderr) {
process.stderr.write(stderr);
}
process.exit(0);
});
child.on('error', () => {
process.exit(0); // fail-open
});
// 将 stdin 数据传递给子进程
child.stdin.write(rawInput);
child.stdin.end();
} catch {
process.exit(0); // fail-open
}
});
}
if (typeof module !== 'undefined') {
module.exports = { main };
}
if (require.main === module) {
main();
}