- 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)
132 lines
4.4 KiB
JavaScript
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();
|
|
}
|