evolclaw 2.7.3 → 2.8.1
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/dist/agents/claude-runner.js +91 -48
- package/dist/channels/aun-ops.js +275 -0
- package/dist/channels/aun.js +285 -125
- package/dist/cli.js +360 -1
- package/dist/config.js +1 -1
- package/dist/core/agent-registry.js +164 -0
- package/dist/core/command-handler.js +147 -97
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +66 -0
- package/dist/core/message/message-bridge.js +14 -2
- package/dist/core/message/message-processor.js +24 -10
- package/dist/core/message/thought-emitter.js +4 -2
- package/dist/core/session/session-manager.js +22 -3
- package/dist/index.js +8 -12
- package/dist/paths.js +2 -0
- package/dist/templates/prompts.md +1 -0
- package/dist/utils/init-channel.js +91 -221
- package/dist/utils/init.js +18 -42
- package/dist/utils/logger.js +58 -2
- package/evolclaw-install-aun.md +48 -7
- package/package.json +2 -2
|
@@ -316,11 +316,17 @@ export class MessageProcessor {
|
|
|
316
316
|
});
|
|
317
317
|
const imageInfo = message.images && message.images.length > 0 ? ` [${message.images.length} image(s)]` : '';
|
|
318
318
|
const modeInfo = isBackground ? ' [\u540e\u53f0]' : '';
|
|
319
|
-
|
|
320
|
-
logger.info(`[
|
|
319
|
+
const e2eeInfo = message.replyContext?.metadata?.encrypted != null ? ` encrypt=${message.replyContext.metadata.encrypted}` : '';
|
|
320
|
+
logger.info(`[${message.channel}] ${message.channelId}: ${message.content}${imageInfo}${modeInfo}${e2eeInfo}`);
|
|
321
|
+
// 构建 peer 标识(优先 peerName,退化到 peerId / channelId)
|
|
322
|
+
const peerName = session.metadata?.peerName ?? message.peerName;
|
|
323
|
+
const peerId = session.metadata?.peerId ?? message.peerId ?? message.channelId;
|
|
324
|
+
const peerShort = peerId ? peerId.split('.')[0].split(':')[0] : '?';
|
|
325
|
+
const peerLabel = peerName && peerName !== peerShort ? `${peerShort}(${peerName})` : peerShort;
|
|
326
|
+
logger.info(`[MessageProcessor] session=${session.id} task=${taskId} peer=${peerLabel} chatType=${session.chatType} sessionMode=${session.sessionMode} agentId=${session.agentId} msgChatType=${message.chatType ?? 'n/a'}`);
|
|
321
327
|
// 记录开始处理
|
|
322
328
|
this.eventBus.publish({ type: 'message:processing', sessionId: session.id });
|
|
323
|
-
adapter.sendProcessingStatus?.(message.channelId, 'start', session.id, taskId,
|
|
329
|
+
adapter.sendProcessingStatus?.(message.channelId, 'start', session.id, taskId, taskReplyContext());
|
|
324
330
|
logger.message({
|
|
325
331
|
msgId: messageId,
|
|
326
332
|
sessionId: session.id,
|
|
@@ -363,7 +369,7 @@ export class MessageProcessor {
|
|
|
363
369
|
// Proactive 模式可观测:创建 ThoughtEmitter,将静默的流式事件转发为 thought
|
|
364
370
|
// selector: context = { type: 'task', id: taskId }
|
|
365
371
|
if (isProactive && adapter.putThought) {
|
|
366
|
-
thoughtEmitter = new ThoughtEmitter(adapter, message.channelId, taskId, chatmode);
|
|
372
|
+
thoughtEmitter = new ThoughtEmitter(adapter, message.channelId, taskId, chatmode, this.getReplyContext(message));
|
|
367
373
|
}
|
|
368
374
|
// 调用 AgentRunner(含上下文过长自动 compact 重试)
|
|
369
375
|
// 捕获当前消息的上下文(闭包),避免后续消息处理时串台
|
|
@@ -389,7 +395,10 @@ export class MessageProcessor {
|
|
|
389
395
|
// 设置 per-session 权限模式(默认 bypass,所有角色统一)
|
|
390
396
|
agent.setMode(session.metadata?.permissionMode ?? DEFAULT_PERMISSION_MODE);
|
|
391
397
|
// 标记会话为处理中(实时持久化,重启后可恢复)
|
|
392
|
-
this.sessionManager.markProcessing(session.id);
|
|
398
|
+
this.sessionManager.markProcessing(session.id, taskId);
|
|
399
|
+
if (message.replyContext?.metadata?.encrypted != null) {
|
|
400
|
+
this.sessionManager.setSessionEncrypt(session.id, !!(message.replyContext.metadata.encrypted));
|
|
401
|
+
}
|
|
393
402
|
logger.info(`[MessageProcessor] session ${session.id} marked as processing task=${taskId}`);
|
|
394
403
|
// 检查是否因新消息自动中断 — 包装 prompt 让 Agent 知道上下文
|
|
395
404
|
const prevInterruptReason = this.interruptedSessions.get(session.id);
|
|
@@ -527,8 +536,13 @@ export class MessageProcessor {
|
|
|
527
536
|
for (const match of fileMatches) {
|
|
528
537
|
// 兼容旧格式 (1组) 和新格式 (2组)
|
|
529
538
|
const hasChannelGroup = match.length >= 3;
|
|
530
|
-
|
|
531
|
-
|
|
539
|
+
let targetSpec = hasChannelGroup ? (match[1] ?? undefined) : undefined;
|
|
540
|
+
let filePath = (hasChannelGroup ? match[2] : match[1]).trim();
|
|
541
|
+
// 白名单校验:targetSpec 必须是已注册通道,否则视为路径的一部分(如 Windows 盘符 C:)
|
|
542
|
+
if (targetSpec && !this.channels.has(targetSpec) && !this.channelTypeMap.has(targetSpec)) {
|
|
543
|
+
filePath = `${targetSpec}:${filePath}`;
|
|
544
|
+
targetSpec = undefined;
|
|
545
|
+
}
|
|
532
546
|
if (this.isPlaceholderPath(filePath)) {
|
|
533
547
|
logger.info(`[${adapter.channelName}] Skipped placeholder file marker: [SEND_FILE:${filePath}]`);
|
|
534
548
|
continue;
|
|
@@ -635,7 +649,7 @@ export class MessageProcessor {
|
|
|
635
649
|
const errorSummary = streamResult.errors?.join('; ') || '任务执行失败';
|
|
636
650
|
const rawSubtype = streamResult.subtype || 'agent_error';
|
|
637
651
|
const errorType = prefixErrorType(ERROR_PREFIX.AGENT, rawSubtype);
|
|
638
|
-
adapter.sendProcessingStatus?.(message.channelId, 'error', session.id, taskId,
|
|
652
|
+
adapter.sendProcessingStatus?.(message.channelId, 'error', session.id, taskId, taskReplyContext());
|
|
639
653
|
this.eventBus.publish({
|
|
640
654
|
type: 'message:error',
|
|
641
655
|
sessionId: session.id,
|
|
@@ -663,7 +677,7 @@ export class MessageProcessor {
|
|
|
663
677
|
}
|
|
664
678
|
else {
|
|
665
679
|
// 真正的成功
|
|
666
|
-
adapter.sendProcessingStatus?.(message.channelId, interruptReason ? 'interrupted' : 'done', session.id, taskId,
|
|
680
|
+
adapter.sendProcessingStatus?.(message.channelId, interruptReason ? 'interrupted' : 'done', session.id, taskId, taskReplyContext());
|
|
667
681
|
await this.sessionManager.recordSuccess(session.id);
|
|
668
682
|
this.eventBus.publish({
|
|
669
683
|
type: 'message:completed',
|
|
@@ -718,7 +732,7 @@ export class MessageProcessor {
|
|
|
718
732
|
// 用户主动中断(新消息打断 或 /stop 命令)时静默,不发送中断/错误提示
|
|
719
733
|
if (!isUserInterrupt) {
|
|
720
734
|
try {
|
|
721
|
-
adapter.sendProcessingStatus?.(message.channelId, procStatus, session.id, taskId,
|
|
735
|
+
adapter.sendProcessingStatus?.(message.channelId, procStatus, session.id, taskId, taskReplyContext());
|
|
722
736
|
}
|
|
723
737
|
catch { }
|
|
724
738
|
}
|
|
@@ -14,8 +14,9 @@ export class ThoughtEmitter {
|
|
|
14
14
|
channelId;
|
|
15
15
|
taskId;
|
|
16
16
|
chatmode;
|
|
17
|
+
replyContext;
|
|
17
18
|
hasEmittedText = false;
|
|
18
|
-
constructor(adapter, channelId, taskId, chatmode = 'proactive') {
|
|
19
|
+
constructor(adapter, channelId, taskId, chatmode = 'proactive', replyContext) {
|
|
19
20
|
if (!taskId) {
|
|
20
21
|
throw new Error('[ThoughtEmitter] taskId is required at construction');
|
|
21
22
|
}
|
|
@@ -23,6 +24,7 @@ export class ThoughtEmitter {
|
|
|
23
24
|
this.channelId = channelId;
|
|
24
25
|
this.taskId = taskId;
|
|
25
26
|
this.chatmode = chatmode;
|
|
27
|
+
this.replyContext = replyContext;
|
|
26
28
|
logger.info(`[ThoughtEmitter] created channel=${channelId} task=${taskId} chatmode=${chatmode}`);
|
|
27
29
|
}
|
|
28
30
|
async emit(event) {
|
|
@@ -45,7 +47,7 @@ export class ThoughtEmitter {
|
|
|
45
47
|
payload.task_id = this.taskId;
|
|
46
48
|
payload.chatmode = this.chatmode;
|
|
47
49
|
try {
|
|
48
|
-
await this.adapter.putThought(this.channelId, this.taskId, payload);
|
|
50
|
+
await this.adapter.putThought(this.channelId, this.taskId, payload, this.replyContext);
|
|
49
51
|
}
|
|
50
52
|
catch (err) {
|
|
51
53
|
logger.debug(`[ThoughtEmitter] putThought failed: ${err.message}`);
|
|
@@ -13,6 +13,7 @@ export class SessionManager {
|
|
|
13
13
|
adminResolver;
|
|
14
14
|
sessionModeResolver;
|
|
15
15
|
fileAdapters = new Map();
|
|
16
|
+
sessionEncryptState = new Map();
|
|
16
17
|
constructor(dbPath = resolvePaths().db, eventBus, ownerResolver, adminResolver) {
|
|
17
18
|
ensureDir(path.dirname(dbPath));
|
|
18
19
|
this.db = new DatabaseSync(dbPath);
|
|
@@ -428,11 +429,21 @@ export class SessionManager {
|
|
|
428
429
|
}
|
|
429
430
|
/**
|
|
430
431
|
* 标记会话为处理中(实时写 DB,crash 也能恢复)
|
|
432
|
+
* processing_state 格式: "timestamp:taskId"
|
|
431
433
|
*/
|
|
432
|
-
markProcessing(sessionId) {
|
|
434
|
+
markProcessing(sessionId, taskId) {
|
|
433
435
|
const now = Date.now();
|
|
436
|
+
const state = taskId ? `${now}:${taskId}` : String(now);
|
|
434
437
|
this.db.prepare(`UPDATE sessions SET processing_state = ?, updated_at = ? WHERE id = ?`)
|
|
435
|
-
.run(
|
|
438
|
+
.run(state, now, sessionId);
|
|
439
|
+
}
|
|
440
|
+
/** 从 processing_state 解析当前活跃 taskId */
|
|
441
|
+
getActiveTaskId(sessionId) {
|
|
442
|
+
const row = this.db.prepare(`SELECT processing_state FROM sessions WHERE id = ?`).get(sessionId);
|
|
443
|
+
if (!row?.processing_state)
|
|
444
|
+
return undefined;
|
|
445
|
+
const colonIdx = row.processing_state.indexOf(':');
|
|
446
|
+
return colonIdx > 0 ? row.processing_state.slice(colonIdx + 1) : undefined;
|
|
436
447
|
}
|
|
437
448
|
/**
|
|
438
449
|
* 清除会话处理中状态
|
|
@@ -440,6 +451,13 @@ export class SessionManager {
|
|
|
440
451
|
clearProcessing(sessionId) {
|
|
441
452
|
this.db.prepare(`UPDATE sessions SET processing_state = NULL, updated_at = ? WHERE id = ?`)
|
|
442
453
|
.run(Date.now(), sessionId);
|
|
454
|
+
this.sessionEncryptState.delete(sessionId);
|
|
455
|
+
}
|
|
456
|
+
setSessionEncrypt(sessionId, encrypted) {
|
|
457
|
+
this.sessionEncryptState.set(sessionId, encrypted);
|
|
458
|
+
}
|
|
459
|
+
getSessionEncrypt(sessionId) {
|
|
460
|
+
return this.sessionEncryptState.get(sessionId);
|
|
443
461
|
}
|
|
444
462
|
/**
|
|
445
463
|
* 获取所有处于 processing 状态的会话(用于重启后恢复)
|
|
@@ -453,7 +471,8 @@ export class SessionManager {
|
|
|
453
471
|
const now = Date.now();
|
|
454
472
|
const result = [];
|
|
455
473
|
for (const row of rows) {
|
|
456
|
-
const
|
|
474
|
+
const colonIdx = row.processing_state.indexOf(':');
|
|
475
|
+
const ts = parseInt(colonIdx > 0 ? row.processing_state.slice(0, colonIdx) : row.processing_state, 10);
|
|
457
476
|
if (!isNaN(ts) && (now - ts) < maxAgeMs) {
|
|
458
477
|
result.push(this.rowToSession(row));
|
|
459
478
|
}
|
package/dist/index.js
CHANGED
|
@@ -289,6 +289,14 @@ async function main() {
|
|
|
289
289
|
});
|
|
290
290
|
});
|
|
291
291
|
}
|
|
292
|
+
// proactive 模式入站白名单:注入 sessionMode 查询器
|
|
293
|
+
if (typeof inst.channel.setSessionModeResolver === 'function') {
|
|
294
|
+
const chName = inst.adapter.channelName;
|
|
295
|
+
inst.channel.setSessionModeResolver(async (channelId) => {
|
|
296
|
+
const session = await sessionManager.getActiveSession(chName, channelId);
|
|
297
|
+
return session?.sessionMode;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
292
300
|
}
|
|
293
301
|
if (channelType === 'dingtalk') {
|
|
294
302
|
msgBridge.register(inst.adapter.channelName, (handler) => inst.channel.onMessage(async (event) => {
|
|
@@ -340,18 +348,6 @@ async function main() {
|
|
|
340
348
|
for (const inst of channelInstances) {
|
|
341
349
|
registerChannelInstance(inst);
|
|
342
350
|
}
|
|
343
|
-
// ── 设置热加载回调 ──
|
|
344
|
-
cmdHandler.setHotLoadChannel(async (inst) => {
|
|
345
|
-
registerChannelInstance(inst);
|
|
346
|
-
channelInstances.push(inst);
|
|
347
|
-
await inst.connect();
|
|
348
|
-
eventBus.publish({
|
|
349
|
-
type: 'channel:connected',
|
|
350
|
-
channel: (inst.channelType || inst.adapter.channelName).toLowerCase(),
|
|
351
|
-
channelName: inst.adapter.channelName,
|
|
352
|
-
timestamp: Date.now(),
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
351
|
// ── 连接所有渠道 ──
|
|
356
352
|
const connected = await channelLoader.connectAll(channelInstances);
|
|
357
353
|
// 预填充 Feishu 已知 thread_id(重启后避免误判话题创建)
|
package/dist/paths.js
CHANGED
|
@@ -32,6 +32,7 @@ export function resolvePaths() {
|
|
|
32
32
|
pid: path.join(root, 'logs', 'evolclaw.pid'),
|
|
33
33
|
dataDir: path.join(root, 'data'),
|
|
34
34
|
logs: path.join(root, 'logs'),
|
|
35
|
+
agentsDir: path.join(root, 'agents'),
|
|
35
36
|
lineStats: path.join(root, 'logs', 'line-stats.log'),
|
|
36
37
|
readySignal: path.join(root, 'logs', 'ready.signal'),
|
|
37
38
|
selfHealLog: path.join(root, 'logs', 'self-heal.md'),
|
|
@@ -49,6 +50,7 @@ export function ensureDataDirs() {
|
|
|
49
50
|
const p = resolvePaths();
|
|
50
51
|
fs.mkdirSync(p.dataDir, { recursive: true });
|
|
51
52
|
fs.mkdirSync(p.logs, { recursive: true });
|
|
53
|
+
fs.mkdirSync(p.agentsDir, { recursive: true });
|
|
52
54
|
}
|
|
53
55
|
export function getPackageRoot() {
|
|
54
56
|
// import.meta.dirname is available in Node.js 21.2+ and always returns
|
|
@@ -10,13 +10,13 @@ import readline from 'readline';
|
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import os from 'os';
|
|
12
12
|
import crypto from 'crypto';
|
|
13
|
-
import {
|
|
14
|
-
import { execFile, execFileSync } from 'child_process';
|
|
13
|
+
import { execFile } from 'child_process';
|
|
15
14
|
import { promisify } from 'util';
|
|
16
15
|
import { resolvePaths } from '../paths.js';
|
|
17
16
|
import { normalizeChannelInstances } from '../config.js';
|
|
18
17
|
import { selectInstance } from './init.js';
|
|
19
18
|
import { isWindows } from './cross-platform.js';
|
|
19
|
+
import { AUN_CORE_SDK_PKG, MIN_AUN_CORE_SDK, resolveAunCoreSdkPkg, isAunSdkVersionOk, isValidAid, aidCreate, agentmdPut, buildInitialAgentMd, } from '../channels/aun-ops.js';
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
21
|
export async function npmInstallGlobal(pkg) {
|
|
22
22
|
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
@@ -580,91 +580,9 @@ export async function cmdInitWechat() {
|
|
|
580
580
|
process.exit(1);
|
|
581
581
|
}
|
|
582
582
|
// ==================== AUN ====================
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
function compareVersion(a, min) {
|
|
587
|
-
const parts = a.split('.').map(n => parseInt(n, 10));
|
|
588
|
-
if (parts.length < 3 || parts.some(isNaN))
|
|
589
|
-
return false;
|
|
590
|
-
if (parts[0] !== min[0])
|
|
591
|
-
return parts[0] > min[0];
|
|
592
|
-
if (parts[1] !== min[1])
|
|
593
|
-
return parts[1] > min[1];
|
|
594
|
-
return parts[2] >= min[2];
|
|
595
|
-
}
|
|
596
|
-
export function resolveAunCoreSdkPkg() {
|
|
597
|
-
const pkgName = AUN_CORE_SDK_PKG;
|
|
598
|
-
// Strategy 1: walk up node_modules from this file (no require.resolve — avoids ESM exports issues)
|
|
599
|
-
try {
|
|
600
|
-
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
601
|
-
while (true) {
|
|
602
|
-
const candidate = path.join(dir, 'node_modules', pkgName, 'package.json');
|
|
603
|
-
if (fs.existsSync(candidate)) {
|
|
604
|
-
const data = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
|
|
605
|
-
if (data.name === pkgName)
|
|
606
|
-
return { version: data.version, path: candidate };
|
|
607
|
-
}
|
|
608
|
-
const parent = path.dirname(dir);
|
|
609
|
-
if (parent === dir)
|
|
610
|
-
break;
|
|
611
|
-
dir = parent;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
catch { /* fall through */ }
|
|
615
|
-
// Strategy 2: npm root -g fallback (globally installed SDK)
|
|
616
|
-
try {
|
|
617
|
-
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
618
|
-
const globalRoot = execFileSync(npmCmd, ['root', '-g'], {
|
|
619
|
-
encoding: 'utf-8', timeout: 10000, shell: isWindows,
|
|
620
|
-
}).trim();
|
|
621
|
-
const pkgPath = path.join(globalRoot, pkgName, 'package.json');
|
|
622
|
-
if (fs.existsSync(pkgPath)) {
|
|
623
|
-
const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
624
|
-
return { version: data.version, path: pkgPath };
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
catch { /* not found */ }
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Download AUN CA root certificate to ~/.aun/CA/root/root.crt.
|
|
632
|
-
* Idempotent: skips if file already exists. Returns true if a cert is on disk
|
|
633
|
-
* after the call (either pre-existing or freshly downloaded).
|
|
634
|
-
*
|
|
635
|
-
* Must be called BEFORE constructing any AUNClient that needs to verify
|
|
636
|
-
* gateway-issued certificates (e.g. for uploadAgentMd) — the SDK loads trusted
|
|
637
|
-
* roots from disk at client construction time and won't pick up later writes.
|
|
638
|
-
*/
|
|
639
|
-
export async function downloadCaRoot(aunPath, gatewayUrl, indent = '') {
|
|
640
|
-
const caDir = path.join(aunPath, 'CA', 'root');
|
|
641
|
-
const caCertPath = path.join(caDir, 'root.crt');
|
|
642
|
-
if (fs.existsSync(caCertPath))
|
|
643
|
-
return true;
|
|
644
|
-
if (!gatewayUrl)
|
|
645
|
-
return false;
|
|
646
|
-
try {
|
|
647
|
-
fs.mkdirSync(caDir, { recursive: true });
|
|
648
|
-
const gwHttp = gatewayUrl.replace(/^wss?:/, 'https:').replace(/\/aun$/, '');
|
|
649
|
-
const resp = await fetch(`${gwHttp}/pki/chain`, { redirect: 'follow' });
|
|
650
|
-
if (!resp.ok) {
|
|
651
|
-
console.warn(`${indent}⚠ CA 根证书下载失败: HTTP ${resp.status}`);
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
const body = await resp.text();
|
|
655
|
-
if (!body.includes('BEGIN CERTIFICATE')) {
|
|
656
|
-
console.warn(`${indent}⚠ CA 根证书响应内容无效,跳过写入`);
|
|
657
|
-
return false;
|
|
658
|
-
}
|
|
659
|
-
fs.writeFileSync(caCertPath, body);
|
|
660
|
-
console.log(`${indent}✓ CA 根证书已下载`);
|
|
661
|
-
return true;
|
|
662
|
-
}
|
|
663
|
-
catch (e) {
|
|
664
|
-
console.warn(`${indent}⚠ CA 根证书下载失败: ${e},可稍后手动下载`);
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
583
|
+
//
|
|
584
|
+
// AUN 原子操作(aidCreate, agentmdPut, downloadCaRoot, isValidAid, ...)
|
|
585
|
+
// 已迁移至 src/channels/aun-ops.ts。本节仅保留交互式 UI 编排。
|
|
668
586
|
export async function checkAunEnvironment(rl) {
|
|
669
587
|
console.log('\n🔍 AUN 环境检查...\n');
|
|
670
588
|
const minVer = MIN_AUN_CORE_SDK.join('.');
|
|
@@ -688,7 +606,7 @@ export async function checkAunEnvironment(rl) {
|
|
|
688
606
|
console.log('');
|
|
689
607
|
return true;
|
|
690
608
|
}
|
|
691
|
-
if (
|
|
609
|
+
if (isAunSdkVersionOk(installed.version)) {
|
|
692
610
|
console.log(` ✓ ${AUN_CORE_SDK_PKG} v${installed.version}`);
|
|
693
611
|
console.log('');
|
|
694
612
|
return true;
|
|
@@ -711,95 +629,8 @@ export async function checkAunEnvironment(rl) {
|
|
|
711
629
|
console.log('');
|
|
712
630
|
return true;
|
|
713
631
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
return labels.length >= 3 && labels.every(l => /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(l));
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Non-interactive AID creation + agent.md publish.
|
|
720
|
-
* Reuses the same logic as `evolclaw init --non-interactive --channel aun`.
|
|
721
|
-
*
|
|
722
|
-
* Returns the created AID string, or throws on failure.
|
|
723
|
-
*/
|
|
724
|
-
export async function createAidSilent(opts) {
|
|
725
|
-
const aunPath = path.join(os.homedir(), '.aun');
|
|
726
|
-
const aidDir = path.join(aunPath, 'AIDs', opts.aid);
|
|
727
|
-
// Skip creation if AID already exists locally
|
|
728
|
-
if (fs.existsSync(aidDir) && fs.existsSync(path.join(aidDir, 'private'))) {
|
|
729
|
-
return { aid: opts.aid, alreadyExisted: true };
|
|
730
|
-
}
|
|
731
|
-
const { AUNClient, GatewayDiscovery } = await import('@agentunion/fastaun');
|
|
732
|
-
let client = new AUNClient({ aun_path: aunPath });
|
|
733
|
-
const result = await client.auth.createAid({ aid: opts.aid });
|
|
734
|
-
// Download CA root cert (if not already present)
|
|
735
|
-
const caDownloaded = await downloadCaRoot(aunPath, result.gateway || '');
|
|
736
|
-
// Rebuild client with CA cert + AID identity for uploadAgentMd
|
|
737
|
-
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
738
|
-
if (caDownloaded && fs.existsSync(caCertPath)) {
|
|
739
|
-
try {
|
|
740
|
-
await client.close();
|
|
741
|
-
}
|
|
742
|
-
catch { /* ignore */ }
|
|
743
|
-
client = new AUNClient({ aun_path: aunPath, root_ca_path: caCertPath, aid: opts.aid });
|
|
744
|
-
}
|
|
745
|
-
// Set gateway URL for uploadAgentMd
|
|
746
|
-
let gatewayUrl = result.gateway || '';
|
|
747
|
-
if (!gatewayUrl) {
|
|
748
|
-
try {
|
|
749
|
-
const discovery = new GatewayDiscovery({});
|
|
750
|
-
gatewayUrl = await discovery.discover(`https://${opts.aid}/.well-known/aun-gateway`);
|
|
751
|
-
}
|
|
752
|
-
catch { /* fall through */ }
|
|
753
|
-
}
|
|
754
|
-
if (gatewayUrl) {
|
|
755
|
-
client._gatewayUrl = gatewayUrl;
|
|
756
|
-
}
|
|
757
|
-
// Write initial agent.md (initialized: false, name = aid first label)
|
|
758
|
-
const agentName = opts.aid.split('.')[0];
|
|
759
|
-
const agentMdContent = `---\naid: "${opts.aid}"\nname: "${agentName}"\ntype: "ai"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
|
|
760
|
-
const agentMdPath = path.join(aidDir, 'agent.md');
|
|
761
|
-
try {
|
|
762
|
-
await client.auth.uploadAgentMd(agentMdContent);
|
|
763
|
-
}
|
|
764
|
-
catch (e) {
|
|
765
|
-
// Non-fatal: first connection will auto-retry
|
|
766
|
-
}
|
|
767
|
-
fs.writeFileSync(agentMdPath, agentMdContent, 'utf-8');
|
|
768
|
-
try {
|
|
769
|
-
await client.close();
|
|
770
|
-
}
|
|
771
|
-
catch { /* ignore */ }
|
|
772
|
-
if (!fs.existsSync(agentMdPath)) {
|
|
773
|
-
throw new Error(`agent.md write verification failed: ${agentMdPath}`);
|
|
774
|
-
}
|
|
775
|
-
return { aid: opts.aid, alreadyExisted: false };
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Append a new AUN instance to the config's channels.aun array and save.
|
|
779
|
-
* Handles upgrade from single-object to array format.
|
|
780
|
-
*/
|
|
781
|
-
export function appendAunInstance(config, inst) {
|
|
782
|
-
if (!config.channels)
|
|
783
|
-
config.channels = {};
|
|
784
|
-
const newInst = {
|
|
785
|
-
name: inst.name,
|
|
786
|
-
enabled: inst.enabled ?? true,
|
|
787
|
-
aid: inst.aid,
|
|
788
|
-
...(inst.owner && { owner: inst.owner }),
|
|
789
|
-
};
|
|
790
|
-
if (Array.isArray(config.channels.aun)) {
|
|
791
|
-
config.channels.aun.push(newInst);
|
|
792
|
-
}
|
|
793
|
-
else if (config.channels.aun) {
|
|
794
|
-
const oldInst = { ...config.channels.aun, name: config.channels.aun.name || 'aun' };
|
|
795
|
-
config.channels.aun = [oldInst, newInst];
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
config.channels.aun = [newInst];
|
|
799
|
-
}
|
|
800
|
-
const p = resolvePaths();
|
|
801
|
-
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
802
|
-
}
|
|
632
|
+
// isValidAid, createAidSilent → 已迁移至 src/channels/aun-ops.ts
|
|
633
|
+
// appendAunInstance → 已迁移至 src/channels/aun-ops.ts
|
|
803
634
|
export async function setupAunAid(rl, _config) {
|
|
804
635
|
let aid = '';
|
|
805
636
|
// Outer loop: allows retrying with a different AID
|
|
@@ -829,64 +660,35 @@ export async function setupAunAid(rl, _config) {
|
|
|
829
660
|
console.log(' 已跳过 AID 创建(启动时可能连接失败)');
|
|
830
661
|
break;
|
|
831
662
|
}
|
|
832
|
-
// Create AID
|
|
663
|
+
// Create AID + agent.md via atomic ops
|
|
833
664
|
console.log(' 正在创建 AID...');
|
|
834
665
|
let failed = false;
|
|
835
666
|
try {
|
|
836
|
-
const
|
|
837
|
-
let client = new AUNClient({ aun_path: aunPath });
|
|
838
|
-
const result = await client.auth.createAid({ aid });
|
|
667
|
+
const result = await aidCreate(aid);
|
|
839
668
|
console.log(` ✓ AID ${result.aid} 创建成功`);
|
|
840
|
-
//
|
|
841
|
-
const caDownloaded = await downloadCaRoot(aunPath, result.gateway || '', ' ');
|
|
842
|
-
// 重建 client:传 root_ca_path 以验证 server cert,传 aid 以加载身份
|
|
843
|
-
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
844
|
-
if (caDownloaded && fs.existsSync(caCertPath)) {
|
|
845
|
-
try {
|
|
846
|
-
await client.close();
|
|
847
|
-
}
|
|
848
|
-
catch { /* ignore */ }
|
|
849
|
-
client = new AUNClient({ aun_path: aunPath, root_ca_path: caCertPath, aid });
|
|
850
|
-
}
|
|
851
|
-
// 设置 gateway URL(从 createAid 返回值或 well-known 自动发现)
|
|
852
|
-
let gatewayUrl = result.gateway || '';
|
|
853
|
-
if (!gatewayUrl) {
|
|
854
|
-
try {
|
|
855
|
-
const discovery = new GatewayDiscovery({});
|
|
856
|
-
gatewayUrl = await discovery.discover(`https://${aid}/.well-known/aun-gateway`);
|
|
857
|
-
}
|
|
858
|
-
catch { /* fall through */ }
|
|
859
|
-
}
|
|
860
|
-
if (gatewayUrl) {
|
|
861
|
-
client._gatewayUrl = gatewayUrl;
|
|
862
|
-
}
|
|
863
|
-
// Collect agent.md info and publish
|
|
669
|
+
// Collect agent.md type and upload
|
|
864
670
|
const typeInput = (await ask(rl, ' Agent 类型 human/ai [ai]: ')).trim().toLowerCase();
|
|
865
671
|
const agentType = typeInput === 'human' ? 'human' : 'ai';
|
|
866
|
-
const
|
|
867
|
-
const agentMdContent = `---\naid: "${aid}"\nname: "${agentName}"\ntype: "${agentType}"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
|
|
868
|
-
const agentMdPath = path.join(aidDir, 'agent.md');
|
|
672
|
+
const content = buildInitialAgentMd({ aid, type: agentType });
|
|
869
673
|
try {
|
|
870
|
-
await client.
|
|
871
|
-
console.log(' ✓ agent.md
|
|
674
|
+
await agentmdPut(content, { aid, client: result.client });
|
|
675
|
+
console.log(' ✓ agent.md 已发布并写入本地');
|
|
872
676
|
}
|
|
873
677
|
catch (e) {
|
|
874
678
|
console.log(` ⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
|
|
875
|
-
|
|
876
|
-
fs.writeFileSync(agentMdPath, agentMdContent, 'utf-8');
|
|
877
|
-
if (!fs.existsSync(agentMdPath)) {
|
|
679
|
+
// Still write local copy as fallback
|
|
878
680
|
try {
|
|
879
|
-
|
|
681
|
+
fs.mkdirSync(aidDir, { recursive: true });
|
|
682
|
+
fs.writeFileSync(path.join(aidDir, 'agent.md'), content, 'utf-8');
|
|
683
|
+
console.log(' ✓ agent.md 已写入本地');
|
|
684
|
+
}
|
|
685
|
+
catch (we) {
|
|
686
|
+
console.log(` ✗ agent.md 本地写入失败: ${String(we.message || we).slice(0, 100)}`);
|
|
687
|
+
failed = true;
|
|
880
688
|
}
|
|
881
|
-
catch { /* ignore */ }
|
|
882
|
-
console.log(` ✗ agent.md 本地写入校验失败: ${agentMdPath}`);
|
|
883
|
-
failed = true;
|
|
884
|
-
}
|
|
885
|
-
else {
|
|
886
|
-
console.log(' ✓ agent.md 已写入本地');
|
|
887
689
|
}
|
|
888
690
|
try {
|
|
889
|
-
await client.close();
|
|
691
|
+
await result.client.close();
|
|
890
692
|
}
|
|
891
693
|
catch { /* ignore */ }
|
|
892
694
|
}
|
|
@@ -1610,3 +1412,71 @@ export async function cmdInitWecom() {
|
|
|
1610
1412
|
console.log(` 配置已写入: ${p.config}`);
|
|
1611
1413
|
console.log(`\n现在可以启动服务: evolclaw restart`);
|
|
1612
1414
|
}
|
|
1415
|
+
export function getChannelCredentialCollector(type) {
|
|
1416
|
+
switch (type) {
|
|
1417
|
+
case 'feishu':
|
|
1418
|
+
return async () => {
|
|
1419
|
+
const result = await runFeishuQrFlow();
|
|
1420
|
+
if (!result)
|
|
1421
|
+
return null;
|
|
1422
|
+
return { appId: result.appId, appSecret: result.appSecret, enabled: true };
|
|
1423
|
+
};
|
|
1424
|
+
case 'wechat':
|
|
1425
|
+
return async () => {
|
|
1426
|
+
const result = await runWechatQrFlow();
|
|
1427
|
+
if (!result)
|
|
1428
|
+
return null;
|
|
1429
|
+
return { baseUrl: result.baseUrl, token: result.token, enabled: true };
|
|
1430
|
+
};
|
|
1431
|
+
case 'aun':
|
|
1432
|
+
return async () => {
|
|
1433
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1434
|
+
try {
|
|
1435
|
+
if (!await checkAunEnvironment(rl))
|
|
1436
|
+
return null;
|
|
1437
|
+
const result = await setupAunAid(rl, {});
|
|
1438
|
+
if (!result)
|
|
1439
|
+
return null;
|
|
1440
|
+
return { aid: result.aid, owner: result.owner, enabled: true };
|
|
1441
|
+
}
|
|
1442
|
+
finally {
|
|
1443
|
+
rl.close();
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
case 'dingtalk':
|
|
1447
|
+
return async () => {
|
|
1448
|
+
const result = await runDingtalkQrFlowSimple();
|
|
1449
|
+
if (!result)
|
|
1450
|
+
return null;
|
|
1451
|
+
return { clientId: result.clientId, clientSecret: result.clientSecret, enabled: true };
|
|
1452
|
+
};
|
|
1453
|
+
case 'qqbot':
|
|
1454
|
+
return async () => {
|
|
1455
|
+
const result = await runQQBotBindFlowSimple();
|
|
1456
|
+
if (!result)
|
|
1457
|
+
return null;
|
|
1458
|
+
return { appId: result.appId, clientSecret: result.clientSecret, enabled: true };
|
|
1459
|
+
};
|
|
1460
|
+
case 'wecom':
|
|
1461
|
+
return async () => {
|
|
1462
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1463
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
1464
|
+
try {
|
|
1465
|
+
console.log('企业微信 AI Bot 配置\n');
|
|
1466
|
+
console.log('请在企业微信管理后台 → AI Bot 页面获取 Bot ID 和 Secret\n');
|
|
1467
|
+
const botId = (await ask(' Bot ID: ')).trim();
|
|
1468
|
+
if (!botId)
|
|
1469
|
+
return null;
|
|
1470
|
+
const secret = (await ask(' Secret: ')).trim();
|
|
1471
|
+
if (!secret)
|
|
1472
|
+
return null;
|
|
1473
|
+
return { botId, secret, enabled: true };
|
|
1474
|
+
}
|
|
1475
|
+
finally {
|
|
1476
|
+
rl.close();
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
default:
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
}
|