daemon 空闲自动退出:30 分钟无调用释放内存,下次调用自动拉起
This commit is contained in:
@@ -152,7 +152,7 @@ node ~/.zcode/gatekeeper/gatekeeper.mjs approve --last # 或直接批准最近
|
|||||||
|
|
||||||
## 守护进程(daemon)
|
## 守护进程(daemon)
|
||||||
|
|
||||||
客户端每次调用先探测 `127.0.0.1:47811`(无副作用 ping),daemon 不在就自动拉起。收益:到审查端点的 TLS 连接常驻、决策缓存与任务状态驻内存、同请求并发去重、配置热重载(客户端每次请求带配置文件 mtime 签名,变了即重载)。
|
客户端每次调用先探测 `127.0.0.1:47811`(无副作用 ping),daemon 不在就自动拉起;**无调用 30 分钟后自动退出释放内存**(`daemonIdleMs` 可调,0 = 常驻),下次调用自动重新拉起。收益:到审查端点的 TLS 连接常驻、决策缓存与任务状态驻内存、同请求并发去重、配置热重载(客户端每次请求带配置文件 mtime 签名,变了即重载)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node gatekeeper/gatekeeper.mjs shutdown-daemon # 停止(下次调用自动重新拉起)
|
node gatekeeper/gatekeeper.mjs shutdown-daemon # 停止(下次调用自动重新拉起)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"recentActions": 10,
|
"recentActions": 10,
|
||||||
"daemon": true,
|
"daemon": true,
|
||||||
"daemonPort": 47811,
|
"daemonPort": 47811,
|
||||||
|
"daemonIdleMs": 1800000,
|
||||||
"extraBody": {},
|
"extraBody": {},
|
||||||
"toolAllowlist": [],
|
"toolAllowlist": [],
|
||||||
"autoMode": {
|
"autoMode": {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const DEFAULTS = {
|
|||||||
recentActions: 10, // 提供给审查器的最近工具调用条数(组合风险判定)
|
recentActions: 10, // 提供给审查器的最近工具调用条数(组合风险判定)
|
||||||
daemon: true, // 守护进程模式(客户端自动拉起,连不上则内联降级)
|
daemon: true, // 守护进程模式(客户端自动拉起,连不上则内联降级)
|
||||||
daemonPort: 47811,
|
daemonPort: 47811,
|
||||||
|
daemonIdleMs: 1800000, // daemon 空闲自动退出(ms):无调用 30 分钟后退出释放内存,下次调用自动拉起;0 = 常驻
|
||||||
extraBody: {}, // 深合并进 LLM 请求体的自定义字段(如 chat_template_kwargs.enable_thinking)
|
extraBody: {}, // 深合并进 LLM 请求体的自定义字段(如 chat_template_kwargs.enable_thinking)
|
||||||
toolAllowlist: [],
|
toolAllowlist: [],
|
||||||
autoMode: { allow: [], soft_deny: [], environment: [] }, // 用户自定义规则(非空则整段替换默认)
|
autoMode: { allow: [], soft_deny: [], environment: [] }, // 用户自定义规则(非空则整段替换默认)
|
||||||
@@ -91,6 +92,9 @@ export function loadAll() {
|
|||||||
if (process.env.GATEKEEPER_ON_UNCERTAIN) {
|
if (process.env.GATEKEEPER_ON_UNCERTAIN) {
|
||||||
config.onUncertain = String(process.env.GATEKEEPER_ON_UNCERTAIN).toLowerCase();
|
config.onUncertain = String(process.env.GATEKEEPER_ON_UNCERTAIN).toLowerCase();
|
||||||
}
|
}
|
||||||
|
if (process.env.GATEKEEPER_DAEMON_IDLE_MS != null) {
|
||||||
|
config.daemonIdleMs = parseInt(process.env.GATEKEEPER_DAEMON_IDLE_MS, 10) || 0;
|
||||||
|
}
|
||||||
// 超时预算钳制:LLM 最坏耗时(超时×(重试+1))+ 5s 余量不得超过 35s,
|
// 超时预算钳制:LLM 最坏耗时(超时×(重试+1))+ 5s 余量不得超过 35s,
|
||||||
// 否则内联降级会撞上 hook 超时(PreToolUse 50s / PermissionRequest 45s)被掐成无决策。
|
// 否则内联降级会撞上 hook 超时(PreToolUse 50s / PermissionRequest 45s)被掐成无决策。
|
||||||
const worst = (config.llmTimeoutMs | 0) * ((config.retries | 0) + 1);
|
const worst = (config.llmTimeoutMs | 0) * ((config.retries | 0) + 1);
|
||||||
|
|||||||
@@ -67,12 +67,22 @@ async function handleHook(req) {
|
|||||||
|
|
||||||
// allowHalfOpen: true —— 客户端会半关闭(write+end),hook 判定是异步的(LLM 数秒),
|
// allowHalfOpen: true —— 客户端会半关闭(write+end),hook 判定是异步的(LLM 数秒),
|
||||||
// 默认模式下 Node 会在收到 FIN 时自动关闭本端,响应就写进了一条已死的连接。
|
// 默认模式下 Node 会在收到 FIN 时自动关闭本端,响应就写进了一条已死的连接。
|
||||||
|
// 空闲自动退出:ZCode 关闭/无调用一段时间后自动退出释放内存(~55MB),下次 hook 调用会自动重新拉起
|
||||||
|
let idleTimer = null;
|
||||||
|
function armIdle() {
|
||||||
|
const ms = all.config.daemonIdleMs | 0;
|
||||||
|
if (idleTimer) clearTimeout(idleTimer);
|
||||||
|
if (ms <= 0) return;
|
||||||
|
idleTimer = setTimeout(() => { log('idle ' + Math.round(ms / 1000) + 's — exit to free memory'); process.exit(0); }, ms);
|
||||||
|
}
|
||||||
|
|
||||||
const server = net.createServer({ allowHalfOpen: true }, (socket) => {
|
const server = net.createServer({ allowHalfOpen: true }, (socket) => {
|
||||||
let buf = '';
|
let buf = '';
|
||||||
socket.setEncoding('utf8');
|
socket.setEncoding('utf8');
|
||||||
socket.setTimeout(42_000, () => socket.destroy()); // 兜底:不允许超过 hook 超时
|
socket.setTimeout(42_000, () => socket.destroy()); // 兜底:不允许超过 hook 超时
|
||||||
|
|
||||||
const dispatch = async (line) => {
|
const dispatch = async (line) => {
|
||||||
|
armIdle(); // 任何请求(含 ping)都重置空闲计时
|
||||||
// 协议异常/未知 op 一律返回 {ok:false}(无 exitCode 字段)——客户端据此降级内联做完整审查,
|
// 协议异常/未知 op 一律返回 {ok:false}(无 exitCode 字段)——客户端据此降级内联做完整审查,
|
||||||
// 绝不能返回 {exitCode:0}(会被当作"明确放行"决策,形成 fail-open)。
|
// 绝不能返回 {exitCode:0}(会被当作"明确放行"决策,形成 fail-open)。
|
||||||
let res = { ok: false, error: 'unknown op' };
|
let res = { ok: false, error: 'unknown op' };
|
||||||
@@ -118,7 +128,8 @@ server.on('error', async (e) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.listen(PORT, '127.0.0.1', () => {
|
server.listen(PORT, '127.0.0.1', () => {
|
||||||
log('daemon listening on 127.0.0.1:' + PORT + ' (model=' + all.config.model + ', guard=' + all.config.fullAccessGuard + ')');
|
log('daemon listening on 127.0.0.1:' + PORT + ' (model=' + all.config.model + ', guard=' + all.config.fullAccessGuard + ', idleExit=' + Math.round((all.config.daemonIdleMs|0)/1000) + 's)');
|
||||||
|
armIdle(); // 启动即计时:无任何调用则按时退出
|
||||||
// 预热:开一条到审查端点的 TLS 连接,让第一次真实审查免握手
|
// 预热:开一条到审查端点的 TLS 连接,让第一次真实审查免握手
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user