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.
Files changed (89) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +27 -2
  3. package/dist/agents/{resolve.js → baseagent.js} +34 -5
  4. package/dist/agents/claude-runner.js +120 -27
  5. package/dist/agents/codex-app-server-client.js +364 -0
  6. package/dist/agents/codex-runner.js +1069 -141
  7. package/dist/agents/gemini-runner.js +2 -2
  8. package/dist/agents/runner-types.js +28 -0
  9. package/dist/aun/aid/control-aid.js +67 -0
  10. package/dist/aun/aid/identity.js +20 -7
  11. package/dist/aun/aid/store.js +2 -2
  12. package/dist/aun/storage/download.js +1 -1
  13. package/dist/aun/storage/upload.js +13 -1
  14. package/dist/channels/aun.js +538 -325
  15. package/dist/channels/dingtalk.js +77 -140
  16. package/dist/channels/feishu.js +98 -151
  17. package/dist/channels/qqbot.js +75 -138
  18. package/dist/channels/wechat.js +75 -136
  19. package/dist/channels/wecom.js +75 -138
  20. package/dist/cli/agent.js +44 -13
  21. package/dist/cli/index.js +207 -46
  22. package/dist/cli/init-channel.js +38 -148
  23. package/dist/cli/init.js +192 -85
  24. package/dist/cli/model.js +1 -1
  25. package/dist/cli/stats.js +558 -0
  26. package/dist/cli/version.js +87 -0
  27. package/dist/cli/watch-msg.js +5 -2
  28. package/dist/config-store.js +48 -11
  29. package/dist/core/channel-loader.js +84 -82
  30. package/dist/core/command-handler.js +754 -172
  31. package/dist/core/daemon-file-cache.js +216 -0
  32. package/dist/core/evolagent-registry.js +4 -0
  33. package/dist/core/evolagent.js +28 -23
  34. package/dist/core/interaction-router.js +8 -0
  35. package/dist/core/message/command-handler-agent-control.js +215 -0
  36. package/dist/core/message/create-status.js +67 -0
  37. package/dist/core/message/im-renderer.js +35 -13
  38. package/dist/core/message/items-formatter.js +9 -1
  39. package/dist/core/message/message-bridge.js +52 -22
  40. package/dist/core/message/message-log.js +1 -0
  41. package/dist/core/message/message-processor.js +336 -68
  42. package/dist/core/message/message-queue.js +15 -8
  43. package/dist/core/message/pending-hints.js +232 -0
  44. package/dist/core/message/response-depth.js +56 -0
  45. package/dist/core/model/model-catalog.js +1 -1
  46. package/dist/core/model/model-scope.js +40 -7
  47. package/dist/core/permission.js +9 -12
  48. package/dist/core/relation/peer-identity.js +16 -1
  49. package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
  50. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  51. package/dist/core/session/session-manager.js +27 -13
  52. package/dist/core/session/session-title.js +26 -0
  53. package/dist/core/stats/billing.js +151 -0
  54. package/dist/core/stats/budget.js +93 -0
  55. package/dist/core/stats/db.js +314 -0
  56. package/dist/core/stats/eck-vars.js +84 -0
  57. package/dist/core/stats/index.js +10 -0
  58. package/dist/core/stats/normalizer.js +78 -0
  59. package/dist/core/stats/query.js +760 -0
  60. package/dist/core/stats/writer.js +115 -0
  61. package/dist/core/trigger/manager.js +34 -0
  62. package/dist/core/trigger/parser.js +9 -3
  63. package/dist/core/trigger/scheduler.js +20 -17
  64. package/dist/{agents → eck}/kit-renderer.js +5 -1
  65. package/dist/{agents → eck}/manifest-engine.js +127 -35
  66. package/dist/{agents → eck}/message-renderer.js +26 -1
  67. package/dist/index.js +185 -8
  68. package/dist/ipc.js +22 -0
  69. package/dist/paths.js +7 -3
  70. package/dist/utils/cross-platform.js +23 -5
  71. package/dist/utils/ecweb-pair.js +20 -0
  72. package/dist/utils/stats.js +14 -0
  73. package/kits/docs/evolclaw/INDEX.md +3 -1
  74. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  75. package/kits/docs/evolclaw/fs.md +131 -0
  76. package/kits/docs/evolclaw/group-fs.md +209 -0
  77. package/kits/docs/evolclaw/stats.md +70 -0
  78. package/kits/docs/venues/aun-group.md +29 -6
  79. package/kits/docs/venues/group.md +5 -4
  80. package/kits/eck_manifest.json +12 -0
  81. package/kits/eck_message_manifest.json +30 -3
  82. package/kits/rules/05-venue.md +1 -1
  83. package/kits/templates/message-fragments/inject-default.md +2 -0
  84. package/kits/templates/message-fragments/item.md +1 -1
  85. package/kits/templates/system-fragments/response-depth.md +16 -0
  86. package/package.json +4 -4
  87. package/dist/agents/baseagent-normalize.js +0 -19
  88. package/dist/core/relation/peer-key.js +0 -16
  89. package/dist/utils/channel-helpers.js +0 -46
@@ -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
- export function loadProcessConfig() {
126
- const raw = atomicReadJson(resolvePaths().processConfig);
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
- return expandEnvRefs(raw);
130
- }
131
- export function saveProcessConfig(value) {
132
- atomicWriteJson(resolvePaths().processConfig, value);
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
- /** 将绝对路径编码为 Claude Code 的目录名格式(/ \ . 替换为 -) */
574
- function encodePath(p) {
575
- return p.replace(/[/\\\.]/g, '-');
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
- * Channel Loader
11
- *
12
- * Manages channel plugin registration and lifecycle.
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
- * 新结构入口:从 EvolAgent channels[] 列表创建 channel 实例。
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 rewrittenChannels = {};
33
- // AUN channel 从 agent.aid 隐式创建——不需要在 channels[] 里显式声明
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
- rewrittenChannels['aun'] = [{
36
- type: 'aun',
37
- name: aunEffName,
38
- aid: agent.aid,
39
- enabled: true,
40
- agentName: agent.aid,
41
- // agent 顶层 owners[0] 透传给 AUN channel.owner(用于首次连接发欢迎消息)
42
- owner: agent.config.owners?.[0],
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; // 跳过显式声明的 AUN(已隐式处理)
61
+ continue;
48
62
  const effName = agent.effectiveChannelName(inst.type, inst.name);
49
- const rewritten = { ...inst, name: effName, agentName: agent.aid };
50
- (rewrittenChannels[inst.type] ??= []).push(rewritten);
63
+ configInsts.push({ ...inst, name: effName });
51
64
  }
52
- // syntheticConfig 是老 Config schema(channel plugin 沿用旧接口),
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
- async createAll(config) {
68
- const instances = [];
69
- for (const [name, plugin] of this.plugins) {
70
- if (!plugin.isEnabled(config)) {
71
- logger.info(`Channel '${name}' is disabled, skipping`);
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
- if (plugin.createChannels) {
76
- const channelInstances = await plugin.createChannels(config);
77
- instances.push(...channelInstances);
78
- logger.info(`✓ Channel '${name}' created ${channelInstances.length} instance(s)`);
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
- const instance = await plugin.createChannel(config);
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 (error) {
87
- logger.error(`✗ Failed to create channel '${name}':`, error);
86
+ catch (err) {
87
+ logger.error(`✗ Failed to create channel '${inst.name}' (${inst.type}):`, err);
88
88
  }
89
89
  }
90
- return instances;
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((inst) => inst.disconnect()));
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
- const channels = agent.config.channels;
201
- let channelType = null;
202
- for (const [type, raw] of Object.entries(channels)) {
203
- const instances = Array.isArray(raw) ? raw : [raw];
204
- for (const inst of instances) {
205
- const name = inst.name ?? type;
206
- if (name === channelName) {
207
- channelType = type;
208
- break;
209
- }
210
- }
211
- if (channelType)
212
- break;
213
- }
214
- if (!channelType) {
215
- const msg = `[Reload] Channel ${channelName} not found in agent ${agent.name} config`;
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 partialConfig = {
220
- agents: agent.config.agents,
221
- channels: { [channelType]: channels[channelType] },
222
- projects: agent.config.projects,
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 newInstances = await channelLoader.createAll(partialConfig);
225
- const newInst = newInstances.find(i => i.adapter.channelName === channelName);
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] Failed to create instance ${channelName}`);
229
+ throw new Error(`[Reload] createInstance returned null for ${channelName}`);
228
230
  registerChannelInstance(newInst);
229
231
  await newInst.connect();
230
232
  channelInstances.push(newInst);