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.
Files changed (66) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/bin/codex-managed-hook.mjs +16 -7
  3. package/bin/install-codex-managed-hooks.mjs +4 -2
  4. package/dist/agents/claude-runner.js +113 -24
  5. package/dist/agents/codex-app-server-client.js +6 -1
  6. package/dist/agents/codex-runner.js +43 -24
  7. package/dist/agents/ecagent-runner.js +39 -10
  8. package/dist/agents/gemini-runner.js +90 -19
  9. package/dist/aun/aid/store.js +36 -0
  10. package/dist/aun/msg/p2p.js +20 -8
  11. package/dist/channels/aun.js +159 -21
  12. package/dist/cli/agent-command.js +67 -6
  13. package/dist/cli/agent.js +26 -0
  14. package/dist/cli/command-log.js +23 -4
  15. package/dist/cli/daemon-commands.js +82 -14
  16. package/dist/cli/init.js +21 -5
  17. package/dist/cli/restart-monitor.js +13 -6
  18. package/dist/cli/watch-logs.js +2 -2
  19. package/dist/config/builtin-roles.js +5 -1
  20. package/dist/config/role-ranks.js +4 -0
  21. package/dist/core/audit/event-key.js +29 -0
  22. package/dist/core/audit/log-integrity.js +13 -3
  23. package/dist/core/auth/auth-gateway.js +14 -18
  24. package/dist/core/auth/authorization-audit.js +110 -3
  25. package/dist/core/auth/authorization-denial.js +17 -0
  26. package/dist/core/auth/operation-authorizer.js +143 -18
  27. package/dist/core/auth/operation-catalog.js +21 -5
  28. package/dist/core/bootstrap-messages.js +11 -6
  29. package/dist/core/bootstrap-service.js +26 -4
  30. package/dist/core/causation/aun-association.js +7 -4
  31. package/dist/core/command/agent-control.js +25 -16
  32. package/dist/core/command/command-handler.js +50 -4
  33. package/dist/core/command/group-menu.js +1 -1
  34. package/dist/core/command/menu-catalog.js +32 -7
  35. package/dist/core/command/menu-handler.js +59 -23
  36. package/dist/core/command/menu-protocol.js +196 -0
  37. package/dist/core/command/slash-gate.js +14 -5
  38. package/dist/core/command/slash-handler.js +81 -99
  39. package/dist/core/data-migration.js +10 -4
  40. package/dist/core/event-catalog.js +18 -0
  41. package/dist/core/message/message-bridge.js +66 -8
  42. package/dist/core/message/response-engine.js +147 -10
  43. package/dist/core/permission/ec-command-parser.js +148 -22
  44. package/dist/core/permission/sandbox-runtime.js +79 -13
  45. package/dist/core/permission/tool-policy.js +19 -7
  46. package/dist/index.js +357 -48
  47. package/dist/ipc.js +81 -5
  48. package/dist/paths.js +0 -3
  49. package/dist/utils/atomic-write.js +45 -11
  50. package/dist/utils/logger.js +27 -0
  51. package/dist/utils/windows-autostart.js +740 -83
  52. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  53. package/ecagent/dist/harness/agent-harness.js +6 -4
  54. package/kits/docs/evolcore/config.md +1 -1
  55. package/kits/docs/evolcore/group-rules.md +2 -1
  56. package/kits/docs/identity/ROLE_DETAIL.md +3 -1
  57. package/kits/docs/path-registry.md +1 -1
  58. package/kits/eck_manifest.json +25 -16
  59. package/kits/rules/01-overview.md +5 -5
  60. package/kits/rules/02-navigation.md +2 -2
  61. package/kits/rules/03-identity.md +1 -1
  62. package/kits/rules/04-relation.md +4 -4
  63. package/kits/rules/05-venue.md +5 -5
  64. package/kits/templates/bootstrap-welcome.md +3 -1
  65. package/kits/templates/system-fragments/bootstrap.md +17 -9
  66. 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
- const timeoutMs = 1500;
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
- // 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
+ // 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(() => finish(null), timeoutMs);
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', () => socket.write(JSON.stringify(payload) + '\n'));
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: typeof input.tool_name === 'string' ? input.tool_name : '',
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(`Managed hook source was not found: ${sourceHook}`);
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
- 'timeout = 3',
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 = createSandboxInitializationError(err, buf ?? []);
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
- ...(runtimeWritePaths.length > 0 ? { allowWrite: runtimeWritePaths } : {}),
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
- throw createSandboxInitializationError(error, stderr);
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
- throw createSandboxInitializationError(error, stderr);
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
- 'timeout = 3',
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, unwrapCodexShellCommandArgv, } from '../core/permission/ec-command-parser.js';
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 classification = classifyEvolcoreShellCommand(command);
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 explicitWrappedCommand = explicitArgv
347
- ? unwrapCodexShellCommandArgv(explicitArgv)
348
+ const explicitCarrier = explicitArgv
349
+ ? resolveCodexShellCarrierArgv(explicitArgv)
348
350
  : undefined;
349
- if (explicitWrappedCommand !== undefined) {
350
- return this.approvedLiteralEvolcoreCommandArgv(explicitWrappedCommand, managedTempDir);
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 wrappedCommand = wrappedArgv
361
- ? unwrapCodexShellCommandArgv(wrappedArgv)
362
+ const shellCarrier = wrappedArgv
363
+ ? resolveCodexShellCarrierArgv(wrappedArgv)
362
364
  : undefined;
363
- return wrappedCommand === undefined
365
+ return shellCarrier === undefined
364
366
  ? undefined
365
- : this.approvedLiteralEvolcoreCommandArgv(wrappedCommand, managedTempDir);
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 wrappedCommand = unwrapCodexShellCommandArgv(rawArgv);
375
- return wrappedCommand !== undefined
376
- && classifyEvolcoreShellCommand(wrappedCommand).kind !== 'none';
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
- hooks: false,
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);