evolclaw 2.5.5 → 2.5.6
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/dist/agents/codex-runner.js +22 -13
- package/dist/channels/dingtalk.js +4 -3
- package/dist/channels/feishu.js +2 -1
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/core/message/message-processor.js +2 -2
- package/dist/utils/init-channel.js +16 -0
- package/package.json +8 -8
- package/aun/pyproject.toml +0 -20
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Implements the same interface surface as AgentRunner (claude-runner.ts)
|
|
6
6
|
* so MessageProcessor and CommandHandler can work with it transparently.
|
|
7
7
|
*/
|
|
8
|
-
import { Codex } from '@openai/codex-sdk';
|
|
9
8
|
import { resolveOpenaiConfig } from '../config.js';
|
|
10
9
|
import { logger } from '../utils/logger.js';
|
|
11
10
|
import fs from 'fs';
|
|
@@ -24,24 +23,33 @@ const CODEX_MODELS = ['gpt-5.3-codex', 'gpt-5.2-codex', 'gpt-5-codex', 'gpt-5.2'
|
|
|
24
23
|
export class CodexRunner {
|
|
25
24
|
name = 'codex';
|
|
26
25
|
capabilities = { clear: false, compact: false, fork: false };
|
|
27
|
-
codex;
|
|
26
|
+
codex = null;
|
|
27
|
+
codexModule = null;
|
|
28
28
|
model;
|
|
29
29
|
effort;
|
|
30
30
|
activeAbortControllers = new Map();
|
|
31
31
|
activeStreams = new Map();
|
|
32
32
|
activeSessions = new Map(); // sessionId → threadId
|
|
33
33
|
onSessionIdUpdate;
|
|
34
|
+
resolvedConfig;
|
|
34
35
|
constructor(config, callbacks) {
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
this.model = resolved.model;
|
|
41
|
-
if (resolved.effort)
|
|
42
|
-
this.effort = resolved.effort;
|
|
36
|
+
this.resolvedConfig = resolveOpenaiConfig(config);
|
|
37
|
+
this.model = this.resolvedConfig.model;
|
|
38
|
+
if (this.resolvedConfig.effort)
|
|
39
|
+
this.effort = this.resolvedConfig.effort;
|
|
43
40
|
this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
|
|
44
41
|
}
|
|
42
|
+
async ensureCodex() {
|
|
43
|
+
if (!this.codex || !this.codexModule) {
|
|
44
|
+
const { requireOptional } = await import('../utils/init-channel.js');
|
|
45
|
+
this.codexModule = await requireOptional('@openai/codex-sdk');
|
|
46
|
+
this.codex = new this.codexModule.Codex({
|
|
47
|
+
apiKey: this.resolvedConfig.apiKey,
|
|
48
|
+
baseUrl: this.resolvedConfig.baseUrl,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return { codex: this.codex, mod: this.codexModule };
|
|
52
|
+
}
|
|
45
53
|
// ── ModelSwitcher ──
|
|
46
54
|
setModel(model) { this.model = model; }
|
|
47
55
|
getModel() { return this.model; }
|
|
@@ -89,6 +97,7 @@ export class CodexRunner {
|
|
|
89
97
|
async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager) {
|
|
90
98
|
// Agent ctl: 注入 EVOLCLAW_SESSION_ID 供子进程使用
|
|
91
99
|
process.env.EVOLCLAW_SESSION_ID = sessionId;
|
|
100
|
+
const { codex } = await this.ensureCodex();
|
|
92
101
|
let agentSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
93
102
|
const threadOptions = {
|
|
94
103
|
workingDirectory: projectPath,
|
|
@@ -99,8 +108,8 @@ export class CodexRunner {
|
|
|
99
108
|
...(this.effort ? { modelReasoningEffort: this.effort } : {}),
|
|
100
109
|
};
|
|
101
110
|
const thread = agentSessionId
|
|
102
|
-
?
|
|
103
|
-
:
|
|
111
|
+
? codex.resumeThread(agentSessionId, threadOptions)
|
|
112
|
+
: codex.startThread(threadOptions);
|
|
104
113
|
const controller = new AbortController();
|
|
105
114
|
this.activeAbortControllers.set(sessionId, controller);
|
|
106
115
|
// 构建输入:将 base64 图片写入临时文件,转换为 Codex SDK 的 local_image 格式
|
|
@@ -226,7 +235,7 @@ export class CodexRunner {
|
|
|
226
235
|
yield { type: 'tool_use', name: `MCP:${item.server}/${item.tool}`, input: item.arguments };
|
|
227
236
|
}
|
|
228
237
|
else if (item.type === 'file_change') {
|
|
229
|
-
const desc = item.changes.map(c => `${c.kind} ${c.path}`).join(', ');
|
|
238
|
+
const desc = item.changes.map((c) => `${c.kind} ${c.path}`).join(', ');
|
|
230
239
|
yield { type: 'tool_use', name: 'FileChange', input: { description: desc } };
|
|
231
240
|
}
|
|
232
241
|
else if (item.type === 'web_search') {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
|
+
import { requireOptional } from '../utils/init-channel.js';
|
|
2
3
|
import { normalizeChannelInstances, getChannelShowActivities } from '../config.js';
|
|
3
4
|
// ── Webhook SSRF validation ────────────────────────────────────────────────────
|
|
4
5
|
const WEBHOOK_RE = /^https:\/\/(api|oapi)\.dingtalk\.com\//;
|
|
@@ -56,7 +57,7 @@ export class DingtalkChannel {
|
|
|
56
57
|
if (!clientId || !clientSecret || clientId.includes('your-') || clientSecret.includes('your-')) {
|
|
57
58
|
throw new Error('DingTalk clientId/clientSecret not configured');
|
|
58
59
|
}
|
|
59
|
-
const { DWClient, TOPIC_ROBOT } = await
|
|
60
|
+
const { DWClient, TOPIC_ROBOT } = await requireOptional('dingtalk-stream');
|
|
60
61
|
this.client = new DWClient({ clientId, clientSecret });
|
|
61
62
|
this.client.registerCallbackListener(TOPIC_ROBOT, async (msg) => {
|
|
62
63
|
await this.handleIncoming(msg);
|
|
@@ -320,7 +321,7 @@ export class DingtalkChannel {
|
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
322
323
|
// Step 1: Upload media
|
|
323
|
-
const FormData = (await
|
|
324
|
+
const FormData = (await requireOptional('form-data')).default;
|
|
324
325
|
const form = new FormData();
|
|
325
326
|
form.append('type', 'image');
|
|
326
327
|
form.append('media', png, { filename: 'image.png', contentType: 'image/png' });
|
|
@@ -360,7 +361,7 @@ export class DingtalkChannel {
|
|
|
360
361
|
return;
|
|
361
362
|
}
|
|
362
363
|
// Step 1: Upload media
|
|
363
|
-
const FormData = (await
|
|
364
|
+
const FormData = (await requireOptional('form-data')).default;
|
|
364
365
|
const form = new FormData();
|
|
365
366
|
form.append('type', 'file');
|
|
366
367
|
form.append('media', fs.createReadStream(filePath), { filename: path.basename(filePath) });
|
package/dist/channels/feishu.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as lark from '@larksuiteoapi/node-sdk';
|
|
2
1
|
import fs from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import imageType from 'image-type';
|
|
@@ -41,6 +40,8 @@ export class FeishuChannel {
|
|
|
41
40
|
if (this.config.appId.startsWith('YOUR_') || this.config.appSecret.startsWith('YOUR_')) {
|
|
42
41
|
throw new Error('Feishu credentials not configured (placeholder values detected)');
|
|
43
42
|
}
|
|
43
|
+
const { requireOptional } = await import('../utils/init-channel.js');
|
|
44
|
+
const lark = await requireOptional('@larksuiteoapi/node-sdk');
|
|
44
45
|
try {
|
|
45
46
|
this.client = new lark.Client({
|
|
46
47
|
appId: this.config.appId,
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
2
|
import { markdownToPlainText } from '../utils/format.js';
|
|
3
|
+
import { requireOptional } from '../utils/init-channel.js';
|
|
3
4
|
import { normalizeChannelInstances, getChannelShowActivities } from '../config.js';
|
|
4
5
|
// ── QQBotChannel ────────────────────────────────────────────────────────────
|
|
5
6
|
export class QQBotChannel {
|
|
@@ -37,7 +38,7 @@ export class QQBotChannel {
|
|
|
37
38
|
if (!appId || !clientSecret || appId.includes('your-') || clientSecret.includes('your-')) {
|
|
38
39
|
throw new Error('QQBot appId/clientSecret not configured');
|
|
39
40
|
}
|
|
40
|
-
const { QQBotClient } = await
|
|
41
|
+
const { QQBotClient } = await requireOptional('pure-qqbot');
|
|
41
42
|
this.client = new QQBotClient({
|
|
42
43
|
appId,
|
|
43
44
|
clientSecret,
|
package/dist/channels/wecom.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { requireOptional } from '../utils/init-channel.js';
|
|
3
4
|
import { normalizeChannelInstances, getChannelShowActivities } from '../config.js';
|
|
4
5
|
// ── WecomChannel ───────────────────────────────────────────────────────────────
|
|
5
6
|
export class WecomChannel {
|
|
@@ -31,7 +32,7 @@ export class WecomChannel {
|
|
|
31
32
|
if (!botId || !secret) {
|
|
32
33
|
throw new Error('WeCom botId/secret not configured');
|
|
33
34
|
}
|
|
34
|
-
const { WSClient } = await
|
|
35
|
+
const { WSClient } = await requireOptional('@wecom/aibot-node-sdk');
|
|
35
36
|
this.client = new WSClient({ botId, secret });
|
|
36
37
|
// Message events
|
|
37
38
|
this.client.on('message', (frame) => {
|
|
@@ -681,7 +681,7 @@ export class MessageProcessor {
|
|
|
681
681
|
// 区分超时 / 中断 / 错误
|
|
682
682
|
const errType = classifyError(error);
|
|
683
683
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
684
|
-
const isUserInterrupt = interruptReason === 'new_message' || interruptReason === 'stop';
|
|
684
|
+
const isUserInterrupt = interruptReason === 'new_message' || interruptReason === 'stop' || interruptReason === 'recalled';
|
|
685
685
|
const procStatus = errType === ErrorType.SDK_TIMEOUT ? 'timeout'
|
|
686
686
|
: errType === ErrorType.STREAM_ERROR ? 'interrupted'
|
|
687
687
|
: 'error';
|
|
@@ -889,7 +889,7 @@ export class MessageProcessor {
|
|
|
889
889
|
// 失败且无前置错误输出:显示 errors 摘要
|
|
890
890
|
// 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
|
|
891
891
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
892
|
-
const isUserInterrupt = interruptReason === 'new_message' || interruptReason === 'stop';
|
|
892
|
+
const isUserInterrupt = interruptReason === 'new_message' || interruptReason === 'stop' || interruptReason === 'recalled';
|
|
893
893
|
if (event.isError && !hasErrorResult && !shouldSuppress() && !isUserInterrupt) {
|
|
894
894
|
const errorSummary = event.errors?.join('; ') || '\u4efb\u52a1\u6267\u884c\u5931\u8d25';
|
|
895
895
|
// 使用 terminalReason 提供更友好的错误提示
|
|
@@ -36,6 +36,22 @@ export async function npmInstallGlobal(pkg) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/** Dynamic import with auto-install fallback for optional dependencies */
|
|
40
|
+
export async function requireOptional(pkg, autoInstall = true) {
|
|
41
|
+
try {
|
|
42
|
+
return await import(pkg);
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
if (e.code !== 'ERR_MODULE_NOT_FOUND' && e.code !== 'MODULE_NOT_FOUND')
|
|
46
|
+
throw e;
|
|
47
|
+
if (!autoInstall)
|
|
48
|
+
throw new Error(`依赖 ${pkg} 未安装。请运行: npm install -g ${pkg}`);
|
|
49
|
+
const { logger } = await import('./logger.js');
|
|
50
|
+
logger.info(`正在安装可选依赖 ${pkg}...`);
|
|
51
|
+
await npmInstallGlobal(pkg);
|
|
52
|
+
return await import(pkg);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
39
55
|
function ask(rl, question) {
|
|
40
56
|
return new Promise(resolve => rl.question(question, resolve));
|
|
41
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolclaw",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.6",
|
|
4
4
|
"description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
"dist/",
|
|
12
12
|
"!dist/experimental/",
|
|
13
13
|
"data/evolclaw.sample.json",
|
|
14
|
-
"evolclaw-install-aun.md"
|
|
15
|
-
"aun/pyproject.toml"
|
|
14
|
+
"evolclaw-install-aun.md"
|
|
16
15
|
],
|
|
17
16
|
"scripts": {
|
|
18
17
|
"dev": "tsx watch src/index.ts",
|
|
@@ -26,19 +25,20 @@
|
|
|
26
25
|
"dependencies": {
|
|
27
26
|
"@anthropic-ai/claude-agent-sdk": "^0.2.100",
|
|
28
27
|
"@agentunion/aun-node": "^0.2.12",
|
|
28
|
+
"image-type": "^6.0.0",
|
|
29
|
+
"qrcode-terminal": "^0.12.0"
|
|
30
|
+
},
|
|
31
|
+
"optionalDependencies": {
|
|
29
32
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
30
33
|
"@openai/codex-sdk": "^0.118.0",
|
|
31
|
-
"@types/form-data": "^2.2.1",
|
|
32
34
|
"@wecom/aibot-node-sdk": "^1.0.6",
|
|
33
35
|
"dingtalk-stream": "^2.1.6-beta.1",
|
|
34
|
-
"fast-xml-parser": "^5.7.2",
|
|
35
36
|
"form-data": "^4.0.5",
|
|
36
|
-
"
|
|
37
|
-
"pure-qqbot": "^2.0.0",
|
|
38
|
-
"qrcode-terminal": "^0.12.0"
|
|
37
|
+
"pure-qqbot": "^2.0.0"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
40
|
"@types/node": "^25.5.0",
|
|
41
|
+
"@types/form-data": "^2.2.1",
|
|
42
42
|
"@types/qrcode-terminal": "^0.12.2",
|
|
43
43
|
"@vitest/coverage-v8": "^4.1.0",
|
|
44
44
|
"tsx": "^4.19.0",
|
package/aun/pyproject.toml
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "aun-cli"
|
|
3
|
-
version = "0.1.0"
|
|
4
|
-
description = "AUN CLI - Interactive command-line client for AUN protocol"
|
|
5
|
-
requires-python = ">=3.10"
|
|
6
|
-
dependencies = [
|
|
7
|
-
"aunp>=0.2.12",
|
|
8
|
-
"prompt-toolkit>=3.0.0",
|
|
9
|
-
"rich>=13.0.0",
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
[project.scripts]
|
|
13
|
-
aun = "aun_cli:cli_main"
|
|
14
|
-
|
|
15
|
-
[build-system]
|
|
16
|
-
requires = ["setuptools>=61.0"]
|
|
17
|
-
build-backend = "setuptools.build_meta"
|
|
18
|
-
|
|
19
|
-
[tool.setuptools]
|
|
20
|
-
py-modules = ["aun_cli"]
|