evolcore 0.0.24 → 0.0.26

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 (112) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +12 -1
  3. package/bin/install-codex-managed-hooks.mjs +28 -1
  4. package/dist/agents/baseagent.js +96 -46
  5. package/dist/agents/claude-runner.js +54 -13
  6. package/dist/agents/codex/app-server-client.js +13 -5
  7. package/dist/agents/codex/catalog.js +1 -1
  8. package/dist/agents/codex/diagnostics.js +34 -1
  9. package/dist/agents/codex-runner.js +128 -42
  10. package/dist/agents/ecagent-runner.js +14 -18
  11. package/dist/agents/gemini-runner.js +24 -9
  12. package/dist/agents/runner-types.js +25 -17
  13. package/dist/aun/aid/agentmd.js +135 -10
  14. package/dist/aun/aid/client.js +1 -1
  15. package/dist/aun/aid/identity.js +1 -1
  16. package/dist/aun/aid/index.js +2 -1
  17. package/dist/aun/aid/managed-operation.js +53 -1
  18. package/dist/aun/aid/store.js +17 -13
  19. package/dist/aun/menu-client.js +74 -0
  20. package/dist/aun/msg/group.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +1 -1
  22. package/dist/aun/msg/p2p.js +6 -1
  23. package/dist/aun/msg/upload.js +30 -22
  24. package/dist/aun/storage/client.js +4 -1
  25. package/dist/aun/storage/index.js +1 -1
  26. package/dist/channels/aun.js +329 -94
  27. package/dist/cli/agent-command.js +125 -5
  28. package/dist/cli/agent-create.js +151 -11
  29. package/dist/cli/agent.js +119 -6
  30. package/dist/cli/aun-commands.js +74 -7
  31. package/dist/cli/config.js +27 -8
  32. package/dist/cli/contact.js +63 -6
  33. package/dist/cli/daemon-commands.js +90 -25
  34. package/dist/cli/diag.js +31 -0
  35. package/dist/cli/handoff-command.js +35 -3
  36. package/dist/cli/index.js +7 -1
  37. package/dist/cli/managed-command-guard.js +3 -0
  38. package/dist/cli/menu-client.js +38 -23
  39. package/dist/cli/restart-monitor.js +57 -10
  40. package/dist/config/builtin-roles.js +2 -0
  41. package/dist/config/config-manager.js +15 -0
  42. package/dist/config/config-operation-service.js +165 -3
  43. package/dist/config/contact-operation-service.js +96 -7
  44. package/dist/config/resolved-config-op.js +66 -8
  45. package/dist/core/agent-application-service.js +2 -1
  46. package/dist/core/auth/authorization-audit.js +20 -12
  47. package/dist/core/auth/operation-authorizer.js +87 -8
  48. package/dist/core/auth/operation-catalog.js +51 -4
  49. package/dist/core/auth/remote-access-store.js +40 -1
  50. package/dist/core/bootstrap-service.js +1 -1
  51. package/dist/core/command/agent-control.js +54 -25
  52. package/dist/core/command/cli-intent-parser.js +18 -0
  53. package/dist/core/command/command-handler.js +152 -20
  54. package/dist/core/command/diag-service.js +118 -0
  55. package/dist/core/command/menu-catalog.js +13 -3
  56. package/dist/core/command/menu-handler.js +88 -31
  57. package/dist/core/command/menu-protocol.js +6 -0
  58. package/dist/core/command/menu-token-store.js +41 -8
  59. package/dist/core/command/slash-gate.js +34 -4
  60. package/dist/core/command/slash-handler.js +77 -12
  61. package/dist/core/event-catalog.js +5 -0
  62. package/dist/core/evolagent.js +3 -3
  63. package/dist/core/handoff/runtime.js +205 -27
  64. package/dist/core/handoff/store.js +193 -4
  65. package/dist/core/message/im-renderer.js +72 -21
  66. package/dist/core/message/message-bridge.js +69 -9
  67. package/dist/core/message/message-queue.js +26 -0
  68. package/dist/core/message/message-utils.js +1 -0
  69. package/dist/core/message/response-engine.js +48 -23
  70. package/dist/core/model/catalog-service.js +79 -49
  71. package/dist/core/model/context-window-suffix.js +33 -0
  72. package/dist/core/model/model-catalog.js +1 -1
  73. package/dist/core/model/model-permission.js +12 -3
  74. package/dist/core/permission/ec-command-parser.js +51 -12
  75. package/dist/core/permission/sandbox-runtime.js +36 -14
  76. package/dist/core/permission/tool-policy.js +47 -7
  77. package/dist/index.js +195 -31
  78. package/dist/ipc.js +54 -2
  79. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  80. package/dist/stats/billing.js +41 -15
  81. package/dist/stats/price-resolver.js +28 -10
  82. package/dist/utils/avatar-upload.js +282 -23
  83. package/dist/utils/cross-platform.js +30 -1
  84. package/dist/utils/error-utils.js +21 -0
  85. package/dist/utils/evolcore-version.js +129 -0
  86. package/dist/utils/logger.js +3 -1
  87. package/dist/utils/model-prices.jsonl +3 -3
  88. package/dist/utils/npm-ops.js +176 -35
  89. package/kits/docs/context-assembly.md +1 -0
  90. package/kits/docs/evolcore/INDEX.md +3 -1
  91. package/kits/docs/evolcore/agent-create.md +30 -11
  92. package/kits/docs/evolcore/agent.md +18 -8
  93. package/kits/docs/evolcore/aid.md +6 -4
  94. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  95. package/kits/docs/evolcore/config.md +7 -0
  96. package/kits/docs/evolcore/contact.md +12 -5
  97. package/kits/docs/evolcore/menu-client.md +11 -3
  98. package/kits/docs/evolcore/msg.md +1 -0
  99. package/kits/docs/prompt-loading-architecture.md +1 -0
  100. package/kits/docs/venues/group.md +12 -0
  101. package/kits/eck_manifest.json +12 -0
  102. package/kits/rules/03-identity.md +3 -1
  103. package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
  104. package/kits/templates/roles/admin.json +14 -2
  105. package/kits/templates/roles/member.json +3 -2
  106. package/kits/templates/roles/visitor.json +4 -3
  107. package/kits/templates/system-fragments/bootstrap.md +16 -5
  108. package/kits/templates/system-fragments/channel.md +1 -1
  109. package/kits/templates/system-fragments/session.md +1 -1
  110. package/package.json +6 -4
  111. package/skills/eclink/SKILL.md +0 -84
  112. package/skills/eclink/agents/openai.yaml +0 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,65 @@
3
3
  本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
4
4
  [`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
5
5
 
6
+ ## 0.0.26 (2026-09-18)
7
+
8
+ ### AUN、联系人与远程诊断
9
+
10
+ - 将 `@agentunion/fastaun` 依赖和运行时最低版本要求提升至 `0.5.16`;
11
+ 消息可靠投递适配独立的应用事件队列。
12
+ - 完善消息卡片操作通知的可靠投递,保留卡片回调及回复的话题路由;
13
+ 收紧 payload 大小检查,停止重试永久失败的 activity,阻止失效会话回投 handoff 结果。
14
+ - 阻止空白正文进入消息发送链路,并保留关联任务的诊断信息。
15
+ - 扩展联系人列表查询,支持按 `requestId` 处理联系人申请;
16
+ 创建 Agent 后同步本地 Owner 联系人,展示 lifecycle 状态,并允许标签使用连字符。
17
+ - 新增 `ec diag` 远程诊断邀请和 `ec diag stop` 撤销入口,校验发起身份并管理诊断授权生命周期。
18
+ - 将群聊 slash 命令按权限收敛为帮助、状态、会话控制和只读查询;
19
+ 群聊配置查询不再生成修改操作卡片。
20
+ - 群聊命令回执统一使用控制通知,保留原始投递上下文,避免中间回执进入其它 Agent 的模型输入。
21
+
22
+ ### 权限与兼容性
23
+
24
+ - npm 包整体排除本机 `skills/` 目录,避免本地 skill 文件随包发布。
25
+ - 新建 agent.md 默认使用 `agent` 类型,并按白名单归一化类型;
26
+ 对齐 SDK 的可选 description、Unicode 字数、avatar 长度及 4 KB 文档上限校验。
27
+ - 收紧联系人申请处理、非管理角色跨 Agent 查询及配对码访问;
28
+ EC Web 支持 Owner 分配 admin 关系角色,并限制 admin 对同级角色的操作。
29
+ - 已认证 EC Web 请求统一获得 daemon-owner 进程管理权限,开放远程基础设置修改与配置密钥查看,
30
+ 取消连接参数额外脱敏,并补充请求关联、无会话范围和实际权限模式审计。
31
+ - 通用 Trigger IPC 仍仅开放历史与事件目录查询;角色操作继续按服务端注入的授权上下文校验。
32
+ - 保留 Codex 上游声明的 `max` 推理强度;模型目录保留上下文窗口后缀,查价和计费使用基础模型 ID。
33
+ - 模型目录始终返回最近一次成功快照,并在后台刷新失败或过期数据。
34
+
35
+ ### Windows 与运行稳定性
36
+
37
+ - 改进 Codex Windows 启动和进程管理,支持 PowerShell 当前会话临时目录引用;
38
+ 为较长运行目录提供 IPC socket 路径回退。
39
+ - Codex managed hook 安装和诊断统一解析包根路径,兼容 `npm link` 场景。
40
+ - 停止与重启前校验进程身份,避免误操作复用的 PID;识别 Linux 僵尸进程并补充运行构建诊断。
41
+ - 升级使用当前 Node 对应的 npm,保持版本查询与安装使用相同源,并校验安装后的实际版本。
42
+ - 修复 echo RTT 时区计算,并为 EvolCore 错误信息补充版本标记。
43
+ - Claude 工具沙箱初始化失败时结束当前查询,并将工具触发的熔断限制在当前会话。
44
+ - Codex app-server 兼容嵌套错误通知,按实际原因分类并保留轮内自动重试;
45
+ 模型输出预算耗尽作为独立终止原因处理,禁止重复提交并提示原会话续接。
46
+ - 本地日志时间戳加入 UTC 偏移,便于跨时区关联诊断记录。
47
+
48
+ ## 0.0.25 (2026-09-15)
49
+
50
+ ### Agent 资料与头像
51
+
52
+ - 新增 `ec aid avatar` 和菜单头像上传入口,支持本地图片与 HTTPS 图片地址,校验图片格式、大小、尺寸和宽高比,并广播头像更新事件。
53
+ - 新增 `ec agent persona` 读写命令,支持在初始化过程中由 owner 确认可选人格设置。
54
+ - 自动加载 `personal.md` 个人资料,并为 owner 会话配置各运行器的文件访问权限。
55
+ - Agent 创建支持传入 tags,统一校验标签内容、数量并去重。
56
+
57
+ ### 权限、配置与运行时
58
+
59
+ - 规范 owner 列表的校验与替换语义,保护最后一个 owner,避免清空管理权限。
60
+ - 允许管理角色查询其他 Agent 的非敏感配置,并开放合法 AID 的公开查询与名片读取。
61
+ - 恢复已认证远程 EC Web 请求的基础设置读写能力,敏感信息继续按需读取。
62
+ - 加强 Windows 临时路径校验,限制临时写入在当前会话的托管目录内。
63
+ - 升级 `@agentunion/fastaun` 至 `0.5.14`。
64
+
6
65
  ## 0.0.24 (2026-09-14)
7
66
 
8
67
  ### Windows 与 Codex 运行时
package/bin/ec.js CHANGED
@@ -12,10 +12,21 @@ const srcEntry = path.join(repoRoot, 'src', 'cli', 'index.ts');
12
12
  const distEntry = path.join(repoRoot, 'dist', 'cli', 'index.js');
13
13
  const args = process.argv.slice(2);
14
14
 
15
+ // tsx derives its cache directory from `process.geteuid()` on POSIX and
16
+ // `os.userInfo()` on Windows. In managed Windows sessions the latter can
17
+ // transiently fail with uv_os_get_passwd/ENOMEM before the CLI starts. Make
18
+ // tsx take its non-POSIX branch without changing the parent process.
19
+ const tsxWindowsCompatImport = process.platform === 'win32'
20
+ ? 'data:text/javascript,globalThis.process.geteuid=()=>-1'
21
+ : undefined;
22
+
15
23
  if (fs.existsSync(srcEntry)) {
16
24
  try {
17
25
  const tsxImport = pathToFileURL(require.resolve('tsx')).href;
18
- const result = spawnSync(process.execPath, ['--import', tsxImport, srcEntry, ...args], { stdio: 'inherit' });
26
+ const imports = tsxWindowsCompatImport
27
+ ? ['--import', tsxWindowsCompatImport, '--import', tsxImport]
28
+ : ['--import', tsxImport];
29
+ const result = spawnSync(process.execPath, [...imports, srcEntry, ...args], { stdio: 'inherit' });
19
30
  process.exit(result.status ?? (result.error ? 1 : 0));
20
31
  } catch {}
21
32
  }
@@ -258,6 +258,33 @@ function sourceHasManagedMarker(source) {
258
258
  return source.includes(markerStart) || source.includes(markerEnd);
259
259
  }
260
260
 
261
+ /** Read back the package root that managedHookLauncher() embedded. */
262
+ export function launcherPackageRoot(launcher) {
263
+ const match = /^\s*const packageRoot = ("(?:[^"\\]|\\.)*")\s*;/m.exec(launcher);
264
+ if (!match) return null;
265
+ try {
266
+ const value = JSON.parse(match[1]);
267
+ return typeof value === 'string' && value ? value : null;
268
+ } catch {
269
+ return null;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * A `npm link` install reaches the package through a symlinked global path, so
275
+ * the launcher and the running CLI legitimately spell the same package root
276
+ * differently. Compare the resolved targets instead of the literal strings;
277
+ * a launcher pointing at a genuinely different package still fails closed.
278
+ */
279
+ export function samePackageRoot(embedded, expected) {
280
+ if (typeof embedded !== 'string' || !embedded) return false;
281
+ const normalize = value => path.normalize(String(value)).replace(/[\\/]+$/, '').toLowerCase();
282
+ const resolved = value => {
283
+ try { return normalize(fs.realpathSync(value)); } catch { return normalize(value); }
284
+ };
285
+ return normalize(embedded) === normalize(expected) || resolved(embedded) === resolved(expected);
286
+ }
287
+
261
288
  /** Inspect only the canonical EvolCore managed-hook targets. */
262
289
  export function inspectManagedHookInstallation({ requirements, managedDir, packageRoot }) {
263
290
  const canonicalRequirements = path.resolve(requirements);
@@ -304,7 +331,7 @@ export function inspectManagedHookInstallation({ requirements, managedDir, packa
304
331
  const normalized = value => path.normalize(String(value || '')).toLowerCase();
305
332
  const launcher = fs.readFileSync(hookPath, 'utf8');
306
333
  const packageMarker = launcher.includes('EVOLCORE_MANAGED_HOOK_LAUNCHER')
307
- && launcher.toLowerCase().includes(JSON.stringify(path.resolve(packageRoot || process.cwd())).toLowerCase());
334
+ && samePackageRoot(launcherPackageRoot(launcher), path.resolve(packageRoot || process.cwd()));
308
335
  const preToolUse = parsed.hooks?.PreToolUse;
309
336
  const canonicalCommand = value => {
310
337
  if (typeof value !== 'string' || !value || /[\r\n;&|<>]/.test(value)) return false;
@@ -14,6 +14,7 @@ import path from 'path';
14
14
  import os from 'os';
15
15
  import { commandExists, isWindows, resolveProbePath } from '../utils/cross-platform.js';
16
16
  import { execFileSync } from 'child_process';
17
+ import { parse as parseToml } from 'smol-toml';
17
18
  /** Resolve the installed Codex CLI entrypoint, including npm shims on Windows. */
18
19
  export function resolveCodexCliPath() {
19
20
  return resolveProbePath('codex');
@@ -185,55 +186,74 @@ export function resolveAnthropicConfig(config, override) {
185
186
  || pickExec(config.agents?.claude?.pathToClaudeCodeExecutable);
186
187
  return { apiKey, baseUrl, headers, queryParams, model, effort, pathToClaudeCodeExecutable };
187
188
  }
189
+ function nonEmptyString(value) {
190
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
191
+ }
192
+ function recordValue(value) {
193
+ return value && typeof value === 'object' && !Array.isArray(value)
194
+ ? value
195
+ : undefined;
196
+ }
188
197
  export function loadCodexSettings() {
198
+ const configuredHome = process.env.CODEX_HOME?.trim();
199
+ const codexHome = configuredHome ? path.resolve(configuredHome) : path.join(os.homedir(), '.codex');
200
+ const authPath = path.join(codexHome, 'auth.json');
201
+ const configPath = path.join(codexHome, 'config.toml');
202
+ let authJsonApiKey;
189
203
  try {
190
- const configuredHome = process.env.CODEX_HOME?.trim();
191
- const codexHome = configuredHome ? path.resolve(configuredHome) : path.join(os.homedir(), '.codex');
192
- const authPath = path.join(codexHome, 'auth.json');
193
- let apiKey;
194
204
  if (fs.existsSync(authPath)) {
195
205
  const auth = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
196
- apiKey = auth.OPENAI_API_KEY;
206
+ authJsonApiKey = nonEmptyString(auth?.OPENAI_API_KEY);
197
207
  }
198
- const configPath = path.join(codexHome, 'config.toml');
199
- let model;
200
- let baseUrl;
201
- let provider;
208
+ }
209
+ catch {
210
+ // A corrupt auth.json must not prevent the provider config from loading.
211
+ }
212
+ let model;
213
+ let baseUrl;
214
+ let provider;
215
+ let bearerToken;
216
+ let requiresOpenaiAuth;
217
+ try {
202
218
  if (fs.existsSync(configPath)) {
203
- const content = fs.readFileSync(configPath, 'utf-8');
204
- const modelMatch = content.match(/^model\s*=\s*"([^"]+)"/m);
205
- if (modelMatch)
206
- model = modelMatch[1];
207
- const providerMatch = content.match(/^model_provider\s*=\s*"([^"]+)"/m);
208
- if (providerMatch) {
209
- provider = providerMatch[1];
210
- baseUrl = readCodexProviderBaseUrl(content, provider);
211
- }
219
+ const parsed = recordValue(parseToml(fs.readFileSync(configPath, 'utf-8')));
220
+ model = nonEmptyString(parsed?.model);
221
+ provider = nonEmptyString(parsed?.model_provider);
222
+ const providers = recordValue(parsed?.model_providers);
223
+ const selectedProvider = provider ? recordValue(providers?.[provider]) : undefined;
224
+ baseUrl = nonEmptyString(selectedProvider?.base_url);
225
+ bearerToken = nonEmptyString(selectedProvider?.experimental_bearer_token);
226
+ requiresOpenaiAuth = typeof selectedProvider?.requires_openai_auth === 'boolean'
227
+ ? selectedProvider.requires_openai_auth
228
+ : undefined;
212
229
  }
213
- return { apiKey, baseUrl, model, provider };
214
230
  }
215
231
  catch { }
216
- return {};
217
- }
218
- function escapeRegExp(value) {
219
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
220
- }
221
- function readCodexProviderBaseUrl(content, provider) {
222
- const segments = /^[A-Za-z0-9_-]+$/.test(provider)
223
- ? [provider, JSON.stringify(provider)]
224
- : [JSON.stringify(provider)];
225
- for (const segment of segments) {
226
- const section = new RegExp(`^\\s*\\[\\s*model_providers\\s*\\.\\s*${escapeRegExp(segment)}\\s*\\]\\s*(?:#.*)?$`, 'm').exec(content);
227
- if (!section || section.index === undefined)
228
- continue;
229
- const tail = content.slice(section.index + section[0].length);
230
- const nextSection = tail.search(/^\s*\[/m);
231
- const block = nextSection >= 0 ? tail.slice(0, nextSection) : tail;
232
- const baseUrl = block.match(/^\s*base_url\s*=\s*"([^"]+)"/m)?.[1];
233
- if (baseUrl)
234
- return baseUrl;
232
+ // Codex applies a provider's explicit bearer token before auth.json. Keep
233
+ // that precedence so EvolCore and the app-server select the same identity.
234
+ if (bearerToken) {
235
+ return {
236
+ apiKey: bearerToken,
237
+ authType: 'bearerToken',
238
+ authSource: 'config.toml',
239
+ baseUrl,
240
+ model,
241
+ provider,
242
+ requiresOpenaiAuth,
243
+ };
235
244
  }
236
- return undefined;
245
+ if (authJsonApiKey) {
246
+ return {
247
+ apiKey: authJsonApiKey,
248
+ authType: 'apiKey',
249
+ authSource: 'auth.json',
250
+ baseUrl,
251
+ model,
252
+ provider,
253
+ requiresOpenaiAuth,
254
+ };
255
+ }
256
+ return { baseUrl, model, provider, requiresOpenaiAuth };
237
257
  }
238
258
  /**
239
259
  * Resolve a complete credential/endpoint pair for direct OpenAI HTTP calls.
@@ -248,14 +268,23 @@ export function resolveOpenaiDirectConfig(override) {
248
268
  ...normalizeRequestMetadata(override),
249
269
  };
250
270
  }
251
- const environment = directInferencePair(process.env.OPENAI_API_KEY, process.env.OPENAI_BASE_URL);
271
+ const settings = loadCodexSettings();
272
+ const configuredBearer = settings.authType === 'bearerToken'
273
+ ? directInferencePair(settings.apiKey, settings.baseUrl)
274
+ : undefined;
275
+ if (configuredBearer) {
276
+ return {
277
+ ...configuredBearer,
278
+ ...normalizeRequestMetadata(override),
279
+ };
280
+ }
281
+ const environment = directInferencePair(process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY, process.env.OPENAI_BASE_URL);
252
282
  if (environment) {
253
283
  return {
254
284
  ...environment,
255
285
  ...normalizeRequestMetadata(override),
256
286
  };
257
287
  }
258
- const settings = loadCodexSettings();
259
288
  const settingsResolved = directInferencePair(settings.apiKey, settings.baseUrl);
260
289
  return settingsResolved
261
290
  ? {
@@ -269,12 +298,31 @@ export function resolveOpenaiConfig(config, override, runtime) {
269
298
  const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
270
299
  const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
271
300
  const globalApiKey = isPlaceholder(config.agents?.codex?.apiKey) ? undefined : config.agents?.codex?.apiKey;
272
- const apiKey = overrideApiKey
273
- || globalApiKey
274
- || process.env.OPENAI_API_KEY
275
- || codexSettings.apiKey;
301
+ const environmentApiKey = [process.env.OPENAI_API_KEY, process.env.CODEX_API_KEY]
302
+ .map(value => isPlaceholder(value) ? undefined : value)
303
+ .find((value) => !!value);
304
+ const configuredBearer = codexSettings.authType === 'bearerToken' ? codexSettings.apiKey : undefined;
305
+ const storedApiKey = codexSettings.authType === 'apiKey' ? codexSettings.apiKey : undefined;
306
+ // A selected provider's explicit token is authoritative for that provider.
307
+ // OPENAI_API_KEY is not used by Codex when the provider declares its own
308
+ // bearer token, so choosing it here would make EvolCore and app-server use
309
+ // different credentials.
310
+ const credential = overrideApiKey
311
+ ? { apiKey: overrideApiKey, authType: 'apiKey', authSource: 'agent-config' }
312
+ : globalApiKey
313
+ ? { apiKey: globalApiKey, authType: 'apiKey', authSource: 'agent-config' }
314
+ : configuredBearer
315
+ ? { apiKey: configuredBearer, authType: 'bearerToken', authSource: 'config.toml' }
316
+ : environmentApiKey
317
+ ? { apiKey: environmentApiKey, authType: 'apiKey', authSource: 'environment' }
318
+ : storedApiKey
319
+ ? { apiKey: storedApiKey, authType: 'apiKey', authSource: 'auth.json' }
320
+ : undefined;
321
+ const apiKey = credential?.apiKey;
322
+ const authType = credential?.authType;
323
+ const authSource = credential?.authSource;
276
324
  if (!apiKey) {
277
- throw new Error('No OpenAI API key found. Set one of: baseagents.codex.apiKey (per-agent or defaults), env OPENAI_API_KEY, or ~/.codex/auth.json');
325
+ throw new Error('No Codex credential found. Set baseagents.codex.apiKey, env OPENAI_API_KEY/CODEX_API_KEY, ~/.codex/auth.json, or a provider experimental_bearer_token in ~/.codex/config.toml');
278
326
  }
279
327
  const isPlaceholderUrl = (v) => !v || v.includes('api.openai.com');
280
328
  const overrideBaseUrl = isPlaceholderUrl(override?.baseUrl) ? undefined : override?.baseUrl;
@@ -303,6 +351,8 @@ export function resolveOpenaiConfig(config, override, runtime) {
303
351
  ?? undefined;
304
352
  return {
305
353
  apiKey,
354
+ authType,
355
+ authSource,
306
356
  baseUrl,
307
357
  provider: codexSettings.provider,
308
358
  headers,
@@ -25,12 +25,13 @@ import { sanitizeSessionTitle } from '../core/session/session-title.js';
25
25
  import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/capability-manager.js';
26
26
  import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
27
27
  import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
28
- import { buildHClassGuardCommand, collectSandboxGuardDiagnostics, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, recordSandboxInitializationFailure, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, summarizeBubblewrapArgsForLog, summarizeSandboxGuardDiagnostics, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
28
+ import { buildHClassGuardCommand, collectSandboxGuardDiagnostics, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, isSandboxInitializationToolResult, recordSandboxInitializationFailure, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, summarizeBubblewrapArgsForLog, summarizeSandboxGuardDiagnostics, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
29
29
  import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
30
30
  import { auditToolInfrastructureFailure, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
31
31
  import { getManagedTaskTempDir, isManagedSessionRuntimeDir } from '../cli/task-context.js';
32
32
  import { hasTrustedFullAccessContext } from './runner-types.js';
33
- import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
33
+ import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel, sdkAutoCompactWindowForModel } from './runner-types.js';
34
+ import { hasContextWindowSuffix, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
34
35
  const resolveModule = createRequire(import.meta.url);
35
36
  /**
36
37
  * Resolve the platform package explicitly instead of relying on the SDK's
@@ -409,11 +410,18 @@ async function refreshGatewayPricing(baseUrl, apiKey, requestHeaders) {
409
410
  }
410
411
  /**
411
412
  * 为支持 1M 上下文的模型追加 `[1m]` 后缀——仅在交给 SDK query() 时调用。
412
- * 目录与校验层始终使用不带后缀的基础 ID,避免与网关 /models 返回值(无 `[1m]`)冲突。
413
+ *
414
+ * 若网关把窗口变体作为独立条目下发(目录里同时有 `claude-opus-5` 与
415
+ * `claude-opus-5[1m]`),裸 ID 就是用户明确选的非 1M 变体,不能擅自升级,
416
+ * 否则两条列表项的实际效果无法区分。只有目录未提供任何窗口变体时,才沿用
417
+ * 按本地名单补 `[1m]` 的旧行为。
413
418
  */
414
- function applyContextWindow(modelId) {
415
- if (/\[1m\]$/.test(modelId))
416
- return modelId; // 已带后缀
419
+ function applyContextWindow(modelId, catalog) {
420
+ if (hasContextWindowSuffix(modelId))
421
+ return modelId; // 窗口已显式指定
422
+ const variantListed = catalog?.models.some(m => hasContextWindowSuffix(m.id) && stripContextWindowSuffix(m.id) === modelId);
423
+ if (variantListed)
424
+ return modelId;
417
425
  if (isOneMillionContextModel(modelId))
418
426
  return `${modelId}[1m]`;
419
427
  return modelId;
@@ -500,9 +508,9 @@ export class AgentRunner {
500
508
  /** 每个 session 最近的子进程 stderr 行(环形缓冲),用于子进程崩溃时还原真正原因 */
501
509
  recentStderr = new Map();
502
510
  static STDERR_BUFFER_MAX = 80;
503
- async recordSandboxInfrastructureFailure(sessionId, inputId, permissionMode, error, stderr = []) {
511
+ async recordSandboxInfrastructureFailure(sessionId, inputId, permissionMode, error, stderr = [], scope = 'process') {
504
512
  const detail = createSandboxInitializationError(error, stderr);
505
- const circuit = recordSandboxInitializationFailure(detail, stderr);
513
+ const circuit = recordSandboxInitializationFailure(detail, stderr, Date.now(), scope === 'session' ? sessionId : undefined);
506
514
  const context = this.permissionContexts.get(sessionId);
507
515
  auditToolInfrastructureFailure({
508
516
  toolName: 'Bash',
@@ -1343,6 +1351,7 @@ export class AgentRunner {
1343
1351
  const taskMetadata = new Map();
1344
1352
  // tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
1345
1353
  const toolUseNames = new Map();
1354
+ const sandboxedBashCalls = new Set();
1346
1355
  const emittedToolResultIds = new Set();
1347
1356
  let turnCount = 0;
1348
1357
  const seenMessageIds = new Set();
@@ -1617,6 +1626,10 @@ export class AgentRunner {
1617
1626
  if (content.type === 'tool_use') {
1618
1627
  if (content.id)
1619
1628
  toolUseNames.set(content.id, content.name);
1629
+ if (content.id && content.name === 'Bash' && content.input?.dangerouslyDisableSandbox !== true
1630
+ && resolvePhaseOneExecutionSandbox(permissionMode, this.permissionContexts.get(sessionId)?.role).state !== 'off') {
1631
+ sandboxedBashCalls.add(content.id);
1632
+ }
1620
1633
  yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id, turn: turnCount, outputTokens: turnOutputChars, assistantUuid: event.uuid };
1621
1634
  }
1622
1635
  else if (content.type === 'text' && content.text) {
@@ -1635,6 +1648,31 @@ export class AgentRunner {
1635
1648
  : block.content != null ? JSON.stringify(block.content) : '';
1636
1649
  if (block.tool_use_id)
1637
1650
  emittedToolResultIds.add(block.tool_use_id);
1651
+ if (sandboxedBashCalls.delete(block.tool_use_id) && block.is_error === true
1652
+ && isSandboxInitializationToolResult(block.content)) {
1653
+ const { detail } = await this.recordSandboxInfrastructureFailure(sessionId, block.tool_use_id, permissionMode, new Error(resultContent), [], 'session');
1654
+ // Stop this SDK query before another model/tool retry. Do not use
1655
+ // interrupt(), which waits for this very generator to finish.
1656
+ this.activeMessageStreams.get(sessionId)?.end();
1657
+ const query = sdkStream;
1658
+ try {
1659
+ await query.close?.();
1660
+ }
1661
+ catch (error) {
1662
+ logger.warn(`[ClaudeSandbox] failed to close sandbox-failed query: ${error}`);
1663
+ }
1664
+ yield {
1665
+ type: 'tool_result', name: toolName, result: resultContent, isError: true,
1666
+ error: detail.message, errorCode: 'CAPABILITY_UNAVAILABLE', callId: block.tool_use_id,
1667
+ correlationId: block.tool_use_id, decision: 'error', decisionSource: 'infrastructure',
1668
+ policyCode: SANDBOX_INITIALIZATION_FAILED, executed: false, executionState: 'blocked',
1669
+ };
1670
+ yield {
1671
+ type: 'complete', isError: true, subtype: 'infrastructure',
1672
+ terminalReason: SANDBOX_INITIALIZATION_FAILED, errors: [detail.message], queryFinal: true,
1673
+ };
1674
+ return;
1675
+ }
1638
1676
  yield {
1639
1677
  type: 'tool_result',
1640
1678
  name: toolName,
@@ -1992,7 +2030,7 @@ export class AgentRunner {
1992
2030
  const executionSandbox = resolvePhaseOneExecutionSandbox(callPermissionMode, runPermissionContext?.role);
1993
2031
  const inputId = modelOverride?.turn?.inputId ?? crypto.randomUUID();
1994
2032
  if (executionSandbox.state !== 'off') {
1995
- const activeSandboxFailure = getActiveSandboxInitializationFailure();
2033
+ const activeSandboxFailure = getActiveSandboxInitializationFailure(undefined, undefined, sessionId);
1996
2034
  if (activeSandboxFailure) {
1997
2035
  const detail = createSandboxInitializationError(new Error(`sandbox bootstrap circuit open; firstObservedAt=${activeSandboxFailure.openedAt} ` +
1998
2036
  `occurrences=${activeSandboxFailure.occurrences}; ${activeSandboxFailure.detail}`));
@@ -2469,7 +2507,7 @@ export class AgentRunner {
2469
2507
  else {
2470
2508
  logger.info(`[AgentRunner] systemPromptAppend: none`);
2471
2509
  }
2472
- const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel));
2510
+ const sdkModel = applyContextWindow(resolveCatalogModel(runCatalog, callModel), runCatalog);
2473
2511
  const capabilityOptions = await this.resolveCapabilityRunOptions(projectPath);
2474
2512
  const managedSettings = callPermissionMode === 'fullaccess'
2475
2513
  ? undefined
@@ -2489,7 +2527,10 @@ export class AgentRunner {
2489
2527
  const bootstrapWritePaths = modelOverride?.bootstrapAgentMdPath
2490
2528
  ? [path.resolve(modelOverride.bootstrapAgentMdPath)]
2491
2529
  : [];
2492
- const allowedWritePaths = [...new Set([...runtimeWritePaths, ...bootstrapWritePaths])];
2530
+ const personalWritePaths = modelOverride?.personalMdPath
2531
+ ? [path.resolve(modelOverride.personalMdPath)]
2532
+ : [];
2533
+ const allowedWritePaths = [...new Set([...runtimeWritePaths, ...bootstrapWritePaths, ...personalWritePaths])];
2493
2534
  await assertClaudeSettingSourcesHaveLiteralSandboxPaths(projectPath, [...settingSources], managedSettings);
2494
2535
  const sandboxProjectionDurationMs = Date.now() - sandboxProjectionStartedAt;
2495
2536
  if (sandboxProjectionDurationMs > 2_000) {
@@ -2549,7 +2590,7 @@ export class AgentRunner {
2549
2590
  ...(executionSandbox.state === 'off' && callPermissionMode !== 'fullaccess' && process.platform === 'linux' ? {
2550
2591
  spawnClaudeCodeProcess: (options) => spawnClaudeWithHClassGuard(options, resolvePaths().root, handleClaudeStderr),
2551
2592
  } : {}),
2552
- autoCompactWindow: autoCompactWindowForModel(sdkModel),
2593
+ autoCompactWindow: sdkAutoCompactWindowForModel(sdkModel),
2553
2594
  advisorModel: 'haiku',
2554
2595
  canUseTool: canUseToolCallback,
2555
2596
  permissionMode: sdkPermissionMode,
@@ -2888,7 +2929,7 @@ export class AgentRunner {
2888
2929
  prompt,
2889
2930
  options: {
2890
2931
  cwd: projectPath,
2891
- ...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel)) } : {}),
2932
+ ...(catalog && callModel ? { model: applyContextWindow(resolveCatalogModel(catalog, callModel), catalog) } : {}),
2892
2933
  resume: agentSessionId,
2893
2934
  maxTurns: 1,
2894
2935
  tools: [],
@@ -232,7 +232,7 @@ export class CodexAppServerClient {
232
232
  : { sandbox: options?.sandbox ?? null }),
233
233
  config: this.buildThreadConfig(effort, options?.config),
234
234
  excludeTurns: false,
235
- persistExtendedHistory: false,
235
+ ...(options?.lastTurnId ? { lastTurnId: options.lastTurnId } : {}),
236
236
  });
237
237
  const forkedThreadId = response.thread?.id;
238
238
  if (title && forkedThreadId) {
@@ -252,16 +252,19 @@ export class CodexAppServerClient {
252
252
  await this.request('thread/compact/start', { threadId });
253
253
  return true;
254
254
  }
255
- async threadTurnsList(threadId, limit) {
255
+ async threadTurnsList(threadId, limit, cursor) {
256
256
  return this.request('thread/turns/list', {
257
257
  threadId,
258
258
  ...(limit ? { limit } : {}),
259
- sortDirection: 'asc',
259
+ ...(cursor ? { cursor } : {}),
260
+ // The app-server defaults to newest-first. `nextCursor` then walks
261
+ // toward older turns, which is the direction needed to hydrate history.
262
+ sortDirection: 'desc',
260
263
  itemsView: 'full',
261
264
  });
262
265
  }
263
266
  async threadTurnsItemsList(threadId, turnId, limit) {
264
- return this.request('thread/turns/items/list', {
267
+ return this.request('thread/items/list', {
265
268
  threadId,
266
269
  turnId,
267
270
  ...(limit ? { limit } : {}),
@@ -643,7 +646,12 @@ export class CodexAppServerClient {
643
646
  logger.warn(`[CodexAppServer] managed runtime lock directory unavailable: ${runtimeLockDir}`);
644
647
  }
645
648
  }
646
- env.CODEX_API_KEY = this.options.apiKey;
649
+ if (this.options.authType === 'bearerToken') {
650
+ delete env.CODEX_API_KEY;
651
+ }
652
+ else {
653
+ env.CODEX_API_KEY = this.options.apiKey;
654
+ }
647
655
  env.EVOLCORE_INSTANCE_SOCKET = resolvePaths().instanceSocket;
648
656
  for (const [name, value] of Object.entries(this.options.headers ?? {})) {
649
657
  env[codexHeaderEnvName(name)] = value;
@@ -28,7 +28,7 @@ export async function codexNativeCatalog(context) {
28
28
  if (context.baseagent !== 'codex')
29
29
  return null;
30
30
  const client = new CodexAppServerClient({
31
- apiKey: context.apiKey ?? '', baseUrl: context.baseUrl, provider: context.provider,
31
+ apiKey: context.apiKey ?? '', authType: context.authType, baseUrl: context.baseUrl, provider: context.provider,
32
32
  headers: context.headers, queryParams: context.queryParams,
33
33
  });
34
34
  let timer;
@@ -179,6 +179,39 @@ function realDirectoryState(directory) {
179
179
  return 'unavailable';
180
180
  }
181
181
  }
182
+ /** Read back the package root that managedHookLauncher() embedded. */
183
+ function launcherPackageRoot(launcher) {
184
+ const match = /^\s*const packageRoot = ("(?:[^"\\]|\\.)*")\s*;/m.exec(launcher);
185
+ if (!match)
186
+ return null;
187
+ try {
188
+ const value = JSON.parse(match[1]);
189
+ return typeof value === 'string' && value ? value : null;
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ }
195
+ /**
196
+ * A `npm link` install reaches the package through a symlinked global path, so
197
+ * the launcher and the running CLI legitimately spell the same package root
198
+ * differently. Compare the resolved targets instead of the literal strings;
199
+ * a launcher pointing at a genuinely different package still fails closed.
200
+ */
201
+ function samePackageRoot(embedded, expected) {
202
+ if (!embedded)
203
+ return false;
204
+ const normalize = (value) => path.normalize(value).replace(/[\\/]+$/, '').toLowerCase();
205
+ const resolved = (value) => {
206
+ try {
207
+ return normalize(fs.realpathSync(value));
208
+ }
209
+ catch {
210
+ return normalize(value);
211
+ }
212
+ };
213
+ return normalize(embedded) === normalize(expected) || resolved(embedded) === resolved(expected);
214
+ }
182
215
  /** Read-only inspection of the machine-wide Windows Codex managed hook. */
183
216
  export function inspectCodexManagedHook(options = {}) {
184
217
  const paths = codexManagedHookPaths(options);
@@ -252,7 +285,7 @@ export function inspectCodexManagedHook(options = {}) {
252
285
  const normalized = (value) => path.normalize(String(value || '')).toLowerCase();
253
286
  const expectedPackageRoot = path.resolve(options.packageRoot || process.cwd());
254
287
  const launcherValid = launcher.includes('EVOLCORE_MANAGED_HOOK_LAUNCHER')
255
- && launcher.toLowerCase().includes(JSON.stringify(expectedPackageRoot).toLowerCase());
288
+ && samePackageRoot(launcherPackageRoot(launcher), expectedPackageRoot);
256
289
  const configuredDir = config.hooks?.windows_managed_dir ?? config.hooks?.managed_dir;
257
290
  const groups = config.hooks?.PreToolUse;
258
291
  const canonicalCommand = (value) => {