evolclaw 3.1.10 → 3.2.0
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 +38 -0
- package/README.md +26 -4
- package/dist/agents/kit-renderer.js +5 -1
- package/dist/agents/manifest-engine.js +108 -35
- package/dist/agents/message-renderer.js +2 -0
- package/dist/aun/aid/control-aid.js +67 -0
- package/dist/aun/aid/identity.js +20 -7
- package/dist/aun/aid/store.js +2 -2
- package/dist/channels/aun.js +212 -158
- package/dist/channels/feishu.js +10 -14
- package/dist/channels/wechat.js +8 -2
- package/dist/cli/agent.js +38 -10
- package/dist/cli/index.js +50 -8
- package/dist/cli/init-channel.js +38 -148
- package/dist/cli/init.js +162 -82
- package/dist/config-store.js +38 -7
- package/dist/core/cache/file-cache.js +216 -0
- package/dist/core/command-handler.js +291 -68
- package/dist/core/evolagent-registry.js +3 -0
- package/dist/core/evolagent.js +28 -23
- package/dist/core/message/command-handler-agent-control.js +153 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/message-bridge.js +5 -3
- package/dist/core/message/message-processor.js +44 -36
- package/dist/core/message/message-queue.js +13 -6
- package/dist/core/model/model-scope.js +39 -6
- package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
- package/dist/evolclaw-config.js +11 -0
- package/dist/index.js +57 -2
- package/dist/ipc.js +6 -0
- package/dist/paths.js +7 -3
- package/dist/utils/media-cache.js +40 -1
- package/dist/utils/npm-ops.js +13 -3
- package/kits/templates/message-fragments/item.md +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-f
|
|
|
3
3
|
import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
|
|
4
4
|
import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath } from './paths.js';
|
|
5
5
|
import { resolveAnthropicConfig } from './agents/resolve.js';
|
|
6
|
-
import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded } from './config-store.js';
|
|
6
|
+
import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded, migrateProcessConfigIfNeeded } from './config-store.js';
|
|
7
|
+
import { loadEvolclawConfig } from './evolclaw-config.js';
|
|
7
8
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
8
9
|
import dotenv from 'dotenv';
|
|
9
10
|
import { SessionManager } from './core/session/session-manager.js';
|
|
@@ -12,7 +13,7 @@ import { CodexAgentPlugin } from './agents/codex-runner.js';
|
|
|
12
13
|
import { GeminiAgentPlugin } from './agents/gemini-runner.js';
|
|
13
14
|
import { FeishuChannelPlugin } from './channels/feishu.js';
|
|
14
15
|
import { WechatChannelPlugin } from './channels/wechat.js';
|
|
15
|
-
import { AUNChannelPlugin } from './channels/aun.js';
|
|
16
|
+
import { AUNChannel, AUNChannelPlugin } from './channels/aun.js';
|
|
16
17
|
import { DingtalkChannelPlugin } from './channels/dingtalk.js';
|
|
17
18
|
import { QQBotChannelPlugin } from './channels/qqbot.js';
|
|
18
19
|
import { WecomChannelPlugin } from './channels/wecom.js';
|
|
@@ -216,6 +217,9 @@ async function main() {
|
|
|
216
217
|
// ── 自动迁移 ──
|
|
217
218
|
migrateIdentitiesIfNeeded();
|
|
218
219
|
autoMigrateIfNeeded();
|
|
220
|
+
// config.json(ProcessConfig)→ evolclaw.json:必须在任何 getAidStore(AUN 连接)之前,
|
|
221
|
+
// 否则首次读 encryptionSeed 时迁移还没发生。
|
|
222
|
+
migrateProcessConfigIfNeeded();
|
|
219
223
|
// ── ECK 运行时初始化 ──
|
|
220
224
|
initEck();
|
|
221
225
|
// 加载 ECK manifest + 清理旧调试文件
|
|
@@ -223,6 +227,13 @@ async function main() {
|
|
|
223
227
|
loadKitManifest();
|
|
224
228
|
// 加载配置(新结构:defaults.json + per-agent config.json)
|
|
225
229
|
const defaults = loadDefaults() ?? { $schema_version: CONFIG_SCHEMA_VERSION };
|
|
230
|
+
const evolclawCfg = loadEvolclawConfig();
|
|
231
|
+
// 进程级 menu 操作(/system /agent)鉴权:owners 来自 evolclaw.json 顶层。
|
|
232
|
+
// owners 为空时这些操作一律 FORBIDDEN,启动时提示如何配置。
|
|
233
|
+
if (!evolclawCfg.owners || evolclawCfg.owners.length === 0) {
|
|
234
|
+
logger.warn('[startup] evolclaw.json.owners 未配置:进程级 menu 操作(/system /agent)将一律拒绝。' +
|
|
235
|
+
'如需远程管理,请在 evolclaw.json 配置 owners: [<你的 AID>]');
|
|
236
|
+
}
|
|
226
237
|
// 应用配置中的日志级别(优先于环境变量)
|
|
227
238
|
// logLevel 现在不在新结构中——若要保留,将来可加 defaults.debug.logLevel
|
|
228
239
|
// 阶段 2c 暂跳过
|
|
@@ -584,6 +595,19 @@ async function main() {
|
|
|
584
595
|
if (inst.registerHooks) {
|
|
585
596
|
inst.registerHooks({ eventBus, sessionManager });
|
|
586
597
|
}
|
|
598
|
+
// 4c. 观察者模式配置读取器(AUN):从 EvolAgent 的 merged config 读 observable/owners,
|
|
599
|
+
// 不另建缓存——EvolAgent 那份在启动/重启/热重载时统一更新,是唯一真相源。
|
|
600
|
+
const channelForObserver = inst.channel;
|
|
601
|
+
if (typeof channelForObserver.setObserverConfigResolver === 'function') {
|
|
602
|
+
const channelKey = inst.adapter.channelKey;
|
|
603
|
+
channelForObserver.setObserverConfigResolver(() => {
|
|
604
|
+
const owningAgent = agentRegistry.resolveByChannel(channelKey);
|
|
605
|
+
return {
|
|
606
|
+
observable: owningAgent?.getObservable() ?? false,
|
|
607
|
+
owners: owningAgent?.config.owners ?? [],
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
}
|
|
587
611
|
// 5. 撤回消息 → 中断执行中任务
|
|
588
612
|
inst.channel.onRecall?.((messageId) => {
|
|
589
613
|
msgBridge.cancel(messageId);
|
|
@@ -675,6 +699,27 @@ async function main() {
|
|
|
675
699
|
timestamp: Date.now()
|
|
676
700
|
});
|
|
677
701
|
}
|
|
702
|
+
// ── 控制 AID(daemon 进程身份):pureIdentity 接入 AUN,独立于 evolagent ──
|
|
703
|
+
let controlChannel;
|
|
704
|
+
if (evolclawCfg.aid) {
|
|
705
|
+
controlChannel = new AUNChannel({
|
|
706
|
+
aid: evolclawCfg.aid,
|
|
707
|
+
agentName: evolclawCfg.aid,
|
|
708
|
+
channelName: 'control',
|
|
709
|
+
pureIdentity: true,
|
|
710
|
+
aunTrace: evolclawCfg.debug?.aunTrace ?? defaults.debug?.aunTrace,
|
|
711
|
+
aunSdkLog: evolclawCfg.debug?.aunSdkLog ?? defaults.debug?.aunSdkLog,
|
|
712
|
+
});
|
|
713
|
+
// connect() 失败不置空实例:AUNChannel 内部有无限重连(SDK auto_reconnect +
|
|
714
|
+
// scheduleReconnect),首连失败后台会自愈;保留实例供 status 显示 disconnected。
|
|
715
|
+
try {
|
|
716
|
+
await controlChannel.connect();
|
|
717
|
+
logger.info(`✓ 控制 AID 已连接: ${evolclawCfg.aid}`);
|
|
718
|
+
}
|
|
719
|
+
catch (e) {
|
|
720
|
+
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
678
723
|
// 上线通知:延迟 1-3 秒后向 owner 发送上线消息(带 name + 工作目录)
|
|
679
724
|
// 需在配置中 debug.upmsg: true 手动开启
|
|
680
725
|
setTimeout(() => {
|
|
@@ -882,6 +927,9 @@ async function main() {
|
|
|
882
927
|
errors: snap.lastHour.errors,
|
|
883
928
|
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
884
929
|
},
|
|
930
|
+
controlAid: evolclawCfg.aid
|
|
931
|
+
? { aid: evolclawCfg.aid, connected: controlChannel?.getAidState().status === 'connected' }
|
|
932
|
+
: undefined,
|
|
885
933
|
};
|
|
886
934
|
}, async (cmd, sessionId) => cmdHandler.handleCtl(cmd, sessionId));
|
|
887
935
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
@@ -1036,6 +1084,13 @@ async function main() {
|
|
|
1036
1084
|
const type = inst.channelType || inst.adapter.channelName;
|
|
1037
1085
|
eventBus.publish({ type: 'channel:disconnected', channel: type, channelName: inst.adapter.channelName, reason: 'shutdown' });
|
|
1038
1086
|
}
|
|
1087
|
+
// 断开控制 AID(daemon 进程身份)
|
|
1088
|
+
if (controlChannel) {
|
|
1089
|
+
try {
|
|
1090
|
+
await controlChannel.disconnect();
|
|
1091
|
+
}
|
|
1092
|
+
catch { /* ignore */ }
|
|
1093
|
+
}
|
|
1039
1094
|
sessionManager.close();
|
|
1040
1095
|
removeAll();
|
|
1041
1096
|
logger.info('✓ Shutdown complete');
|
package/dist/ipc.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { logger } from './utils/logger.js';
|
|
4
|
+
import { fileCache } from './core/cache/file-cache.js';
|
|
4
5
|
const isWindows = process.platform === 'win32';
|
|
5
6
|
const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
|
|
6
7
|
export class IpcServer {
|
|
@@ -102,6 +103,11 @@ export class IpcServer {
|
|
|
102
103
|
const stats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
|
|
103
104
|
return { ok: true, stats };
|
|
104
105
|
}
|
|
106
|
+
case 'cache-stats': {
|
|
107
|
+
// daemon 统一 FileCache 的只读运行统计(watch web Cache 页用)。
|
|
108
|
+
// 直接读单例,无需 provider 注入(与 manifest-engine 等 import 单例同款)。
|
|
109
|
+
return { ok: true, stats: fileCache.stats() };
|
|
110
|
+
}
|
|
105
111
|
case 'aun-aid-stats-record-outbound': {
|
|
106
112
|
if (!this.aunAidStatsRecorder)
|
|
107
113
|
return { ok: false, error: 'recorder not configured' };
|
package/dist/paths.js
CHANGED
|
@@ -38,7 +38,8 @@ export function resolvePaths() {
|
|
|
38
38
|
socket: resolveInstanceSocketPath(root),
|
|
39
39
|
// ── 新结构(evolclaw-directory-design.md)────────────────
|
|
40
40
|
defaultsConfig: path.join(root, 'agents', 'defaults.json'),
|
|
41
|
-
processConfig: path.join(root, 'config.json'),
|
|
41
|
+
processConfig: path.join(root, 'config.json'), // legacy ProcessConfig path — only for migration source
|
|
42
|
+
evolclawJson: path.join(root, 'evolclaw.json'),
|
|
42
43
|
eckDir: path.join(root, 'eck'),
|
|
43
44
|
instanceReadySignal: path.join(root, 'data', 'instance', 'ready.signal'),
|
|
44
45
|
instanceSocket: resolveInstanceSocketPath(root),
|
|
@@ -182,14 +183,17 @@ function migrateFromAun() {
|
|
|
182
183
|
catch { }
|
|
183
184
|
}
|
|
184
185
|
/**
|
|
185
|
-
* 首次运行时从 assets/ 模板拷贝
|
|
186
|
+
* 首次运行时从 assets/ 模板拷贝 .env 到 $EVOLCLAW_HOME。
|
|
186
187
|
* 已存在则跳过,不覆盖用户修改。
|
|
188
|
+
*
|
|
189
|
+
* 注:config.json(旧 ProcessConfig)已废弃——不再 seed,否则会与
|
|
190
|
+
* migrateProcessConfigIfNeeded 的归档形成每次启动重建+重迁移的循环。
|
|
191
|
+
* 进程级配置统一走 evolclaw.json。
|
|
187
192
|
*/
|
|
188
193
|
function seedConfigTemplates() {
|
|
189
194
|
const root = resolveRoot();
|
|
190
195
|
const assetsDir = path.join(getPackageRoot(), 'assets');
|
|
191
196
|
const templates = [
|
|
192
|
-
{ src: 'config.json.template', dst: 'config.json' },
|
|
193
197
|
{ src: '.env.template', dst: '.env' },
|
|
194
198
|
];
|
|
195
199
|
for (const { src, dst } of templates) {
|
|
@@ -85,7 +85,14 @@ export async function validateImage(buffer, opts) {
|
|
|
85
85
|
}
|
|
86
86
|
// 动态导入 image-type(ESM only)
|
|
87
87
|
const { default: imageType } = await import('image-type');
|
|
88
|
-
|
|
88
|
+
// image-type 对极短/截断的 buffer 会抛 EndOfStreamError,捕获后按「无法识别」处理。
|
|
89
|
+
let type;
|
|
90
|
+
try {
|
|
91
|
+
type = await imageType(buffer);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
return { mime: null, reason: `Image type detection error: ${e.message}` };
|
|
95
|
+
}
|
|
89
96
|
if (!type) {
|
|
90
97
|
return { mime: null, reason: 'Unable to detect image type' };
|
|
91
98
|
}
|
|
@@ -94,6 +101,38 @@ export async function validateImage(buffer, opts) {
|
|
|
94
101
|
}
|
|
95
102
|
return { mime: type.mime };
|
|
96
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* 把已下载的附件 Buffer 转成入站图片条目(供 baseagent 视觉通道)。
|
|
106
|
+
*
|
|
107
|
+
* 适用于所有「需要先下载再注入」的通道(AUN ticket 下载、未来其它 CDN 下载等)。
|
|
108
|
+
* 飞书/微信已有各自的 SDK 下载路径,可按需复用本函数统一 MIME 判定。
|
|
109
|
+
*
|
|
110
|
+
* 判定优先级:
|
|
111
|
+
* 1. magic bytes(image-type 库,最可靠,同时做大小/白名单校验)
|
|
112
|
+
* 2. 元数据 MIME 字段(att.content_type / mime_type / mimeType)
|
|
113
|
+
* 3. 文件名后缀
|
|
114
|
+
*
|
|
115
|
+
* @returns 是图片返回 InboundImage;非图片或校验失败返回 null。
|
|
116
|
+
*/
|
|
117
|
+
export async function bufferToInboundImage(buffer, hints) {
|
|
118
|
+
// 1. magic bytes(含大小 + 白名单校验;validateImage 已吞掉 image-type 的异常)
|
|
119
|
+
const validated = await validateImage(buffer);
|
|
120
|
+
if (validated.mime) {
|
|
121
|
+
return { data: buffer.toString('base64'), mimeType: validated.mime };
|
|
122
|
+
}
|
|
123
|
+
// 2/3. magic bytes 未识别时,回退到元数据字段 / 文件名后缀
|
|
124
|
+
// (仍受 image 白名单约束,避免把任意文件当图片注入)
|
|
125
|
+
const metaCt = hints?.contentType || hints?.mimeType || '';
|
|
126
|
+
const byMeta = typeof metaCt === 'string' && ALLOWED_IMAGE_MIMES.has(metaCt) ? metaCt : '';
|
|
127
|
+
const byExt = hints?.filename
|
|
128
|
+
? (ALLOWED_IMAGE_MIMES.has(guessMime(hints.filename)) ? guessMime(hints.filename) : '')
|
|
129
|
+
: '';
|
|
130
|
+
const fallback = byMeta || byExt;
|
|
131
|
+
if (fallback && buffer.length > 0 && buffer.length <= DEFAULT_MAX_IMAGE_SIZE) {
|
|
132
|
+
return { data: buffer.toString('base64'), mimeType: fallback };
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
97
136
|
/**
|
|
98
137
|
* 保存 Buffer 到 uploads 目录
|
|
99
138
|
* - 自动创建目录
|
package/dist/utils/npm-ops.js
CHANGED
|
@@ -15,10 +15,20 @@ import { isWindows } from './cross-platform.js';
|
|
|
15
15
|
const execFileAsync = promisify(execFile);
|
|
16
16
|
// ── npm install -g (shared) ────────────────────────────────────────────────
|
|
17
17
|
export async function npmInstallGlobal(pkg) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let cmd;
|
|
19
|
+
let args;
|
|
20
|
+
// Windows: run via cmd /c to avoid shell:true deprecation warning on Node 22.
|
|
21
|
+
// Unix: npm directly, no shell needed.
|
|
22
|
+
if (isWindows) {
|
|
23
|
+
cmd = 'cmd';
|
|
24
|
+
args = ['/c', 'npm', 'install', '-g', pkg];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
cmd = 'npm';
|
|
28
|
+
args = ['install', '-g', pkg];
|
|
29
|
+
}
|
|
20
30
|
try {
|
|
21
|
-
await execFileAsync(
|
|
31
|
+
await execFileAsync(cmd, args, { timeout: 180000 });
|
|
22
32
|
}
|
|
23
33
|
catch (e) {
|
|
24
34
|
if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES')) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
‹{{now}}{{?chatType=group}} · {{
|
|
1
|
+
‹{{now}} · from:{{peerId}}{{?peerName}}({{peerName}}){{/}}{{?chatType=group}} → 群:{{groupLabel}} · self:{{selfAid}}{{/}}{{?chatType=private}} → self:{{selfAid}}{{/}}{{?sameDevice}} · 📍同设备{{/}}{{?sameNetwork}} · 🌐同网络{{/}}{{?sameEgressIp}} · 🔀同出口IP{{/}}{{?mentionAids}} · @:{{mentionAids}}{{/}}›
|
|
2
2
|
{{content}}
|
package/package.json
CHANGED