evolclaw 2.8.1 → 2.8.3
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/claude-runner.js +18 -7
- package/dist/agents/codex-runner.js +16 -5
- package/dist/agents/gemini-runner.js +15 -4
- package/dist/agents/templates.js +122 -0
- package/dist/channels/aun-ops.js +1 -1
- package/dist/channels/aun.js +22 -0
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +345 -48
- package/dist/config.js +152 -65
- package/dist/core/agent-loader.js +34 -19
- package/dist/core/agent-registry.js +287 -1
- package/dist/core/command-handler.js +643 -192
- package/dist/core/evolagent-registry.js +514 -0
- package/dist/core/evolagent.js +250 -1
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +64 -15
- package/dist/core/message/message-queue.js +61 -6
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +140 -21
- package/dist/ipc.js +47 -0
- package/dist/types.js +2 -0
- package/dist/utils/reload-hooks.js +87 -0
- package/dist/utils/rich-content-renderer.js +33 -0
- package/dist/utils/stats-collector.js +15 -10
- package/package.json +1 -1
|
@@ -988,15 +988,26 @@ export class AgentRunner {
|
|
|
988
988
|
// Plugin implementation
|
|
989
989
|
export class ClaudeAgentPlugin {
|
|
990
990
|
name = 'claude';
|
|
991
|
-
isEnabled(
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
991
|
+
isEnabled(_globalConfig, agent) {
|
|
992
|
+
// Only instantiate this baseagent for agents that declare it.
|
|
993
|
+
return !!agent.config.agents?.claude;
|
|
994
|
+
}
|
|
995
|
+
createAgent(globalConfig, agent, callbacks) {
|
|
996
|
+
// Per-agent override: read from agent.json's agents.claude block first.
|
|
997
|
+
const override = agent.config.agents?.claude;
|
|
998
|
+
const anthropic = resolveAnthropicConfig(globalConfig, override);
|
|
999
|
+
// Merge per-agent claude block into config so runner reads useSettingSources etc.
|
|
1000
|
+
const merged = {
|
|
1001
|
+
...globalConfig,
|
|
1002
|
+
agents: {
|
|
1003
|
+
...(globalConfig.agents || {}),
|
|
1004
|
+
claude: { ...(globalConfig.agents?.claude || {}), ...(override || {}) },
|
|
1005
|
+
},
|
|
1006
|
+
};
|
|
1007
|
+
const agentRunner = new AgentRunner(anthropic.apiKey, anthropic.model, callbacks.onSessionIdUpdate, anthropic.baseUrl, merged);
|
|
997
1008
|
if (anthropic.effort) {
|
|
998
1009
|
agentRunner.setEffort(anthropic.effort);
|
|
999
1010
|
}
|
|
1000
|
-
return { agent: agentRunner };
|
|
1011
|
+
return { evolagentName: agent.name, baseagent: 'claude', agent: agentRunner };
|
|
1001
1012
|
}
|
|
1002
1013
|
}
|
|
@@ -301,17 +301,28 @@ export class CodexRunner {
|
|
|
301
301
|
// ── Plugin ──
|
|
302
302
|
export class CodexAgentPlugin {
|
|
303
303
|
name = 'codex';
|
|
304
|
-
isEnabled(
|
|
304
|
+
isEnabled(globalConfig, agent) {
|
|
305
|
+
if (!agent.config.agents?.codex)
|
|
306
|
+
return false;
|
|
305
307
|
try {
|
|
306
|
-
const
|
|
308
|
+
const override = agent.config.agents.codex;
|
|
309
|
+
const resolved = resolveOpenaiConfig(globalConfig, override);
|
|
307
310
|
return !!resolved.apiKey;
|
|
308
311
|
}
|
|
309
312
|
catch {
|
|
310
313
|
return false;
|
|
311
314
|
}
|
|
312
315
|
}
|
|
313
|
-
createAgent(
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
+
createAgent(globalConfig, agent, callbacks) {
|
|
317
|
+
const override = agent.config.agents?.codex;
|
|
318
|
+
// Synthesize a per-agent config view so CodexRunner sees its own credentials.
|
|
319
|
+
const merged = {
|
|
320
|
+
...globalConfig,
|
|
321
|
+
agents: {
|
|
322
|
+
...(globalConfig.agents || {}),
|
|
323
|
+
codex: { ...(globalConfig.agents?.codex || {}), ...(override || {}) },
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks) };
|
|
316
327
|
}
|
|
317
328
|
}
|
|
@@ -405,16 +405,27 @@ export class GeminiRunner {
|
|
|
405
405
|
// ── Plugin ──
|
|
406
406
|
export class GeminiAgentPlugin {
|
|
407
407
|
name = 'gemini';
|
|
408
|
-
isEnabled(
|
|
408
|
+
isEnabled(globalConfig, agent) {
|
|
409
|
+
if (!agent.config.agents?.gemini)
|
|
410
|
+
return false;
|
|
409
411
|
try {
|
|
410
|
-
const
|
|
412
|
+
const override = agent.config.agents.gemini;
|
|
413
|
+
const resolved = resolveGoogleConfig(globalConfig, override);
|
|
411
414
|
return !!resolved.cliPath;
|
|
412
415
|
}
|
|
413
416
|
catch {
|
|
414
417
|
return false;
|
|
415
418
|
}
|
|
416
419
|
}
|
|
417
|
-
createAgent(
|
|
418
|
-
|
|
420
|
+
createAgent(globalConfig, agent, callbacks) {
|
|
421
|
+
const override = agent.config.agents?.gemini;
|
|
422
|
+
const merged = {
|
|
423
|
+
...globalConfig,
|
|
424
|
+
agents: {
|
|
425
|
+
...(globalConfig.agents || {}),
|
|
426
|
+
gemini: { ...(globalConfig.agents?.gemini || {}), ...(override || {}) },
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
return { evolagentName: agent.name, baseagent: 'gemini', agent: new GeminiRunner(merged, callbacks) };
|
|
419
430
|
}
|
|
420
431
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getPackageRoot, resolveRoot } from '../paths.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
const KNOWN_SECTIONS = new Set(['runtime', 'group', 'proactive']);
|
|
6
|
+
const SECTION_RE = /^##\s+(\w+)\s*$/;
|
|
7
|
+
let sections = null;
|
|
8
|
+
let builtinSections = null;
|
|
9
|
+
function parseTemplate(content) {
|
|
10
|
+
const result = new Map();
|
|
11
|
+
let currentSection = null;
|
|
12
|
+
let currentLines = [];
|
|
13
|
+
for (const line of content.split('\n')) {
|
|
14
|
+
// Stop parsing at horizontal rule separator (documentation follows)
|
|
15
|
+
if (/^---\s*$/.test(line)) {
|
|
16
|
+
if (currentSection) {
|
|
17
|
+
result.set(currentSection, currentLines.join('\n').trim());
|
|
18
|
+
}
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const m = line.match(SECTION_RE);
|
|
22
|
+
if (m) {
|
|
23
|
+
if (currentSection) {
|
|
24
|
+
result.set(currentSection, currentLines.join('\n').trim());
|
|
25
|
+
}
|
|
26
|
+
const name = m[1];
|
|
27
|
+
if (KNOWN_SECTIONS.has(name)) {
|
|
28
|
+
currentSection = name;
|
|
29
|
+
currentLines = [];
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
currentSection = null;
|
|
33
|
+
currentLines = [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (currentSection) {
|
|
37
|
+
currentLines.push(line);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (currentSection) {
|
|
41
|
+
result.set(currentSection, currentLines.join('\n').trim());
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
function loadBuiltinTemplate() {
|
|
46
|
+
const builtinPath = path.join(getPackageRoot(), 'dist', 'templates', 'prompts.md');
|
|
47
|
+
const srcPath = path.join(getPackageRoot(), 'src', 'templates', 'prompts.md');
|
|
48
|
+
const filePath = fs.existsSync(builtinPath) ? builtinPath : srcPath;
|
|
49
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
50
|
+
return parseTemplate(content);
|
|
51
|
+
}
|
|
52
|
+
export function loadPromptTemplates() {
|
|
53
|
+
builtinSections = loadBuiltinTemplate();
|
|
54
|
+
const userPath = path.join(resolveRoot(), 'data', 'prompts.md');
|
|
55
|
+
if (fs.existsSync(userPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(userPath, 'utf-8');
|
|
58
|
+
const parsed = parseTemplate(content);
|
|
59
|
+
sections = new Map(builtinSections);
|
|
60
|
+
for (const [key, value] of parsed) {
|
|
61
|
+
sections.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
logger.info(`[PromptTemplates] Loaded user override: ${userPath}`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
logger.warn(`[PromptTemplates] Failed to load user override (${userPath}), using builtin:`, err);
|
|
67
|
+
sections = builtinSections;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
sections = builtinSections;
|
|
72
|
+
logger.info(`[PromptTemplates] Using builtin templates`);
|
|
73
|
+
}
|
|
74
|
+
for (const name of KNOWN_SECTIONS) {
|
|
75
|
+
if (!sections.has(name)) {
|
|
76
|
+
logger.warn(`[PromptTemplates] Section "${name}" missing, using builtin fallback`);
|
|
77
|
+
const fallback = builtinSections.get(name);
|
|
78
|
+
if (fallback)
|
|
79
|
+
sections.set(name, fallback);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isTruthy(val) {
|
|
84
|
+
if (val === undefined || val === null || val === false || val === '' || val === 0)
|
|
85
|
+
return false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
function renderTemplate(template, vars) {
|
|
89
|
+
// Pass 1: conditional sections {{?key}}...{{/}}
|
|
90
|
+
let result = template.replace(/\{\{\?(\w+)\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, body) => {
|
|
91
|
+
return isTruthy(vars[key]) ? body : '';
|
|
92
|
+
});
|
|
93
|
+
// Pass 2: variable substitution {{key}}
|
|
94
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
95
|
+
const val = vars[key];
|
|
96
|
+
if (!isTruthy(val))
|
|
97
|
+
return '';
|
|
98
|
+
return String(val);
|
|
99
|
+
});
|
|
100
|
+
// Pass 3: remove blank lines
|
|
101
|
+
return result.split('\n').filter(line => line.trim() !== '').join('\n');
|
|
102
|
+
}
|
|
103
|
+
export function renderPromptSection(section, vars) {
|
|
104
|
+
if (!sections)
|
|
105
|
+
loadPromptTemplates();
|
|
106
|
+
const template = sections.get(section);
|
|
107
|
+
if (!template) {
|
|
108
|
+
logger.warn(`[PromptTemplates] Section "${section}" not found`);
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
return renderTemplate(template, vars);
|
|
112
|
+
}
|
|
113
|
+
/** Reset loaded templates (for testing) */
|
|
114
|
+
export function _resetTemplates() {
|
|
115
|
+
sections = null;
|
|
116
|
+
builtinSections = null;
|
|
117
|
+
}
|
|
118
|
+
/** Load templates from a raw string (for testing) */
|
|
119
|
+
export function _loadFromString(content) {
|
|
120
|
+
builtinSections = parseTemplate(content);
|
|
121
|
+
sections = builtinSections;
|
|
122
|
+
}
|
package/dist/channels/aun-ops.js
CHANGED
|
@@ -12,7 +12,7 @@ import { execFileSync } from 'child_process';
|
|
|
12
12
|
import { isWindows } from '../utils/cross-platform.js';
|
|
13
13
|
import { resolvePaths } from '../paths.js';
|
|
14
14
|
// ==================== Constants ====================
|
|
15
|
-
export const MIN_AUN_CORE_SDK = [0, 2,
|
|
15
|
+
export const MIN_AUN_CORE_SDK = [0, 2, 17];
|
|
16
16
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
17
17
|
// ==================== SDK & Environment ====================
|
|
18
18
|
function compareVersion(a, min) {
|
package/dist/channels/aun.js
CHANGED
|
@@ -80,6 +80,11 @@ export class AUNChannel {
|
|
|
80
80
|
*/
|
|
81
81
|
async callAndTrace(method, params, opts) {
|
|
82
82
|
this.trace('OUT', method, params);
|
|
83
|
+
// [DIAG-STALE] 记录调用瞬间 SDK 内部 _state,证明是否在 reconnecting 中误发
|
|
84
|
+
const sdkStateBefore = this.client?._state ?? 'no-client';
|
|
85
|
+
if (sdkStateBefore !== 'connected') {
|
|
86
|
+
logger.warn(`[AUN][DIAG-STALE] callAndTrace ${method} on non-connected SDK: sdk_state=${sdkStateBefore} evolclaw_connected=${this.connected}`);
|
|
87
|
+
}
|
|
83
88
|
try {
|
|
84
89
|
const result = await this.client.call(method, params);
|
|
85
90
|
if (!opts?.silentOk) {
|
|
@@ -97,6 +102,9 @@ export class AUNChannel {
|
|
|
97
102
|
code: e?.code,
|
|
98
103
|
name: e?.name,
|
|
99
104
|
});
|
|
105
|
+
// [DIAG-STALE] 失败时再记录一次 SDK _state,看错误类型是否为 ConnectionError
|
|
106
|
+
const sdkStateAfter = this.client?._state ?? 'no-client';
|
|
107
|
+
logger.warn(`[AUN][DIAG-STALE] callAndTrace ${method} FAILED: err_name=${e?.name ?? '?'} err_code=${e?.code ?? '?'} sdk_state_before=${sdkStateBefore} sdk_state_after=${sdkStateAfter} evolclaw_connected=${this.connected}`);
|
|
100
108
|
logger.warn(`${this.logPrefix()} rpc ${method} failed: ${e?.name ?? ''}(${e?.code ?? ''}) ${e?.message ?? e}`);
|
|
101
109
|
throw e;
|
|
102
110
|
}
|
|
@@ -887,6 +895,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
887
895
|
if (!data || typeof data !== 'object')
|
|
888
896
|
return;
|
|
889
897
|
const state = data.state ?? '';
|
|
898
|
+
// [DIAG-STALE] 记录状态切换瞬间 evolclaw 的 connected 标志和 SDK 的内部 _state,
|
|
899
|
+
// 用于证明"reconnecting 时 connected 保持 true,导致 sendMessage 误放行"的假设
|
|
900
|
+
const sdkState = this.client?._state ?? 'no-client';
|
|
901
|
+
const connectedBefore = this.connected;
|
|
902
|
+
logger.info(`[AUN][DIAG-STALE] connection.state event: state=${state} attempt=${data.attempt ?? '-'} | connected_before=${connectedBefore} sdk_state=${sdkState}`);
|
|
890
903
|
if (state === 'connected') {
|
|
891
904
|
this.connected = true;
|
|
892
905
|
this.reconnectAttempt = 0;
|
|
@@ -952,6 +965,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
952
965
|
this.recallHandler = handler;
|
|
953
966
|
}
|
|
954
967
|
async sendMessage(channelId, text, context) {
|
|
968
|
+
// [DIAG-STALE] 进入 sendMessage 时记录 evolclaw connected 标志和 SDK _state,
|
|
969
|
+
// 用于检测两者是否一致:若 connected=true 但 sdk_state != 'connected',即为 stale 状态
|
|
970
|
+
const sdkStateOnEntry = this.client?._state ?? 'no-client';
|
|
971
|
+
if (this.connected !== (sdkStateOnEntry === 'connected')) {
|
|
972
|
+
logger.warn(`[AUN][DIAG-STALE] sendMessage entry MISMATCH: connected=${this.connected} sdk_state=${sdkStateOnEntry} channel=${channelId} text=${text.slice(0, 40)}`);
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
logger.debug(`[AUN][DIAG-STALE] sendMessage entry: connected=${this.connected} sdk_state=${sdkStateOnEntry} channel=${channelId}`);
|
|
976
|
+
}
|
|
955
977
|
if (!this.connected || !this.client) {
|
|
956
978
|
logger.warn(`${this.logPrefix()} Cannot send: not connected`);
|
|
957
979
|
return;
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
|
-
import { markdownToPlainText } from '../utils/
|
|
2
|
+
import { markdownToPlainText } from '../utils/rich-content-renderer.js';
|
|
3
3
|
import { requireOptional } from '../utils/init-channel.js';
|
|
4
4
|
import { normalizeChannelInstances, getChannelShowActivities } from '../config.js';
|
|
5
5
|
// ── QQBotChannel ────────────────────────────────────────────────────────────
|
package/dist/channels/wechat.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { resolvePaths } from '../paths.js';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
6
|
import { sanitizeFileName, saveToUploads, safeFetch } from '../utils/media-cache.js';
|
|
7
|
-
import { markdownToPlainText } from '../utils/
|
|
7
|
+
import { markdownToPlainText } from '../utils/rich-content-renderer.js';
|
|
8
8
|
const CHANNEL_VERSION = '1.0.0';
|
|
9
9
|
const ILINK_APP_ID = 'bot';
|
|
10
10
|
// iLink-App-ClientVersion: major<<16 | minor<<8 | patch (uint32)
|