evolcore 0.0.10 → 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.
Files changed (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
package/CHANGELOG.md CHANGED
@@ -3,6 +3,50 @@
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
+
28
+ ## 0.0.11 (2026-08-11)
29
+
30
+ ### Agent 初始化与引导
31
+
32
+ - `ec init` 支持在任意交互步骤连续按两次 Esc 或使用 Ctrl+C 安全退出,并完善二维码超时后的重新生成、手动绑定与取消清理流程。
33
+ - 重新设计 Agent bootstrap 引导消息,在 Agent 完成初始化后可靠投递,并通过持久化和去重避免断线重试造成消息丢失或重复。
34
+
35
+ ### 权限与消息可靠性
36
+
37
+ - 扩展安全只读 Shell 查询和环境变量解析能力,统一工具授权、危险命令识别与审批边界。
38
+ - 完善 AUN 关键消息的持久化、去重和日志落盘,提升断线恢复与异常重试时的一致性。
39
+
40
+ ### 请求身份与模型目录
41
+
42
+ - 模型请求统一携带 EvolCore 版本、Agent AID 和 BaseAgent 类型,便于网关进行来源识别、审计和问题定位。
43
+ - Claude 元数据请求复用 Agent 自定义请求头,同时隔离公共模型目录请求中的 API Key、租户头和 Agent 身份信息。
44
+
45
+ ### 热重载
46
+
47
+ - 同一 Agent 的重载操作改为串行执行;任务繁忙时默认拒绝重载,也可通过 `--force` 清理队列并等待运行中任务中断。
48
+ - 为 runner 与渠道重载建立明确的提交边界和失败回滚,完善 resync 下线清理,避免重载失败后残留旧实例或半更新状态。
49
+
6
50
  ## 0.0.10 (2026-08-06)
7
51
 
8
52
  ### 数据迁移
package/README.md CHANGED
@@ -86,13 +86,13 @@ ResponseEngine.processMessage()
86
86
  **一行安装(macOS / Linux)**:
87
87
 
88
88
  ```bash
89
- curl -fsSL https://download.evolai.cn/releases/evolcore/2026-08-05.2/install.sh | bash
89
+ curl -fsSL https://download.evolai.cn/install.sh | bash
90
90
  ```
91
91
 
92
92
  **Windows PowerShell**:
93
93
 
94
94
  ```powershell
95
- & ([scriptblock]::Create((irm https://download.evolai.cn/releases/evolcore/2026-08-05.2/install.ps1))) -AutoStart
95
+ & ([scriptblock]::Create((irm https://download.evolai.cn/install.ps1))) -AutoStart
96
96
  ```
97
97
 
98
98
  安装器会自动进入 `ec init` 交互向导;只有已有配置同时包含控制 AID 和 Owner 时才跳过。上面的 Windows 命令会在绑定完成后自动运行 `ec start`;macOS/Linux 需手动运行 `ec start`。安装器不会停止或重启已有服务,详细流程见 [一键安装脚本说明](docs/evolcore-install-script.md)。
@@ -145,7 +145,7 @@ ec init aun
145
145
  - 渠道选择(飞书 / 微信 / 钉钉 / QQ 频道 / 企业微信 / AUN)并扫码登录
146
146
  - 默认项目路径
147
147
  - 模型选择(sonnet/opus/haiku)
148
- - owner 配置:默认走 **Evol App 扫码绑定**,若无 Evol App 可手工录入 AID 兜底
148
+ - owner 配置:默认走 **Evol App 扫码绑定**,若无 Evol App 可手工录入 AID 兜底。扫码流程只追加 owner;移除 owner 请直接编辑对应 JSON 配置。
149
149
  - 自动写入 `EVOLCORE_HOME` 到 shell profile(Unix)或用户环境变量(Windows)
150
150
 
151
151
  配置文件生成在 `{EVOLCORE_HOME}/data/daemon.json`(默认 `~/.evolcore/data/daemon.json`)。
@@ -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();
@@ -279,6 +279,8 @@ export function resolveOpenaiConfig(config, override) {
279
279
  effort,
280
280
  enableRequestUserInput,
281
281
  approvalsReviewer,
282
+ evolcoreAgentAid: override?.evolcoreAgentAid ?? config.agents?.codex?.evolcoreAgentAid,
283
+ evolcoreAgentConfig: override?.evolcoreAgentConfig ?? config.agents?.codex?.evolcoreAgentConfig,
282
284
  };
283
285
  }
284
286
  export function resolveGoogleConfig(config, override) {
@@ -325,11 +327,13 @@ export function resolveEcagentConfig(config, override) {
325
327
  || process.env.ECAGENT_BASE_URL
326
328
  || DEFAULT_ECAGENT_BASE_URL;
327
329
  const model = override?.model || configured?.model || DEFAULT_ECAGENT_MODEL;
330
+ const headers = normalizeStringMap(override?.headers ?? configured?.headers, 'headers');
328
331
  const effort = override?.effort ?? configured?.effort;
329
332
  const numberOr = (value, fallback) => (typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback);
330
333
  return {
331
334
  apiKey,
332
335
  baseUrl,
336
+ headers,
333
337
  model,
334
338
  effort,
335
339
  timeoutMs: numberOr(override?.timeoutMs ?? configured?.timeoutMs, DEFAULT_ECAGENT_TIMEOUT_MS),
@@ -1,6 +1,8 @@
1
- import { query, forkSession as sdkForkSession, getSessionMessages as sdkGetSessionMessages, resolveSettings as sdkResolveSettings } from '@anthropic-ai/claude-agent-sdk';
1
+ import { query, forkSession as sdkForkSession, getSessionMessages as sdkGetSessionMessages, resolveSettings as sdkResolveSettings, } from '@anthropic-ai/claude-agent-sdk';
2
+ import { spawn } from 'child_process';
2
3
  import { atomicReadJson, atomicWriteJson, ensureDir } from '../utils/atomic-write.js';
3
4
  import { resolveAnthropicConfig } from './baseagent.js';
5
+ import { buildModelRequestHeaders } from './request-identity.js';
4
6
  import { DEFAULT_PERMISSION_MODE } from '../types.js';
5
7
  import { renderActionAsText } from '../core/interaction-router.js';
6
8
  import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
@@ -11,7 +13,8 @@ import crypto from 'crypto';
11
13
  import { logger } from '../utils/logger.js';
12
14
  import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
13
15
  import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
14
- import { summarizeToolInput } from '../utils/tool-summary.js';
16
+ import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
17
+ import { summarizeToolInput, summarizeToolInputForAudit } from '../utils/tool-summary.js';
15
18
  import { encodePath } from '../utils/cross-platform.js';
16
19
  import { getPackageRoot, resolvePaths } from '../paths.js';
17
20
  import { resolveEffective } from '../config/config-manager.js';
@@ -20,7 +23,7 @@ import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/
20
23
  import { normalizePermissionMode } from '../core/permission/mode.js';
21
24
  import { resolvePhaseOneExecutionSandbox } from '../core/permission/execution-sandbox.js';
22
25
  import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, resolveProtectedCandidate, } from '../core/protected-paths.js';
23
- import { ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable } from '../core/permission/sandbox-runtime.js';
26
+ import { buildHClassGuardCommand, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
24
27
  import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
25
28
  import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
26
29
  export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
@@ -59,6 +62,22 @@ const CLAUDE_BUILTIN_TOOLS = new Set([
59
62
  'WebSearch',
60
63
  'Write',
61
64
  ]);
65
+ function spawnClaudeWithHClassGuard(options, root, onStderr) {
66
+ const guarded = buildHClassGuardCommand(options.command, options.args, root);
67
+ if (!guarded) {
68
+ throw new Error('Claude owner bypass 缺少 EvolCore H 类路径隔离运行时,已拒绝启动');
69
+ }
70
+ const child = spawn(guarded.command, guarded.args, {
71
+ cwd: options.cwd,
72
+ env: options.env,
73
+ signal: options.signal,
74
+ stdio: ['pipe', 'pipe', 'pipe'],
75
+ windowsHide: true,
76
+ });
77
+ child.stderr.on('data', chunk => onStderr(String(chunk)));
78
+ child.stderr.on('error', error => onStderr(`[ClaudeGuard] stderr read failed: ${error.message}`));
79
+ return child;
80
+ }
62
81
  function isClaudeMcpTool(toolName) {
63
82
  return toolName.startsWith('mcp__');
64
83
  }
@@ -337,7 +356,7 @@ function apiPricingToQuad(p) {
337
356
  }
338
357
  /** 拉取网关 /v1/models 的官方价(pricing) + 网关价(effective_pricing),写入 gatewayPricingCache。
339
358
  * 失败静默——保持回退到本地价表 / official。 */
340
- async function refreshGatewayPricing(baseUrl, apiKey) {
359
+ async function refreshGatewayPricing(baseUrl, apiKey, requestHeaders) {
341
360
  if (gatewayPricingInFlight.has(baseUrl))
342
361
  return;
343
362
  gatewayPricingInFlight.add(baseUrl);
@@ -347,7 +366,10 @@ async function refreshGatewayPricing(baseUrl, apiKey) {
347
366
  const timer = setTimeout(() => controller.abort(), 5000);
348
367
  const resp = await fetch(url, {
349
368
  signal: controller.signal,
350
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
369
+ headers: {
370
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
371
+ ...requestHeaders,
372
+ },
351
373
  });
352
374
  clearTimeout(timer);
353
375
  if (!resp.ok)
@@ -408,7 +430,7 @@ function deriveAliasesFromModelIds(ids) {
408
430
  return aliases;
409
431
  }
410
432
  /** 异步刷新某 baseUrl 的别名缓存(失败静默,不抛出) */
411
- async function refreshModelAliases(baseUrl, apiKey) {
433
+ async function refreshModelAliases(baseUrl, apiKey, requestHeaders) {
412
434
  const cacheKey = normalizeModelGatewayUrl(baseUrl);
413
435
  if (modelAliasInFlight.has(cacheKey))
414
436
  return;
@@ -420,7 +442,10 @@ async function refreshModelAliases(baseUrl, apiKey) {
420
442
  const timer = setTimeout(() => controller.abort(), 5000);
421
443
  const resp = await fetch(url, {
422
444
  signal: controller.signal,
423
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
445
+ headers: {
446
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
447
+ ...requestHeaders,
448
+ },
424
449
  });
425
450
  clearTimeout(timer);
426
451
  if (!resp.ok)
@@ -571,13 +596,18 @@ export class AgentRunner {
571
596
  if (config) {
572
597
  const anthropic = resolveAnthropicConfig(config);
573
598
  this.claudeExecutablePath = anthropic.pathToClaudeCodeExecutable;
574
- this.customHeaders = anthropic.headers;
599
+ this.customHeaders = buildModelRequestHeaders({
600
+ baseagent: 'claude',
601
+ baseUrl: anthropic.baseUrl,
602
+ agentAid: config.agents?.claude?.evolcoreAgentAid,
603
+ configuredHeaders: anthropic.headers,
604
+ });
575
605
  this.customQueryParams = anthropic.queryParams;
576
606
  }
577
607
  }
578
- getAgentEnv(runtimeEnv, evolcoreSessionId) {
608
+ getAgentEnv(runtimeEnv, evolcoreSessionId, permissionMode) {
579
609
  const anthropicBaseUrl = appendQueryParams(this.baseUrl, this.customQueryParams);
580
- const env = {
610
+ const env = sanitizeShellExecutionEnvironment({
581
611
  ...process.env,
582
612
  ANTHROPIC_AUTH_TOKEN: this.apiKey,
583
613
  ANTHROPIC_API_KEY: this.apiKey,
@@ -589,7 +619,7 @@ export class AgentRunner {
589
619
  : {}),
590
620
  ...(evolcoreSessionId ? { EVOLCORE_SESSION_ID: evolcoreSessionId } : {}),
591
621
  ...(runtimeEnv ?? {}),
592
- };
622
+ }, { lockExecutableResolution: permissionMode === 'readonly' });
593
623
  const bubblewrapPath = resolveBubblewrapPath();
594
624
  return bubblewrapPath
595
625
  ? prependExecutableDirectory(env, bubblewrapPath)
@@ -608,7 +638,7 @@ export class AgentRunner {
608
638
  return undefined;
609
639
  const entry = gatewayPricingCache.get(this.baseUrl);
610
640
  if (!entry || Date.now() - entry.fetchedAt > GATEWAY_PRICING_TTL_MS) {
611
- refreshGatewayPricing(this.baseUrl, this.apiKey); // 不 await,stale-while-revalidate
641
+ refreshGatewayPricing(this.baseUrl, this.apiKey, this.customHeaders); // 不 await,stale-while-revalidate
612
642
  }
613
643
  return entry?.cache;
614
644
  }
@@ -617,11 +647,26 @@ export class AgentRunner {
617
647
  let agentConfig = claudeConfig?.evolcoreAgentConfig;
618
648
  if (claudeConfig?.evolcoreAgentAid) {
619
649
  try {
620
- agentConfig = resolveEffective({ self: claudeConfig.evolcoreAgentAid }, { cache: true });
650
+ agentConfig = resolveEffective({ self: claudeConfig.evolcoreAgentAid }, { cache: true, expand: true });
621
651
  }
622
652
  catch { }
623
653
  }
624
- return await resolveClaudeCapabilityRunOptionsForProject(agentConfig, projectPath, 'claude');
654
+ const resolved = await resolveClaudeCapabilityRunOptionsForProject(agentConfig, projectPath, 'claude');
655
+ const settings = resolved.settings && typeof resolved.settings === 'object' && !Array.isArray(resolved.settings)
656
+ ? resolved.settings
657
+ : {};
658
+ const skillOverrides = settings.skillOverrides
659
+ && typeof settings.skillOverrides === 'object'
660
+ && !Array.isArray(settings.skillOverrides)
661
+ ? settings.skillOverrides
662
+ : {};
663
+ return {
664
+ ...resolved,
665
+ settings: {
666
+ ...settings,
667
+ skillOverrides: { ...skillOverrides, eclink: 'off' },
668
+ },
669
+ };
625
670
  }
626
671
  async listModels() {
627
672
  if (this.baseUrl) {
@@ -631,11 +676,11 @@ export class AgentRunner {
631
676
  const stale = !cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS);
632
677
  // 缓存为空(首次打开)→ 等待刷新;缓存仅过期 → 后台刷新不阻塞
633
678
  if (!cached) {
634
- await refreshModelAliases(this.baseUrl, this.apiKey);
679
+ await refreshModelAliases(this.baseUrl, this.apiKey, this.customHeaders);
635
680
  cached = modelAliasCache.get(cacheKey);
636
681
  }
637
682
  else if (stale) {
638
- refreshModelAliases(this.baseUrl, this.apiKey);
683
+ refreshModelAliases(this.baseUrl, this.apiKey, this.customHeaders);
639
684
  }
640
685
  // 有缓存时返回网关 /models 的全量原始 ID
641
686
  if (cached && cached.ids.length > 0)
@@ -1729,12 +1774,12 @@ export class AgentRunner {
1729
1774
  const cacheKey = normalizeModelGatewayUrl(this.baseUrl);
1730
1775
  const cached = modelAliasCache.get(cacheKey);
1731
1776
  if (!cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS)) {
1732
- refreshModelAliases(this.baseUrl, this.apiKey);
1777
+ refreshModelAliases(this.baseUrl, this.apiKey, this.customHeaders);
1733
1778
  }
1734
1779
  // 顺带预热网关价格缓存(1h TTL),让本轮结束写库时已就绪
1735
1780
  const pricing = gatewayPricingCache.get(this.baseUrl);
1736
1781
  if (!pricing || (Date.now() - pricing.fetchedAt > GATEWAY_PRICING_TTL_MS)) {
1737
- refreshGatewayPricing(this.baseUrl, this.apiKey);
1782
+ refreshGatewayPricing(this.baseUrl, this.apiKey, this.customHeaders);
1738
1783
  }
1739
1784
  }
1740
1785
  ensureDir(projectPath);
@@ -1807,6 +1852,27 @@ export class AgentRunner {
1807
1852
  ...(updatedInput ? { updatedInput } : {}),
1808
1853
  },
1809
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
+ };
1810
1876
  // A rewritten helper command is trusted only after this hook produced its
1811
1877
  // random one-shot payload from a validated output wrapper.
1812
1878
  const trustedSafeOutputCommands = new Set();
@@ -1851,6 +1917,7 @@ export class AgentRunner {
1851
1917
  // proactive 模式行为策略(首次工具调用必须是 ec msg send)
1852
1918
  const policyResult = this.permissionContexts.get(sessionId)?.policyHook?.(toolName, toolInput);
1853
1919
  if (policyResult?.block) {
1920
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, policyResult.policyCode ?? 'session_policy_hook');
1854
1921
  return hookDecision('deny', policyResult.reason || '当前会话策略拒绝此工具调用', updatedInput);
1855
1922
  }
1856
1923
  const permCtx = this.permissionContexts.get(sessionId);
@@ -1859,6 +1926,7 @@ export class AgentRunner {
1859
1926
  : undefined;
1860
1927
  const preflight = evaluateToolPreflight(toolName, toolInput, {
1861
1928
  sessionId,
1929
+ selfAid: permCtx?.selfAid,
1862
1930
  channel: permCtx?.channel,
1863
1931
  userId: permCtx?.userId,
1864
1932
  role: permCtx?.role,
@@ -1870,8 +1938,10 @@ export class AgentRunner {
1870
1938
  toolInput = preflight.input;
1871
1939
  if (toolInput !== originalInput)
1872
1940
  updatedInput = toolInput;
1873
- if (preflight.behavior === 'deny')
1941
+ if (preflight.behavior === 'deny') {
1942
+ await recordPreflightDenial(toolName, toolInput, preflight, input.tool_use_id ?? input.toolUseID);
1874
1943
  return hookDecision('deny', preflight.message, updatedInput);
1944
+ }
1875
1945
  if (preflight.behavior === 'allow') {
1876
1946
  const commandAfterPreflight = typeof toolInput.command === 'string' ? toolInput.command : undefined;
1877
1947
  if (commandAfterPreflight && commandAfterPreflight !== commandBeforePreflight) {
@@ -1893,10 +1963,10 @@ export class AgentRunner {
1893
1963
  channel: permCtx?.channel,
1894
1964
  peerId: permCtx?.userId,
1895
1965
  role: permCtx?.role,
1896
- allowLiteralReadShell: true,
1897
1966
  };
1898
1967
  const roResult = checkReadonly(toolName, toolInput, projectPath, readonlyContext);
1899
1968
  if (roResult.behavior === 'deny') {
1969
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'readonly_mode');
1900
1970
  return hookDecision('deny', roResult.message, updatedInput);
1901
1971
  }
1902
1972
  return hookDecision('allow', undefined, updatedInput);
@@ -1906,6 +1976,7 @@ export class AgentRunner {
1906
1976
  }
1907
1977
  if (isUnknownClaudeTool(toolName)) {
1908
1978
  if (callPermissionMode === 'auto') {
1979
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'auto_unknown_tool');
1909
1980
  return hookDecision('deny', `auto 模式拒绝未知工具 ${toolName}:该工具不受 Claude 本地 sandbox 的完整约束`, updatedInput);
1910
1981
  }
1911
1982
  // request/bypass only receive external tools admitted by the session's
@@ -1915,6 +1986,7 @@ export class AgentRunner {
1915
1986
  const danger = checkDangerousCommand(toolName, toolInput);
1916
1987
  if (danger.isDangerous) {
1917
1988
  if (callPermissionMode === 'auto') {
1989
+ await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'auto_dangerous_command');
1918
1990
  return hookDecision('deny', `危险操作已由 auto 模式拒绝:${danger.reason}`, updatedInput);
1919
1991
  }
1920
1992
  if (callPermissionMode === 'request' || callPermissionMode === 'bypass') {
@@ -1974,6 +2046,7 @@ export class AgentRunner {
1974
2046
  const permCtx = this.permissionContexts.get(sessionId);
1975
2047
  const preflight = evaluateToolPreflight(toolName, input, {
1976
2048
  sessionId,
2049
+ selfAid: permCtx?.selfAid,
1977
2050
  channel: permCtx?.channel,
1978
2051
  userId: permCtx?.userId,
1979
2052
  role: permCtx?.role,
@@ -1984,6 +2057,7 @@ export class AgentRunner {
1984
2057
  });
1985
2058
  input = preflight.input;
1986
2059
  if (preflight.behavior === 'deny') {
2060
+ await recordPreflightDenial(toolName, input, preflight, options.toolUseID);
1987
2061
  return { behavior: 'deny', message: preflight.message, decisionClassification: 'user_reject' };
1988
2062
  }
1989
2063
  if (preflight.behavior === 'allow') {
@@ -1995,6 +2069,7 @@ export class AgentRunner {
1995
2069
  }
1996
2070
  const permissionExpansion = inspectClaudePermissionExpansion(input, options, projectPath);
1997
2071
  if (permissionExpansion.protectedReason) {
2072
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'protected_permission_expansion');
1998
2073
  return {
1999
2074
  behavior: 'deny',
2000
2075
  message: permissionExpansion.protectedReason,
@@ -2003,6 +2078,7 @@ export class AgentRunner {
2003
2078
  }
2004
2079
  if (permissionExpansion.requiresApproval) {
2005
2080
  if (callPermissionMode === 'readonly' || callPermissionMode === 'auto') {
2081
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, callPermissionMode === 'readonly' ? 'readonly_permission_expansion' : 'auto_permission_expansion');
2006
2082
  return {
2007
2083
  behavior: 'deny',
2008
2084
  message: `${callPermissionMode} 模式拒绝 Claude SDK 文件系统或未知权限扩权请求`,
@@ -2043,10 +2119,10 @@ export class AgentRunner {
2043
2119
  channel: permCtx?.channel,
2044
2120
  peerId: permCtx?.userId,
2045
2121
  role: permCtx?.role,
2046
- allowLiteralReadShell: true,
2047
2122
  };
2048
2123
  const roResult = checkReadonly(toolName, input, projectPath, readonlyContext);
2049
2124
  if (roResult.behavior === 'deny') {
2125
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'readonly_mode');
2050
2126
  return { behavior: 'deny', message: roResult.message, decisionClassification: 'user_reject' };
2051
2127
  }
2052
2128
  return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
@@ -2056,6 +2132,7 @@ export class AgentRunner {
2056
2132
  }
2057
2133
  if (isUnknownClaudeTool(toolName)) {
2058
2134
  if (callPermissionMode === 'auto') {
2135
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'auto_unknown_tool');
2059
2136
  return {
2060
2137
  behavior: 'deny',
2061
2138
  message: `auto 模式拒绝未知工具 ${toolName}:该工具不受 Claude 本地 sandbox 的完整约束`,
@@ -2067,6 +2144,7 @@ export class AgentRunner {
2067
2144
  const danger = checkDangerousCommand(toolName, input);
2068
2145
  if (danger.isDangerous) {
2069
2146
  if (callPermissionMode === 'auto') {
2147
+ await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'auto_dangerous_command');
2070
2148
  return { behavior: 'deny', message: `auto 模式拒绝危险操作:${danger.reason}`, decisionClassification: 'user_reject' };
2071
2149
  }
2072
2150
  if (callPermissionMode === 'request' || callPermissionMode === 'bypass') {
@@ -2129,7 +2207,9 @@ export class AgentRunner {
2129
2207
  : await (async () => {
2130
2208
  const sandboxProjectionStartedAt = Date.now();
2131
2209
  ensureClaudeGitWorktreeConfig(projectPath);
2132
- const sandboxFilesystem = buildClaudeProtectedFilesystem(resolvePaths().root);
2210
+ const sandboxFilesystem = buildClaudeProtectedFilesystem(resolvePaths().root, {
2211
+ denyLClassRead: callPermissionMode === 'readonly',
2212
+ });
2133
2213
  const sandboxUnixSockets = buildClaudeUnixSocketAllowlist(projectPath, {
2134
2214
  role: runPermissionContext?.role,
2135
2215
  });
@@ -2153,8 +2233,29 @@ export class AgentRunner {
2153
2233
  };
2154
2234
  })();
2155
2235
  if (executionSandbox.state === 'off') {
2156
- logger.info('[ClaudeSandbox] disabled for authenticated owner bypass task');
2236
+ logger.info(process.platform === 'linux'
2237
+ ? '[ClaudeSandbox] task sandbox disabled for authenticated owner bypass; outer H-class guard remains active'
2238
+ : '[ClaudeSandbox] disabled for authenticated owner bypass task');
2157
2239
  }
2240
+ const handleClaudeStderr = (msg) => {
2241
+ const trimmed = msg.trim();
2242
+ if (trimmed) {
2243
+ let buf = this.recentStderr.get(sessionId);
2244
+ if (!buf) {
2245
+ buf = [];
2246
+ this.recentStderr.set(sessionId, buf);
2247
+ }
2248
+ buf.push(trimmed);
2249
+ if (buf.length > AgentRunner.STDERR_BUFFER_MAX)
2250
+ buf.shift();
2251
+ }
2252
+ if (msg.includes('[ERROR]') || msg.includes('[WARN]') || msg.includes('Stream started')) {
2253
+ logger.info(`[Claude-stderr] ${trimmed}`);
2254
+ }
2255
+ else {
2256
+ logger.debug(`[Claude-stderr] ${trimmed}`);
2257
+ }
2258
+ };
2158
2259
  const commonOptions = {
2159
2260
  cwd: projectPath,
2160
2261
  model: sdkModel,
@@ -2163,6 +2264,9 @@ export class AgentRunner {
2163
2264
  managedSettings,
2164
2265
  ...(callEffort ? { effort: callEffort } : {}),
2165
2266
  ...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
2267
+ ...(executionSandbox.state === 'off' && process.platform === 'linux' ? {
2268
+ spawnClaudeCodeProcess: (options) => spawnClaudeWithHClassGuard(options, resolvePaths().root, handleClaudeStderr),
2269
+ } : {}),
2166
2270
  autoCompactWindow: autoCompactWindowForModel(sdkModel),
2167
2271
  advisorModel: 'haiku',
2168
2272
  canUseTool: canUseToolCallback,
@@ -2177,27 +2281,8 @@ export class AgentRunner {
2177
2281
  PermissionDenied: [{ matcher: '.*', hooks: [permissionDeniedHook] }]
2178
2282
  },
2179
2283
  ...(enableSummaries ? { agentProgressSummaries: true } : {}),
2180
- stderr: (msg) => {
2181
- const trimmed = msg.trim();
2182
- if (trimmed) {
2183
- // 环形缓冲:保留最近 N 行,供子进程崩溃时还原真实原因
2184
- let buf = this.recentStderr.get(sessionId);
2185
- if (!buf) {
2186
- buf = [];
2187
- this.recentStderr.set(sessionId, buf);
2188
- }
2189
- buf.push(trimmed);
2190
- if (buf.length > AgentRunner.STDERR_BUFFER_MAX)
2191
- buf.shift();
2192
- }
2193
- if (msg.includes('[ERROR]') || msg.includes('[WARN]') || msg.includes('Stream started')) {
2194
- logger.info(`[Claude-stderr] ${trimmed}`);
2195
- }
2196
- else {
2197
- logger.debug(`[Claude-stderr] ${trimmed}`);
2198
- }
2199
- },
2200
- env: this.getAgentEnv(runtimeEnv, sessionId)
2284
+ stderr: handleClaudeStderr,
2285
+ env: this.getAgentEnv(runtimeEnv, sessionId, callPermissionMode)
2201
2286
  };
2202
2287
  const createQuery = (promptInput, resumeSessionId, resumeAt) => {
2203
2288
  if (useSettingSources) {
@@ -2479,6 +2564,35 @@ export class AgentRunner {
2479
2564
  this.activeQueries.delete(sessionId);
2480
2565
  this.permissionContexts.delete(sessionId);
2481
2566
  }
2567
+ async dispose() {
2568
+ const interrupts = [...this.interruptFns.values()].map(async (interrupt) => {
2569
+ try {
2570
+ await interrupt();
2571
+ }
2572
+ catch { }
2573
+ });
2574
+ await Promise.allSettled(interrupts);
2575
+ for (const stream of this.activeMessageStreams.values())
2576
+ stream.end();
2577
+ for (const query of this.activeQueries.values()) {
2578
+ try {
2579
+ if (typeof query?.close === 'function')
2580
+ await query.close();
2581
+ }
2582
+ catch { }
2583
+ }
2584
+ for (const resolve of this.streamDoneResolvers.values())
2585
+ resolve();
2586
+ this.activeSessions.clear();
2587
+ this.activeStreams.clear();
2588
+ this.activeMessageStreams.clear();
2589
+ this.interruptFns.clear();
2590
+ this.activeQueries.clear();
2591
+ this.streamDone.clear();
2592
+ this.streamDoneResolvers.clear();
2593
+ this.permissionContexts.clear();
2594
+ this.recentStderr.clear();
2595
+ }
2482
2596
  resolveSessionFile(agentSessionId, projectPath) {
2483
2597
  const encodedProjectPath = encodePath(projectPath);
2484
2598
  const sessionFile = path.join(os.homedir(), '.claude', 'projects', encodedProjectPath, `${agentSessionId}.jsonl`);