2026-04-21 17:57:05 +08:00
|
|
|
|
#!/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);
|
2026-04-27 17:59:44 +08:00
|
|
|
|
// [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 {}
|
2026-04-21 17:57:05 +08:00
|
|
|
|
} 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();
|
|
|
|
|
|
}
|