evolcore 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/claude-runner.js +35 -2
- package/dist/agents/codex-app-server-client.js +120 -14
- package/dist/agents/codex-runner.js +273 -35
- package/dist/agents/ecagent-runner.js +36 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/channels/aun.js +288 -108
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +170 -6
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +31 -31
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +4 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +30 -7
- package/dist/core/auth/operation-catalog.js +187 -25
- package/dist/core/bootstrap-messages.js +18 -0
- package/dist/core/bootstrap-service.js +63 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +147 -35
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +41 -21
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +236 -78
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +1 -0
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/tool-policy.js +73 -30
- package/dist/core/protected-paths.js +7 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +384 -98
- package/dist/ipc.js +218 -9
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +22 -0
- package/kits/templates/roles/member.json +27 -0
- package/kits/templates/roles/visitor.json +33 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/core/command/evol-menu-version-gate.js +0 -39
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## 0.0.12 (2026-08-19)
|
|
7
|
+
|
|
8
|
+
### AUN 通信
|
|
9
|
+
|
|
10
|
+
- 规范结构化提及在消息收发、队列和响应链路中的传递,完善 @all、目标校验和非法载荷处理。
|
|
11
|
+
- 增强入站去重、断线重投和错误回传,避免消息重复处理与错误循环。
|
|
12
|
+
|
|
13
|
+
### 会话与 Agent
|
|
14
|
+
|
|
15
|
+
- 串行化 Codex 会话的继续、压缩、分叉和回滚操作,完善 app-server 进程管理和安全清理。
|
|
16
|
+
- 优化 proactive 会话、Handoff 和 Bootstrap 的生命周期处理,隔离并发会话上下文。
|
|
17
|
+
|
|
18
|
+
### 权限与菜单
|
|
19
|
+
|
|
20
|
+
- 收敛菜单执行上下文和托管命令授权,强化 EC shell 与 Codex 命令解析边界。
|
|
21
|
+
- 完善菜单批量查询、关系上下文、联系人状态和工具拦截审计。
|
|
22
|
+
|
|
23
|
+
### 运行兼容性
|
|
24
|
+
|
|
25
|
+
- 统一跨平台子进程启动和 Windows 窗口处理,改进升级监控与内置 AUN SDK 管理。
|
|
26
|
+
- 统一 Claude、Codex 的技能发现和运行时上下文注入。
|
|
27
|
+
|
|
6
28
|
## 0.0.11 (2026-08-11)
|
|
7
29
|
|
|
8
30
|
### Agent 初始化与引导
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
|
|
4
|
+
const timeoutMs = 1500;
|
|
5
|
+
|
|
6
|
+
function deny(reason) {
|
|
7
|
+
process.stdout.write(JSON.stringify({
|
|
8
|
+
hookSpecificOutput: {
|
|
9
|
+
hookEventName: 'PreToolUse',
|
|
10
|
+
permissionDecision: 'deny',
|
|
11
|
+
permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
// Codex 0.147 app-server runs this managed hook but does not reliably honor
|
|
15
|
+
// the JSON denial alone for thread/shellCommand. Exit 2 is the documented
|
|
16
|
+
// blocking transport; retain the JSON shape for compatible hook consumers.
|
|
17
|
+
process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
|
|
18
|
+
process.exitCode = 2;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function queryDaemon(socketPath, payload) {
|
|
22
|
+
return new Promise(resolve => {
|
|
23
|
+
const socket = net.createConnection(socketPath);
|
|
24
|
+
let buffer = '';
|
|
25
|
+
let settled = false;
|
|
26
|
+
let timer;
|
|
27
|
+
const finish = value => {
|
|
28
|
+
if (settled) return;
|
|
29
|
+
settled = true;
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
socket.destroy();
|
|
32
|
+
resolve(value);
|
|
33
|
+
};
|
|
34
|
+
timer = setTimeout(() => finish(null), timeoutMs);
|
|
35
|
+
timer.unref?.();
|
|
36
|
+
socket.once('error', () => finish(null));
|
|
37
|
+
socket.once('connect', () => socket.write(JSON.stringify(payload) + '\n'));
|
|
38
|
+
socket.on('data', chunk => {
|
|
39
|
+
buffer += String(chunk);
|
|
40
|
+
const index = buffer.indexOf('\n');
|
|
41
|
+
if (index < 0) return;
|
|
42
|
+
try { finish(JSON.parse(buffer.slice(0, index))); } catch { finish(null); }
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const socketPath = process.argv[2];
|
|
49
|
+
let input;
|
|
50
|
+
try {
|
|
51
|
+
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
52
|
+
} catch {
|
|
53
|
+
deny('Invalid Codex PreToolUse payload.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!socketPath
|
|
57
|
+
|| input?.hook_event_name !== 'PreToolUse'
|
|
58
|
+
|| typeof input?.session_id !== 'string'
|
|
59
|
+
|| !input.session_id
|
|
60
|
+
|| typeof input?.tool_name !== 'string'
|
|
61
|
+
|| !input.tool_name) {
|
|
62
|
+
deny('Incomplete Codex PreToolUse payload.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = await queryDaemon(socketPath, {
|
|
67
|
+
type: 'codex-pre-tool-use',
|
|
68
|
+
threadId: input.session_id,
|
|
69
|
+
toolName: typeof input.tool_name === 'string' ? input.tool_name : '',
|
|
70
|
+
toolInput: input.tool_input ?? null,
|
|
71
|
+
});
|
|
72
|
+
if (!result) {
|
|
73
|
+
deny('EvolCore policy service is unavailable.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (result.ok === true && result.applicable === true && result.decision === 'allow') return;
|
|
77
|
+
deny(result.reason || 'EvolCore policy did not explicitly allow this tool call.');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void main();
|
|
@@ -14,7 +14,7 @@ import { logger } from '../utils/logger.js';
|
|
|
14
14
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
15
15
|
import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
16
16
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
17
|
-
import { summarizeToolInput } from '../utils/tool-summary.js';
|
|
17
|
+
import { summarizeToolInput, summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
18
18
|
import { encodePath } from '../utils/cross-platform.js';
|
|
19
19
|
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
20
20
|
import { resolveEffective } from '../config/config-manager.js';
|
|
@@ -1852,6 +1852,27 @@ export class AgentRunner {
|
|
|
1852
1852
|
...(updatedInput ? { updatedInput } : {}),
|
|
1853
1853
|
},
|
|
1854
1854
|
});
|
|
1855
|
+
const recordPreflightDenial = async (toolName, toolInput, preflight, requestId, policyCode) => {
|
|
1856
|
+
const code = policyCode ?? (preflight?.behavior === 'deny' ? preflight.policyCode : undefined);
|
|
1857
|
+
if (!code)
|
|
1858
|
+
return;
|
|
1859
|
+
try {
|
|
1860
|
+
await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
|
|
1861
|
+
code: 'operation_blocked',
|
|
1862
|
+
severity: 'warning',
|
|
1863
|
+
phase: 'execution',
|
|
1864
|
+
occurredAt: Date.now(),
|
|
1865
|
+
toolName,
|
|
1866
|
+
...(typeof requestId === 'string' && requestId ? { requestId } : {}),
|
|
1867
|
+
policyCode: code,
|
|
1868
|
+
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1869
|
+
effect: 'operation_skipped',
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
catch (error) {
|
|
1873
|
+
logger.warn(`[AgentRunner] failed to record preflight denial: session=${sessionId} tool=${toolName} code=${code} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1855
1876
|
// A rewritten helper command is trusted only after this hook produced its
|
|
1856
1877
|
// random one-shot payload from a validated output wrapper.
|
|
1857
1878
|
const trustedSafeOutputCommands = new Set();
|
|
@@ -1896,6 +1917,7 @@ export class AgentRunner {
|
|
|
1896
1917
|
// proactive 模式行为策略(首次工具调用必须是 ec msg send)
|
|
1897
1918
|
const policyResult = this.permissionContexts.get(sessionId)?.policyHook?.(toolName, toolInput);
|
|
1898
1919
|
if (policyResult?.block) {
|
|
1920
|
+
await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, policyResult.policyCode ?? 'session_policy_hook');
|
|
1899
1921
|
return hookDecision('deny', policyResult.reason || '当前会话策略拒绝此工具调用', updatedInput);
|
|
1900
1922
|
}
|
|
1901
1923
|
const permCtx = this.permissionContexts.get(sessionId);
|
|
@@ -1916,8 +1938,10 @@ export class AgentRunner {
|
|
|
1916
1938
|
toolInput = preflight.input;
|
|
1917
1939
|
if (toolInput !== originalInput)
|
|
1918
1940
|
updatedInput = toolInput;
|
|
1919
|
-
if (preflight.behavior === 'deny')
|
|
1941
|
+
if (preflight.behavior === 'deny') {
|
|
1942
|
+
await recordPreflightDenial(toolName, toolInput, preflight, input.tool_use_id ?? input.toolUseID);
|
|
1920
1943
|
return hookDecision('deny', preflight.message, updatedInput);
|
|
1944
|
+
}
|
|
1921
1945
|
if (preflight.behavior === 'allow') {
|
|
1922
1946
|
const commandAfterPreflight = typeof toolInput.command === 'string' ? toolInput.command : undefined;
|
|
1923
1947
|
if (commandAfterPreflight && commandAfterPreflight !== commandBeforePreflight) {
|
|
@@ -1942,6 +1966,7 @@ export class AgentRunner {
|
|
|
1942
1966
|
};
|
|
1943
1967
|
const roResult = checkReadonly(toolName, toolInput, projectPath, readonlyContext);
|
|
1944
1968
|
if (roResult.behavior === 'deny') {
|
|
1969
|
+
await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'readonly_mode');
|
|
1945
1970
|
return hookDecision('deny', roResult.message, updatedInput);
|
|
1946
1971
|
}
|
|
1947
1972
|
return hookDecision('allow', undefined, updatedInput);
|
|
@@ -1951,6 +1976,7 @@ export class AgentRunner {
|
|
|
1951
1976
|
}
|
|
1952
1977
|
if (isUnknownClaudeTool(toolName)) {
|
|
1953
1978
|
if (callPermissionMode === 'auto') {
|
|
1979
|
+
await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'auto_unknown_tool');
|
|
1954
1980
|
return hookDecision('deny', `auto 模式拒绝未知工具 ${toolName}:该工具不受 Claude 本地 sandbox 的完整约束`, updatedInput);
|
|
1955
1981
|
}
|
|
1956
1982
|
// request/bypass only receive external tools admitted by the session's
|
|
@@ -1960,6 +1986,7 @@ export class AgentRunner {
|
|
|
1960
1986
|
const danger = checkDangerousCommand(toolName, toolInput);
|
|
1961
1987
|
if (danger.isDangerous) {
|
|
1962
1988
|
if (callPermissionMode === 'auto') {
|
|
1989
|
+
await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'auto_dangerous_command');
|
|
1963
1990
|
return hookDecision('deny', `危险操作已由 auto 模式拒绝:${danger.reason}`, updatedInput);
|
|
1964
1991
|
}
|
|
1965
1992
|
if (callPermissionMode === 'request' || callPermissionMode === 'bypass') {
|
|
@@ -2030,6 +2057,7 @@ export class AgentRunner {
|
|
|
2030
2057
|
});
|
|
2031
2058
|
input = preflight.input;
|
|
2032
2059
|
if (preflight.behavior === 'deny') {
|
|
2060
|
+
await recordPreflightDenial(toolName, input, preflight, options.toolUseID);
|
|
2033
2061
|
return { behavior: 'deny', message: preflight.message, decisionClassification: 'user_reject' };
|
|
2034
2062
|
}
|
|
2035
2063
|
if (preflight.behavior === 'allow') {
|
|
@@ -2041,6 +2069,7 @@ export class AgentRunner {
|
|
|
2041
2069
|
}
|
|
2042
2070
|
const permissionExpansion = inspectClaudePermissionExpansion(input, options, projectPath);
|
|
2043
2071
|
if (permissionExpansion.protectedReason) {
|
|
2072
|
+
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'protected_permission_expansion');
|
|
2044
2073
|
return {
|
|
2045
2074
|
behavior: 'deny',
|
|
2046
2075
|
message: permissionExpansion.protectedReason,
|
|
@@ -2049,6 +2078,7 @@ export class AgentRunner {
|
|
|
2049
2078
|
}
|
|
2050
2079
|
if (permissionExpansion.requiresApproval) {
|
|
2051
2080
|
if (callPermissionMode === 'readonly' || callPermissionMode === 'auto') {
|
|
2081
|
+
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, callPermissionMode === 'readonly' ? 'readonly_permission_expansion' : 'auto_permission_expansion');
|
|
2052
2082
|
return {
|
|
2053
2083
|
behavior: 'deny',
|
|
2054
2084
|
message: `${callPermissionMode} 模式拒绝 Claude SDK 文件系统或未知权限扩权请求`,
|
|
@@ -2092,6 +2122,7 @@ export class AgentRunner {
|
|
|
2092
2122
|
};
|
|
2093
2123
|
const roResult = checkReadonly(toolName, input, projectPath, readonlyContext);
|
|
2094
2124
|
if (roResult.behavior === 'deny') {
|
|
2125
|
+
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'readonly_mode');
|
|
2095
2126
|
return { behavior: 'deny', message: roResult.message, decisionClassification: 'user_reject' };
|
|
2096
2127
|
}
|
|
2097
2128
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
|
@@ -2101,6 +2132,7 @@ export class AgentRunner {
|
|
|
2101
2132
|
}
|
|
2102
2133
|
if (isUnknownClaudeTool(toolName)) {
|
|
2103
2134
|
if (callPermissionMode === 'auto') {
|
|
2135
|
+
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'auto_unknown_tool');
|
|
2104
2136
|
return {
|
|
2105
2137
|
behavior: 'deny',
|
|
2106
2138
|
message: `auto 模式拒绝未知工具 ${toolName}:该工具不受 Claude 本地 sandbox 的完整约束`,
|
|
@@ -2112,6 +2144,7 @@ export class AgentRunner {
|
|
|
2112
2144
|
const danger = checkDangerousCommand(toolName, input);
|
|
2113
2145
|
if (danger.isDangerous) {
|
|
2114
2146
|
if (callPermissionMode === 'auto') {
|
|
2147
|
+
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'auto_dangerous_command');
|
|
2115
2148
|
return { behavior: 'deny', message: `auto 模式拒绝危险操作:${danger.reason}`, decisionClassification: 'user_reject' };
|
|
2116
2149
|
}
|
|
2117
2150
|
if (callPermissionMode === 'request' || callPermissionMode === 'bypass') {
|
|
@@ -5,6 +5,11 @@ import { buildHClassGuardCommand } from '../core/permission/sandbox-runtime.js';
|
|
|
5
5
|
import { resolveCodexLaunchCommand } from '../utils/codex-cli.js';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
7
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
12
|
+
import { registerCodexAppServerProcess, unregisterCodexAppServerProcess, } from '../utils/codex-app-server-registry.js';
|
|
8
13
|
const BUILT_IN_CODEX_PROVIDER_IDS = new Set(['openai']);
|
|
9
14
|
function tomlProviderKey(value) {
|
|
10
15
|
if (!value || /[\0\r\n]/.test(value)) {
|
|
@@ -41,6 +46,8 @@ export class CodexAppServerClient {
|
|
|
41
46
|
initialized = false;
|
|
42
47
|
initializing = null;
|
|
43
48
|
stderrBuffer = [];
|
|
49
|
+
managedHookRuntimeDir = null;
|
|
50
|
+
processRegistrationPath = null;
|
|
44
51
|
constructor(options) {
|
|
45
52
|
this.options = options;
|
|
46
53
|
}
|
|
@@ -298,25 +305,33 @@ export class CodexAppServerClient {
|
|
|
298
305
|
pending.reject(new Error('Codex app-server closed'));
|
|
299
306
|
}
|
|
300
307
|
this.pending.clear();
|
|
301
|
-
if (!proc)
|
|
308
|
+
if (!proc) {
|
|
309
|
+
this.cleanupManagedHookRuntime();
|
|
302
310
|
return;
|
|
311
|
+
}
|
|
303
312
|
// 优雅关闭:先关闭 stdin,然后发送 SIGTERM,等待进程退出
|
|
304
313
|
proc.stdin.end();
|
|
305
314
|
proc.kill('SIGTERM');
|
|
306
315
|
// 等待进程退出,最多等待 5 秒
|
|
307
316
|
await new Promise((resolve) => {
|
|
317
|
+
let exited = false;
|
|
308
318
|
const timeout = setTimeout(() => {
|
|
309
|
-
if (
|
|
310
|
-
|
|
319
|
+
if (exited)
|
|
320
|
+
return;
|
|
321
|
+
logger.warn('[CodexAppServer] Process did not exit after SIGTERM, sending SIGKILL');
|
|
322
|
+
try {
|
|
311
323
|
proc.kill('SIGKILL');
|
|
312
324
|
}
|
|
325
|
+
catch { }
|
|
313
326
|
resolve();
|
|
314
327
|
}, 5000);
|
|
315
328
|
proc.once('exit', () => {
|
|
329
|
+
exited = true;
|
|
316
330
|
clearTimeout(timeout);
|
|
317
331
|
resolve();
|
|
318
332
|
});
|
|
319
333
|
});
|
|
334
|
+
this.cleanupManagedHookRuntime();
|
|
320
335
|
}
|
|
321
336
|
async ensureStarted() {
|
|
322
337
|
if (this.initialized)
|
|
@@ -352,22 +367,56 @@ export class CodexAppServerClient {
|
|
|
352
367
|
const launchCommand = resolveCodexLaunchCommand(args);
|
|
353
368
|
if (!launchCommand)
|
|
354
369
|
throw new Error('Codex CLI not found');
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
370
|
+
try {
|
|
371
|
+
let command = launchCommand.command;
|
|
372
|
+
let commandArgs = launchCommand.args;
|
|
373
|
+
if (process.platform === 'linux') {
|
|
374
|
+
const managedRequirements = this.ensureManagedHookRuntime();
|
|
375
|
+
const guardedCommand = buildHClassGuardCommand(command, commandArgs, undefined, {
|
|
376
|
+
managedCodexRequirements: managedRequirements,
|
|
377
|
+
});
|
|
378
|
+
if (!guardedCommand) {
|
|
379
|
+
throw new Error('Codex 缺少 EvolCore managed-hook 隔离运行时,已拒绝启动 app-server');
|
|
380
|
+
}
|
|
381
|
+
command = guardedCommand.command;
|
|
382
|
+
commandArgs = guardedCommand.args;
|
|
383
|
+
}
|
|
384
|
+
this.proc = spawn(command, commandArgs, {
|
|
385
|
+
cwd: this.options.cwd,
|
|
386
|
+
env,
|
|
387
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
388
|
+
windowsHide: process.platform === 'win32',
|
|
389
|
+
});
|
|
390
|
+
try {
|
|
391
|
+
this.processRegistrationPath = registerCodexAppServerProcess(this.proc.pid);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
logger.warn(`[CodexAppServer] failed to register owned process pid=${this.proc.pid ?? '<unknown>'}: ${error}`);
|
|
395
|
+
}
|
|
358
396
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
397
|
+
catch (error) {
|
|
398
|
+
this.cleanupManagedHookRuntime();
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
const proc = this.proc;
|
|
402
|
+
const processRegistrationPath = this.processRegistrationPath;
|
|
403
|
+
proc.once('error', error => {
|
|
404
|
+
unregisterCodexAppServerProcess(processRegistrationPath);
|
|
405
|
+
if (this.processRegistrationPath === processRegistrationPath) {
|
|
406
|
+
this.processRegistrationPath = null;
|
|
407
|
+
}
|
|
408
|
+
this.failAll(error);
|
|
363
409
|
});
|
|
364
|
-
|
|
365
|
-
|
|
410
|
+
proc.once('exit', (code, signal) => {
|
|
411
|
+
unregisterCodexAppServerProcess(processRegistrationPath);
|
|
412
|
+
if (this.processRegistrationPath === processRegistrationPath) {
|
|
413
|
+
this.processRegistrationPath = null;
|
|
414
|
+
}
|
|
366
415
|
this.failAll(new Error(`Codex app-server exited: code=${code ?? 'null'} signal=${signal ?? 'null'}`));
|
|
367
416
|
});
|
|
368
|
-
const rl = readline.createInterface({ input:
|
|
417
|
+
const rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
|
|
369
418
|
rl.on('line', line => this.handleLine(line));
|
|
370
|
-
|
|
419
|
+
proc.stderr.on('data', chunk => {
|
|
371
420
|
const text = String(chunk).trim();
|
|
372
421
|
if (!text)
|
|
373
422
|
return;
|
|
@@ -377,6 +426,62 @@ export class CodexAppServerClient {
|
|
|
377
426
|
logger.debug(`[CodexAppServer] ${text}`);
|
|
378
427
|
});
|
|
379
428
|
}
|
|
429
|
+
ensureManagedHookRuntime() {
|
|
430
|
+
if (this.managedHookRuntimeDir) {
|
|
431
|
+
return path.join(this.managedHookRuntimeDir, 'requirements.toml');
|
|
432
|
+
}
|
|
433
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'evolcore-codex-hooks-'));
|
|
434
|
+
try {
|
|
435
|
+
fs.chmodSync(directory, 0o700);
|
|
436
|
+
const hookPath = path.join(directory, 'pre-tool-use.mjs');
|
|
437
|
+
const sourcePath = path.join(getPackageRoot(), 'bin', 'codex-managed-hook.mjs');
|
|
438
|
+
fs.copyFileSync(sourcePath, hookPath, fs.constants.COPYFILE_EXCL);
|
|
439
|
+
fs.chmodSync(hookPath, 0o500);
|
|
440
|
+
const command = `${this.shellQuote(process.execPath)} ${this.shellQuote(hookPath)} ${this.shellQuote(resolvePaths().instanceSocket)}`;
|
|
441
|
+
const requirementsPath = path.join(directory, 'requirements.toml');
|
|
442
|
+
fs.writeFileSync(requirementsPath, [
|
|
443
|
+
'allow_managed_hooks_only = true',
|
|
444
|
+
'',
|
|
445
|
+
'[features]',
|
|
446
|
+
'hooks = true',
|
|
447
|
+
'',
|
|
448
|
+
'[hooks]',
|
|
449
|
+
`managed_dir = ${JSON.stringify(directory)}`,
|
|
450
|
+
'',
|
|
451
|
+
'[[hooks.PreToolUse]]',
|
|
452
|
+
'matcher = "*"',
|
|
453
|
+
'',
|
|
454
|
+
'[[hooks.PreToolUse.hooks]]',
|
|
455
|
+
'type = "command"',
|
|
456
|
+
`command = ${JSON.stringify(command)}`,
|
|
457
|
+
'timeout = 3',
|
|
458
|
+
'statusMessage = "Checking EvolCore session policy"',
|
|
459
|
+
'',
|
|
460
|
+
].join('\n'), { mode: 0o400, flag: 'wx' });
|
|
461
|
+
this.managedHookRuntimeDir = directory;
|
|
462
|
+
return requirementsPath;
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
try {
|
|
466
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
467
|
+
}
|
|
468
|
+
catch { }
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
shellQuote(value) {
|
|
473
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
474
|
+
}
|
|
475
|
+
cleanupManagedHookRuntime() {
|
|
476
|
+
const directory = this.managedHookRuntimeDir;
|
|
477
|
+
this.managedHookRuntimeDir = null;
|
|
478
|
+
if (!directory || !path.basename(directory).startsWith('evolcore-codex-hooks-'))
|
|
479
|
+
return;
|
|
480
|
+
try {
|
|
481
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
482
|
+
}
|
|
483
|
+
catch { }
|
|
484
|
+
}
|
|
380
485
|
buildProcessEnvironment() {
|
|
381
486
|
const env = sanitizeShellExecutionEnvironment({
|
|
382
487
|
...process.env,
|
|
@@ -571,5 +676,6 @@ export class CodexAppServerClient {
|
|
|
571
676
|
pending.reject(error);
|
|
572
677
|
}
|
|
573
678
|
this.pending.clear();
|
|
679
|
+
this.cleanupManagedHookRuntime();
|
|
574
680
|
}
|
|
575
681
|
}
|