evolclaw 2.8.3 → 3.0.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 (105) hide show
  1. package/README.md +21 -12
  2. package/dist/agents/claude-runner.js +102 -38
  3. package/dist/agents/codex-runner.js +11 -14
  4. package/dist/agents/gemini-runner.js +10 -12
  5. package/dist/agents/resolve.js +134 -0
  6. package/dist/agents/templates.js +3 -3
  7. package/dist/aun/aid/agentmd.js +186 -0
  8. package/dist/aun/aid/client.js +134 -0
  9. package/dist/aun/aid/identity.js +131 -0
  10. package/dist/aun/aid/index.js +3 -0
  11. package/dist/aun/aid/types.js +1 -0
  12. package/dist/aun/aid/validation.js +21 -0
  13. package/dist/aun/msg/group.js +291 -0
  14. package/dist/aun/msg/index.js +4 -0
  15. package/dist/aun/msg/p2p.js +144 -0
  16. package/dist/aun/msg/payload-type.js +27 -0
  17. package/dist/aun/msg/upload.js +98 -0
  18. package/dist/aun/outbox.js +138 -0
  19. package/dist/aun/rpc/caller.js +42 -0
  20. package/dist/aun/rpc/connection.js +34 -0
  21. package/dist/aun/rpc/index.js +2 -0
  22. package/dist/aun/storage/download.js +29 -0
  23. package/dist/aun/storage/index.js +3 -0
  24. package/dist/aun/storage/manage.js +10 -0
  25. package/dist/aun/storage/upload.js +35 -0
  26. package/dist/channels/aun.js +1051 -288
  27. package/dist/channels/dingtalk.js +58 -5
  28. package/dist/channels/feishu.js +266 -30
  29. package/dist/channels/qqbot.js +67 -12
  30. package/dist/channels/wechat.js +61 -4
  31. package/dist/channels/wecom.js +58 -5
  32. package/dist/cli/agent.js +800 -0
  33. package/dist/cli/index.js +4253 -0
  34. package/dist/{utils → cli}/init-channel.js +211 -621
  35. package/dist/cli/init.js +178 -0
  36. package/dist/config-store.js +613 -0
  37. package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
  38. package/dist/core/channel-loader.js +162 -11
  39. package/dist/core/command-handler.js +858 -847
  40. package/dist/core/evolagent-registry.js +191 -371
  41. package/dist/core/evolagent.js +203 -234
  42. package/dist/core/interaction-router.js +52 -5
  43. package/dist/core/message/im-renderer.js +480 -0
  44. package/dist/core/message/items-formatter.js +61 -0
  45. package/dist/core/message/message-bridge.js +104 -56
  46. package/dist/core/message/message-log.js +91 -0
  47. package/dist/core/message/message-processor.js +309 -142
  48. package/dist/core/message/message-queue.js +3 -3
  49. package/dist/core/permission.js +21 -8
  50. package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
  51. package/dist/core/session/session-fs-store.js +230 -0
  52. package/dist/core/session/session-manager.js +704 -775
  53. package/dist/core/session/session-mapper.js +87 -0
  54. package/dist/core/trigger/manager.js +122 -0
  55. package/dist/core/trigger/parser.js +128 -0
  56. package/dist/core/trigger/scheduler.js +224 -0
  57. package/dist/{templates → data}/prompts.md +34 -1
  58. package/dist/index.js +431 -275
  59. package/dist/ipc.js +49 -0
  60. package/dist/paths.js +82 -9
  61. package/dist/types.js +8 -2
  62. package/dist/utils/atomic-write.js +79 -0
  63. package/dist/utils/channel-helpers.js +46 -0
  64. package/dist/utils/cross-platform.js +0 -18
  65. package/dist/utils/instance-registry.js +433 -0
  66. package/dist/utils/log-writer.js +216 -0
  67. package/dist/utils/logger.js +24 -77
  68. package/dist/utils/media-cache.js +23 -0
  69. package/dist/utils/{upgrade.js → npm-ops.js} +52 -21
  70. package/dist/utils/process-introspect.js +144 -0
  71. package/dist/utils/stats.js +192 -0
  72. package/dist/watch-msg.js +529 -0
  73. package/evolclaw-install-aun.md +114 -46
  74. package/kits/aun/meta.md +25 -0
  75. package/kits/aun/role.md +25 -0
  76. package/kits/channels/aun.md +25 -0
  77. package/kits/evolclaw/commands.md +31 -0
  78. package/kits/evolclaw/identity-tools.md +26 -0
  79. package/kits/evolclaw/self-summary.md +29 -0
  80. package/kits/evolclaw/tools.md +25 -0
  81. package/kits/templates/group.md +20 -0
  82. package/kits/templates/private.md +9 -0
  83. package/kits/templates/system-fragments/personal-context.md +3 -0
  84. package/kits/templates/system-fragments/self-intro.md +5 -0
  85. package/kits/templates/system-fragments/speaker-intro.md +5 -0
  86. package/kits/templates/system-fragments/venue-intro.md +5 -0
  87. package/package.json +7 -5
  88. package/data/evolclaw.sample.json +0 -60
  89. package/dist/channels/aun-ops.js +0 -275
  90. package/dist/cli.js +0 -2178
  91. package/dist/config.js +0 -591
  92. package/dist/core/agent-registry.js +0 -450
  93. package/dist/core/evolagent-schema.js +0 -72
  94. package/dist/core/message/stream-flusher.js +0 -238
  95. package/dist/core/message/thought-emitter.js +0 -162
  96. package/dist/core/reload-hooks.js +0 -87
  97. package/dist/prompts/templates.js +0 -122
  98. package/dist/templates/skills.md +0 -66
  99. package/dist/utils/channel-fingerprint.js +0 -59
  100. package/dist/utils/error-dict.js +0 -63
  101. package/dist/utils/format.js +0 -32
  102. package/dist/utils/init.js +0 -645
  103. package/dist/utils/migrate-project.js +0 -122
  104. package/dist/utils/reload-hooks.js +0 -87
  105. package/dist/utils/stats-collector.js +0 -99
@@ -0,0 +1,800 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import readline from 'readline';
5
+ import { resolvePaths } from '../paths.js';
6
+ import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
7
+ import { ipcQuery } from '../ipc.js';
8
+ import { CONFIG_SCHEMA_VERSION } from '../types.js';
9
+ import { isValidChannelName } from '../core/channel-loader.js';
10
+ import { commandExists } from '../utils/cross-platform.js';
11
+ // ==================== Helpers ====================
12
+ const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
13
+ const BASEAGENT_ENV_KEY = {
14
+ claude: 'ANTHROPIC_API_KEY',
15
+ codex: 'OPENAI_API_KEY',
16
+ gemini: 'GEMINI_API_KEY',
17
+ };
18
+ function detectAvailableBaseagents() {
19
+ return BASEAGENT_CANDIDATES.filter(b => commandExists(b));
20
+ }
21
+ function pickDefaultBaseagent(available) {
22
+ if (available.length === 0)
23
+ return null;
24
+ return available.includes('claude') ? 'claude' : available[0];
25
+ }
26
+ function buildBaseagentsBlock(chosen) {
27
+ const env = BASEAGENT_ENV_KEY[chosen];
28
+ return { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} };
29
+ }
30
+ const DEFAULT_CHATMODE = { private: 'interactive', group: 'proactive', nothuman: 'proactive' };
31
+ const DEFAULT_DISPATCH = 'mention';
32
+ function deriveAgentProjectPath(rootPath, aid) {
33
+ const baseName = aid.split('.')[0];
34
+ let candidate = path.join(rootPath, baseName);
35
+ if (!fs.existsSync(candidate))
36
+ return candidate;
37
+ let i = 1;
38
+ while (fs.existsSync(`${candidate}~${i}`))
39
+ i++;
40
+ return `${candidate}~${i}`;
41
+ }
42
+ function readAgentMdIdentity(aid) {
43
+ const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
44
+ const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
45
+ try {
46
+ if (!fs.existsSync(agentMdPath))
47
+ return { name: null, description: null };
48
+ const content = fs.readFileSync(agentMdPath, 'utf-8');
49
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
50
+ if (!fmMatch)
51
+ return { name: null, description: null };
52
+ const fm = fmMatch[1];
53
+ const nameMatch = fm.match(/^name:\s*["']?(.+?)["']?\s*$/m);
54
+ const descMatch = fm.match(/^description:\s*["']?(.+?)["']?\s*$/m);
55
+ return {
56
+ name: nameMatch?.[1]?.trim() || null,
57
+ description: descMatch?.[1]?.trim() || null,
58
+ };
59
+ }
60
+ catch {
61
+ return { name: null, description: null };
62
+ }
63
+ }
64
+ function getAgentMdPath(aid) {
65
+ const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
66
+ return path.join(aunPath, 'AIDs', aid, 'agent.md');
67
+ }
68
+ function getNestedValue(obj, keyPath) {
69
+ const keys = keyPath.split('.');
70
+ let cur = obj;
71
+ for (const k of keys) {
72
+ if (cur == null || typeof cur !== 'object')
73
+ return undefined;
74
+ cur = cur[k];
75
+ }
76
+ return cur;
77
+ }
78
+ function setNestedValue(obj, keyPath, value) {
79
+ const keys = keyPath.split('.');
80
+ let cur = obj;
81
+ for (let i = 0; i < keys.length - 1; i++) {
82
+ if (cur[keys[i]] == null || typeof cur[keys[i]] !== 'object') {
83
+ cur[keys[i]] = {};
84
+ }
85
+ cur = cur[keys[i]];
86
+ }
87
+ cur[keys[keys.length - 1]] = value;
88
+ }
89
+ function parseJsonValue(raw) {
90
+ if (raw === 'true')
91
+ return true;
92
+ if (raw === 'false')
93
+ return false;
94
+ if (raw === 'null')
95
+ return null;
96
+ if (/^-?\d+(\.\d+)?$/.test(raw))
97
+ return Number(raw);
98
+ try {
99
+ return JSON.parse(raw);
100
+ }
101
+ catch { }
102
+ return raw;
103
+ }
104
+ /** Normalize path separators to forward slashes for cross-platform consistent display. */
105
+ function toPosix(p) {
106
+ if (!p)
107
+ return null;
108
+ return p.replace(/\\/g, '/');
109
+ }
110
+ // ==================== agentList ====================
111
+ export async function agentList() {
112
+ const p = resolvePaths();
113
+ // Try IPC first (running process has real status)
114
+ try {
115
+ const result = await ipcQuery(p.socket, { type: 'evolagent.list' });
116
+ if (result && result.ok && result.agents) {
117
+ const agents = result.agents.map((info) => ({
118
+ aid: info.aid || info.name,
119
+ name: info.name,
120
+ status: info.status || 'stopped',
121
+ channels: info.channels || [],
122
+ projectPath: toPosix(info.projectPath || null),
123
+ baseagent: info.baseagent || null,
124
+ lastActivity: info.lastActivity || null,
125
+ }));
126
+ return { ok: true, agents };
127
+ }
128
+ }
129
+ catch { }
130
+ // Cold mode: read from disk
131
+ const { EvolAgentRegistry } = await import('../core/evolagent-registry.js');
132
+ const registry = new EvolAgentRegistry(p.agentsDir);
133
+ registry.loadAll();
134
+ const agents = registry.list().map((info) => ({
135
+ aid: info.aid || info.name,
136
+ name: info.name,
137
+ status: info.status || 'stopped',
138
+ channels: info.channels || [],
139
+ projectPath: toPosix(info.projectPath || null),
140
+ baseagent: info.baseagent || null,
141
+ lastActivity: info.lastActivity || null,
142
+ }));
143
+ return { ok: true, agents };
144
+ }
145
+ // ==================== agentShow ====================
146
+ export async function agentShow(aid) {
147
+ const p = resolvePaths();
148
+ let agentInfo = null;
149
+ let connectionInfo = null;
150
+ // Try IPC for live data
151
+ try {
152
+ const [showResp, aidsResp, statsResp] = await Promise.all([
153
+ ipcQuery(p.socket, { type: 'evolagent.show', name: aid }),
154
+ ipcQuery(p.socket, { type: 'aun-aids' }),
155
+ ipcQuery(p.socket, { type: 'aun-aid-stats' }),
156
+ ]);
157
+ if (showResp?.ok && showResp.agent) {
158
+ agentInfo = showResp.agent;
159
+ }
160
+ // Build connection info from aun-aids + aun-aid-stats (same source as watch aid)
161
+ if (aidsResp?.ok && aidsResp.aids) {
162
+ const aidEntry = aidsResp.aids.find((a) => a.aid === aid);
163
+ if (aidEntry) {
164
+ const stats = statsResp?.ok && statsResp.stats
165
+ ? statsResp.stats.find((s) => s.aid === aid)
166
+ : null;
167
+ const now = Date.now();
168
+ connectionInfo = {
169
+ status: aidEntry.status || 'unknown',
170
+ uptime_ms: (aidEntry.status === 'connected' && aidEntry.lastConnectedAt)
171
+ ? now - aidEntry.lastConnectedAt : null,
172
+ reconnect_count: aidEntry.reconnectCount ?? 0,
173
+ messages_received: stats?.messagesReceived ?? 0,
174
+ messages_sent: stats?.messagesSent ?? 0,
175
+ bytes_received: stats?.bytesReceived ?? 0,
176
+ bytes_sent: stats?.bytesSent ?? 0,
177
+ last_received_at: stats?.lastReceivedAt ? new Date(stats.lastReceivedAt).toISOString() : null,
178
+ last_sent_at: stats?.lastSentAt ? new Date(stats.lastSentAt).toISOString() : null,
179
+ unique_peer_count: stats?.uniquePeerCount ?? 0,
180
+ };
181
+ }
182
+ }
183
+ }
184
+ catch { }
185
+ // Cold mode fallback for agent info
186
+ if (!agentInfo) {
187
+ const { EvolAgentRegistry } = await import('../core/evolagent-registry.js');
188
+ const registry = new EvolAgentRegistry(p.agentsDir);
189
+ registry.loadAll();
190
+ const agent = registry.get(aid);
191
+ if (!agent) {
192
+ const allList = registry.list();
193
+ const available = allList.map((i) => i.name).join(', ');
194
+ return { ok: false, error: `Agent "${aid}" not found.${available ? ` Available: ${available}` : ''}` };
195
+ }
196
+ agentInfo = {
197
+ name: agent.name,
198
+ aid: agent.aid || agent.name,
199
+ status: agent.status,
200
+ baseagent: agent.baseagent,
201
+ model: agent.model,
202
+ effort: agent.effort,
203
+ projectPath: agent.projectPath,
204
+ channels: agent.channelInstanceNames?.() || [],
205
+ activeSessions: agent.activeSessions || 0,
206
+ lastActivity: agent.lastActivity || null,
207
+ error: agent.error,
208
+ chatmode: agent.config?.chatmode || null,
209
+ owners: agent.config?.owners || [],
210
+ };
211
+ }
212
+ const identity = readAgentMdIdentity(aid);
213
+ const agentMdPath = getAgentMdPath(aid);
214
+ return {
215
+ ok: true,
216
+ aid,
217
+ status: agentInfo.status || 'stopped',
218
+ identity,
219
+ config: {
220
+ baseagent: agentInfo.baseagent || null,
221
+ model: agentInfo.model || null,
222
+ effort: agentInfo.effort || null,
223
+ chatmode: agentInfo.chatmode || null,
224
+ owners: agentInfo.owners || [],
225
+ channels: agentInfo.channels || [],
226
+ },
227
+ connection: connectionInfo,
228
+ sessions: {
229
+ active: agentInfo.activeSessions || 0,
230
+ last_activity: agentInfo.lastActivity ? new Date(agentInfo.lastActivity).toISOString() : null,
231
+ },
232
+ paths: {
233
+ config: toPosix(path.join(p.agentsDir, aid, 'config.json')),
234
+ agent_md: toPosix(agentMdPath),
235
+ project: toPosix(agentInfo.projectPath || null),
236
+ data: toPosix(path.join(p.agentsDir, aid, 'data')),
237
+ },
238
+ };
239
+ }
240
+ export async function agentCreateInteractive(opts = {}) {
241
+ const p = resolvePaths();
242
+ const rl = readline.createInterface({
243
+ input: opts.stdin || process.stdin,
244
+ output: opts.stdout || process.stdout,
245
+ });
246
+ const ask = (q) => new Promise(r => rl.question(q, r));
247
+ try {
248
+ const aidPrompt = opts.suggestedName
249
+ ? `AID [${opts.suggestedName}]: `
250
+ : 'AID (e.g. mybot.agentid.pub): ';
251
+ const aidInput = (await ask(aidPrompt)).trim();
252
+ const aid = aidInput || opts.suggestedName;
253
+ if (!aid)
254
+ return { ok: false, error: 'AID is required.' };
255
+ const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
256
+ if (!isValidAid(aid)) {
257
+ return { ok: false, error: `Invalid AID "${aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)` };
258
+ }
259
+ const agentDirPath = path.join(p.agentsDir, aid);
260
+ const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
261
+ if (configExists) {
262
+ const ans = (await ask(`Agent "${aid}" already exists. Overwrite config.json? (AID 与 agent.md 保留) [y/N]: `)).trim().toLowerCase();
263
+ if (ans !== 'y' && ans !== 'yes') {
264
+ return { ok: false, error: 'Aborted.' };
265
+ }
266
+ }
267
+ console.log(`\nCreating agent: ${aid}\n`);
268
+ let aidCreated = false;
269
+ try {
270
+ const result = await aidCreate(aid);
271
+ try {
272
+ await result.client.close();
273
+ }
274
+ catch { /* ignore */ }
275
+ aidCreated = !result.alreadyExisted;
276
+ console.log(` ✓ AID ${result.alreadyExisted ? 'reused' : 'created'}: ${aid}`);
277
+ }
278
+ catch (e) {
279
+ return { ok: false, error: `AID creation failed: ${e?.message || e}` };
280
+ }
281
+ // Project path
282
+ let suggestedProjectPath = '';
283
+ try {
284
+ const defaults = loadDefaults();
285
+ const rootPath = defaults?.projects?.rootPath
286
+ || (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
287
+ || path.join(os.homedir(), 'evolclaw-projects');
288
+ suggestedProjectPath = deriveAgentProjectPath(rootPath, aid);
289
+ }
290
+ catch {
291
+ suggestedProjectPath = deriveAgentProjectPath(path.join(os.homedir(), 'evolclaw-projects'), aid);
292
+ }
293
+ const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
294
+ const projectPath = projectInput || suggestedProjectPath;
295
+ if (!path.isAbsolute(projectPath)) {
296
+ return { ok: false, error: 'Project path must be an absolute path.' };
297
+ }
298
+ if (!fs.existsSync(projectPath)) {
299
+ const create = (await ask(`Project path does not exist. Create? [Y/n]: `)).trim().toLowerCase();
300
+ if (create === '' || create === 'y' || create === 'yes') {
301
+ fs.mkdirSync(projectPath, { recursive: true });
302
+ console.log(` ✓ Created ${projectPath}`);
303
+ }
304
+ else {
305
+ return { ok: false, error: 'Aborted.' };
306
+ }
307
+ }
308
+ // Baseagent
309
+ const available = detectAvailableBaseagents();
310
+ if (available.length === 0) {
311
+ return { ok: false, error: `No baseagent CLI detected on PATH. Install one of: ${BASEAGENT_CANDIDATES.join('/')}` };
312
+ }
313
+ const defaultBa = pickDefaultBaseagent(available);
314
+ let baseagent = null;
315
+ while (baseagent === null) {
316
+ const input = (await ask(`Baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
317
+ if (!BASEAGENT_CANDIDATES.includes(input)) {
318
+ console.log(` Invalid choice. Options: ${BASEAGENT_CANDIDATES.join('/')}`);
319
+ continue;
320
+ }
321
+ if (!available.includes(input)) {
322
+ console.log(` ${input} not detected on PATH. Available: ${available.join('/')}`);
323
+ continue;
324
+ }
325
+ baseagent = input;
326
+ }
327
+ // Owner
328
+ const owner = (await ask('Owner AID (leave empty for auto-bind on first message): ')).trim() || undefined;
329
+ // Name + description for agent.md
330
+ const defaultName = aid.split('.')[0];
331
+ const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
332
+ const agentDescription = (await ask('Description (optional): ')).trim() || '';
333
+ rl.close();
334
+ const agentConfig = {
335
+ $schema_version: CONFIG_SCHEMA_VERSION,
336
+ aid,
337
+ enabled: true,
338
+ initialized: false,
339
+ owners: owner ? [owner] : [],
340
+ channels: [],
341
+ active_baseagent: baseagent,
342
+ baseagents: buildBaseagentsBlock(baseagent),
343
+ projects: { defaultPath: projectPath },
344
+ chatmode: { ...DEFAULT_CHATMODE },
345
+ dispatch: DEFAULT_DISPATCH,
346
+ };
347
+ saveAgent(agentConfig);
348
+ ensureAgentDirSkeleton(aid);
349
+ // Generate and upload agent.md
350
+ let agentmdUploaded = false;
351
+ try {
352
+ const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
353
+ let content = buildInitialAgentMd({ aid });
354
+ content = content.replace(/^name:\s*".*?"$/m, `name: "${agentName}"`);
355
+ if (agentDescription) {
356
+ content = content.replace(/^description:\s*".*?"$/m, `description: "${agentDescription}"`);
357
+ }
358
+ const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
359
+ const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
360
+ fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
361
+ fs.writeFileSync(agentMdPath, content, 'utf-8');
362
+ try {
363
+ await agentmdPut(content, { aid, aunPath });
364
+ agentmdUploaded = true;
365
+ }
366
+ catch (e) {
367
+ console.warn(` ⚠ agent.md upload failed: ${e?.message || e}`);
368
+ console.warn(` → Retry later with: evolclaw aid agentmd put ${aid}`);
369
+ }
370
+ }
371
+ catch (e) {
372
+ console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
373
+ }
374
+ return {
375
+ ok: true,
376
+ aid,
377
+ configPath: toPosix(path.join(agentDirPath, 'config.json')),
378
+ aidCreated,
379
+ agentmdUploaded,
380
+ };
381
+ }
382
+ finally {
383
+ try {
384
+ rl.close();
385
+ }
386
+ catch { /* ignore */ }
387
+ }
388
+ }
389
+ export async function agentCreateNonInteractive(opts) {
390
+ const p = resolvePaths();
391
+ const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
392
+ if (!isValidAid(opts.aid)) {
393
+ return { ok: false, error: `Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)` };
394
+ }
395
+ const agentDirPath = path.join(p.agentsDir, opts.aid);
396
+ const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
397
+ if (configExists && !opts.force) {
398
+ return { ok: false, error: `Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)` };
399
+ }
400
+ // Baseagent
401
+ const available = detectAvailableBaseagents();
402
+ if (available.length === 0) {
403
+ return { ok: false, error: `No baseagent CLI detected on PATH. Install one of: ${BASEAGENT_CANDIDATES.join('/')}` };
404
+ }
405
+ let baseagent;
406
+ if (opts.baseagent) {
407
+ if (!BASEAGENT_CANDIDATES.includes(opts.baseagent)) {
408
+ return { ok: false, error: `Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})` };
409
+ }
410
+ if (!available.includes(opts.baseagent)) {
411
+ return { ok: false, error: `${opts.baseagent} not detected on PATH (available: ${available.join('/')})` };
412
+ }
413
+ baseagent = opts.baseagent;
414
+ }
415
+ else {
416
+ baseagent = pickDefaultBaseagent(available);
417
+ }
418
+ if (!path.isAbsolute(opts.project)) {
419
+ return { ok: false, error: `--project must be absolute: ${opts.project}` };
420
+ }
421
+ if (!fs.existsSync(opts.project)) {
422
+ try {
423
+ fs.mkdirSync(opts.project, { recursive: true });
424
+ }
425
+ catch (e) {
426
+ return { ok: false, error: `Failed to create ${opts.project}: ${e?.message || e}` };
427
+ }
428
+ }
429
+ if (opts.owner && !isValidAid(opts.owner)) {
430
+ return { ok: false, error: `Invalid owner: ${opts.owner}` };
431
+ }
432
+ // Register AID
433
+ let aidCreated = false;
434
+ try {
435
+ const result = await aidCreate(opts.aid);
436
+ try {
437
+ await result.client.close();
438
+ }
439
+ catch { /* ignore */ }
440
+ aidCreated = !result.alreadyExisted;
441
+ }
442
+ catch (e) {
443
+ return { ok: false, error: `AID creation failed: ${e?.message || e}` };
444
+ }
445
+ // Force 模式下若 agent 已存在且已 initialized,保留该状态(避免重复发欢迎)
446
+ let preservedInitialized = false;
447
+ if (configExists) {
448
+ try {
449
+ const existing = loadAgent(opts.aid);
450
+ if (existing?.initialized === true)
451
+ preservedInitialized = true;
452
+ }
453
+ catch { /* ignore */ }
454
+ }
455
+ const agentConfig = {
456
+ $schema_version: CONFIG_SCHEMA_VERSION,
457
+ aid: opts.aid,
458
+ enabled: true,
459
+ initialized: preservedInitialized,
460
+ owners: opts.owner ? [opts.owner] : [],
461
+ channels: [],
462
+ active_baseagent: baseagent,
463
+ baseagents: buildBaseagentsBlock(baseagent),
464
+ projects: { defaultPath: opts.project },
465
+ chatmode: { ...DEFAULT_CHATMODE },
466
+ dispatch: DEFAULT_DISPATCH,
467
+ };
468
+ saveAgent(agentConfig);
469
+ ensureAgentDirSkeleton(opts.aid);
470
+ // Generate and upload agent.md
471
+ let agentmdUploaded = false;
472
+ try {
473
+ const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
474
+ const agentName = opts.name || opts.aid.split('.')[0];
475
+ const agentDescription = opts.description || '';
476
+ let content = buildInitialAgentMd({ aid: opts.aid });
477
+ content = content.replace(/^name:\s*".*?"$/m, `name: "${agentName}"`);
478
+ if (agentDescription) {
479
+ content = content.replace(/^description:\s*".*?"$/m, `description: "${agentDescription}"`);
480
+ }
481
+ const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
482
+ const agentMdPath = path.join(aunPath, 'AIDs', opts.aid, 'agent.md');
483
+ fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
484
+ fs.writeFileSync(agentMdPath, content, 'utf-8');
485
+ try {
486
+ await agentmdPut(content, { aid: opts.aid, aunPath });
487
+ agentmdUploaded = true;
488
+ }
489
+ catch (e) {
490
+ console.warn(`⚠ agent.md upload failed: ${e?.message || e}`);
491
+ console.warn(` Retry later with: evolclaw aid agentmd put ${opts.aid}`);
492
+ }
493
+ }
494
+ catch (e) {
495
+ console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
496
+ }
497
+ return {
498
+ ok: true,
499
+ aid: opts.aid,
500
+ configPath: toPosix(path.join(agentDirPath, 'config.json')),
501
+ aidCreated,
502
+ agentmdUploaded,
503
+ };
504
+ }
505
+ // ==================== agentSyncAids ====================
506
+ export async function agentSyncAids() {
507
+ const p = resolvePaths();
508
+ const { aidList } = await import('../aun/aid/index.js');
509
+ const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
510
+ const allAids = aidList(aunPath);
511
+ const localAids = allAids.filter(a => a.hasPrivateKey).map(a => a.aid);
512
+ if (localAids.length === 0) {
513
+ return { ok: true, created: [], template: null, hotReloaded: false };
514
+ }
515
+ const { agents } = loadAllAgents();
516
+ const existingAids = new Set(agents.map(a => a.aid));
517
+ // Find template (earliest mtime)
518
+ let templateAgent = agents[0] || null;
519
+ if (agents.length > 1) {
520
+ let earliestMtime = Infinity;
521
+ for (const a of agents) {
522
+ const configPath = path.join(p.agentsDir, a.aid, 'config.json');
523
+ try {
524
+ const stat = fs.statSync(configPath);
525
+ if (stat.mtimeMs < earliestMtime) {
526
+ earliestMtime = stat.mtimeMs;
527
+ templateAgent = a;
528
+ }
529
+ }
530
+ catch { }
531
+ }
532
+ }
533
+ if (!templateAgent) {
534
+ return { ok: false, error: '没有可用的模板 agent。请先创建第一个 agent:evolclaw agent new <aid>' };
535
+ }
536
+ const defaults = loadDefaults();
537
+ const rootPath = defaults?.projects?.rootPath
538
+ || (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
539
+ || path.join(os.homedir(), 'evolclaw-projects');
540
+ const created = [];
541
+ for (const aid of localAids) {
542
+ if (existingAids.has(aid))
543
+ continue;
544
+ const projectPath = deriveAgentProjectPath(rootPath, aid);
545
+ const newConfig = {
546
+ ...JSON.parse(JSON.stringify(templateAgent)),
547
+ aid,
548
+ channels: [],
549
+ projects: { defaultPath: projectPath },
550
+ $schema_version: CONFIG_SCHEMA_VERSION,
551
+ };
552
+ try {
553
+ saveAgent(newConfig);
554
+ ensureAgentDirSkeleton(aid);
555
+ created.push(aid);
556
+ }
557
+ catch { }
558
+ }
559
+ // Hot-reload if running
560
+ let hotReloaded = false;
561
+ if (created.length > 0) {
562
+ try {
563
+ const result = await ipcQuery(p.socket, { type: 'evolagent.resync' });
564
+ hotReloaded = !!result?.ok;
565
+ }
566
+ catch { }
567
+ }
568
+ return { ok: true, created, template: templateAgent.aid, hotReloaded };
569
+ }
570
+ // ==================== agentReload ====================
571
+ export async function agentReload(aid) {
572
+ const p = resolvePaths();
573
+ if (!aid) {
574
+ // Full resync
575
+ try {
576
+ const result = await ipcQuery(p.socket, { type: 'evolagent.resync' });
577
+ if (result === null) {
578
+ return { ok: false, error: 'evolclaw 未运行,请先 evolclaw start' };
579
+ }
580
+ if (result?.ok) {
581
+ return { ok: true, results: result.results || [] };
582
+ }
583
+ return { ok: false, error: result?.error || 'unknown error' };
584
+ }
585
+ catch {
586
+ return { ok: false, error: 'evolclaw 未运行,请先 evolclaw start' };
587
+ }
588
+ }
589
+ // Single agent reload
590
+ try {
591
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid });
592
+ if (result === null) {
593
+ return { ok: false, error: 'evolclaw 未运行,请先 evolclaw start 后再 reload' };
594
+ }
595
+ if (result?.ok) {
596
+ return { ok: true };
597
+ }
598
+ return { ok: false, error: result?.error || 'unknown error' };
599
+ }
600
+ catch {
601
+ return { ok: false, error: 'evolclaw 未运行,请先 evolclaw start 后再 reload' };
602
+ }
603
+ }
604
+ // ==================== agentEnable / agentDisable ====================
605
+ export async function agentEnable(aid) {
606
+ return agentSetEnabled(aid, true);
607
+ }
608
+ export async function agentDisable(aid) {
609
+ return agentSetEnabled(aid, false);
610
+ }
611
+ async function agentSetEnabled(aid, enabled) {
612
+ const p = resolvePaths();
613
+ const configPath = path.join(p.agentsDir, aid, 'config.json');
614
+ if (!fs.existsSync(configPath)) {
615
+ return { ok: false, error: `Agent "${aid}" not found` };
616
+ }
617
+ let config;
618
+ try {
619
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
620
+ }
621
+ catch (e) {
622
+ return { ok: false, error: `Failed to read config: ${e?.message || e}` };
623
+ }
624
+ config.enabled = enabled;
625
+ saveAgent(config);
626
+ // Try hot-reload
627
+ let reloaded = false;
628
+ try {
629
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid });
630
+ reloaded = !!result?.ok;
631
+ }
632
+ catch { }
633
+ return { ok: true, aid, enabled, reloaded };
634
+ }
635
+ // ==================== agentGet ====================
636
+ export async function agentGet(aid, key) {
637
+ const p = resolvePaths();
638
+ const configPath = path.join(p.agentsDir, aid, 'config.json');
639
+ if (!fs.existsSync(configPath)) {
640
+ return { ok: false, error: `Agent "${aid}" not found` };
641
+ }
642
+ let config;
643
+ try {
644
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
645
+ }
646
+ catch (e) {
647
+ return { ok: false, error: `Failed to read config: ${e?.message || e}` };
648
+ }
649
+ const value = getNestedValue(config, key);
650
+ return { ok: true, aid, key, value };
651
+ }
652
+ // ==================== agentSet ====================
653
+ export async function agentSet(aid, key, rawValue) {
654
+ const p = resolvePaths();
655
+ const configPath = path.join(p.agentsDir, aid, 'config.json');
656
+ if (!fs.existsSync(configPath)) {
657
+ return { ok: false, error: `Agent "${aid}" not found` };
658
+ }
659
+ let config;
660
+ try {
661
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
662
+ }
663
+ catch (e) {
664
+ return { ok: false, error: `Failed to read config: ${e?.message || e}` };
665
+ }
666
+ const value = parseJsonValue(rawValue);
667
+ setNestedValue(config, key, value);
668
+ saveAgent(config);
669
+ // Try hot-reload
670
+ let reloaded = false;
671
+ try {
672
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid });
673
+ reloaded = !!result?.ok;
674
+ }
675
+ catch { }
676
+ return { ok: true, aid, key, value, reloaded };
677
+ }
678
+ /**
679
+ * Add or overwrite a channel instance on a per-agent config.
680
+ *
681
+ * - AUN type is rejected: AUN is implicit (managed by agent.aid + channel-loader),
682
+ * writing to channels[] has no effect.
683
+ * - mode='add' + existing (type, name) → error
684
+ * - mode='overwrite' + missing (type, name) → error
685
+ *
686
+ * Saves config atomically and triggers a hot-reload IPC. If the daemon is not
687
+ * running or reload fails, returns reloaded:false (no error).
688
+ */
689
+ export async function agentChannelUpsert(opts) {
690
+ const p = resolvePaths();
691
+ const SUPPORTED_TYPES = new Set(['feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom']);
692
+ if (opts.channel.type === 'aun') {
693
+ return { ok: false, error: 'AUN channel cannot be configured via channels[] (managed implicitly by agent.aid)' };
694
+ }
695
+ if (!SUPPORTED_TYPES.has(opts.channel.type)) {
696
+ return { ok: false, error: `Unsupported channel type: ${opts.channel.type} (allowed: ${[...SUPPORTED_TYPES].join('/')})` };
697
+ }
698
+ if (!isValidChannelName(opts.channel.name)) {
699
+ return { ok: false, error: `Invalid channel name: ${JSON.stringify(opts.channel.name)} (empty or contains '#')` };
700
+ }
701
+ let config;
702
+ try {
703
+ config = loadAgent(opts.aid);
704
+ }
705
+ catch (e) {
706
+ return { ok: false, error: `Failed to load agent config: ${e?.message || e}` };
707
+ }
708
+ if (!config) {
709
+ return { ok: false, error: `Agent "${opts.aid}" not found` };
710
+ }
711
+ const channels = config.channels || [];
712
+ const matchIdx = channels.findIndex(c => c.type === opts.channel.type && c.name === opts.channel.name);
713
+ if (opts.mode === 'add') {
714
+ if (matchIdx >= 0) {
715
+ return { ok: false, error: `Channel (${opts.channel.type}, ${opts.channel.name}) already exists; pick a different name or use overwrite` };
716
+ }
717
+ channels.push(opts.channel);
718
+ }
719
+ else {
720
+ if (matchIdx < 0) {
721
+ return { ok: false, error: `Channel (${opts.channel.type}, ${opts.channel.name}) not found` };
722
+ }
723
+ channels[matchIdx] = opts.channel;
724
+ }
725
+ config.channels = channels;
726
+ saveAgent(config);
727
+ let reloaded = false;
728
+ try {
729
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: opts.aid });
730
+ reloaded = !!result?.ok;
731
+ }
732
+ catch { /* daemon not running or reload failed */ }
733
+ return {
734
+ ok: true,
735
+ aid: opts.aid,
736
+ channelKey: `${opts.aid}#${opts.channel.type}#${opts.channel.name}`,
737
+ reloaded,
738
+ };
739
+ }
740
+ // ==================== agentDelete ====================
741
+ export async function agentDelete(aid, purge = false) {
742
+ const p = resolvePaths();
743
+ const agentDir = path.join(p.agentsDir, aid);
744
+ const configPath = path.join(agentDir, 'config.json');
745
+ if (!fs.existsSync(configPath)) {
746
+ return { ok: false, error: `Agent "${aid}" not found` };
747
+ }
748
+ // Try to stop via IPC first
749
+ let stopped = false;
750
+ try {
751
+ const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name: aid });
752
+ stopped = !!result?.ok;
753
+ }
754
+ catch { }
755
+ if (purge) {
756
+ fs.rmSync(agentDir, { recursive: true, force: true });
757
+ }
758
+ else {
759
+ fs.unlinkSync(configPath);
760
+ }
761
+ // Trigger resync so daemon drops the agent
762
+ try {
763
+ await ipcQuery(p.socket, { type: 'evolagent.resync' });
764
+ }
765
+ catch { }
766
+ return { ok: true, aid, purged: purge, stopped };
767
+ }
768
+ // ==================== agentRename ====================
769
+ export async function agentRename(aid, newName) {
770
+ const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
771
+ const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
772
+ if (!fs.existsSync(agentMdPath)) {
773
+ return { ok: false, error: `agent.md not found for ${aid}. Run: evolclaw aid agentmd put ${aid}` };
774
+ }
775
+ let content = fs.readFileSync(agentMdPath, 'utf-8');
776
+ const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
777
+ if (!fmMatch) {
778
+ return { ok: false, error: `agent.md has no valid frontmatter for ${aid}` };
779
+ }
780
+ const fm = fmMatch[2];
781
+ const nameRegex = /^name:\s*["']?.*?["']?\s*$/m;
782
+ let newFm;
783
+ if (nameRegex.test(fm)) {
784
+ newFm = fm.replace(nameRegex, `name: "${newName}"`);
785
+ }
786
+ else {
787
+ newFm = `name: "${newName}"\n${fm}`;
788
+ }
789
+ content = fmMatch[1] + newFm + fmMatch[3] + content.slice(fmMatch[0].length);
790
+ fs.writeFileSync(agentMdPath, content, 'utf-8');
791
+ // Upload
792
+ let uploaded = false;
793
+ try {
794
+ const { agentmdPut } = await import('../aun/aid/index.js');
795
+ await agentmdPut(content, { aid, aunPath });
796
+ uploaded = true;
797
+ }
798
+ catch { }
799
+ return { ok: true, aid, name: newName, uploaded };
800
+ }