evolcore 0.0.16 → 0.0.17
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 +14 -0
- package/dist/agents/claude-runner.js +29 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +3 -1
- package/dist/cli/task-context.js +46 -1
- package/dist/core/message/response-engine.js +1 -0
- package/dist/utils/error-utils.js +38 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## 0.0.17 (2026-08-25)
|
|
7
|
+
|
|
8
|
+
### Claude 与任务边界
|
|
9
|
+
|
|
10
|
+
- 统一识别 Claude 启动参数过长错误,记录结构化能力不可用审计并以明确终态结束任务,避免无效重试和敏感启动参数泄露。
|
|
11
|
+
|
|
12
|
+
### Codex 与 AUN 消息兼容
|
|
13
|
+
|
|
14
|
+
- 将运行时 BaseAgent 信息传入任务上下文,仅对 Codex AUN 私聊/群聊文本载荷解码一层 `\\n`/`\\r`/`\\t` 转义,保持其它 Runner 和原始换行不变。
|
|
15
|
+
|
|
16
|
+
### 临时目录与跨平台稳定性
|
|
17
|
+
|
|
18
|
+
- 改进受管临时目录创建根路径解析,在拒绝不可信 TMPDIR 时使用固定系统回退并兼容 macOS `/var` 实路径,强化跨平台私有目录校验。
|
|
19
|
+
|
|
6
20
|
## 0.0.16 (2026-08-25)
|
|
7
21
|
|
|
8
22
|
### 任务与消息可靠性
|
|
@@ -11,6 +11,7 @@ import fs from 'fs';
|
|
|
11
11
|
import os from 'os';
|
|
12
12
|
import crypto from 'crypto';
|
|
13
13
|
import { logger } from '../utils/logger.js';
|
|
14
|
+
import { createClaudeStartupArgumentsTooLongError, isClaudeStartupArgumentsTooLong, } from '../utils/error-utils.js';
|
|
14
15
|
import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
|
|
15
16
|
import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
16
17
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
@@ -1825,6 +1826,30 @@ export class AgentRunner {
|
|
|
1825
1826
|
else {
|
|
1826
1827
|
logger.error(`[AgentRunner] Subprocess stream failed (session=${sessionId}) with no captured stderr.`);
|
|
1827
1828
|
}
|
|
1829
|
+
if (isClaudeStartupArgumentsTooLong(err)) {
|
|
1830
|
+
const detail = createClaudeStartupArgumentsTooLongError(err);
|
|
1831
|
+
await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
|
|
1832
|
+
code: 'capability_unavailable',
|
|
1833
|
+
severity: 'warning',
|
|
1834
|
+
phase: 'execution',
|
|
1835
|
+
occurredAt: Date.now(),
|
|
1836
|
+
toolName: 'Bash',
|
|
1837
|
+
policyCode: 'claude_startup_arguments_too_long',
|
|
1838
|
+
decisionSource: 'infrastructure',
|
|
1839
|
+
agentAid: this.permissionContexts.get(sessionId)?.selfAid,
|
|
1840
|
+
summary: detail.message.slice(0, 512),
|
|
1841
|
+
effect: 'operation_skipped',
|
|
1842
|
+
});
|
|
1843
|
+
yield {
|
|
1844
|
+
type: 'complete',
|
|
1845
|
+
isError: true,
|
|
1846
|
+
subtype: 'infrastructure',
|
|
1847
|
+
terminalReason: 'claude_startup_arguments_too_long',
|
|
1848
|
+
errors: [detail.message],
|
|
1849
|
+
queryFinal: true,
|
|
1850
|
+
};
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1828
1853
|
if (isSandboxInitializationFailure(err, buf ?? [])) {
|
|
1829
1854
|
const detail = createSandboxInitializationError(err, buf ?? []);
|
|
1830
1855
|
await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
|
|
@@ -2538,6 +2563,8 @@ export class AgentRunner {
|
|
|
2538
2563
|
catch (error) {
|
|
2539
2564
|
this.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2540
2565
|
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2566
|
+
if (isClaudeStartupArgumentsTooLong(error))
|
|
2567
|
+
throw createClaudeStartupArgumentsTooLongError(error);
|
|
2541
2568
|
if (isSandboxInitializationFailure(error, stderr))
|
|
2542
2569
|
throw createSandboxInitializationError(error, stderr);
|
|
2543
2570
|
throw error;
|
|
@@ -2552,6 +2579,8 @@ export class AgentRunner {
|
|
|
2552
2579
|
catch (error) {
|
|
2553
2580
|
this.cleanupSandboxSettingsFile(sessionId, sandboxSettingsFile);
|
|
2554
2581
|
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2582
|
+
if (isClaudeStartupArgumentsTooLong(error))
|
|
2583
|
+
throw createClaudeStartupArgumentsTooLongError(error);
|
|
2555
2584
|
if (isSandboxInitializationFailure(error, stderr))
|
|
2556
2585
|
throw createSandboxInitializationError(error, stderr);
|
|
2557
2586
|
throw error;
|
package/dist/aun/msg/group.js
CHANGED
|
@@ -9,7 +9,7 @@ import { daemonTaskSendIpcTimeoutMs, resolveAunMessageEncrypt } from './p2p.js';
|
|
|
9
9
|
import { checkGroupIndex, getGroupIndex } from './group-index.js';
|
|
10
10
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from '../../core/message/message-log.js';
|
|
11
11
|
import { chatDirPath } from '../../core/session/session-fs-store.js';
|
|
12
|
-
import { readBestTaskRuntimeContext } from '../../cli/task-context.js';
|
|
12
|
+
import { normalizeCodexTextPayload, readBestTaskRuntimeContext } from '../../cli/task-context.js';
|
|
13
13
|
import { ipcQuery } from '../../ipc.js';
|
|
14
14
|
import { resolvePaths } from '../../paths.js';
|
|
15
15
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
|
|
@@ -151,6 +151,7 @@ export async function groupSend(args) {
|
|
|
151
151
|
const runtimeContext = await readBestTaskRuntimeContext();
|
|
152
152
|
if (args.body.mode !== 'file') {
|
|
153
153
|
const payload = buildGroupPayload(args.body);
|
|
154
|
+
normalizeCodexTextPayload(payload, runtimeContext);
|
|
154
155
|
applyGroupRoutingPayloadFields(payload, args, runtimeContext);
|
|
155
156
|
const daemonResult = await tryDaemonGroupSend(args, payload, runtimeContext);
|
|
156
157
|
if (daemonResult)
|
|
@@ -192,6 +193,7 @@ export async function groupSend(args) {
|
|
|
192
193
|
break;
|
|
193
194
|
}
|
|
194
195
|
}
|
|
196
|
+
normalizeCodexTextPayload(payload, runtimeContext);
|
|
195
197
|
const payloadMentions = Object.prototype.hasOwnProperty.call(payload, 'mentions')
|
|
196
198
|
? normalizeAunMentionEntries(payload.mentions)
|
|
197
199
|
: undefined;
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createShortConnection } from '../rpc/index.js';
|
|
|
4
4
|
import { getAidStore, SLOT } from '../aid/store.js';
|
|
5
5
|
import { uploadFileAndBuildPayload } from './upload.js';
|
|
6
6
|
import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from '../../core/message/message-log.js';
|
|
7
|
-
import { readBestTaskRuntimeContext, runtimeRefMessageIdForMsgSend } from '../../cli/task-context.js';
|
|
7
|
+
import { normalizeCodexTextPayload, readBestTaskRuntimeContext, runtimeRefMessageIdForMsgSend } from '../../cli/task-context.js';
|
|
8
8
|
import { chatDirPath } from '../../core/session/session-fs-store.js';
|
|
9
9
|
import { resolvePaths } from '../../paths.js';
|
|
10
10
|
import { ipcQuery } from '../../ipc.js';
|
|
@@ -207,6 +207,7 @@ export async function msgSend(args) {
|
|
|
207
207
|
let payload;
|
|
208
208
|
if (args.body.mode !== 'file') {
|
|
209
209
|
payload = buildSimplePayload(args.body);
|
|
210
|
+
normalizeCodexTextPayload(payload, runtimeContext);
|
|
210
211
|
if (Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
|
|
211
212
|
payload.mentions = normalizeAunMentionEntries(payload.mentions);
|
|
212
213
|
}
|
|
@@ -263,6 +264,7 @@ export async function msgSend(args) {
|
|
|
263
264
|
// 3. 构建 payload
|
|
264
265
|
if (!payload) {
|
|
265
266
|
payload = buildSimplePayload(args.body);
|
|
267
|
+
normalizeCodexTextPayload(payload, runtimeContext);
|
|
266
268
|
}
|
|
267
269
|
// 4. 写入 payload.chatmode
|
|
268
270
|
payload.chatmode = chatmode;
|
package/dist/cli/task-context.js
CHANGED
|
@@ -23,6 +23,7 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
23
23
|
taskId: optionalString(value.taskId),
|
|
24
24
|
sessionId: optionalString(value.sessionId),
|
|
25
25
|
messageId: optionalString(value.messageId),
|
|
26
|
+
baseagent: optionalString(value.baseagent),
|
|
26
27
|
channel: optionalString(value.channel),
|
|
27
28
|
channelId: optionalString(value.channelId),
|
|
28
29
|
chatType: optionalString(value.chatType),
|
|
@@ -38,6 +39,29 @@ function normalizeTaskRuntimeContext(value) {
|
|
|
38
39
|
causation: normalizeCausation(value.causation),
|
|
39
40
|
};
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Codex's shell carrier can leave one JSON-style escape layer in a message
|
|
44
|
+
* argument (for example, the two characters `\\` and `n`). Decode only that
|
|
45
|
+
* layer, and only for a task that is explicitly running the Codex backend.
|
|
46
|
+
* Actual line breaks and all non-Codex CLI input remain unchanged.
|
|
47
|
+
*/
|
|
48
|
+
export function decodeCodexMessageText(text, runtime) {
|
|
49
|
+
if (runtime?.baseagent?.trim().toLowerCase() !== 'codex')
|
|
50
|
+
return text;
|
|
51
|
+
return text.replace(/\\([nrt])/g, (_match, escape) => {
|
|
52
|
+
if (escape === 'n')
|
|
53
|
+
return '\n';
|
|
54
|
+
if (escape === 'r')
|
|
55
|
+
return '\r';
|
|
56
|
+
return '\t';
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Apply the Codex-only normalization to a text message payload. */
|
|
60
|
+
export function normalizeCodexTextPayload(payload, runtime) {
|
|
61
|
+
if (payload.type !== 'text' || typeof payload.text !== 'string')
|
|
62
|
+
return;
|
|
63
|
+
payload.text = decodeCodexMessageText(payload.text, runtime);
|
|
64
|
+
}
|
|
41
65
|
function optionalAbsolutePath(value) {
|
|
42
66
|
if (typeof value !== 'string' || !path.isAbsolute(value))
|
|
43
67
|
return undefined;
|
|
@@ -75,6 +99,27 @@ function isPrivateDirectory(directory) {
|
|
|
75
99
|
return false;
|
|
76
100
|
}
|
|
77
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the platform temp root before creating a managed directory. macOS
|
|
104
|
+
* exposes its per-user temp directory through `/var`, which is a symlink to
|
|
105
|
+
* `/private/var`; walking that lexical path would otherwise reject a freshly
|
|
106
|
+
* created directory even though its real path is private. When an inherited
|
|
107
|
+
* TMPDIR was rejected, use a fixed system fallback instead of following that
|
|
108
|
+
* untrusted path back into the fallback.
|
|
109
|
+
*/
|
|
110
|
+
function managedTempCreationRoot(configured) {
|
|
111
|
+
const candidate = configured
|
|
112
|
+
? process.platform === 'win32'
|
|
113
|
+
? path.join(process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows', 'Temp')
|
|
114
|
+
: '/tmp'
|
|
115
|
+
: os.tmpdir();
|
|
116
|
+
try {
|
|
117
|
+
return fs.realpathSync.native(candidate);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return path.resolve(candidate);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
78
123
|
/**
|
|
79
124
|
* Ensure the daemon has a private process-wide temporary root.
|
|
80
125
|
*
|
|
@@ -95,7 +140,7 @@ export function ensureProcessManagedTempDir() {
|
|
|
95
140
|
if (isPrivateDirectory(resolved))
|
|
96
141
|
return resolved;
|
|
97
142
|
}
|
|
98
|
-
const directory = fs.mkdtempSync(path.join(
|
|
143
|
+
const directory = fs.mkdtempSync(path.join(managedTempCreationRoot(configured), 'evolcore-managed-tmp-'));
|
|
99
144
|
try {
|
|
100
145
|
fs.chmodSync(directory, 0o700);
|
|
101
146
|
if (!isPrivateDirectory(directory)) {
|
|
@@ -2775,6 +2775,7 @@ export class ResponseEngine {
|
|
|
2775
2775
|
taskId,
|
|
2776
2776
|
sessionId: session.id,
|
|
2777
2777
|
messageId: message.messageId,
|
|
2778
|
+
baseagent: normalizedBaseagent.canonical,
|
|
2778
2779
|
channel: configChannelType,
|
|
2779
2780
|
channelId: message.channelId,
|
|
2780
2781
|
chatType: configChatType,
|
|
@@ -3,6 +3,32 @@ import path from 'path';
|
|
|
3
3
|
import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
4
4
|
import { logger } from './logger.js';
|
|
5
5
|
import { isSandboxInitializationFailure } from '../core/permission/sandbox-runtime.js';
|
|
6
|
+
export const CLAUDE_STARTUP_ARGUMENTS_TOO_LONG = 'claude_startup_arguments_too_long';
|
|
7
|
+
/** Identify a Claude process-start failure without treating ordinary tool errors as startup failures. */
|
|
8
|
+
export function isClaudeStartupArgumentsTooLong(error) {
|
|
9
|
+
const code = error && typeof error === 'object' && 'code' in error
|
|
10
|
+
? String(error.code ?? '')
|
|
11
|
+
: '';
|
|
12
|
+
const message = error && typeof error === 'object' && 'message' in error
|
|
13
|
+
? String(error.message ?? '')
|
|
14
|
+
: String(error ?? '');
|
|
15
|
+
const text = `${code}\n${message}`.toLowerCase();
|
|
16
|
+
return text.includes(CLAUDE_STARTUP_ARGUMENTS_TOO_LONG)
|
|
17
|
+
|| text.includes('enametoolong')
|
|
18
|
+
|| text.includes('e2big')
|
|
19
|
+
|| text.includes('argument list too long')
|
|
20
|
+
|| text.includes('command line too long');
|
|
21
|
+
}
|
|
22
|
+
export function createClaudeStartupArgumentsTooLongError(error) {
|
|
23
|
+
const wrapped = new Error(`${CLAUDE_STARTUP_ARGUMENTS_TOO_LONG}: Claude 启动参数过长,任务未执行`);
|
|
24
|
+
wrapped.name = 'ClaudeStartupArgumentsTooLongError';
|
|
25
|
+
wrapped.code = CLAUDE_STARTUP_ARGUMENTS_TOO_LONG;
|
|
26
|
+
// Keep the original error available to diagnostics without echoing argv,
|
|
27
|
+
// settings contents, or absolute paths to users.
|
|
28
|
+
if (error !== undefined)
|
|
29
|
+
wrapped.cause = error;
|
|
30
|
+
return wrapped;
|
|
31
|
+
}
|
|
6
32
|
export var ErrorType;
|
|
7
33
|
(function (ErrorType) {
|
|
8
34
|
ErrorType["SDK_TIMEOUT"] = "sdk_timeout";
|
|
@@ -13,6 +39,7 @@ export var ErrorType;
|
|
|
13
39
|
ErrorType["CONTEXT_TOO_LONG"] = "context_too_long";
|
|
14
40
|
ErrorType["MODEL_UNAVAILABLE"] = "model_unavailable";
|
|
15
41
|
ErrorType["SANDBOX_INITIALIZATION_FAILED"] = "sandbox_initialization_failed";
|
|
42
|
+
ErrorType["CLAUDE_STARTUP_ARGUMENTS_TOO_LONG"] = "claude_startup_arguments_too_long";
|
|
16
43
|
ErrorType["UNKNOWN"] = "unknown";
|
|
17
44
|
})(ErrorType || (ErrorType = {}));
|
|
18
45
|
/**
|
|
@@ -251,6 +278,11 @@ function hasRetryableHttpStatus(text) {
|
|
|
251
278
|
}
|
|
252
279
|
export function classifyError(error) {
|
|
253
280
|
const msg = (error?.message || '').toLowerCase();
|
|
281
|
+
// Startup-size failures are deterministic local failures and must win over
|
|
282
|
+
// dictionary rules that would otherwise turn them into generic retries.
|
|
283
|
+
if (isClaudeStartupArgumentsTooLong(error)) {
|
|
284
|
+
return ErrorType.CLAUDE_STARTUP_ARGUMENTS_TOO_LONG;
|
|
285
|
+
}
|
|
254
286
|
// 字典优先 — 命中则直接返回
|
|
255
287
|
const rule = matchErrorRule(msg);
|
|
256
288
|
if (rule) {
|
|
@@ -314,6 +346,10 @@ export function isRetryableError(error) {
|
|
|
314
346
|
return false;
|
|
315
347
|
}
|
|
316
348
|
export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
349
|
+
if (isClaudeStartupArgumentsTooLong(error)) {
|
|
350
|
+
const prefix = includeEmoji ? '⚠️ ' : '';
|
|
351
|
+
return `${prefix}当前 Claude 启动参数过长,任务未执行`;
|
|
352
|
+
}
|
|
317
353
|
// terminalReason 提供更精确的错误提示(SDK 0.2.100+)
|
|
318
354
|
if (terminalReason) {
|
|
319
355
|
const prefix = includeEmoji ? '❌ ' : '';
|
|
@@ -335,6 +371,8 @@ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
|
335
371
|
return `${prefix}权限被拒绝,操作已取消`;
|
|
336
372
|
case 'sandbox_initialization_failed':
|
|
337
373
|
return `${warnPrefix}只读执行沙箱启动失败(sandbox_initialization_failed),当前任务未执行;请检查 user namespace/seccomp 配置后重试`;
|
|
374
|
+
case 'claude_startup_arguments_too_long':
|
|
375
|
+
return `${warnPrefix}当前 Claude 启动参数过长,任务未执行`;
|
|
338
376
|
case 'aborted_streaming':
|
|
339
377
|
case 'aborted_tools':
|
|
340
378
|
return `${prefix}任务已中断`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolcore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "AI Agent gateway connecting Claude, Codex, Gemini, and the bundled ecagent runner to messaging channels with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|