bookworm-smart-assistant/hooks/token-saver-post-output-guard.js.bak-6in1
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

70 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
/**
* token-saver-post-output-guard.js · TSE Layer 4 · 2026-04-27
* PostToolUse (Read|Bash) · 超长输出检测, 注入聚焦提示
* 行为: fail-open, 不修改输出, 仅 additionalContext
*/
'use strict';
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = require('./lib/root.js');
const readStdin = require('./lib/read-stdin.js');
const STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
const STATE_PATH = path.join(STATE_DIR, 'tse-post-output-guard.json');
const THRESHOLD = 5000;
const CRIT = 15000;
const THROTTLE_MS = 60 * 1000;
function loadState() {
try { return fs.existsSync(STATE_PATH) ? JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')) : {}; } catch { return {}; }
}
function saveState(s) {
try {
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
const tmp = STATE_PATH + '.tmp.' + process.pid;
fs.writeFileSync(tmp, JSON.stringify(s), 'utf8');
fs.renameSync(tmp, STATE_PATH);
} catch {}
}
(async () => {
try {
const hd = await readStdin({ maxSize: 2 * 1024 * 1024 });
const tn = hd.tool_name;
if (tn !== 'Read' && tn !== 'Bash') process.exit(0);
const out = hd.tool_output;
if (!out || typeof out !== 'string' || out.length < THRESHOLD) process.exit(0);
const state = loadState();
const now = Date.now();
if (state[tn] && (now - state[tn]) < THROTTLE_MS) process.exit(0);
state[tn] = now;
for (const k of Object.keys(state)) { if (state[k] < now - 600000) delete state[k]; }
saveState(state);
const len = out.length;
const tokens = Math.round(len / 3.5);
const cr = len >= CRIT;
var msg;
if (tn === 'Read') {
msg = cr
? '[TSE\xb7POST_GUARD] Read ' + len + ' chars (' + tokens + ' tokens). \u4ec5\u63d0\u53d6\u4e0e\u5f53\u524d\u4efb\u52a1\u76f4\u63a5\u76f8\u5173\u7684\u4fe1\u606f\u3002\u4e0d\u8981\u5728\u56de\u590d\u4e2d\u91cd\u590d\u5b8c\u6574\u6587\u4ef6\u5185\u5bb9\u3002\u5982\u9700\u591a\u6b21\u5f15\u7528\uff0c\u8bb0\u4e0b\u884c\u53f7\u7528 offset+limit \u7cbe\u786e\u8bfb\u53d6\u3002'
: '[TSE\xb7POST_GUARD] Read ' + len + ' chars. \u805a\u7126\u76f8\u5173\u6bb5\u843d\uff0c\u907f\u514d\u5f15\u7528\u5927\u6bb5\u539f\u6587\u3002';
} else {
msg = cr
? '[TSE\xb7POST_GUARD] Bash ' + len + ' chars (' + tokens + ' tokens). \u805a\u7126\u9519\u8bef/\u8b66\u544a\u884c\u548c\u6700\u7ec8\u72b6\u6001\uff0c\u5ffd\u7565\u5197\u4f59\u65e5\u5fd7\u3002\u5982\u9700\u5b8c\u6574\u5206\u6790\uff0c\u5199\u5165\u6587\u4ef6\u540e\u5206\u6bb5\u8bfb\u53d6\u3002'
: '[TSE\xb7POST_GUARD] Bash ' + len + ' chars. \u805a\u7126\u5173\u952e\u8f93\u51fa\u884c\uff0c\u8df3\u8fc7\u5197\u4f59\u4fe1\u606f\u3002';
}
process.stdout.write(JSON.stringify({
continue: true,
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: msg }
}));
process.exit(0);
} catch { process.exit(0); }
})();