evolcore 0.0.10 → 0.0.11

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 (62) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -3
  3. package/dist/agents/baseagent.js +4 -0
  4. package/dist/agents/claude-runner.js +123 -42
  5. package/dist/agents/codex-app-server-client.js +33 -9
  6. package/dist/agents/codex-runner.js +58 -8
  7. package/dist/agents/ecagent-runner.js +17 -2
  8. package/dist/agents/request-identity.js +55 -0
  9. package/dist/aun/outbox.js +28 -31
  10. package/dist/channels/aun.js +131 -128
  11. package/dist/cli/agent-command.js +16 -9
  12. package/dist/cli/agent.js +82 -19
  13. package/dist/cli/daemon-commands.js +21 -2
  14. package/dist/cli/index.js +76 -61
  15. package/dist/cli/init-cancel.js +208 -0
  16. package/dist/cli/init-channel.js +343 -195
  17. package/dist/cli/init.js +21 -9
  18. package/dist/config/builtin-roles.js +1 -0
  19. package/dist/config/gateway-config.js +26 -10
  20. package/dist/core/agent-reload-coordinator.js +53 -0
  21. package/dist/core/auth/operation-authorizer.js +32 -147
  22. package/dist/core/auth/operation-catalog.js +80 -0
  23. package/dist/core/bootstrap-messages.js +50 -0
  24. package/dist/core/bootstrap-service.js +85 -10
  25. package/dist/core/channel-loader.js +23 -6
  26. package/dist/core/command/agent-control.js +14 -11
  27. package/dist/core/command/menu-handler.js +67 -76
  28. package/dist/core/command/slash-handler.js +4 -4
  29. package/dist/core/evolagent-registry.js +125 -35
  30. package/dist/core/evolagent.js +8 -3
  31. package/dist/core/inference/text-inference.js +38 -4
  32. package/dist/core/message/message-bridge.js +1 -1
  33. package/dist/core/message/message-log.js +22 -0
  34. package/dist/core/message/message-queue.js +19 -4
  35. package/dist/core/model/model-catalog.js +143 -24
  36. package/dist/core/model/model-diagnostics.js +28 -10
  37. package/dist/core/permission/index.js +1 -0
  38. package/dist/core/permission/readonly-shell-query.js +532 -0
  39. package/dist/core/permission/shell-environment.js +46 -0
  40. package/dist/core/permission/tool-policy.js +231 -93
  41. package/dist/core/protected-paths.js +10 -7
  42. package/dist/core/runner-reload-transaction.js +57 -0
  43. package/dist/index.js +262 -84
  44. package/dist/ipc.js +29 -11
  45. package/dist/utils/aid-bind.js +3 -8
  46. package/dist/utils/log-writer.js +6 -10
  47. package/dist/utils/logger.js +5 -5
  48. package/kits/docs/evolcore/msg.md +13 -0
  49. package/kits/rules/01-overview.md +9 -0
  50. package/kits/schemas/agent-config.schema.3.json +1 -1
  51. package/kits/schemas/agent-config.schema.4.json +1 -1
  52. package/kits/schemas/relation-config.schema.2.json +1 -1
  53. package/kits/schemas/role-config.schema.1.json +1 -1
  54. package/kits/templates/roles/admin.json +5 -0
  55. package/kits/templates/roles/member.json +17 -0
  56. package/kits/templates/roles/visitor.json +8 -0
  57. package/kits/templates/system-fragments/bootstrap.md +12 -6
  58. package/kits/templates/system-fragments/channel.md +6 -0
  59. package/kits/templates/system-fragments/session.md +2 -0
  60. package/package.json +2 -1
  61. package/skills/eclink/SKILL.md +15 -3
  62. package/skills/eclink/agents/openai.yaml +3 -3
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
4
4
  [`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
5
5
 
6
+ ## 0.0.11 (2026-08-11)
7
+
8
+ ### Agent 初始化与引导
9
+
10
+ - `ec init` 支持在任意交互步骤连续按两次 Esc 或使用 Ctrl+C 安全退出,并完善二维码超时后的重新生成、手动绑定与取消清理流程。
11
+ - 重新设计 Agent bootstrap 引导消息,在 Agent 完成初始化后可靠投递,并通过持久化和去重避免断线重试造成消息丢失或重复。
12
+
13
+ ### 权限与消息可靠性
14
+
15
+ - 扩展安全只读 Shell 查询和环境变量解析能力,统一工具授权、危险命令识别与审批边界。
16
+ - 完善 AUN 关键消息的持久化、去重和日志落盘,提升断线恢复与异常重试时的一致性。
17
+
18
+ ### 请求身份与模型目录
19
+
20
+ - 模型请求统一携带 EvolCore 版本、Agent AID 和 BaseAgent 类型,便于网关进行来源识别、审计和问题定位。
21
+ - Claude 元数据请求复用 Agent 自定义请求头,同时隔离公共模型目录请求中的 API Key、租户头和 Agent 身份信息。
22
+
23
+ ### 热重载
24
+
25
+ - 同一 Agent 的重载操作改为串行执行;任务繁忙时默认拒绝重载,也可通过 `--force` 清理队列并等待运行中任务中断。
26
+ - 为 runner 与渠道重载建立明确的提交边界和失败回滚,完善 resync 下线清理,避免重载失败后残留旧实例或半更新状态。
27
+
6
28
  ## 0.0.10 (2026-08-06)
7
29
 
8
30
  ### 数据迁移
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`)。
@@ -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,6 +13,7 @@ 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';
16
+ import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
14
17
  import { summarizeToolInput } from '../utils/tool-summary.js';
15
18
  import { encodePath } from '../utils/cross-platform.js';
16
19
  import { getPackageRoot, resolvePaths } from '../paths.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);
@@ -1859,6 +1904,7 @@ export class AgentRunner {
1859
1904
  : undefined;
1860
1905
  const preflight = evaluateToolPreflight(toolName, toolInput, {
1861
1906
  sessionId,
1907
+ selfAid: permCtx?.selfAid,
1862
1908
  channel: permCtx?.channel,
1863
1909
  userId: permCtx?.userId,
1864
1910
  role: permCtx?.role,
@@ -1893,7 +1939,6 @@ export class AgentRunner {
1893
1939
  channel: permCtx?.channel,
1894
1940
  peerId: permCtx?.userId,
1895
1941
  role: permCtx?.role,
1896
- allowLiteralReadShell: true,
1897
1942
  };
1898
1943
  const roResult = checkReadonly(toolName, toolInput, projectPath, readonlyContext);
1899
1944
  if (roResult.behavior === 'deny') {
@@ -1974,6 +2019,7 @@ export class AgentRunner {
1974
2019
  const permCtx = this.permissionContexts.get(sessionId);
1975
2020
  const preflight = evaluateToolPreflight(toolName, input, {
1976
2021
  sessionId,
2022
+ selfAid: permCtx?.selfAid,
1977
2023
  channel: permCtx?.channel,
1978
2024
  userId: permCtx?.userId,
1979
2025
  role: permCtx?.role,
@@ -2043,7 +2089,6 @@ export class AgentRunner {
2043
2089
  channel: permCtx?.channel,
2044
2090
  peerId: permCtx?.userId,
2045
2091
  role: permCtx?.role,
2046
- allowLiteralReadShell: true,
2047
2092
  };
2048
2093
  const roResult = checkReadonly(toolName, input, projectPath, readonlyContext);
2049
2094
  if (roResult.behavior === 'deny') {
@@ -2129,7 +2174,9 @@ export class AgentRunner {
2129
2174
  : await (async () => {
2130
2175
  const sandboxProjectionStartedAt = Date.now();
2131
2176
  ensureClaudeGitWorktreeConfig(projectPath);
2132
- const sandboxFilesystem = buildClaudeProtectedFilesystem(resolvePaths().root);
2177
+ const sandboxFilesystem = buildClaudeProtectedFilesystem(resolvePaths().root, {
2178
+ denyLClassRead: callPermissionMode === 'readonly',
2179
+ });
2133
2180
  const sandboxUnixSockets = buildClaudeUnixSocketAllowlist(projectPath, {
2134
2181
  role: runPermissionContext?.role,
2135
2182
  });
@@ -2153,8 +2200,29 @@ export class AgentRunner {
2153
2200
  };
2154
2201
  })();
2155
2202
  if (executionSandbox.state === 'off') {
2156
- logger.info('[ClaudeSandbox] disabled for authenticated owner bypass task');
2203
+ logger.info(process.platform === 'linux'
2204
+ ? '[ClaudeSandbox] task sandbox disabled for authenticated owner bypass; outer H-class guard remains active'
2205
+ : '[ClaudeSandbox] disabled for authenticated owner bypass task');
2157
2206
  }
2207
+ const handleClaudeStderr = (msg) => {
2208
+ const trimmed = msg.trim();
2209
+ if (trimmed) {
2210
+ let buf = this.recentStderr.get(sessionId);
2211
+ if (!buf) {
2212
+ buf = [];
2213
+ this.recentStderr.set(sessionId, buf);
2214
+ }
2215
+ buf.push(trimmed);
2216
+ if (buf.length > AgentRunner.STDERR_BUFFER_MAX)
2217
+ buf.shift();
2218
+ }
2219
+ if (msg.includes('[ERROR]') || msg.includes('[WARN]') || msg.includes('Stream started')) {
2220
+ logger.info(`[Claude-stderr] ${trimmed}`);
2221
+ }
2222
+ else {
2223
+ logger.debug(`[Claude-stderr] ${trimmed}`);
2224
+ }
2225
+ };
2158
2226
  const commonOptions = {
2159
2227
  cwd: projectPath,
2160
2228
  model: sdkModel,
@@ -2163,6 +2231,9 @@ export class AgentRunner {
2163
2231
  managedSettings,
2164
2232
  ...(callEffort ? { effort: callEffort } : {}),
2165
2233
  ...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
2234
+ ...(executionSandbox.state === 'off' && process.platform === 'linux' ? {
2235
+ spawnClaudeCodeProcess: (options) => spawnClaudeWithHClassGuard(options, resolvePaths().root, handleClaudeStderr),
2236
+ } : {}),
2166
2237
  autoCompactWindow: autoCompactWindowForModel(sdkModel),
2167
2238
  advisorModel: 'haiku',
2168
2239
  canUseTool: canUseToolCallback,
@@ -2177,27 +2248,8 @@ export class AgentRunner {
2177
2248
  PermissionDenied: [{ matcher: '.*', hooks: [permissionDeniedHook] }]
2178
2249
  },
2179
2250
  ...(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)
2251
+ stderr: handleClaudeStderr,
2252
+ env: this.getAgentEnv(runtimeEnv, sessionId, callPermissionMode)
2201
2253
  };
2202
2254
  const createQuery = (promptInput, resumeSessionId, resumeAt) => {
2203
2255
  if (useSettingSources) {
@@ -2479,6 +2531,35 @@ export class AgentRunner {
2479
2531
  this.activeQueries.delete(sessionId);
2480
2532
  this.permissionContexts.delete(sessionId);
2481
2533
  }
2534
+ async dispose() {
2535
+ const interrupts = [...this.interruptFns.values()].map(async (interrupt) => {
2536
+ try {
2537
+ await interrupt();
2538
+ }
2539
+ catch { }
2540
+ });
2541
+ await Promise.allSettled(interrupts);
2542
+ for (const stream of this.activeMessageStreams.values())
2543
+ stream.end();
2544
+ for (const query of this.activeQueries.values()) {
2545
+ try {
2546
+ if (typeof query?.close === 'function')
2547
+ await query.close();
2548
+ }
2549
+ catch { }
2550
+ }
2551
+ for (const resolve of this.streamDoneResolvers.values())
2552
+ resolve();
2553
+ this.activeSessions.clear();
2554
+ this.activeStreams.clear();
2555
+ this.activeMessageStreams.clear();
2556
+ this.interruptFns.clear();
2557
+ this.activeQueries.clear();
2558
+ this.streamDone.clear();
2559
+ this.streamDoneResolvers.clear();
2560
+ this.permissionContexts.clear();
2561
+ this.recentStderr.clear();
2562
+ }
2482
2563
  resolveSessionFile(agentSessionId, projectPath) {
2483
2564
  const encodedProjectPath = encodePath(projectPath);
2484
2565
  const sessionFile = path.join(os.homedir(), '.claude', 'projects', encodedProjectPath, `${agentSessionId}.jsonl`);
@@ -4,6 +4,8 @@ import { logger } from '../utils/logger.js';
4
4
  import { buildHClassGuardCommand } from '../core/permission/sandbox-runtime.js';
5
5
  import { resolveCodexLaunchCommand } from '../utils/codex-cli.js';
6
6
  import { createHash } from 'crypto';
7
+ import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
8
+ const BUILT_IN_CODEX_PROVIDER_IDS = new Set(['openai']);
7
9
  function tomlProviderKey(value) {
8
10
  if (!value || /[\0\r\n]/.test(value)) {
9
11
  throw new Error(`Invalid Codex provider name: ${value}`);
@@ -19,6 +21,10 @@ function codexHeaderEnvName(headerName) {
19
21
  const digest = createHash('sha256').update(headerName).digest('hex').slice(0, 16).toUpperCase();
20
22
  return `EVOLCORE_CODEX_HEADER_${digest}`;
21
23
  }
24
+ function runtimeProviderId(baseUrl) {
25
+ const digest = createHash('sha256').update(baseUrl || 'default').digest('hex').slice(0, 12);
26
+ return `evolcore_runtime_${digest}`;
27
+ }
22
28
  function hasPermissionProfile(config) {
23
29
  if (!config || typeof config.default_permissions !== 'string' || !config.default_permissions)
24
30
  return false;
@@ -341,14 +347,7 @@ export class CodexAppServerClient {
341
347
  }
342
348
  }
343
349
  startProcess() {
344
- const env = { ...process.env, ...this.options.env };
345
- env.CODEX_API_KEY = this.options.apiKey;
346
- for (const [name, value] of Object.entries(this.options.headers ?? {})) {
347
- env[codexHeaderEnvName(name)] = value;
348
- }
349
- if (this.options.baseUrl) {
350
- env.OPENAI_BASE_URL = this.options.baseUrl;
351
- }
350
+ const env = this.buildProcessEnvironment();
352
351
  const args = this.buildProcessArgs();
353
352
  const launchCommand = resolveCodexLaunchCommand(args);
354
353
  if (!launchCommand)
@@ -378,6 +377,20 @@ export class CodexAppServerClient {
378
377
  logger.debug(`[CodexAppServer] ${text}`);
379
378
  });
380
379
  }
380
+ buildProcessEnvironment() {
381
+ const env = sanitizeShellExecutionEnvironment({
382
+ ...process.env,
383
+ ...this.options.env,
384
+ });
385
+ env.CODEX_API_KEY = this.options.apiKey;
386
+ for (const [name, value] of Object.entries(this.options.headers ?? {})) {
387
+ env[codexHeaderEnvName(name)] = value;
388
+ }
389
+ if (this.options.baseUrl) {
390
+ env.OPENAI_BASE_URL = this.options.baseUrl;
391
+ }
392
+ return env;
393
+ }
381
394
  buildProcessArgs() {
382
395
  const args = ['app-server', '--listen', 'stdio://'];
383
396
  if (this.options.enableRequestUserInput !== false) {
@@ -390,7 +403,18 @@ export class CodexAppServerClient {
390
403
  const hasHeaders = Object.keys(headerEnvNames).length > 0;
391
404
  const hasQueryParams = Object.keys(this.options.queryParams ?? {}).length > 0;
392
405
  if (hasHeaders || hasQueryParams) {
393
- const provider = tomlProviderKey(this.options.provider || 'openai');
406
+ const configuredProvider = this.options.provider?.trim();
407
+ const useRuntimeProvider = !configuredProvider || BUILT_IN_CODEX_PROVIDER_IDS.has(configuredProvider.toLowerCase());
408
+ const providerName = !useRuntimeProvider
409
+ ? configuredProvider
410
+ : runtimeProviderId(this.options.baseUrl);
411
+ const provider = tomlProviderKey(providerName);
412
+ args.push('--config', `model_provider=${JSON.stringify(providerName)}`);
413
+ args.push('--config', `model_providers.${provider}.name=${JSON.stringify('EvolCore runtime gateway')}`);
414
+ args.push('--config', `model_providers.${provider}.wire_api=${JSON.stringify('responses')}`);
415
+ if (useRuntimeProvider || this.options.baseUrl) {
416
+ args.push('--config', `model_providers.${provider}.base_url=${JSON.stringify(this.options.baseUrl || 'https://api.openai.com/v1')}`);
417
+ }
394
418
  if (hasHeaders) {
395
419
  args.push('--config', `model_providers.${provider}.env_http_headers=${tomlInlineStringTable(headerEnvNames)}`);
396
420
  }
@@ -18,6 +18,7 @@ import { renderActionAsText } from '../core/interaction-router.js';
18
18
  import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
19
19
  import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
20
20
  import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
21
+ import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
21
22
  import { classifyEvolcoreShellCommand, parseLiteralShellArgv } from '../core/permission/ec-command-parser.js';
22
23
  import { compareVersions } from '../utils/npm-ops.js';
23
24
  import { resolvePaths, resolveRoot } from '../paths.js';
@@ -25,6 +26,7 @@ import { buildSessionTurnList } from '../core/session/session-turns.js';
25
26
  import { execFileSync } from 'child_process';
26
27
  import { execCodexCliSync, resolveCodexCliPath } from '../utils/codex-cli.js';
27
28
  import { createHash, randomBytes } from 'crypto';
29
+ import { buildModelRequestHeaders } from './request-identity.js';
28
30
  import fs from 'fs';
29
31
  import path from 'path';
30
32
  import os from 'os';
@@ -231,6 +233,12 @@ export class CodexRunner {
231
233
  pendingInterruptTtlMs = 30_000;
232
234
  constructor(config, callbacks) {
233
235
  this.resolvedConfig = resolveOpenaiConfig(config);
236
+ this.resolvedConfig.headers = buildModelRequestHeaders({
237
+ baseagent: 'codex',
238
+ baseUrl: this.resolvedConfig.baseUrl,
239
+ agentAid: config.agents?.codex?.evolcoreAgentAid,
240
+ configuredHeaders: this.resolvedConfig.headers,
241
+ });
234
242
  this.resolvedConfig.evolcoreAgentAid = config.agents?.codex?.evolcoreAgentAid;
235
243
  this.resolvedConfig.evolcoreAgentConfig = config.agents?.codex?.evolcoreAgentConfig;
236
244
  this.capabilities = {
@@ -416,14 +424,35 @@ export class CodexRunner {
416
424
  }
417
425
  async resolveCapabilityThreadConfig(projectPath) {
418
426
  const agentConfig = this.resolvedConfig.evolcoreAgentConfig;
419
- if (!agentConfig)
420
- return {};
427
+ if (!agentConfig) {
428
+ return {
429
+ skills: { config: [{ name: 'eclink', enabled: false }] },
430
+ };
431
+ }
421
432
  try {
422
- return await resolveCodexCapabilityThreadConfigForProject(agentConfig, projectPath, 'codex');
433
+ const resolved = await resolveCodexCapabilityThreadConfigForProject(agentConfig, projectPath, 'codex');
434
+ const skills = this.configRecord(resolved.skills);
435
+ const configured = Array.isArray(skills?.config)
436
+ ? skills.config.filter(entry => {
437
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
438
+ return true;
439
+ return entry.name !== 'eclink';
440
+ })
441
+ : [];
442
+ return this.mergeThreadConfig(resolved, {
443
+ skills: {
444
+ ...(skills ?? {}),
445
+ config: [...configured, { name: 'eclink', enabled: false }],
446
+ },
447
+ }) ?? {
448
+ skills: { config: [{ name: 'eclink', enabled: false }] },
449
+ };
423
450
  }
424
451
  catch (error) {
425
452
  logger.warn(`[CodexRunner] Failed to resolve Codex capability thread config: ${error}`);
426
- return {};
453
+ return {
454
+ skills: { config: [{ name: 'eclink', enabled: false }] },
455
+ };
427
456
  }
428
457
  }
429
458
  configRecord(value) {
@@ -721,7 +750,9 @@ export class CodexRunner {
721
750
  [profileName]: {
722
751
  extends: mode === 'readonly' ? ':read-only' : ':workspace',
723
752
  filesystem: {
724
- ...buildCodexProtectedFilesystemRules(resolveRoot()),
753
+ ...buildCodexProtectedFilesystemRules(resolveRoot(), {
754
+ denyLClassRead: mode === 'readonly',
755
+ }),
725
756
  ...this.resolveGitMetadataWriteRules(projectPath, mode),
726
757
  ...(mode !== 'readonly' && managedTempDir ? {
727
758
  [managedTempDir]: 'write',
@@ -819,7 +850,7 @@ export class CodexRunner {
819
850
  approvalPolicy: effectiveApprovalPolicy,
820
851
  approvalsReviewer: 'user',
821
852
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
822
- config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()),
853
+ config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()), callMode),
823
854
  ...(developerInstructions ? { developerInstructions } : {}),
824
855
  };
825
856
  logger.info(`[CodexRunner] runQuery permMode=${requestedPermissionMode}->${callMode} ` +
@@ -1086,7 +1117,7 @@ export class CodexRunner {
1086
1117
  approvalPolicy: compactPolicy,
1087
1118
  approvalsReviewer: 'user',
1088
1119
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
1089
- config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()),
1120
+ config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()), compactMode),
1090
1121
  });
1091
1122
  this.threadProjectPaths.set(agentSessionId, path.resolve(_projectPath));
1092
1123
  this.threadDelegationCarriers.set(agentSessionId, compactDelegationCarrier);
@@ -1236,7 +1267,7 @@ export class CodexRunner {
1236
1267
  approvalPolicy: this.toApprovalPolicy(mode),
1237
1268
  approvalsReviewer: 'user',
1238
1269
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
1239
- config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()),
1270
+ config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()), mode),
1240
1271
  });
1241
1272
  const forkedThreadId = response.thread?.id;
1242
1273
  if (!forkedThreadId)
@@ -1951,6 +1982,7 @@ export class CodexRunner {
1951
1982
  const mode = normalizePermissionMode(rawMode).mode;
1952
1983
  const preflight = evaluateToolPreflight(toolName, toolInput, {
1953
1984
  sessionId: sessionKey,
1985
+ selfAid: permissionContext?.selfAid,
1954
1986
  channel: permissionContext?.channel,
1955
1987
  userId: permissionContext?.userId,
1956
1988
  role: permissionContext?.role,
@@ -2810,6 +2842,24 @@ export class CodexRunner {
2810
2842
  },
2811
2843
  };
2812
2844
  }
2845
+ sanitizeThreadEnvironmentConfig(config, permissionMode) {
2846
+ if (!config)
2847
+ return null;
2848
+ const policy = this.configRecord(config.shell_environment_policy);
2849
+ const set = this.configRecord(policy?.set);
2850
+ if (!policy || !set)
2851
+ return config;
2852
+ const safeSet = sanitizeShellExecutionEnvironment(set, {
2853
+ lockExecutableResolution: permissionMode === 'readonly',
2854
+ });
2855
+ return {
2856
+ ...config,
2857
+ shell_environment_policy: {
2858
+ ...policy,
2859
+ set: safeSet,
2860
+ },
2861
+ };
2862
+ }
2813
2863
  async dispose() {
2814
2864
  // Abort all active streams
2815
2865
  for (const [key, controller] of this.activeAbortControllers) {