171 lines
4.7 KiB
JavaScript
171 lines
4.7 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* 逃生舱状态管理 (Phase 0)
|
|||
|
|
*
|
|||
|
|
* 管理 /force, /checks, /reset 用户覆盖。
|
|||
|
|
*
|
|||
|
|
* 状态文件: debug/user-overrides.json
|
|||
|
|
* 自动过期: 1 小时(匹配 session-memory.js MAX_SESSION_AGE)
|
|||
|
|
* /force: 单次生效(compliance-gate 放行后自动清除)
|
|||
|
|
* /checks off: 会话级(手动 /checks on 或 /reset 或过期后恢复)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
// 路径解析
|
|||
|
|
let overridesPath, debugDir;
|
|||
|
|
try {
|
|||
|
|
const { PATHS } = require('./paths.config.js');
|
|||
|
|
overridesPath = PATHS.userOverridesJson;
|
|||
|
|
debugDir = PATHS.debugDir;
|
|||
|
|
} catch {
|
|||
|
|
const root = path.resolve(__dirname, '..');
|
|||
|
|
debugDir = path.join(root, 'debug');
|
|||
|
|
overridesPath = path.join(debugDir, 'user-overrides.json');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const MAX_AGE_MS = 60 * 60 * 1000; // 1 小时
|
|||
|
|
|
|||
|
|
// ─── 内部辅助 ─────────────────────────────────────────
|
|||
|
|
|
|||
|
|
function ensureDir() {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 读取覆盖状态(检查过期)
|
|||
|
|
* @returns {Object} overrides 状态
|
|||
|
|
*/
|
|||
|
|
function readState() {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(overridesPath)) return null;
|
|||
|
|
const raw = fs.readFileSync(overridesPath, 'utf8');
|
|||
|
|
const state = JSON.parse(raw);
|
|||
|
|
|
|||
|
|
// 检查过期
|
|||
|
|
if (state.expiresAt && new Date(state.expiresAt).getTime() < Date.now()) {
|
|||
|
|
// 过期 → 清除文件
|
|||
|
|
try { fs.unlinkSync(overridesPath); } catch {}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return state;
|
|||
|
|
} catch {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 写入覆盖状态
|
|||
|
|
* @param {Object} overrides - { force, checksOff, forceSkill }
|
|||
|
|
*/
|
|||
|
|
function writeState(overrides) {
|
|||
|
|
ensureDir();
|
|||
|
|
const now = new Date();
|
|||
|
|
const state = {
|
|||
|
|
createdAt: now.toISOString(),
|
|||
|
|
expiresAt: new Date(now.getTime() + MAX_AGE_MS).toISOString(),
|
|||
|
|
overrides: {
|
|||
|
|
force: overrides.force || false,
|
|||
|
|
checksOff: overrides.checksOff || false,
|
|||
|
|
forceSkill: overrides.forceSkill || null,
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
fs.writeFileSync(overridesPath, JSON.stringify(state, null, 2) + '\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 公开 API ─────────────────────────────────────────
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置 force 模式(单次生效)
|
|||
|
|
* @param {string} [skillName] - 可选指定技能
|
|||
|
|
*/
|
|||
|
|
function setForce(skillName) {
|
|||
|
|
const current = readState();
|
|||
|
|
const overrides = current ? current.overrides : { force: false, checksOff: false, forceSkill: null };
|
|||
|
|
overrides.force = true;
|
|||
|
|
overrides.forceSkill = skillName || null;
|
|||
|
|
writeState(overrides);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置 checks 开关
|
|||
|
|
* @param {boolean} enabled - true=开启检查, false=关闭检查
|
|||
|
|
*/
|
|||
|
|
function setChecks(enabled) {
|
|||
|
|
const current = readState();
|
|||
|
|
const overrides = current ? current.overrides : { force: false, checksOff: false, forceSkill: null };
|
|||
|
|
overrides.checksOff = !enabled;
|
|||
|
|
writeState(overrides);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 重置所有覆盖
|
|||
|
|
*/
|
|||
|
|
function resetAll() {
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(overridesPath)) fs.unlinkSync(overridesPath);
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查 force 是否激活
|
|||
|
|
* @returns {{ active: boolean, skill?: string }}
|
|||
|
|
*/
|
|||
|
|
function isForceActive() {
|
|||
|
|
const state = readState();
|
|||
|
|
if (!state || !state.overrides || !state.overrides.force) {
|
|||
|
|
return { active: false };
|
|||
|
|
}
|
|||
|
|
return { active: true, skill: state.overrides.forceSkill || undefined };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查 checks 是否被关闭
|
|||
|
|
* @returns {boolean}
|
|||
|
|
*/
|
|||
|
|
function isChecksDisabled() {
|
|||
|
|
const state = readState();
|
|||
|
|
if (!state || !state.overrides) return false;
|
|||
|
|
return state.overrides.checksOff === true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除 force 标志(compliance-gate 放行后调用)
|
|||
|
|
*/
|
|||
|
|
function clearForce() {
|
|||
|
|
const state = readState();
|
|||
|
|
if (!state || !state.overrides) return;
|
|||
|
|
state.overrides.force = false;
|
|||
|
|
state.overrides.forceSkill = null;
|
|||
|
|
writeState(state.overrides);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 导出 ─────────────────────────────────────────────
|
|||
|
|
module.exports = {
|
|||
|
|
setForce,
|
|||
|
|
setChecks,
|
|||
|
|
resetAll,
|
|||
|
|
isForceActive,
|
|||
|
|
isChecksDisabled,
|
|||
|
|
clearForce,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// CLI: 直接运行时打印当前状态
|
|||
|
|
if (require.main === module) {
|
|||
|
|
const state = readState();
|
|||
|
|
console.log('=== User Overrides ===');
|
|||
|
|
console.log(`Path: ${overridesPath}`);
|
|||
|
|
if (!state) {
|
|||
|
|
console.log('Status: No active overrides');
|
|||
|
|
} else {
|
|||
|
|
console.log(`Created: ${state.createdAt}`);
|
|||
|
|
console.log(`Expires: ${state.expiresAt}`);
|
|||
|
|
console.log(`Force: ${state.overrides.force ? 'ON' + (state.overrides.forceSkill ? ` (${state.overrides.forceSkill})` : '') : 'OFF'}`);
|
|||
|
|
console.log(`Checks: ${state.overrides.checksOff ? 'DISABLED' : 'ENABLED'}`);
|
|||
|
|
}
|
|||
|
|
}
|