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.
- package/CHANGELOG.md +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- 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
|
|
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
|
|
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;
|
package/dist/agents/baseagent.js
CHANGED
|
@@ -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
|
-
|
|
206
|
+
authJsonApiKey = nonEmptyString(auth?.OPENAI_API_KEY);
|
|
197
207
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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
|
-
*
|
|
413
|
+
*
|
|
414
|
+
* 若网关把窗口变体作为独立条目下发(目录里同时有 `claude-opus-5` 与
|
|
415
|
+
* `claude-opus-5[1m]`),裸 ID 就是用户明确选的非 1M 变体,不能擅自升级,
|
|
416
|
+
* 否则两条列表项的实际效果无法区分。只有目录未提供任何窗口变体时,才沿用
|
|
417
|
+
* 按本地名单补 `[1m]` 的旧行为。
|
|
413
418
|
*/
|
|
414
|
-
function applyContextWindow(modelId) {
|
|
415
|
-
if (
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
|
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) => {
|