evolcore 0.0.17 → 0.0.19
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 +51 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +113 -24
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +43 -24
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/p2p.js +20 -8
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +82 -14
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/data-migration.js +10 -4
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +66 -8
- package/dist/core/message/response-engine.js +147 -10
- package/dist/core/permission/ec-command-parser.js +148 -22
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +19 -7
- package/dist/index.js +357 -48
- package/dist/ipc.js +81 -5
- package/dist/paths.js +0 -3
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/docs/path-registry.md +1 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/02-navigation.md +2 -2
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,57 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## 0.0.19 (2026-08-27)
|
|
7
|
+
|
|
8
|
+
### PowerShell EC 命令安全
|
|
9
|
+
|
|
10
|
+
- 支持 PowerShell `pwsh`/`powershell` 受管命令载体解析,拒绝拼接、展开和重定向。
|
|
11
|
+
- 统一 Codex EC 命令委派与预检解析,补充 PowerShell 引号、展开和错误指引。
|
|
12
|
+
|
|
13
|
+
### Bootstrap 与运行时控制
|
|
14
|
+
|
|
15
|
+
- Bootstrap 阶段保留 Menu 与斜杠协议处理,普通模型任务继续按生命周期门禁。
|
|
16
|
+
- 命令意外泄漏至响应引擎时统一丢弃并记录失败审计,避免进入 Agent 执行。
|
|
17
|
+
|
|
18
|
+
### 跨平台安装与诊断
|
|
19
|
+
|
|
20
|
+
- Windows 数据迁移固定调用系统 `bsdtar`,避免 Git Bash GNU tar 误解析 Windows 绝对路径。
|
|
21
|
+
- Linux 安装器幂等创建真实 `/etc/codex` 目录,拒绝同名文件和符号链接。
|
|
22
|
+
- 统一数据根解析,仅使用 `EVOLCORE_HOME` 或 `~/.evolcore`,避免因启动目录不同误用数据目录。
|
|
23
|
+
- `ec start` 已运行提示新增 Daemon 持续运行时长,并支持 IPC uptime 回退探测。
|
|
24
|
+
|
|
25
|
+
## 0.0.18 (2026-08-27)
|
|
26
|
+
|
|
27
|
+
### DaemonOwner 与受管 Agent 操作
|
|
28
|
+
|
|
29
|
+
- 新增 `daemon-owner` 进程角色,仅由 `daemon.json.owners` 的控制 AID 解析。
|
|
30
|
+
- 进程操作、跨 Agent Trigger 与内部 Trigger 目录统一要求 DaemonOwner。
|
|
31
|
+
- Agent channel 保持仅能操作自身;拒绝结果附带原因码与下一步指引。
|
|
32
|
+
- 受管任务通过委派 IPC 执行授权的 `ec agent` 查询、非交互创建和生命周期操作。
|
|
33
|
+
|
|
34
|
+
### 任务暂停与 Bootstrap 门禁
|
|
35
|
+
|
|
36
|
+
- 新增 `/pause`、`/resume`,在下一次工具调用前暂停或恢复当前任务。
|
|
37
|
+
- Codex、Claude 与 ecagent 的工具边界均接入暂停门禁;任务结束自动解除。
|
|
38
|
+
- Agent 仅在 `active`、`bootstrapping` 生命周期运行,不再回退默认 Runner。
|
|
39
|
+
- Bootstrap 隔离普通任务与上下文,仅允许受限的 `agent.md` 确认、发布和 ready。
|
|
40
|
+
- 首次欢迎消息按实际私聊或群聊路由持久化投递。
|
|
41
|
+
|
|
42
|
+
### AUN 投递与连接可靠性
|
|
43
|
+
|
|
44
|
+
- 群消息恢复支持有界多页补拉,避免尾部同步后遗留大积压。
|
|
45
|
+
- 适配新版 AUN 连接状态事件,隔离旧客户端事件并记录传输/入站时间。
|
|
46
|
+
- 受管沙箱发送 AUN 消息改经配置存储读取超时和默认加密设置。
|
|
47
|
+
|
|
48
|
+
### 审计、安全与跨平台运行
|
|
49
|
+
|
|
50
|
+
- 菜单响应新增 `processing_ms` 与独立的协议业务流日志。
|
|
51
|
+
- 工具与授权审计新增生命周期键、执行状态和敏感参数脱敏。
|
|
52
|
+
- 统一 Codex 受管 `PreToolUse` Hook,强化 Bubblewrap 挂载与失败熔断诊断。
|
|
53
|
+
- 原子写入保留正式文件并刷新热备,避免受保护状态挂载竞态。
|
|
54
|
+
- `ec stop`、`ec restart` 优先经 IPC 请求守护进程优雅退出并校验 PID。
|
|
55
|
+
- Windows 自启增加计划任务校验、用户级 Run 回退和非 EvolCore 配置保护。
|
|
56
|
+
|
|
6
57
|
## 0.0.17 (2026-08-25)
|
|
7
58
|
|
|
8
59
|
### Claude 与任务边界
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import net from 'node:net';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Only bound socket establishment. Once connected, the official Codex hook
|
|
5
|
+
// timeout owns the lifetime of this process; an application timeout here would
|
|
6
|
+
// turn /pause into a denial/allow race.
|
|
7
|
+
const connectTimeoutMs = 1500;
|
|
5
8
|
|
|
6
9
|
function deny(reason) {
|
|
7
10
|
process.stdout.write(JSON.stringify({
|
|
@@ -11,9 +14,8 @@ function deny(reason) {
|
|
|
11
14
|
permissionDecisionReason: reason || 'EvolCore policy denied this tool call.',
|
|
12
15
|
},
|
|
13
16
|
}));
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// blocking transport; retain the JSON shape for compatible hook consumers.
|
|
17
|
+
// Exit 2 is the documented blocking transport. Keep the structured JSON as
|
|
18
|
+
// well so Codex can surface the policy reason through the PreToolUse result.
|
|
17
19
|
process.stderr.write(`${reason || 'EvolCore policy denied this tool call.'}\n`);
|
|
18
20
|
process.exitCode = 2;
|
|
19
21
|
}
|
|
@@ -24,6 +26,7 @@ function queryDaemon(socketPath, payload) {
|
|
|
24
26
|
let buffer = '';
|
|
25
27
|
let settled = false;
|
|
26
28
|
let timer;
|
|
29
|
+
let connected = false;
|
|
27
30
|
const finish = value => {
|
|
28
31
|
if (settled) return;
|
|
29
32
|
settled = true;
|
|
@@ -31,10 +34,16 @@ function queryDaemon(socketPath, payload) {
|
|
|
31
34
|
socket.destroy();
|
|
32
35
|
resolve(value);
|
|
33
36
|
};
|
|
34
|
-
timer = setTimeout(() =>
|
|
37
|
+
timer = setTimeout(() => {
|
|
38
|
+
if (!connected) finish(null);
|
|
39
|
+
}, connectTimeoutMs);
|
|
35
40
|
timer.unref?.();
|
|
36
41
|
socket.once('error', () => finish(null));
|
|
37
|
-
socket.once('connect', () =>
|
|
42
|
+
socket.once('connect', () => {
|
|
43
|
+
connected = true;
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
socket.write(JSON.stringify(payload) + '\n');
|
|
46
|
+
});
|
|
38
47
|
socket.on('data', chunk => {
|
|
39
48
|
buffer += String(chunk);
|
|
40
49
|
const index = buffer.indexOf('\n');
|
|
@@ -69,7 +78,7 @@ async function main() {
|
|
|
69
78
|
const result = await queryDaemon(socketPath, {
|
|
70
79
|
type: 'codex-pre-tool-use',
|
|
71
80
|
threadId: input.session_id,
|
|
72
|
-
toolName:
|
|
81
|
+
toolName: input.tool_name,
|
|
73
82
|
toolInput: input.tool_input ?? null,
|
|
74
83
|
});
|
|
75
84
|
if (!result) {
|
|
@@ -151,7 +151,7 @@ function main() {
|
|
|
151
151
|
const targetDirectory = argument('--managed-dir')
|
|
152
152
|
|| path.join(path.dirname(requirements), 'EvolCore', 'hooks');
|
|
153
153
|
const sourceHook = path.join(packageRoot, 'bin', 'codex-managed-hook.mjs');
|
|
154
|
-
if (!fs.statSync(sourceHook).isFile()) fail(`
|
|
154
|
+
if (!fs.existsSync(sourceHook) || !fs.statSync(sourceHook).isFile()) fail(`Codex PreToolUse hook source was not found: ${sourceHook}`);
|
|
155
155
|
|
|
156
156
|
fs.mkdirSync(targetDirectory, { recursive: true, mode: 0o700 });
|
|
157
157
|
const hookPath = path.join(targetDirectory, 'pre-tool-use.mjs');
|
|
@@ -180,7 +180,9 @@ function main() {
|
|
|
180
180
|
'type = "command"',
|
|
181
181
|
`command = ${tomlString(command)}`,
|
|
182
182
|
`command_windows = ${tomlString(command)}`,
|
|
183
|
-
|
|
183
|
+
// Intentionally omit timeout: Codex applies its documented 600-second
|
|
184
|
+
// default for synchronous PreToolUse hooks. The application pause gate
|
|
185
|
+
// itself has no timeout; Codex remains the outer infrastructure ceiling.
|
|
184
186
|
'statusMessage = "Checking EvolCore session policy"',
|
|
185
187
|
markerEnd,
|
|
186
188
|
'',
|
|
@@ -23,9 +23,9 @@ import { sanitizeSessionTitle } from '../core/session/session-title.js';
|
|
|
23
23
|
import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/capability-manager.js';
|
|
24
24
|
import { normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
25
25
|
import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, isSameOrDescendant, resolveProtectedCandidate, } from '../core/protected-paths.js';
|
|
26
|
-
import { buildHClassGuardCommand, createSandboxInitializationError, isSandboxInitializationFailure, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
|
|
26
|
+
import { buildHClassGuardCommand, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, recordSandboxInitializationFailure, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
|
|
27
27
|
import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
|
|
28
|
-
import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
28
|
+
import { auditToolInfrastructureFailure, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
29
29
|
import { isManagedSessionRuntimeDir } from '../cli/task-context.js';
|
|
30
30
|
import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
|
|
31
31
|
export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
|
|
@@ -626,6 +626,40 @@ export class AgentRunner {
|
|
|
626
626
|
/** 每个 session 最近的子进程 stderr 行(环形缓冲),用于子进程崩溃时还原真正原因 */
|
|
627
627
|
recentStderr = new Map();
|
|
628
628
|
static STDERR_BUFFER_MAX = 80;
|
|
629
|
+
async recordSandboxInfrastructureFailure(sessionId, inputId, permissionMode, error, stderr = []) {
|
|
630
|
+
const detail = createSandboxInitializationError(error, stderr);
|
|
631
|
+
const circuit = recordSandboxInitializationFailure(detail, stderr);
|
|
632
|
+
const context = this.permissionContexts.get(sessionId);
|
|
633
|
+
auditToolInfrastructureFailure({
|
|
634
|
+
toolName: 'Bash',
|
|
635
|
+
policyCode: SANDBOX_INITIALIZATION_FAILED,
|
|
636
|
+
reason: detail.message.slice(0, 512),
|
|
637
|
+
sessionId,
|
|
638
|
+
agentAid: context?.selfAid,
|
|
639
|
+
permissionMode,
|
|
640
|
+
role: context?.role,
|
|
641
|
+
callId: inputId,
|
|
642
|
+
taskId: context?.taskId,
|
|
643
|
+
occurrences: circuit.occurrences,
|
|
644
|
+
});
|
|
645
|
+
await context?.recordExecutionAnomaly?.({
|
|
646
|
+
code: 'capability_unavailable',
|
|
647
|
+
severity: 'warning',
|
|
648
|
+
phase: 'execution',
|
|
649
|
+
occurredAt: Date.now(),
|
|
650
|
+
correlationId: inputId,
|
|
651
|
+
requestId: inputId,
|
|
652
|
+
sessionId,
|
|
653
|
+
toolName: 'Bash',
|
|
654
|
+
policyCode: SANDBOX_INITIALIZATION_FAILED,
|
|
655
|
+
decisionSource: 'infrastructure',
|
|
656
|
+
agentAid: context?.selfAid,
|
|
657
|
+
permissionMode: permissionMode,
|
|
658
|
+
summary: detail.message.slice(0, 512),
|
|
659
|
+
effect: 'operation_skipped',
|
|
660
|
+
});
|
|
661
|
+
return { detail, occurrences: circuit.occurrences };
|
|
662
|
+
}
|
|
629
663
|
trackSandboxSettingsFile(sessionId, filePath) {
|
|
630
664
|
const files = this.sandboxSettingsFiles.get(sessionId) ?? new Set();
|
|
631
665
|
files.add(filePath);
|
|
@@ -1407,7 +1441,7 @@ export class AgentRunner {
|
|
|
1407
1441
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
1408
1442
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
1409
1443
|
*/
|
|
1410
|
-
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile) {
|
|
1444
|
+
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode) {
|
|
1411
1445
|
let lastSessionId;
|
|
1412
1446
|
let hasTurnActivity = false;
|
|
1413
1447
|
let ignoredPreTurnResult = false;
|
|
@@ -1851,19 +1885,7 @@ export class AgentRunner {
|
|
|
1851
1885
|
return;
|
|
1852
1886
|
}
|
|
1853
1887
|
if (isSandboxInitializationFailure(err, buf ?? [])) {
|
|
1854
|
-
const detail =
|
|
1855
|
-
await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
|
|
1856
|
-
code: 'capability_unavailable',
|
|
1857
|
-
severity: 'warning',
|
|
1858
|
-
phase: 'execution',
|
|
1859
|
-
occurredAt: Date.now(),
|
|
1860
|
-
toolName: 'Bash',
|
|
1861
|
-
policyCode: SANDBOX_INITIALIZATION_FAILED,
|
|
1862
|
-
decisionSource: 'infrastructure',
|
|
1863
|
-
agentAid: this.permissionContexts.get(sessionId)?.selfAid,
|
|
1864
|
-
summary: detail.message.slice(0, 512),
|
|
1865
|
-
effect: 'operation_skipped',
|
|
1866
|
-
});
|
|
1888
|
+
const { detail } = await this.recordSandboxInfrastructureFailure(sessionId, inputId, permissionMode, err, buf ?? []);
|
|
1867
1889
|
yield {
|
|
1868
1890
|
type: 'complete',
|
|
1869
1891
|
isError: true,
|
|
@@ -1955,6 +1977,57 @@ export class AgentRunner {
|
|
|
1955
1977
|
const callPermissionMode = normalizedPermission.mode;
|
|
1956
1978
|
const runPermissionContext = this.permissionContexts.get(sessionId);
|
|
1957
1979
|
const executionSandbox = resolvePhaseOneExecutionSandbox(callPermissionMode, runPermissionContext?.role);
|
|
1980
|
+
const inputId = modelOverride?.turn?.inputId ?? crypto.randomUUID();
|
|
1981
|
+
if (executionSandbox.state !== 'off') {
|
|
1982
|
+
const activeSandboxFailure = getActiveSandboxInitializationFailure();
|
|
1983
|
+
if (activeSandboxFailure) {
|
|
1984
|
+
const detail = createSandboxInitializationError(new Error(`sandbox bootstrap circuit open; firstObservedAt=${activeSandboxFailure.openedAt} ` +
|
|
1985
|
+
`occurrences=${activeSandboxFailure.occurrences}; ${activeSandboxFailure.detail}`));
|
|
1986
|
+
const context = this.permissionContexts.get(sessionId);
|
|
1987
|
+
auditToolInfrastructureFailure({
|
|
1988
|
+
toolName: 'Bash',
|
|
1989
|
+
policyCode: SANDBOX_INITIALIZATION_FAILED,
|
|
1990
|
+
reason: detail.message.slice(0, 512),
|
|
1991
|
+
sessionId,
|
|
1992
|
+
agentAid: context?.selfAid,
|
|
1993
|
+
permissionMode: callPermissionMode,
|
|
1994
|
+
role: context?.role,
|
|
1995
|
+
callId: inputId,
|
|
1996
|
+
taskId: context?.taskId,
|
|
1997
|
+
occurrences: activeSandboxFailure.occurrences,
|
|
1998
|
+
});
|
|
1999
|
+
await context?.recordExecutionAnomaly?.({
|
|
2000
|
+
code: 'capability_unavailable',
|
|
2001
|
+
severity: 'warning',
|
|
2002
|
+
phase: 'execution',
|
|
2003
|
+
occurredAt: Date.now(),
|
|
2004
|
+
correlationId: inputId,
|
|
2005
|
+
requestId: inputId,
|
|
2006
|
+
sessionId,
|
|
2007
|
+
toolName: 'Bash',
|
|
2008
|
+
policyCode: SANDBOX_INITIALIZATION_FAILED,
|
|
2009
|
+
decisionSource: 'infrastructure',
|
|
2010
|
+
agentAid: context?.selfAid,
|
|
2011
|
+
permissionMode: callPermissionMode,
|
|
2012
|
+
summary: detail.message.slice(0, 512),
|
|
2013
|
+
effect: 'operation_skipped',
|
|
2014
|
+
});
|
|
2015
|
+
if (shouldLogSandboxCircuitNotice()) {
|
|
2016
|
+
logger.warn(`[ClaudeSandbox] bootstrap circuit open; skipping subprocess spawn ` +
|
|
2017
|
+
`cooldownMs=${SANDBOX_INITIALIZATION_COOLDOWN_MS} occurrences=${activeSandboxFailure.occurrences}`);
|
|
2018
|
+
}
|
|
2019
|
+
return (async function* () {
|
|
2020
|
+
yield {
|
|
2021
|
+
type: 'complete',
|
|
2022
|
+
isError: true,
|
|
2023
|
+
subtype: 'infrastructure',
|
|
2024
|
+
terminalReason: SANDBOX_INITIALIZATION_FAILED,
|
|
2025
|
+
errors: [detail.message],
|
|
2026
|
+
queryFinal: true,
|
|
2027
|
+
};
|
|
2028
|
+
})();
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
1958
2031
|
const permissionPrompt = runPermissionContext?.sendPrompt ?? this.sendPromptFn;
|
|
1959
2032
|
const permissionContext = (signal) => {
|
|
1960
2033
|
const context = this.permissionContexts.get(sessionId);
|
|
@@ -2013,7 +2086,14 @@ export class AgentRunner {
|
|
|
2013
2086
|
// random one-shot payload from a validated output wrapper.
|
|
2014
2087
|
const trustedSafeOutputCommands = new Set();
|
|
2015
2088
|
// PreToolUse Hook - 黑名单检查 + input 修正(不可绕过,所有模式都走)
|
|
2016
|
-
const preToolUseHook = async (input) => {
|
|
2089
|
+
const preToolUseHook = async (input, _toolUseId, hookOptions) => {
|
|
2090
|
+
const pauseController = this.permissionContexts.get(sessionId)?.pauseController;
|
|
2091
|
+
if (pauseController) {
|
|
2092
|
+
const pauseResult = await pauseController.waitAtToolBoundary(hookOptions?.signal);
|
|
2093
|
+
if (pauseResult === 'cancelled') {
|
|
2094
|
+
return hookDecision('deny', '工具调用已取消');
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2017
2097
|
const toolName = input.tool_name;
|
|
2018
2098
|
const originalInput = (input.tool_input || {});
|
|
2019
2099
|
let toolInput = originalInput;
|
|
@@ -2358,6 +2438,10 @@ export class AgentRunner {
|
|
|
2358
2438
|
role: runPermissionContext?.role,
|
|
2359
2439
|
});
|
|
2360
2440
|
const runtimeWritePaths = trustedRuntimeWritePaths(runtimeEnv);
|
|
2441
|
+
const bootstrapWritePaths = modelOverride?.bootstrapAgentMdPath
|
|
2442
|
+
? [path.resolve(modelOverride.bootstrapAgentMdPath)]
|
|
2443
|
+
: [];
|
|
2444
|
+
const allowedWritePaths = [...new Set([...runtimeWritePaths, ...bootstrapWritePaths])];
|
|
2361
2445
|
await assertClaudeSettingSourcesHaveLiteralSandboxPaths(projectPath, [...settingSources], managedSettings);
|
|
2362
2446
|
const sandboxProjectionDurationMs = Date.now() - sandboxProjectionStartedAt;
|
|
2363
2447
|
if (sandboxProjectionDurationMs > 2_000) {
|
|
@@ -2376,7 +2460,7 @@ export class AgentRunner {
|
|
|
2376
2460
|
},
|
|
2377
2461
|
filesystem: {
|
|
2378
2462
|
...sandboxFilesystem,
|
|
2379
|
-
...(
|
|
2463
|
+
...(allowedWritePaths.length > 0 ? { allowWrite: allowedWritePaths } : {}),
|
|
2380
2464
|
},
|
|
2381
2465
|
};
|
|
2382
2466
|
})();
|
|
@@ -2552,7 +2636,6 @@ export class AgentRunner {
|
|
|
2552
2636
|
}
|
|
2553
2637
|
let sdkStream;
|
|
2554
2638
|
const msgStream = new MessageStream();
|
|
2555
|
-
const inputId = modelOverride?.turn?.inputId ?? crypto.randomUUID();
|
|
2556
2639
|
if (images && images.length > 0) {
|
|
2557
2640
|
logger.info('[AgentRunner] Creating query with images:', images.length, 'first image size:', images[0]?.data?.length ?? 0);
|
|
2558
2641
|
logger.debug('[AgentRunner] Skipping resume for image message to avoid history conflict');
|
|
@@ -2565,8 +2648,10 @@ export class AgentRunner {
|
|
|
2565
2648
|
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2566
2649
|
if (isClaudeStartupArgumentsTooLong(error))
|
|
2567
2650
|
throw createClaudeStartupArgumentsTooLongError(error);
|
|
2568
|
-
if (isSandboxInitializationFailure(error, stderr))
|
|
2569
|
-
|
|
2651
|
+
if (isSandboxInitializationFailure(error, stderr)) {
|
|
2652
|
+
const { detail } = await this.recordSandboxInfrastructureFailure(sessionId, inputId, callPermissionMode, error, stderr);
|
|
2653
|
+
throw detail;
|
|
2654
|
+
}
|
|
2570
2655
|
throw error;
|
|
2571
2656
|
}
|
|
2572
2657
|
}
|
|
@@ -2581,8 +2666,10 @@ export class AgentRunner {
|
|
|
2581
2666
|
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2582
2667
|
if (isClaudeStartupArgumentsTooLong(error))
|
|
2583
2668
|
throw createClaudeStartupArgumentsTooLongError(error);
|
|
2584
|
-
if (isSandboxInitializationFailure(error, stderr))
|
|
2585
|
-
|
|
2669
|
+
if (isSandboxInitializationFailure(error, stderr)) {
|
|
2670
|
+
const { detail } = await this.recordSandboxInfrastructureFailure(sessionId, inputId, callPermissionMode, error, stderr);
|
|
2671
|
+
throw detail;
|
|
2672
|
+
}
|
|
2586
2673
|
throw error;
|
|
2587
2674
|
}
|
|
2588
2675
|
}
|
|
@@ -2595,7 +2682,7 @@ export class AgentRunner {
|
|
|
2595
2682
|
this.interruptFns.set(sessionId, () => sdkStream.interrupt());
|
|
2596
2683
|
}
|
|
2597
2684
|
// 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
|
|
2598
|
-
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile);
|
|
2685
|
+
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode);
|
|
2599
2686
|
const self = this;
|
|
2600
2687
|
return (async function* () {
|
|
2601
2688
|
try {
|
|
@@ -2611,6 +2698,7 @@ export class AgentRunner {
|
|
|
2611
2698
|
})();
|
|
2612
2699
|
}
|
|
2613
2700
|
async interrupt(sessionId) {
|
|
2701
|
+
this.permissionContexts.get(sessionId)?.pauseController?.cancel();
|
|
2614
2702
|
const streamRegistered = this.activeStreams.has(sessionId);
|
|
2615
2703
|
const fn = this.interruptFns.get(sessionId);
|
|
2616
2704
|
const query = this.activeQueries.get(sessionId);
|
|
@@ -2764,6 +2852,7 @@ export class AgentRunner {
|
|
|
2764
2852
|
}
|
|
2765
2853
|
}
|
|
2766
2854
|
async closeSession(sessionId) {
|
|
2855
|
+
this.permissionContexts.get(sessionId)?.pauseController?.cancel();
|
|
2767
2856
|
const query = this.activeQueries.get(sessionId);
|
|
2768
2857
|
if (query && typeof query.close === 'function')
|
|
2769
2858
|
query.close();
|
|
@@ -475,6 +475,8 @@ export class CodexAppServerClient {
|
|
|
475
475
|
fs.chmodSync(directory, 0o700);
|
|
476
476
|
const hookPath = path.join(directory, 'pre-tool-use.mjs');
|
|
477
477
|
const sourcePath = path.join(getPackageRoot(), 'bin', 'codex-managed-hook.mjs');
|
|
478
|
+
if (!fs.existsSync(sourcePath))
|
|
479
|
+
throw new Error('Codex PreToolUse hook source is unavailable');
|
|
478
480
|
fs.copyFileSync(sourcePath, hookPath, fs.constants.COPYFILE_EXCL);
|
|
479
481
|
fs.chmodSync(hookPath, 0o500);
|
|
480
482
|
const command = `${this.shellQuote(process.execPath)} ${this.shellQuote(hookPath)} ${this.shellQuote(resolvePaths().instanceSocket)}`;
|
|
@@ -494,7 +496,10 @@ export class CodexAppServerClient {
|
|
|
494
496
|
'[[hooks.PreToolUse.hooks]]',
|
|
495
497
|
'type = "command"',
|
|
496
498
|
`command = ${JSON.stringify(command)}`,
|
|
497
|
-
|
|
499
|
+
// Intentionally omit timeout: Codex applies its documented 600-second
|
|
500
|
+
// default for synchronous PreToolUse hooks. The application pause
|
|
501
|
+
// gate itself has no timeout; Codex remains the outer infrastructure
|
|
502
|
+
// ceiling.
|
|
498
503
|
'statusMessage = "Checking EvolCore session policy"',
|
|
499
504
|
'',
|
|
500
505
|
].join('\n'), { mode: 0o400, flag: 'wx' });
|
|
@@ -20,7 +20,7 @@ import { buildEnvelope, sendInteractionPayload } from '../core/message/message-u
|
|
|
20
20
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
21
21
|
import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
|
|
22
22
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
23
|
-
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, parseLiteralShellArgv,
|
|
23
|
+
import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, parseLiteralShellArgv, resolveCodexShellCarrierArgv, } from '../core/permission/ec-command-parser.js';
|
|
24
24
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
25
25
|
import { resolvePaths, resolveRoot } from '../paths.js';
|
|
26
26
|
import { ensureProcessManagedTempDir } from '../cli/task-context.js';
|
|
@@ -316,18 +316,20 @@ export class CodexRunner {
|
|
|
316
316
|
createDelegationCarrier() {
|
|
317
317
|
return randomBytes(32).toString('base64url');
|
|
318
318
|
}
|
|
319
|
-
approvedLiteralEvolcoreCommandArgv(command, managedTempDir) {
|
|
320
|
-
const
|
|
319
|
+
approvedLiteralEvolcoreCommandArgv(command, managedTempDir, dialect = 'posix') {
|
|
320
|
+
const parseOptions = {
|
|
321
|
+
allowManagedTmpDir: true,
|
|
322
|
+
dialect,
|
|
323
|
+
...(managedTempDir ? { managedTempDir } : {}),
|
|
324
|
+
};
|
|
325
|
+
const classification = classifyEvolcoreShellCommand(command, parseOptions);
|
|
321
326
|
if (classification.kind === 'bounded-output')
|
|
322
327
|
return classification.command.argv;
|
|
323
328
|
if (classification.kind !== 'literal')
|
|
324
329
|
return undefined;
|
|
325
330
|
// Keep the approval/delegation parser aligned with the preflight parser:
|
|
326
331
|
// the session-managed `$TMPDIR` token is a permitted EC path reference.
|
|
327
|
-
const argv = parseLiteralShellArgv(command,
|
|
328
|
-
allowManagedTmpDir: true,
|
|
329
|
-
...(managedTempDir ? { managedTempDir } : {}),
|
|
330
|
-
});
|
|
332
|
+
const argv = parseLiteralShellArgv(command, parseOptions);
|
|
331
333
|
if (argv && containsLiteralManagedTmpDirOutsideSendContent(argv))
|
|
332
334
|
return undefined;
|
|
333
335
|
return argv?.[0] === 'ec' ? argv : undefined;
|
|
@@ -343,11 +345,11 @@ export class CodexRunner {
|
|
|
343
345
|
? undefined
|
|
344
346
|
: explicitArgv;
|
|
345
347
|
}
|
|
346
|
-
const
|
|
347
|
-
?
|
|
348
|
+
const explicitCarrier = explicitArgv
|
|
349
|
+
? resolveCodexShellCarrierArgv(explicitArgv)
|
|
348
350
|
: undefined;
|
|
349
|
-
if (
|
|
350
|
-
return this.approvedLiteralEvolcoreCommandArgv(
|
|
351
|
+
if (explicitCarrier !== undefined) {
|
|
352
|
+
return this.approvedLiteralEvolcoreCommandArgv(explicitCarrier.command, managedTempDir, explicitCarrier.dialect);
|
|
351
353
|
}
|
|
352
354
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
353
355
|
const directArgv = this.approvedLiteralEvolcoreCommandArgv(command, managedTempDir);
|
|
@@ -357,12 +359,12 @@ export class CodexRunner {
|
|
|
357
359
|
allowManagedTmpDir: true,
|
|
358
360
|
...(managedTempDir ? { managedTempDir } : {}),
|
|
359
361
|
});
|
|
360
|
-
const
|
|
361
|
-
?
|
|
362
|
+
const shellCarrier = wrappedArgv
|
|
363
|
+
? resolveCodexShellCarrierArgv(wrappedArgv)
|
|
362
364
|
: undefined;
|
|
363
|
-
return
|
|
365
|
+
return shellCarrier === undefined
|
|
364
366
|
? undefined
|
|
365
|
-
: this.approvedLiteralEvolcoreCommandArgv(
|
|
367
|
+
: this.approvedLiteralEvolcoreCommandArgv(shellCarrier.command, managedTempDir, shellCarrier.dialect);
|
|
366
368
|
}
|
|
367
369
|
isManagedEvolcoreCommandIntent(toolInput) {
|
|
368
370
|
if (Array.isArray(toolInput.commandArgv)) {
|
|
@@ -371,9 +373,11 @@ export class CodexRunner {
|
|
|
371
373
|
return true;
|
|
372
374
|
if (!rawArgv.every(value => typeof value === 'string'))
|
|
373
375
|
return false;
|
|
374
|
-
const
|
|
375
|
-
return
|
|
376
|
-
&& classifyEvolcoreShellCommand(
|
|
376
|
+
const shellCarrier = resolveCodexShellCarrierArgv(rawArgv);
|
|
377
|
+
return shellCarrier !== undefined
|
|
378
|
+
&& classifyEvolcoreShellCommand(shellCarrier.command, {
|
|
379
|
+
dialect: shellCarrier.dialect,
|
|
380
|
+
}).kind !== 'none';
|
|
377
381
|
}
|
|
378
382
|
const command = typeof toolInput.command === 'string' ? toolInput.command : '';
|
|
379
383
|
return classifyEvolcoreShellCommand(command).kind !== 'none';
|
|
@@ -795,7 +799,7 @@ export class CodexRunner {
|
|
|
795
799
|
}
|
|
796
800
|
return Object.fromEntries([...paths].map(candidate => [candidate, 'write']));
|
|
797
801
|
}
|
|
798
|
-
buildPermissionProfileConfig(sessionId, mode, projectPath) {
|
|
802
|
+
buildPermissionProfileConfig(sessionId, mode, projectPath, bootstrapAgentMdPath) {
|
|
799
803
|
const profileName = this.permissionProfileName(sessionId);
|
|
800
804
|
const managedTempDir = this.ensureSessionTempDir(sessionId);
|
|
801
805
|
const network = {
|
|
@@ -812,6 +816,9 @@ export class CodexRunner {
|
|
|
812
816
|
denyLClassRead: mode === 'readonly',
|
|
813
817
|
}),
|
|
814
818
|
...this.resolveGitMetadataWriteRules(projectPath, mode),
|
|
819
|
+
...(bootstrapAgentMdPath ? {
|
|
820
|
+
[path.resolve(bootstrapAgentMdPath)]: mode === 'readonly' ? 'read' : 'write',
|
|
821
|
+
} : {}),
|
|
815
822
|
...(mode !== 'readonly' && managedTempDir ? {
|
|
816
823
|
[managedTempDir]: 'write',
|
|
817
824
|
[path.join(managedTempDir, '**')]: 'write',
|
|
@@ -824,12 +831,12 @@ export class CodexRunner {
|
|
|
824
831
|
},
|
|
825
832
|
};
|
|
826
833
|
}
|
|
827
|
-
buildExecutionSandboxOptions(sessionId, mode, projectPath) {
|
|
834
|
+
buildExecutionSandboxOptions(sessionId, mode, projectPath, bootstrapAgentMdPath) {
|
|
828
835
|
const decision = resolvePhaseOneExecutionSandbox(mode, this.permissionContexts.get(sessionId)?.role);
|
|
829
836
|
return {
|
|
830
837
|
decision,
|
|
831
838
|
permissionProfileConfig: decision.state === 'active'
|
|
832
|
-
? this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath)
|
|
839
|
+
? this.buildPermissionProfileConfig(sessionId, decision.permissionMode, projectPath, bootstrapAgentMdPath)
|
|
833
840
|
: undefined,
|
|
834
841
|
sandbox: decision.state === 'off' ? 'danger-full-access' : undefined,
|
|
835
842
|
};
|
|
@@ -837,7 +844,11 @@ export class CodexRunner {
|
|
|
837
844
|
buildLifecycleLockdownConfig() {
|
|
838
845
|
return {
|
|
839
846
|
features: {
|
|
840
|
-
|
|
847
|
+
// The app-server is launched with allow_managed_hooks_only=true, so
|
|
848
|
+
// enabling the feature exposes only EvolCore's requirements-managed
|
|
849
|
+
// PreToolUse hook. Disabling it here suppresses that managed hook too
|
|
850
|
+
// and bypasses the pause/proactive policy boundary entirely.
|
|
851
|
+
hooks: true,
|
|
841
852
|
},
|
|
842
853
|
};
|
|
843
854
|
}
|
|
@@ -867,7 +878,7 @@ export class CodexRunner {
|
|
|
867
878
|
throw new Error('Codex proactive managed PreToolUse enforcement requires Linux or an installed Windows managed hook');
|
|
868
879
|
}
|
|
869
880
|
}
|
|
870
|
-
async evaluatePreToolUse(threadId, toolName, toolInput) {
|
|
881
|
+
async evaluatePreToolUse(threadId, toolName, toolInput, signal) {
|
|
871
882
|
const sessionKey = this.findSessionKeyByThread(threadId);
|
|
872
883
|
const activeThread = this.activeSessions.get(sessionKey);
|
|
873
884
|
const context = this.permissionContexts.get(sessionKey);
|
|
@@ -878,6 +889,12 @@ export class CodexRunner {
|
|
|
878
889
|
if (!context || (proactiveSession && !context.preToolUsePolicyHook)) {
|
|
879
890
|
return { ok: false, applicable: true, decision: 'deny', reason: 'EvolCore proactive session policy context is unavailable' };
|
|
880
891
|
}
|
|
892
|
+
if (context.pauseController) {
|
|
893
|
+
const pauseResult = await context.pauseController.waitAtToolBoundary(signal);
|
|
894
|
+
if (pauseResult === 'cancelled') {
|
|
895
|
+
return { ok: true, applicable: true, decision: 'deny', reason: '工具调用已取消' };
|
|
896
|
+
}
|
|
897
|
+
}
|
|
881
898
|
// A tracked interactive thread still needs an authenticated, explicit
|
|
882
899
|
// allow so the managed hook can fail closed when no runner matches.
|
|
883
900
|
if (!context.preToolUsePolicyHook)
|
|
@@ -1044,7 +1061,7 @@ export class CodexRunner {
|
|
|
1044
1061
|
const callMode = normalizedPermission.mode;
|
|
1045
1062
|
this.chatModes.set(sessionId, callMode);
|
|
1046
1063
|
const callApprovalPolicy = this.toApprovalPolicy(callMode);
|
|
1047
|
-
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath);
|
|
1064
|
+
const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath);
|
|
1048
1065
|
const appServer = this.getAppServerClient();
|
|
1049
1066
|
this.dropExpiredPendingInterrupt(sessionId);
|
|
1050
1067
|
const effectiveApprovalPolicy = callApprovalPolicy;
|
|
@@ -1203,6 +1220,7 @@ export class CodexRunner {
|
|
|
1203
1220
|
}
|
|
1204
1221
|
// ── Interrupt ──
|
|
1205
1222
|
async interrupt(sessionKey) {
|
|
1223
|
+
this.permissionContexts.get(sessionKey)?.pauseController?.cancel();
|
|
1206
1224
|
const controller = this.activeAbortControllers.get(sessionKey);
|
|
1207
1225
|
const activeTurn = this.activeTurns.get(sessionKey);
|
|
1208
1226
|
const hadActiveState = !!controller || !!activeTurn || this.activeStreams.has(sessionKey);
|
|
@@ -1265,6 +1283,7 @@ export class CodexRunner {
|
|
|
1265
1283
|
this.onSessionIdUpdate?.(sessionId, agentSessionId);
|
|
1266
1284
|
}
|
|
1267
1285
|
async closeSession(sessionId) {
|
|
1286
|
+
this.permissionContexts.get(sessionId)?.pauseController?.cancel();
|
|
1268
1287
|
const threadId = this.activeSessions.get(sessionId);
|
|
1269
1288
|
this.activeSessions.delete(sessionId);
|
|
1270
1289
|
this.activeStreams.delete(sessionId);
|