evolclaw 3.1.11 → 3.3.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 +41 -0
- package/README.md +27 -2
- package/dist/agents/{resolve.js → baseagent.js} +34 -5
- package/dist/agents/claude-runner.js +120 -27
- package/dist/agents/codex-app-server-client.js +364 -0
- package/dist/agents/codex-runner.js +1069 -141
- package/dist/agents/gemini-runner.js +2 -2
- package/dist/agents/runner-types.js +28 -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/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +538 -325
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +98 -151
- package/dist/channels/qqbot.js +75 -138
- package/dist/channels/wechat.js +75 -136
- package/dist/channels/wecom.js +75 -138
- package/dist/cli/agent.js +44 -13
- package/dist/cli/index.js +207 -46
- package/dist/cli/init-channel.js +38 -148
- package/dist/cli/init.js +192 -85
- package/dist/cli/model.js +1 -1
- package/dist/cli/stats.js +558 -0
- package/dist/cli/version.js +87 -0
- package/dist/cli/watch-msg.js +5 -2
- package/dist/config-store.js +48 -11
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +754 -172
- package/dist/core/daemon-file-cache.js +216 -0
- package/dist/core/evolagent-registry.js +4 -0
- package/dist/core/evolagent.js +28 -23
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +215 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/im-renderer.js +35 -13
- package/dist/core/message/items-formatter.js +9 -1
- package/dist/core/message/message-bridge.js +52 -22
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +336 -68
- package/dist/core/message/message-queue.js +15 -8
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +56 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-scope.js +40 -7
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
- package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
- package/dist/core/session/session-manager.js +27 -13
- package/dist/core/session/session-title.js +26 -0
- package/dist/core/stats/billing.js +151 -0
- package/dist/core/stats/budget.js +93 -0
- package/dist/core/stats/db.js +314 -0
- package/dist/core/stats/eck-vars.js +84 -0
- package/dist/core/stats/index.js +10 -0
- package/dist/core/stats/normalizer.js +78 -0
- package/dist/core/stats/query.js +760 -0
- package/dist/core/stats/writer.js +115 -0
- package/dist/core/trigger/manager.js +34 -0
- package/dist/core/trigger/parser.js +9 -3
- package/dist/core/trigger/scheduler.js +20 -17
- package/dist/{agents → eck}/kit-renderer.js +5 -1
- package/dist/{agents → eck}/manifest-engine.js +127 -35
- package/dist/{agents → eck}/message-renderer.js +26 -1
- package/dist/index.js +185 -8
- package/dist/ipc.js +22 -0
- package/dist/paths.js +7 -3
- package/dist/utils/cross-platform.js +23 -5
- package/dist/utils/ecweb-pair.js +20 -0
- package/dist/utils/stats.js +14 -0
- package/kits/docs/evolclaw/INDEX.md +3 -1
- package/kits/docs/evolclaw/fs-architecture.md +1215 -0
- package/kits/docs/evolclaw/fs.md +131 -0
- package/kits/docs/evolclaw/group-fs.md +209 -0
- package/kits/docs/evolclaw/stats.md +70 -0
- package/kits/docs/venues/aun-group.md +29 -6
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +30 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/system-fragments/response-depth.md +16 -0
- package/package.json +4 -4
- package/dist/agents/baseagent-normalize.js +0 -19
- package/dist/core/relation/peer-key.js +0 -16
- package/dist/utils/channel-helpers.js +0 -46
package/dist/config-store.js
CHANGED
|
@@ -21,6 +21,15 @@ import { checkAgentDir, isValidAid } from './aun/aid/validation.js';
|
|
|
21
21
|
import { isValidChannelName } from './core/channel-loader.js';
|
|
22
22
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
23
23
|
import { logger } from './utils/logger.js';
|
|
24
|
+
/** 读 {root}/evolclaw.json。文件不存在返回 {},不报错。 */
|
|
25
|
+
export function loadEvolclawConfig() {
|
|
26
|
+
const raw = atomicReadJson(resolvePaths().evolclawJson);
|
|
27
|
+
return raw ?? {};
|
|
28
|
+
}
|
|
29
|
+
/** 原子写入 {root}/evolclaw.json。调用方负责传完整对象(含要保留的字段)。 */
|
|
30
|
+
export function saveEvolclawConfig(value) {
|
|
31
|
+
atomicWriteJson(resolvePaths().evolclawJson, value);
|
|
32
|
+
}
|
|
24
33
|
const SUPPORTED_CHANNEL_TYPES = new Set([
|
|
25
34
|
'aun', 'feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom',
|
|
26
35
|
]);
|
|
@@ -122,14 +131,41 @@ export function saveDefaultsSafe(patch) {
|
|
|
122
131
|
: { $schema_version: CONFIG_SCHEMA_VERSION, ...patch };
|
|
123
132
|
atomicWriteJson(p, merged);
|
|
124
133
|
}
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
// ── 进程配置迁移(旧 {root}/config.json ProcessConfig → evolclaw.json)──────
|
|
135
|
+
//
|
|
136
|
+
// ProcessConfig 类型 + loadProcessConfig/saveProcessConfig 已废弃并删除:
|
|
137
|
+
// 唯一有效字段 aun.encryptionSeed 已迁入 evolclaw.json(见 migrateProcessConfigIfNeeded),
|
|
138
|
+
// 读取源切到 loadEvolclawConfig(store.ts)。log / aun.gateway / aun.keystorePath 是死字段。
|
|
139
|
+
/**
|
|
140
|
+
* 一次性迁移:{root}/config.json(旧 ProcessConfig)→ {root}/evolclaw.json。
|
|
141
|
+
* - 仅搬运 aun.encryptionSeed(逐字节原样,含 null);log / aun.gateway / aun.keystorePath 是死字段,丢弃。
|
|
142
|
+
* - 合并写入(不覆盖 evolclaw.json 已有字段如 aid)。
|
|
143
|
+
* - 完成后归档 config.json → config.json.migrated(保留备份,不直接删)。
|
|
144
|
+
*
|
|
145
|
+
* ⚠️ encryptionSeed 是 AID 私钥的加密种子,迁移前后 getAidStore 拿到的 seed 必须逐字节一致,
|
|
146
|
+
* 否则所有已注册 AID 私钥解不开。这里只搬运不改值(含 null)。
|
|
147
|
+
*/
|
|
148
|
+
export function migrateProcessConfigIfNeeded() {
|
|
149
|
+
const p = resolvePaths();
|
|
150
|
+
const oldPath = p.processConfig; // {root}/config.json
|
|
151
|
+
const raw = atomicReadJson(oldPath);
|
|
127
152
|
if (raw === null)
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
153
|
+
return; // 不存在 → no-op
|
|
154
|
+
const evc = loadEvolclawConfig();
|
|
155
|
+
// 仅当旧文件确实带 aun.encryptionSeed 字段时才搬(hasOwnProperty,保 null 语义)
|
|
156
|
+
const didMigrateSeed = !!(raw.aun && Object.prototype.hasOwnProperty.call(raw.aun, 'encryptionSeed'));
|
|
157
|
+
if (didMigrateSeed) {
|
|
158
|
+
evc.aun = { ...(evc.aun ?? {}), encryptionSeed: raw.aun.encryptionSeed };
|
|
159
|
+
}
|
|
160
|
+
evc.$schema_version = evc.$schema_version ?? 1;
|
|
161
|
+
saveEvolclawConfig(evc);
|
|
162
|
+
// 归档旧文件(不删,留备份)
|
|
163
|
+
try {
|
|
164
|
+
fs.renameSync(oldPath, oldPath + '.migrated');
|
|
165
|
+
}
|
|
166
|
+
catch { /* ignore */ }
|
|
167
|
+
const what = didMigrateSeed ? 'aun.encryptionSeed 已搬运' : 'aun.encryptionSeed 不存在(无需搬运)';
|
|
168
|
+
logger.info(`[migrate] config.json → evolclaw.json (${what},config.json 已归档为 .migrated)`);
|
|
133
169
|
}
|
|
134
170
|
// ── 自动迁移 ───────────────────────────────────────────────────────────
|
|
135
171
|
/**
|
|
@@ -492,6 +528,8 @@ export function mergeForAgent(agent, defaults) {
|
|
|
492
528
|
debounce: agent.debounce ?? d.debounce,
|
|
493
529
|
debug: deepMergeBlocks(d.debug, agent.debug),
|
|
494
530
|
enable_rich_content: agent.enable_rich_content ?? d.enable_rich_content,
|
|
531
|
+
dispatch: agent.dispatch,
|
|
532
|
+
observable: agent.observable,
|
|
495
533
|
};
|
|
496
534
|
return merged;
|
|
497
535
|
}
|
|
@@ -570,10 +608,9 @@ export function migrateIdentitiesIfNeeded() {
|
|
|
570
608
|
}
|
|
571
609
|
// ── Project Migration ────────────────────────────────────────────────────────
|
|
572
610
|
import os from 'os';
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
611
|
+
// 复用与 Claude SDK 对齐的统一编码(resolve→realpath→NFC→非字母数字替换为 -),
|
|
612
|
+
// 避免本地实现规则不一致导致中文/非 ASCII 路径迁移时找不到 SDK 会话目录。
|
|
613
|
+
import { encodePath } from './utils/cross-platform.js';
|
|
577
614
|
/** 查找最新的 ~/.codex/state_*.sqlite */
|
|
578
615
|
function findCodexDb() {
|
|
579
616
|
const codexHome = path.join(os.homedir(), '.codex');
|
|
@@ -6,11 +6,21 @@
|
|
|
6
6
|
* The main service (index.ts) handles registration and message flow wiring.
|
|
7
7
|
*/
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
9
|
+
/** Resolve showActivities for a single instance (instance overrides default). */
|
|
10
|
+
export function resolveShowActivities(inst) {
|
|
11
|
+
return inst.showActivities ?? 'all';
|
|
12
|
+
}
|
|
13
|
+
/** Standard showMiddleResult / showIdleMonitor policy function. */
|
|
14
|
+
export function showActivitiesPolicy(mode, chatType, identity) {
|
|
15
|
+
if (mode === 'none')
|
|
16
|
+
return false;
|
|
17
|
+
if (mode === 'dm-only')
|
|
18
|
+
return chatType === 'private';
|
|
19
|
+
if (mode === 'owner-dm-only')
|
|
20
|
+
return chatType === 'private' && identity === 'owner';
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// ── ChannelLoader ──────────────────────────────────────────────────────────
|
|
14
24
|
export class ChannelLoader {
|
|
15
25
|
plugins = new Map();
|
|
16
26
|
register(plugin) {
|
|
@@ -20,81 +30,70 @@ export class ChannelLoader {
|
|
|
20
30
|
this.plugins.set(plugin.name, plugin);
|
|
21
31
|
logger.debug(`Registered channel plugin: ${plugin.name}`);
|
|
22
32
|
}
|
|
33
|
+
/** Look up a registered plugin by channel type (used by reload hooks). */
|
|
34
|
+
getPlugin(type) {
|
|
35
|
+
return this.plugins.get(type);
|
|
36
|
+
}
|
|
23
37
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* 内部把 ChannelInstance[] 翻成各 plugin 期望的 dict 形态(`{ type: [instances...] }`),
|
|
27
|
-
* 然后调用现有 plugin.createChannels / createChannel。
|
|
28
|
-
*
|
|
29
|
-
* 当所有 channel plugin 重写为直接吃 ChannelInstance[] 后,本方法可简化。
|
|
38
|
+
* Create all runtime channels for an agent directly from its config.channels[].
|
|
39
|
+
* AUN is always created implicitly from agent.aid (no explicit entry required).
|
|
30
40
|
*/
|
|
31
41
|
async createForAgent(agent) {
|
|
32
|
-
const
|
|
33
|
-
|
|
42
|
+
const ctx = {
|
|
43
|
+
agentName: agent.aid,
|
|
44
|
+
defaultProjectPath: agent.config.projects?.defaultPath ?? process.cwd(),
|
|
45
|
+
enableRichContent: agent.config.enable_rich_content,
|
|
46
|
+
debug: agent.config.debug,
|
|
47
|
+
};
|
|
48
|
+
// Build the full list of config instances to create.
|
|
49
|
+
// AUN is synthesised from agent.aid; any explicit aun entry in channels[] is skipped.
|
|
34
50
|
const aunEffName = agent.effectiveChannelName('aun', 'main');
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}];
|
|
44
|
-
// 其它 channels(非 AUN)从 config.channels[] 取
|
|
51
|
+
const aunInst = {
|
|
52
|
+
type: 'aun',
|
|
53
|
+
name: aunEffName,
|
|
54
|
+
aid: agent.aid,
|
|
55
|
+
enabled: true,
|
|
56
|
+
owner: agent.config.owners?.[0],
|
|
57
|
+
};
|
|
58
|
+
const configInsts = [aunInst];
|
|
45
59
|
for (const inst of agent.config.channels) {
|
|
46
60
|
if (inst.type === 'aun')
|
|
47
|
-
continue;
|
|
61
|
+
continue;
|
|
48
62
|
const effName = agent.effectiveChannelName(inst.type, inst.name);
|
|
49
|
-
|
|
50
|
-
(rewrittenChannels[inst.type] ??= []).push(rewritten);
|
|
63
|
+
configInsts.push({ ...inst, name: effName });
|
|
51
64
|
}
|
|
52
|
-
|
|
53
|
-
// 新 schema 字段命名为 snake_case,这里转 camelCase 透传。
|
|
54
|
-
const syntheticConfig = {
|
|
55
|
-
agents: agent.config.baseagents,
|
|
56
|
-
channels: rewrittenChannels,
|
|
57
|
-
projects: agent.config.projects,
|
|
58
|
-
chatmode: agent.config.chatmode,
|
|
59
|
-
debug: agent.config.debug,
|
|
60
|
-
showActivities: agent.config.show_activities,
|
|
61
|
-
flushDelay: agent.config.flush_delay,
|
|
62
|
-
debounce: agent.config.debounce,
|
|
63
|
-
enableRichContent: agent.config.enable_rich_content,
|
|
64
|
-
};
|
|
65
|
-
return this.createAll(syntheticConfig);
|
|
65
|
+
return this._buildInstances(configInsts, ctx);
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
/** Build runtime instances for a list of config instances + context. */
|
|
68
|
+
async _buildInstances(configInsts, ctx) {
|
|
69
|
+
const result = [];
|
|
70
|
+
for (const inst of configInsts) {
|
|
71
|
+
const plugin = this.plugins.get(inst.type);
|
|
72
|
+
if (!plugin) {
|
|
73
|
+
logger.debug(`No plugin for channel type '${inst.type}', skipping`);
|
|
72
74
|
continue;
|
|
73
75
|
}
|
|
74
76
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
logger.info(`✓ Channel '${name}'
|
|
77
|
+
const runtime = await plugin.createInstance(inst, ctx);
|
|
78
|
+
if (runtime) {
|
|
79
|
+
result.push(runtime);
|
|
80
|
+
logger.info(`✓ Channel '${inst.name}' (${inst.type}) created`);
|
|
79
81
|
}
|
|
80
82
|
else {
|
|
81
|
-
|
|
82
|
-
instances.push(instance);
|
|
83
|
-
logger.info(`✓ Channel '${name}' instance created`);
|
|
83
|
+
logger.info(`Channel '${inst.name}' (${inst.type}) disabled or invalid credentials, skipping`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
catch (
|
|
87
|
-
logger.error(`✗ Failed to create channel '${name}':`,
|
|
86
|
+
catch (err) {
|
|
87
|
+
logger.error(`✗ Failed to create channel '${inst.name}' (${inst.type}):`, err);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
return
|
|
90
|
+
return result;
|
|
91
91
|
}
|
|
92
92
|
async connectAll(instances, { concurrency = 3, intervalMs = 50 } = {}) {
|
|
93
93
|
const connected = [];
|
|
94
94
|
const failed = [];
|
|
95
95
|
const inflight = new Set();
|
|
96
96
|
for (const inst of instances) {
|
|
97
|
-
// 等待并发数降到 concurrency 以下
|
|
98
97
|
while (inflight.size >= concurrency) {
|
|
99
98
|
await Promise.race(inflight);
|
|
100
99
|
}
|
|
@@ -110,12 +109,10 @@ export class ChannelLoader {
|
|
|
110
109
|
})();
|
|
111
110
|
const tracked = task.then(() => { inflight.delete(tracked); });
|
|
112
111
|
inflight.add(tracked);
|
|
113
|
-
// 间隔发起,避免瞬间并发冲击网关
|
|
114
112
|
if (intervalMs > 0) {
|
|
115
113
|
await new Promise(r => setTimeout(r, intervalMs));
|
|
116
114
|
}
|
|
117
115
|
}
|
|
118
|
-
// 等待所有剩余任务完成
|
|
119
116
|
await Promise.allSettled(inflight);
|
|
120
117
|
if (failed.length > 0) {
|
|
121
118
|
logger.warn(`[connectAll] ${failed.length} channel(s) failed initial connect (will retry in background): ${failed.map(f => f.name).join(', ')}`);
|
|
@@ -123,7 +120,7 @@ export class ChannelLoader {
|
|
|
123
120
|
return connected;
|
|
124
121
|
}
|
|
125
122
|
async disconnectAll(instances) {
|
|
126
|
-
await Promise.allSettled(instances.map(
|
|
123
|
+
await Promise.allSettled(instances.map(inst => inst.disconnect()));
|
|
127
124
|
}
|
|
128
125
|
}
|
|
129
126
|
const SEP = '#';
|
|
@@ -197,34 +194,39 @@ export function buildReloadHooks(deps) {
|
|
|
197
194
|
}
|
|
198
195
|
},
|
|
199
196
|
async startChannel(agent, channelName) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
197
|
+
// The implicit AUN channel is synthesised from agent.aid (not in config.channels[]).
|
|
198
|
+
// Reconstruct it the same way createForAgent does before falling back to config scan.
|
|
199
|
+
const aid = agent.aid ?? agent.config?.aid;
|
|
200
|
+
const aunEffName = agent.effectiveChannelName?.('aun', 'main') ?? 'aun-main';
|
|
201
|
+
const isImplicitAun = channelName === aunEffName;
|
|
202
|
+
// Find config instance: implicit AUN gets a synthetic entry; others scan channels[].
|
|
203
|
+
const cfgInst = isImplicitAun
|
|
204
|
+
? { type: 'aun', name: aunEffName, aid, enabled: true, owner: agent.config?.owners?.[0] }
|
|
205
|
+
: (() => {
|
|
206
|
+
const agentChannels = agent.config?.channels ?? [];
|
|
207
|
+
return agentChannels.find((i) => {
|
|
208
|
+
const effName = agent.effectiveChannelName?.(i.type, i.name) ?? i.name;
|
|
209
|
+
return effName === channelName;
|
|
210
|
+
}) ?? null;
|
|
211
|
+
})();
|
|
212
|
+
if (!cfgInst) {
|
|
213
|
+
const msg = `[Reload] Channel ${channelName} not found in agent config`;
|
|
216
214
|
logger.error(msg);
|
|
217
215
|
throw new Error(msg);
|
|
218
216
|
}
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
const ctx = {
|
|
218
|
+
agentName: agent.aid ?? agent.config?.aid,
|
|
219
|
+
defaultProjectPath: agent.config?.projects?.defaultPath ?? process.cwd(),
|
|
220
|
+
enableRichContent: agent.config?.enable_rich_content,
|
|
221
|
+
debug: agent.config?.debug,
|
|
223
222
|
};
|
|
224
|
-
const
|
|
225
|
-
|
|
223
|
+
const plugin = channelLoader.getPlugin(cfgInst.type);
|
|
224
|
+
if (!plugin)
|
|
225
|
+
throw new Error(`[Reload] No plugin for channel type '${cfgInst.type}'`);
|
|
226
|
+
const effInst = { ...cfgInst, name: channelName };
|
|
227
|
+
const newInst = await plugin.createInstance(effInst, ctx);
|
|
226
228
|
if (!newInst)
|
|
227
|
-
throw new Error(`[Reload]
|
|
229
|
+
throw new Error(`[Reload] createInstance returned null for ${channelName}`);
|
|
228
230
|
registerChannelInstance(newInst);
|
|
229
231
|
await newInst.connect();
|
|
230
232
|
channelInstances.push(newInst);
|