evolclaw 3.1.11 → 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 +24 -0
- package/README.md +26 -0
- 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 +161 -61
- package/dist/channels/feishu.js +3 -3
- package/dist/cli/agent.js +38 -10
- package/dist/cli/index.js +31 -3
- 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/kits/templates/message-fragments/item.md +1 -1
- package/package.json +1 -1
|
@@ -95,24 +95,67 @@ export class ClaudeSessionFileAdapter {
|
|
|
95
95
|
}
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
|
+
// CLI 会话白名单:只有真正由人手发起的终端会话才值得导入。
|
|
99
|
+
// 其它 entrypoint(sdk-ts=EvolClaw 自身、sdk-py=security-guidance 等插件的后台 SDK 会话)
|
|
100
|
+
// 同样把 JSONL 写进项目目录,但不是用户会话,导入它们没有意义。
|
|
101
|
+
static CLI_ENTRYPOINTS = new Set(['cli', 'sdk-cli']);
|
|
102
|
+
// entrypoint 不可变,进程内缓存 filePath→entrypoint,避免重复读文件头部
|
|
103
|
+
entrypointCache = new Map();
|
|
104
|
+
// entrypoint 字段位于首个 user 事件行(实测恒在前 4 行内,cli 会话首次出现 < 8KB)。
|
|
105
|
+
// 插件会话首行 queue-operation 可能携带巨大 diff(数百 KB),把 entrypoint 推到很后面,
|
|
106
|
+
// 因此读取上限设为 32KB:cli 会话必命中,插件会话要么命中 sdk-py、要么读不到 → 一律排除。
|
|
107
|
+
static ENTRYPOINT_SCAN_BYTES = 32 * 1024;
|
|
108
|
+
/** 读取会话文件的 entrypoint,结果缓存在实例内(entrypoint 不可变,无需失效)。 */
|
|
109
|
+
readEntrypoint(filePath) {
|
|
110
|
+
if (this.entrypointCache.has(filePath))
|
|
111
|
+
return this.entrypointCache.get(filePath);
|
|
112
|
+
let fd;
|
|
113
|
+
let result = null;
|
|
114
|
+
try {
|
|
115
|
+
fd = fs.openSync(filePath, 'r');
|
|
116
|
+
const buf = Buffer.allocUnsafe(ClaudeSessionFileAdapter.ENTRYPOINT_SCAN_BYTES);
|
|
117
|
+
const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
118
|
+
const head = buf.toString('utf-8', 0, bytesRead);
|
|
119
|
+
const m = head.match(/"entrypoint":"([^"]*)"/);
|
|
120
|
+
result = m ? m[1] : null;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// keep null
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
if (fd !== undefined)
|
|
127
|
+
fs.closeSync(fd);
|
|
128
|
+
}
|
|
129
|
+
this.entrypointCache.set(filePath, result);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
98
132
|
scanCliSessions(projectPath) {
|
|
99
133
|
const homeDir = os.homedir();
|
|
100
134
|
const encodedPath = encodePath(projectPath);
|
|
101
135
|
const sessionDir = path.join(homeDir, '.claude', 'projects', encodedPath);
|
|
102
136
|
if (!fs.existsSync(sessionDir))
|
|
103
137
|
return [];
|
|
104
|
-
const
|
|
138
|
+
const candidates = fs.readdirSync(sessionDir)
|
|
105
139
|
.filter(f => f.endsWith('.jsonl'))
|
|
106
140
|
.filter(f => !f.startsWith('agent-'))
|
|
107
141
|
.map(f => {
|
|
108
142
|
const filePath = path.join(sessionDir, f);
|
|
109
143
|
const stat = fs.statSync(filePath);
|
|
110
|
-
return { uuid: f.replace('.jsonl', ''), mtime: stat.mtimeMs, size: stat.size };
|
|
144
|
+
return { uuid: f.replace('.jsonl', ''), filePath, mtime: stat.mtimeMs, size: stat.size };
|
|
111
145
|
})
|
|
112
146
|
.filter(f => f.size > 0)
|
|
113
|
-
.sort((a, b) => b.mtime - a.mtime)
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
148
|
+
// 按 mtime 降序惰性判定 entrypoint,凑够 10 个白名单会话即停,避免读取全部文件。
|
|
149
|
+
const result = [];
|
|
150
|
+
for (const f of candidates) {
|
|
151
|
+
const entrypoint = this.readEntrypoint(f.filePath);
|
|
152
|
+
if (entrypoint && ClaudeSessionFileAdapter.CLI_ENTRYPOINTS.has(entrypoint)) {
|
|
153
|
+
result.push({ uuid: f.uuid, mtime: f.mtime });
|
|
154
|
+
if (result.length >= 10)
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
116
159
|
}
|
|
117
160
|
async listSdkSessions(projectPath) {
|
|
118
161
|
try {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { resolvePaths } from './paths.js';
|
|
2
|
+
import { atomicReadJson, atomicWriteJson } from './utils/atomic-write.js';
|
|
3
|
+
/** 读 {root}/evolclaw.json。文件不存在返回 {},不报错。 */
|
|
4
|
+
export function loadEvolclawConfig() {
|
|
5
|
+
const raw = atomicReadJson(resolvePaths().evolclawJson);
|
|
6
|
+
return raw ?? {};
|
|
7
|
+
}
|
|
8
|
+
/** 原子写入 {root}/evolclaw.json。调用方负责传完整对象(含要保留的字段)。 */
|
|
9
|
+
export function saveEvolclawConfig(value) {
|
|
10
|
+
atomicWriteJson(resolvePaths().evolclawJson, value);
|
|
11
|
+
}
|
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) {
|
|
@@ -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