bookworm-smart-assistant/hooks/subagent-route-injector.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- VERSION file as authoritative version source
- export.mjs reads VERSION with package.json fallback
- bw-ota.ps1 DryRun mode for safe testing
- auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
2026-04-27 17:59:44 +08:00

132 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
/**
* SubagentStart Hook: 子 Agent 路由上下文注入 (v5.2 Neural Gateway)
*
* 将父级路由信息注入子 Agent 上下文,
* 确保子 Agent 感知当前路由决策。
*
* stdin: { session_id, hook_event_name: "SubagentStart" }
* stdout: JSON { hookSpecificOutput: { additionalContext } }
* 退出码: 始终 0
*/
const fs = require('fs');
const path = require('path');
const readStdin = require('./lib/read-stdin.js');
const STATE_EXPIRY_MS = 5 * 60 * 1000; // 5 分钟过期
const CLAUDE_ROOT = require('./lib/root.js');
const DEBUG_DIR = path.join(CLAUDE_ROOT, 'debug');
const STATE_FILE = path.join(DEBUG_DIR, 'route-state-current.json');
/**
* 加载路由状态 (含过期检查)
* @returns {Object|null}
*/
function loadRouteState() {
try {
if (!fs.existsSync(STATE_FILE)) return null;
const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
// 过期检查
if (state.ts) {
const tsMs = new Date(state.ts).getTime();
if (isNaN(tsMs)) return null;
const age = Date.now() - tsMs;
if (age > STATE_EXPIRY_MS) return null;
}
return state;
} catch {
return null;
}
}
// === 主流程 ===
function main() {
readStdin({ maxSize: 128 * 1024 }).then(input => {
// 读取路由状态
const state = loadRouteState();
if (!state) {
// 无状态或已过期 → 静默放行
process.exit(0);
return;
}
const primary = state.routing?.primary || 'developer-expert';
const confidence = state.routing?.confidence || 0;
const complexity = state.intent?.complexity || 'medium';
const intents = (state.intent?.intents || []).join(',');
const chain = (state.routing?.chain || []).join(' → ');
let ctx = `[BWR 子Agent上下文] 父级路由: ${primary}, 置信度: ${(confidence * 100).toFixed(0)}%`;
ctx += `, 复杂度: ${complexity}, 意图: ${intents}`;
if (chain) {
ctx += `, 技能链: ${chain}`;
}
// --- v6.6: Context Anchors Lite + 重读纪律 ---
// 安全设计: 只注入路径提示, Agent 自行用 Read 工具获取 (经过 block-sensitive-reads)
try {
const anchorsFile = path.join(CLAUDE_ROOT, "context-anchors.json");
if (fs.existsSync(anchorsFile)) {
const anchors = JSON.parse(fs.readFileSync(anchorsFile, "utf8"));
const files = (anchors.files || []).slice(0, 10);
if (files.length > 0) {
ctx += String.fromCharCode(10)+"[CONTEXT_ANCHOR] 上下文锚点文件 (请用 Read 工具重读): " + files.join(", ");
}
}
} catch {}
ctx += String.fromCharCode(10)+"[重读纪律] 你是 fresh 实例。不要假设之前的 Agent 做了什么,从当前代码状态独立判断。";
// --- v6.6-rc2-02 AGENT_TRACE_INJECTOR_P0 ---
try {
const _crypto = require('crypto');
const _tsC = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
const _rand6 = _crypto.randomBytes(3).toString('hex');
const _traceId = 'bwr-' + _tsC + '-' + _rand6;
const _traceFile = path.join(CLAUDE_ROOT, 'state', 'agent-traces', _traceId + '.json');
try {
fs.mkdirSync(path.dirname(_traceFile), { recursive: true });
const _placeholder = {
traceId: _traceId,
session_id: input.session_id || '',
agent_type: (input.tool_input && input.tool_input.subagent_type) || '',
started_at: new Date().toISOString(),
status: 'started',
pre_snapshot: null,
claims: null,
};
const _tmp = _traceFile + '.tmp.' + process.pid;
fs.writeFileSync(_tmp, JSON.stringify(_placeholder, null, 2));
fs.renameSync(_tmp, _traceFile);
} catch {}
ctx += String.fromCharCode(10) + '[trace: ' + _traceId + '] 请在回复末尾标注 <trace>' + _traceId + '</trace> 以便审计';
} catch {}
// --- end AGENT_TRACE_INJECTOR_P0 ---
const output = {
hookSpecificOutput: {
hookEventName: 'SubagentStart',
additionalContext: ctx,
},
};
process.stdout.write(JSON.stringify(output));
process.exit(0);
}).catch(() => process.exit(0));
}
// 模块导出 (供测试)
if (typeof module !== 'undefined') {
module.exports = { loadRouteState, CLAUDE_ROOT: require("./lib/root.js") };
}
if (require.main === module) {
main();
}