evolclaw 2.5.0 → 2.5.2
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 +53 -8
- package/dist/channels/aun.js +91 -3
- package/dist/cli.js +17 -1
- package/dist/core/command-handler.js +103 -29
- package/dist/core/message/message-bridge.js +19 -5
- package/dist/core/message/message-processor.js +6 -3
- package/dist/core/permission.js +7 -11
- package/dist/utils/init-channel.js +59 -11
- package/dist/utils/init.js +71 -1
- package/evolclaw-install-aun.md +148 -0
- package/package.json +3 -2
|
@@ -259,14 +259,7 @@ export class AgentRunner {
|
|
|
259
259
|
}
|
|
260
260
|
// 等待用户交互
|
|
261
261
|
const answer = await new Promise((resolve) => {
|
|
262
|
-
const timer = setTimeout(() => {
|
|
263
|
-
permCtx?.interactionRouter?.cancel(requestId);
|
|
264
|
-
permCtx?.cancelIntercept?.(sessionId);
|
|
265
|
-
logger.info(`[AgentRunner] AskUserQuestion timeout for ${requestId}`);
|
|
266
|
-
resolve(null);
|
|
267
|
-
}, 5 * 60 * 1000);
|
|
268
262
|
permCtx?.interactionRouter?.register(requestId, sessionId, (action, values) => {
|
|
269
|
-
clearTimeout(timer);
|
|
270
263
|
if (action === 'cancel') {
|
|
271
264
|
resolve(null);
|
|
272
265
|
}
|
|
@@ -292,7 +285,7 @@ export class AgentRunner {
|
|
|
292
285
|
});
|
|
293
286
|
});
|
|
294
287
|
if (answer === null) {
|
|
295
|
-
//
|
|
288
|
+
// 取消,自动选第一项
|
|
296
289
|
const firstLabel = q.options[0]?.label || '';
|
|
297
290
|
answers[q.question] = q.multiSelect ? [firstLabel] : firstLabel;
|
|
298
291
|
}
|
|
@@ -323,6 +316,54 @@ export class AgentRunner {
|
|
|
323
316
|
const updatedInput = { ...input, answers };
|
|
324
317
|
return { behavior: 'allow', updatedInput, decisionClassification: 'user_temporary' };
|
|
325
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* 处理 ExitPlanMode 工具调用:plan mode 审批,等待用户批准后才继续执行
|
|
321
|
+
*/
|
|
322
|
+
async handleExitPlanMode(sessionId, input, options) {
|
|
323
|
+
const permCtx = this.permissionContexts.get(sessionId);
|
|
324
|
+
const sendPrompt = this.sendPromptFn;
|
|
325
|
+
// 无交互上下文,直接 allow(防御性兜底)
|
|
326
|
+
if (!permCtx?.adapter?.sendInteraction || !permCtx?.channelId || !sendPrompt) {
|
|
327
|
+
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' };
|
|
328
|
+
}
|
|
329
|
+
const requestId = `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
330
|
+
const interaction = {
|
|
331
|
+
type: 'interaction',
|
|
332
|
+
id: requestId,
|
|
333
|
+
kind: {
|
|
334
|
+
kind: 'action',
|
|
335
|
+
title: '📋 计划审批',
|
|
336
|
+
body: 'AI 已完成规划,等待审批。\n请查看以上计划内容后决定。',
|
|
337
|
+
buttons: [
|
|
338
|
+
{ key: 'approve', label: '✅ 批准执行', style: 'primary' },
|
|
339
|
+
{ key: 'reject', label: '❌ 拒绝', style: 'danger' },
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
channelId: permCtx.channelId,
|
|
343
|
+
sessionId,
|
|
344
|
+
};
|
|
345
|
+
let cardSent = false;
|
|
346
|
+
try {
|
|
347
|
+
const result = await permCtx.adapter.sendInteraction(permCtx.channelId, interaction, permCtx.replyContext);
|
|
348
|
+
cardSent = !!result;
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
logger.warn('[AgentRunner] ExitPlanMode card send failed:', err);
|
|
352
|
+
}
|
|
353
|
+
if (!cardSent) {
|
|
354
|
+
await sendPrompt('📋 计划审批\nAI 已完成规划,等待审批。\n回复 /plan approve 批准执行 | /plan reject 拒绝');
|
|
355
|
+
}
|
|
356
|
+
return new Promise((resolve) => {
|
|
357
|
+
permCtx?.interactionRouter?.register(requestId, sessionId, (action) => {
|
|
358
|
+
if (action === 'approve') {
|
|
359
|
+
resolve({ behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' });
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
resolve({ behavior: 'deny', message: '用户拒绝了计划', decisionClassification: 'user_reject' });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
326
367
|
/**
|
|
327
368
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
328
369
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
@@ -538,6 +579,10 @@ export class AgentRunner {
|
|
|
538
579
|
if (toolName === 'AskUserQuestion') {
|
|
539
580
|
return await this.handleAskUserQuestion(sessionId, input, options);
|
|
540
581
|
}
|
|
582
|
+
// 特殊处理:ExitPlanMode 工具(plan mode 审批)
|
|
583
|
+
if (toolName === 'ExitPlanMode') {
|
|
584
|
+
return await this.handleExitPlanMode(sessionId, input, options);
|
|
585
|
+
}
|
|
541
586
|
// bypass 模式:一律 allow
|
|
542
587
|
if (this.permissionMode === 'bypass') {
|
|
543
588
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
package/dist/channels/aun.js
CHANGED
|
@@ -2,6 +2,7 @@ import { AUNClient, GatewayDiscovery } from '@eleans/aun-core-sdk';
|
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
5
6
|
import { logger, localTimestamp } from '../utils/logger.js';
|
|
6
7
|
import { normalizeChannelInstances, getChannelShowActivities } from '../config.js';
|
|
7
8
|
import { resolvePaths } from '../paths.js';
|
|
@@ -171,7 +172,7 @@ export class AUNChannel {
|
|
|
171
172
|
}
|
|
172
173
|
if (!gateway) {
|
|
173
174
|
logger.error('[AUN] Cannot resolve gateway URL from AID');
|
|
174
|
-
|
|
175
|
+
throw new Error('Cannot resolve gateway URL from AID');
|
|
175
176
|
}
|
|
176
177
|
logger.info(`[AUN] Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
|
|
177
178
|
// Create client with FileSecretStore (AES-256-GCM)
|
|
@@ -251,7 +252,7 @@ export class AUNChannel {
|
|
|
251
252
|
if (!accessToken) {
|
|
252
253
|
logger.error(`[AUN] No accessToken fallback available, scheduling retry`);
|
|
253
254
|
this.scheduleReconnect();
|
|
254
|
-
|
|
255
|
+
throw new Error('Authentication failed and no accessToken fallback available');
|
|
255
256
|
}
|
|
256
257
|
logger.warn(`[AUN] Using accessToken fallback`);
|
|
257
258
|
}
|
|
@@ -275,11 +276,97 @@ export class AUNChannel {
|
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
logger.info(`[AUN] Connected as ${this._aid}`);
|
|
279
|
+
// Send welcome message to owner after first connection
|
|
280
|
+
await this.sendWelcomeMessage();
|
|
278
281
|
}
|
|
279
282
|
catch (e) {
|
|
280
283
|
logger.error(`[AUN] Connection failed: ${e}`);
|
|
281
284
|
this.scheduleReconnect();
|
|
282
|
-
|
|
285
|
+
throw e;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async sendWelcomeMessage() {
|
|
289
|
+
try {
|
|
290
|
+
const owner = this.config.owner;
|
|
291
|
+
if (!owner) {
|
|
292
|
+
logger.info('[AUN] No owner configured, skipping welcome message');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Check agent.md initialized field
|
|
296
|
+
const aid = this.config.aid;
|
|
297
|
+
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
298
|
+
const agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
|
|
299
|
+
if (!fs.existsSync(agentMdPath)) {
|
|
300
|
+
logger.warn('[AUN] agent.md not found, skipping welcome message');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const agentMdContent = fs.readFileSync(agentMdPath, 'utf-8');
|
|
304
|
+
const match = agentMdContent.match(/^---\n([\s\S]*?)\n---/);
|
|
305
|
+
if (!match) {
|
|
306
|
+
logger.warn('[AUN] agent.md frontmatter not found');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const frontmatter = match[1];
|
|
310
|
+
const initializedMatch = frontmatter.match(/^initialized:\s*(true|false)/m);
|
|
311
|
+
if (!initializedMatch || initializedMatch[1] === 'true') {
|
|
312
|
+
logger.info('[AUN] Agent already initialized, skipping welcome message');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
// Generate new agent.md with proper fields
|
|
316
|
+
const ownerShortId = owner.split('@')[0].slice(0, 8);
|
|
317
|
+
const newAgentMd = `---
|
|
318
|
+
aid: "${aid}"
|
|
319
|
+
name: "${ownerShortId}的Evol助手"
|
|
320
|
+
type: "codeagent"
|
|
321
|
+
version: "1.0.0"
|
|
322
|
+
description: "EvolClaw AI Agent Gateway - 连接 Claude/Codex 到消息通道"
|
|
323
|
+
tags:
|
|
324
|
+
- evolclaw
|
|
325
|
+
- ai-agent
|
|
326
|
+
- gateway
|
|
327
|
+
initialized: true
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
# ${ownerShortId}的Evol助手
|
|
331
|
+
|
|
332
|
+
EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
333
|
+
`;
|
|
334
|
+
// Write locally
|
|
335
|
+
fs.writeFileSync(agentMdPath, newAgentMd, 'utf-8');
|
|
336
|
+
logger.info('[AUN] Updated agent.md with initialized=true');
|
|
337
|
+
// Publish to AUN network via auth.uploadAgentMd
|
|
338
|
+
try {
|
|
339
|
+
await this.client.auth.uploadAgentMd(newAgentMd);
|
|
340
|
+
logger.info('[AUN] Published agent.md to AUN network');
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
logger.warn(`[AUN] Failed to publish agent.md: ${e}`);
|
|
344
|
+
}
|
|
345
|
+
// Send welcome message
|
|
346
|
+
const welcomeText = `🎉 欢迎使用 EvolClaw!
|
|
347
|
+
|
|
348
|
+
我是您的 AI Agent 网关,已成功连接到 AUN 网络。
|
|
349
|
+
|
|
350
|
+
📋 **日常使用方法**:
|
|
351
|
+
|
|
352
|
+
1. **绑定项目**:发送 \`/bind <项目路径>\` 绑定工作目录
|
|
353
|
+
2. **查看帮助**:发送 \`/help\` 查看所有可用命令
|
|
354
|
+
3. **切换项目**:发送 \`/project <项目名>\` 切换到其他项目
|
|
355
|
+
4. **查看状态**:发送 \`/status\` 查看当前会话状态
|
|
356
|
+
5. **查看 Agent 信息**:发送 \`/agentmd\` 查看 agent.md 内容
|
|
357
|
+
6. **会话管理**:发送 \`/session\` 查看和切换会话
|
|
358
|
+
|
|
359
|
+
💡 **提示**:
|
|
360
|
+
- 直接发送消息即可与 Claude/Codex 对话
|
|
361
|
+
- 支持多项目会话管理,每个项目独立会话
|
|
362
|
+
- 所有命令以 \`/\` 开头
|
|
363
|
+
|
|
364
|
+
现在,请先使用 \`/bind\` 命令绑定您的项目目录,然后就可以开始工作了!`;
|
|
365
|
+
await this.sendMessage(owner, welcomeText);
|
|
366
|
+
logger.info(`[AUN] Welcome message sent to owner: ${owner}`);
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
logger.warn(`[AUN] Failed to send welcome message: ${e}`);
|
|
283
370
|
}
|
|
284
371
|
}
|
|
285
372
|
// ── Event handlers ──────────────────────────────────────────
|
|
@@ -939,6 +1026,7 @@ export class AUNChannelPlugin {
|
|
|
939
1026
|
accessToken: inst.accessToken,
|
|
940
1027
|
flushDelay: inst.flushDelay,
|
|
941
1028
|
encryptionSeed: inst.encryptionSeed,
|
|
1029
|
+
owner: inst.owner,
|
|
942
1030
|
aunTrace: config.debug?.aunTrace,
|
|
943
1031
|
});
|
|
944
1032
|
const adapter = {
|
package/dist/cli.js
CHANGED
|
@@ -1306,6 +1306,10 @@ async function cmdCtl(args) {
|
|
|
1306
1306
|
}
|
|
1307
1307
|
}
|
|
1308
1308
|
// ==================== Main ====================
|
|
1309
|
+
function getArgValue(args, flag) {
|
|
1310
|
+
const idx = args.indexOf(flag);
|
|
1311
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
1312
|
+
}
|
|
1309
1313
|
export async function main(args) {
|
|
1310
1314
|
const cmd = args[0] || 'start';
|
|
1311
1315
|
switch (cmd) {
|
|
@@ -1329,7 +1333,19 @@ export async function main(args) {
|
|
|
1329
1333
|
await cmdInitWecom();
|
|
1330
1334
|
}
|
|
1331
1335
|
else {
|
|
1332
|
-
|
|
1336
|
+
const nonInteractive = args.includes('--non-interactive');
|
|
1337
|
+
if (nonInteractive) {
|
|
1338
|
+
await cmdInit({
|
|
1339
|
+
nonInteractive: true,
|
|
1340
|
+
defaultPath: getArgValue(args, '--default-path') || process.cwd(),
|
|
1341
|
+
channel: getArgValue(args, '--channel') || 'aun',
|
|
1342
|
+
aunAid: getArgValue(args, '--aun-aid'),
|
|
1343
|
+
aunOwner: getArgValue(args, '--aun-owner'),
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
await cmdInit();
|
|
1348
|
+
}
|
|
1333
1349
|
}
|
|
1334
1350
|
break;
|
|
1335
1351
|
case 'start':
|
|
@@ -112,7 +112,7 @@ const aliases = {
|
|
|
112
112
|
'/rw': '/rewind'
|
|
113
113
|
};
|
|
114
114
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
115
|
-
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/send', '/check', '/p ', '/s ', '/name
|
|
115
|
+
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/send', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity'];
|
|
116
116
|
export class CommandHandler {
|
|
117
117
|
sessionManager;
|
|
118
118
|
config;
|
|
@@ -338,23 +338,23 @@ export class CommandHandler {
|
|
|
338
338
|
items.push({
|
|
339
339
|
group: '项目管理',
|
|
340
340
|
commands: [
|
|
341
|
-
{ cmd: '/pwd', label: '显示当前项目路径' },
|
|
342
|
-
{ cmd: '/p',
|
|
343
|
-
...(isOwner ? [{ cmd: '/bind',
|
|
341
|
+
{ cmd: '/pwd', label: '显示当前项目路径', desc: '查看当前会话绑定的项目目录' },
|
|
342
|
+
{ cmd: '/p', label: '列出或切换项目', desc: '切换到其他已配置的项目', next: { type: 'select', dynamic: true } },
|
|
343
|
+
...(isOwner ? [{ cmd: '/bind', label: '绑定新项目目录', desc: '将当前会话绑定到指定项目路径', next: { type: 'text' } }] : []),
|
|
344
344
|
]
|
|
345
345
|
});
|
|
346
346
|
}
|
|
347
347
|
items.push({
|
|
348
348
|
group: '会话管理',
|
|
349
349
|
commands: [
|
|
350
|
-
{ cmd: '/new',
|
|
351
|
-
{ cmd: '/s',
|
|
352
|
-
{ cmd: '/name',
|
|
353
|
-
{ cmd: '/del',
|
|
350
|
+
{ cmd: '/new', label: '创建新会话', desc: '清空历史,开始全新对话', next: { type: 'text' } },
|
|
351
|
+
{ cmd: '/s', label: '切换会话', desc: '切换到同项目下的其他会话', next: { type: 'select', dynamic: true } },
|
|
352
|
+
{ cmd: '/name', label: '重命名当前会话', desc: '为当前会话设置一个易识别的名称', next: { type: 'text' } },
|
|
353
|
+
{ cmd: '/del', label: '删除指定会话', desc: '永久删除一个非活跃会话', next: { type: 'select', dynamic: true } },
|
|
354
354
|
...(isAdmin ? [
|
|
355
|
-
{ cmd: '/fork',
|
|
356
|
-
{ cmd: '/rewind',
|
|
357
|
-
{ cmd: '/compact', label: '压缩会话上下文' },
|
|
355
|
+
{ cmd: '/fork', label: '分支当前会话', desc: '基于当前会话创建独立分支', next: { type: 'text' } },
|
|
356
|
+
{ cmd: '/rewind', label: '查看历史/撤销指定轮次', desc: '回退会话到指定轮次,可选择撤销文件改动' },
|
|
357
|
+
{ cmd: '/compact', label: '压缩会话上下文', desc: '将长对话压缩为摘要以节省 token' },
|
|
358
358
|
] : []),
|
|
359
359
|
]
|
|
360
360
|
});
|
|
@@ -362,31 +362,55 @@ export class CommandHandler {
|
|
|
362
362
|
items.push({
|
|
363
363
|
group: 'Agent 与模型',
|
|
364
364
|
commands: [
|
|
365
|
-
{ cmd: '/agent',
|
|
366
|
-
{ cmd: '/model',
|
|
367
|
-
{ cmd: '/effort',
|
|
365
|
+
{ cmd: '/agent', label: '切换 Agent 后端', desc: '切换当前会话使用的 AI 后端', next: { type: 'select', dynamic: true } },
|
|
366
|
+
{ cmd: '/model', label: '切换模型', desc: '切换当前 Agent 使用的模型版本', next: { type: 'select', dynamic: true } },
|
|
367
|
+
{ cmd: '/effort', label: '切换推理强度', desc: '调整模型推理深度,影响响应速度与质量', next: { type: 'select', items: [
|
|
368
|
+
{ value: 'low', label: 'Low' },
|
|
369
|
+
{ value: 'medium', label: 'Medium' },
|
|
370
|
+
{ value: 'high', label: 'High' },
|
|
371
|
+
{ value: 'max', label: 'Max' },
|
|
372
|
+
] } },
|
|
368
373
|
]
|
|
369
374
|
});
|
|
370
375
|
items.push({
|
|
371
376
|
group: '权限管理',
|
|
372
377
|
commands: [
|
|
373
|
-
{ cmd: '/perm',
|
|
378
|
+
{ cmd: '/perm', label: '权限模式管理', desc: '控制工具调用的审批策略', next: { type: 'select', items: [
|
|
379
|
+
...(isOwner ? [
|
|
380
|
+
{ value: 'auto', label: '自动模式', desc: '根据风险等级自动决定是否审批' },
|
|
381
|
+
{ value: 'bypass', label: '免审批模式', desc: '跳过所有工具审批确认' },
|
|
382
|
+
{ value: 'plan', label: '计划模式', desc: '仅允许只读操作,写操作需审批' },
|
|
383
|
+
{ value: 'edit', label: '编辑模式', desc: '允许文件编辑,其他操作需审批' },
|
|
384
|
+
{ value: 'request', label: '请求模式', desc: '所有操作均需审批' },
|
|
385
|
+
{ value: 'noask', label: '静默模式', desc: '不弹出审批,自动拒绝未授权操作' },
|
|
386
|
+
] : []),
|
|
387
|
+
{ value: 'allow', label: '允许此操作', desc: '本次允许当前待审批操作' },
|
|
388
|
+
{ value: 'always', label: '始终允许', desc: '永久允许同类操作' },
|
|
389
|
+
{ value: 'deny', label: '拒绝此操作', desc: '拒绝当前待审批操作' },
|
|
390
|
+
] } },
|
|
374
391
|
]
|
|
375
392
|
});
|
|
376
393
|
items.push({
|
|
377
394
|
group: '运维',
|
|
378
395
|
commands: [
|
|
379
|
-
{ cmd: '/status', label: '显示会话状态' },
|
|
380
|
-
{ cmd: '/stop', label: '中断当前任务' },
|
|
381
|
-
{ cmd: '/check', label: '检查渠道状态' },
|
|
382
|
-
{ cmd: '/activity',
|
|
396
|
+
{ cmd: '/status', label: '显示会话状态', desc: '查看当前会话、项目、Agent 的详细状态' },
|
|
397
|
+
{ cmd: '/stop', label: '中断当前任务', desc: '立即中断正在执行的 Agent 任务' },
|
|
398
|
+
{ cmd: '/check', label: '检查渠道状态', desc: '检查各消息渠道的连接健康状态' },
|
|
399
|
+
{ cmd: '/activity', label: '控制中间输出显示', desc: '设置工具调用过程的可见范围', next: { type: 'select', items: [
|
|
400
|
+
{ value: 'all', label: '全部显示', desc: '所有用户均可见中间输出' },
|
|
401
|
+
{ value: 'dm', label: '仅私聊', desc: '仅私聊中显示中间输出' },
|
|
402
|
+
{ value: 'owner', label: '仅 owner 私聊', desc: '仅 owner 的私聊中显示' },
|
|
403
|
+
{ value: 'none', label: '不显示', desc: '关闭所有中间输出' },
|
|
404
|
+
] } },
|
|
383
405
|
...(isAdmin ? [
|
|
384
|
-
{ cmd: '/restart',
|
|
406
|
+
{ cmd: '/restart', label: '重启/重连', desc: '重启服务或重连指定渠道', next: { type: 'select', dynamic: true } },
|
|
385
407
|
] : []),
|
|
386
408
|
...(isOwner ? [
|
|
387
|
-
{ cmd: '/
|
|
388
|
-
{ cmd: '/
|
|
389
|
-
|
|
409
|
+
{ cmd: '/send', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
|
|
410
|
+
{ cmd: '/agentmd', label: '管理 agent.md', desc: '查看或更新 AUN 网络上的 agent.md 身份文件', next: { type: 'select', items: [
|
|
411
|
+
{ value: 'put', label: '上传当前', desc: '将本地 agent.md 上传到 AUN 网络' },
|
|
412
|
+
{ value: 'set', label: '直接设置', desc: '输入内容直接更新 agent.md', next: { type: 'text' } },
|
|
413
|
+
] } },
|
|
390
414
|
] : []),
|
|
391
415
|
]
|
|
392
416
|
});
|
|
@@ -395,22 +419,67 @@ export class CommandHandler {
|
|
|
395
419
|
items.push({
|
|
396
420
|
group: '其他',
|
|
397
421
|
commands: [
|
|
398
|
-
{ cmd: '/status', label: '显示会话状态' },
|
|
399
|
-
{ cmd: '/check', label: '检查渠道健康' },
|
|
422
|
+
{ cmd: '/status', label: '显示会话状态', desc: '查看当前会话的基本状态' },
|
|
423
|
+
{ cmd: '/check', label: '检查渠道健康', desc: '检查消息渠道连接状态' },
|
|
400
424
|
]
|
|
401
425
|
});
|
|
402
426
|
}
|
|
403
427
|
items.push({
|
|
404
428
|
group: '帮助',
|
|
405
429
|
commands: [
|
|
406
|
-
{ cmd: '/help', label: '显示帮助信息' },
|
|
430
|
+
{ cmd: '/help', label: '显示帮助信息', desc: '列出所有可用命令及说明' },
|
|
407
431
|
]
|
|
408
432
|
});
|
|
409
433
|
return items;
|
|
410
434
|
}
|
|
411
|
-
/**
|
|
412
|
-
|
|
413
|
-
|
|
435
|
+
/** 动态子菜单:根据 cmd 路径返回选项列表(供 menu.query + cmd 使用) */
|
|
436
|
+
async getSubMenuItems(cmd, channel, channelId, userId) {
|
|
437
|
+
const session = await this.sessionManager.getActiveSession(channel, channelId);
|
|
438
|
+
if (cmd === '/s' || cmd === '/del') {
|
|
439
|
+
const sessions = await this.sessionManager.listSessions(channel, channelId);
|
|
440
|
+
const active = cmd === '/del' ? await this.sessionManager.getActiveSession(channel, channelId) : null;
|
|
441
|
+
const items = sessions
|
|
442
|
+
.filter(s => !active || s.id !== active.id)
|
|
443
|
+
.map(s => {
|
|
444
|
+
const shortId = s.agentSessionId ? s.agentSessionId.substring(0, 8) : '';
|
|
445
|
+
const time = s.updatedAt ? formatIdleTime(Date.now() - s.updatedAt) : '';
|
|
446
|
+
const parts = [shortId, time].filter(Boolean).join(' · ');
|
|
447
|
+
return {
|
|
448
|
+
value: s.name || s.id.slice(0, 8),
|
|
449
|
+
label: s.name || s.id.slice(0, 8),
|
|
450
|
+
desc: parts || undefined,
|
|
451
|
+
};
|
|
452
|
+
});
|
|
453
|
+
if (cmd === '/s') {
|
|
454
|
+
items.push({ value: 'cli', label: '查看 CLI 会话', desc: '列出未导入的 CLI 本地会话' });
|
|
455
|
+
}
|
|
456
|
+
return items;
|
|
457
|
+
}
|
|
458
|
+
if (cmd === '/p') {
|
|
459
|
+
const list = this.config.projects?.list || {};
|
|
460
|
+
return Object.entries(list).map(([name, path]) => ({ value: name, label: name, desc: path }));
|
|
461
|
+
}
|
|
462
|
+
if (cmd === '/agent') {
|
|
463
|
+
return [...this.agentMap.keys()].map(name => ({ value: name, label: name }));
|
|
464
|
+
}
|
|
465
|
+
if (cmd === '/model') {
|
|
466
|
+
const agent = this.getAgent(session?.agentId);
|
|
467
|
+
if (hasModelSwitcher(agent) && agent.listModels) {
|
|
468
|
+
const models = await agent.listModels() ?? [];
|
|
469
|
+
if (models.length > 0)
|
|
470
|
+
return models.map((m) => ({ value: m, label: m }));
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (cmd === '/restart') {
|
|
475
|
+
const isOwner = userId ? this.sessionManager.resolveIdentity(channel, userId).role === 'owner' : false;
|
|
476
|
+
const channels = [...this.adapters.keys()].map(name => ({ value: name, label: name, desc: '重连此渠道' }));
|
|
477
|
+
if (isOwner)
|
|
478
|
+
channels.unshift({ value: '', label: '重启服务', desc: '重启整个 EvolClaw 服务进程' });
|
|
479
|
+
return channels;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
414
483
|
isCommand(content) {
|
|
415
484
|
return content === '/p' || content === '/s' || quickCommandPrefixes.some(cmd => content.startsWith(cmd));
|
|
416
485
|
}
|
|
@@ -1879,6 +1948,8 @@ export class CommandHandler {
|
|
|
1879
1948
|
return response;
|
|
1880
1949
|
}
|
|
1881
1950
|
// /bind 命令:持久化项目到配置(不切换)(owner only)
|
|
1951
|
+
if (normalizedContent === '/bind')
|
|
1952
|
+
return '用法: /bind <路径>';
|
|
1882
1953
|
if (normalizedContent.startsWith('/bind ')) {
|
|
1883
1954
|
if (!isOwner)
|
|
1884
1955
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
@@ -2205,6 +2276,9 @@ export class CommandHandler {
|
|
|
2205
2276
|
return `✓ 已切换到会话: ${targetSession.name || sessionName}${continueHint}${lastInputLine}`;
|
|
2206
2277
|
}
|
|
2207
2278
|
// /rename 或 /name 命令:重命名当前会话
|
|
2279
|
+
if (normalizedContent === '/rename' || normalizedContent === '/name') {
|
|
2280
|
+
return '用法: /name <新名称> 或 /rename <新名称>';
|
|
2281
|
+
}
|
|
2208
2282
|
if (normalizedContent.startsWith('/rename ')) {
|
|
2209
2283
|
const newName = normalizedContent.slice(8).trim();
|
|
2210
2284
|
if (!newName)
|
|
@@ -152,13 +152,27 @@ export class MessageBridge {
|
|
|
152
152
|
return false;
|
|
153
153
|
if (parsed.type === 'menu.query') {
|
|
154
154
|
const identity = this.sessionManager.resolveIdentity(channel, msg.peerId);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
if (parsed.cmd) {
|
|
156
|
+
// 动态子菜单查询
|
|
157
|
+
const items = await this.cmdHandler.getSubMenuItems(parsed.cmd, channel, msg.channelId, msg.peerId);
|
|
158
|
+
const response = JSON.stringify({ type: 'menu.response', cmd: parsed.cmd, items: items ?? [] });
|
|
159
|
+
if (adapter?.sendCustomPayload) {
|
|
160
|
+
adapter.sendCustomPayload(msg.channelId, response);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
await sendReply(msg.channelId, response);
|
|
164
|
+
}
|
|
159
165
|
}
|
|
160
166
|
else {
|
|
161
|
-
|
|
167
|
+
// 全量菜单
|
|
168
|
+
const items = this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private');
|
|
169
|
+
const response = JSON.stringify({ type: 'menu.response', items });
|
|
170
|
+
if (adapter?.sendCustomPayload) {
|
|
171
|
+
adapter.sendCustomPayload(msg.channelId, response);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
await sendReply(msg.channelId, response);
|
|
175
|
+
}
|
|
162
176
|
}
|
|
163
177
|
return true;
|
|
164
178
|
}
|
|
@@ -337,15 +337,18 @@ export class MessageProcessor {
|
|
|
337
337
|
// 保存当前 flusher,用于 compact 事件
|
|
338
338
|
this.currentFlusher = flusher;
|
|
339
339
|
// 调用 AgentRunner(含上下文过长自动 compact 重试)
|
|
340
|
+
// 捕获当前消息的上下文(闭包),避免后续消息处理时串台
|
|
341
|
+
const capturedChannelId = message.channelId;
|
|
342
|
+
const capturedReplyContext = this.getReplyContext(message);
|
|
340
343
|
// 设置权限审批的消息发送回调(指向当前渠道)
|
|
341
344
|
agent.setSendPrompt(async (text) => {
|
|
342
|
-
await adapter.sendText(
|
|
345
|
+
await adapter.sendText(capturedChannelId, text, capturedReplyContext);
|
|
343
346
|
});
|
|
344
347
|
// 设置权限审批的交互上下文(支持交互卡片)
|
|
345
348
|
agent.setPermissionContext?.(session.id, {
|
|
346
349
|
adapter,
|
|
347
|
-
channelId:
|
|
348
|
-
replyContext:
|
|
350
|
+
channelId: capturedChannelId,
|
|
351
|
+
replyContext: capturedReplyContext,
|
|
349
352
|
interactionRouter: this.interactionRouter,
|
|
350
353
|
interceptNextMessage: this.messageQueue
|
|
351
354
|
? (sessionKey, handler) => this.messageQueue.interceptNext(sessionKey, handler)
|
package/dist/core/permission.js
CHANGED
|
@@ -94,6 +94,12 @@ export function summarizeToolInput(toolName, input) {
|
|
|
94
94
|
'Glob': (i) => `pattern: ${i.pattern}`,
|
|
95
95
|
'Agent': (i) => i.description || i.prompt?.substring(0, 80),
|
|
96
96
|
'Skill': (i) => i.skill ? `${i.skill}${i.args ? ' ' + i.args : ''}` : undefined,
|
|
97
|
+
'ExitPlanMode': (i) => {
|
|
98
|
+
if (i.allowedPrompts?.length) {
|
|
99
|
+
return `计划包含 ${i.allowedPrompts.length} 项操作权限`;
|
|
100
|
+
}
|
|
101
|
+
return '计划审批';
|
|
102
|
+
},
|
|
97
103
|
'TodoWrite': (i) => {
|
|
98
104
|
if (Array.isArray(i.todos)) {
|
|
99
105
|
return i.todos.map((t) => t.content || t.task || t.text).filter(Boolean).join(', ').substring(0, 80);
|
|
@@ -186,7 +192,6 @@ export class PermissionGateway {
|
|
|
186
192
|
},
|
|
187
193
|
channelId: context?.channelId || '',
|
|
188
194
|
sessionId,
|
|
189
|
-
expiresAt: Date.now() + this.timeout,
|
|
190
195
|
};
|
|
191
196
|
// 尝试富交互
|
|
192
197
|
let interactionSent = false;
|
|
@@ -204,16 +209,7 @@ export class PermissionGateway {
|
|
|
204
209
|
await sendPrompt(`🔐 权限请求\n工具:${toolName}\n操作:${displaySummary}${reasonLine}\n\n回复 /perm allow 本次允许 | always 始终允许 | deny 拒绝`);
|
|
205
210
|
}
|
|
206
211
|
return new Promise((resolve) => {
|
|
207
|
-
|
|
208
|
-
this.pending.delete(requestId);
|
|
209
|
-
// 清理 router 注册(仅删除本次请求,不影响其他交互)
|
|
210
|
-
if (interactionSent && context?.interactionRouter) {
|
|
211
|
-
context.interactionRouter.cancel(requestId);
|
|
212
|
-
}
|
|
213
|
-
this.eventBus?.publish({ type: 'permission:timeout', sessionId, requestId });
|
|
214
|
-
resolve('deny');
|
|
215
|
-
}, this.timeout);
|
|
216
|
-
this.pending.set(requestId, { sessionId, toolName, resolve, timer });
|
|
212
|
+
this.pending.set(requestId, { sessionId, toolName, resolve, timer: setTimeout(() => { }, 0) });
|
|
217
213
|
// 如果发了交互卡片,同时注册到 InteractionRouter
|
|
218
214
|
if (interactionSent && context?.interactionRouter) {
|
|
219
215
|
context.interactionRouter.register(requestId, sessionId, (action) => {
|
|
@@ -11,16 +11,17 @@ import path from 'path';
|
|
|
11
11
|
import os from 'os';
|
|
12
12
|
import crypto from 'crypto';
|
|
13
13
|
import { createRequire } from 'module';
|
|
14
|
-
import { execFile } from 'child_process';
|
|
14
|
+
import { execFile, execFileSync } from 'child_process';
|
|
15
15
|
import { promisify } from 'util';
|
|
16
16
|
import { resolvePaths } from '../paths.js';
|
|
17
17
|
import { normalizeChannelInstances } from '../config.js';
|
|
18
18
|
import { selectInstance } from './init.js';
|
|
19
19
|
import { isWindows } from './cross-platform.js';
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
-
async function npmInstallGlobal(pkg) {
|
|
21
|
+
export async function npmInstallGlobal(pkg) {
|
|
22
|
+
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
22
23
|
try {
|
|
23
|
-
await execFileAsync(
|
|
24
|
+
await execFileAsync(npmCmd, ['install', '-g', pkg], { timeout: 180000 });
|
|
24
25
|
}
|
|
25
26
|
catch (e) {
|
|
26
27
|
if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES')) {
|
|
@@ -575,7 +576,8 @@ function compareVersion(a, min) {
|
|
|
575
576
|
return parts[1] > min[1];
|
|
576
577
|
return parts[2] >= min[2];
|
|
577
578
|
}
|
|
578
|
-
function resolveAunCoreSdkPkg() {
|
|
579
|
+
export function resolveAunCoreSdkPkg() {
|
|
580
|
+
// Strategy 1: createRequire (works when evolclaw and SDK share the same node_modules)
|
|
579
581
|
try {
|
|
580
582
|
const esmRequire = createRequire(import.meta.url);
|
|
581
583
|
const entry = esmRequire.resolve(AUN_CORE_SDK_PKG);
|
|
@@ -592,14 +594,25 @@ function resolveAunCoreSdkPkg() {
|
|
|
592
594
|
}
|
|
593
595
|
dir = path.dirname(dir);
|
|
594
596
|
}
|
|
595
|
-
return null;
|
|
596
597
|
}
|
|
597
|
-
|
|
598
|
-
|
|
598
|
+
else {
|
|
599
|
+
const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
600
|
+
return { version: data.version, path: pkgPath };
|
|
601
|
+
}
|
|
599
602
|
}
|
|
600
|
-
catch {
|
|
601
|
-
|
|
603
|
+
catch { /* fall through to strategy 2 */ }
|
|
604
|
+
// Strategy 2: npm root -g fallback (handles Windows / path mismatch)
|
|
605
|
+
try {
|
|
606
|
+
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
607
|
+
const globalRoot = execFileSync(npmCmd, ['root', '-g'], { encoding: 'utf-8', timeout: 10000 }).trim();
|
|
608
|
+
const pkgPath = path.join(globalRoot, AUN_CORE_SDK_PKG, 'package.json');
|
|
609
|
+
if (fs.existsSync(pkgPath)) {
|
|
610
|
+
const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
611
|
+
return { version: data.version, path: pkgPath };
|
|
612
|
+
}
|
|
602
613
|
}
|
|
614
|
+
catch { /* not found */ }
|
|
615
|
+
return null;
|
|
603
616
|
}
|
|
604
617
|
export async function checkAunEnvironment(rl) {
|
|
605
618
|
console.log('\n🔍 AUN 环境检查...\n');
|
|
@@ -699,11 +712,28 @@ export async function setupAunAid(rl, _config) {
|
|
|
699
712
|
client._gatewayUrl = `wss://gateway.${domain}:${port}/aun`;
|
|
700
713
|
const result = await client.auth.createAid({ aid });
|
|
701
714
|
console.log(` ✓ AID ${result.aid} 创建成功`);
|
|
715
|
+
// 下载 CA 根证书(如果本地不存在)
|
|
716
|
+
const caDir = path.join(aunPath, 'CA', 'root');
|
|
717
|
+
const caCertPath = path.join(caDir, 'root.crt');
|
|
718
|
+
if (!fs.existsSync(caCertPath)) {
|
|
719
|
+
try {
|
|
720
|
+
fs.mkdirSync(caDir, { recursive: true });
|
|
721
|
+
const httpPort = gatewayPort === 443 ? 20001 : gatewayPort;
|
|
722
|
+
const resp = await fetch(`https://gateway.${domain}:${httpPort}/pki/chain`);
|
|
723
|
+
if (resp.ok) {
|
|
724
|
+
fs.writeFileSync(caCertPath, await resp.text());
|
|
725
|
+
console.log(' ✓ CA 根证书已下载');
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
console.warn(` ⚠ CA 根证书下载失败: ${e},可稍后手动下载`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
702
732
|
// Collect agent.md info and publish
|
|
703
733
|
const typeInput = (await ask(rl, ' Agent 类型 human/ai [ai]: ')).trim().toLowerCase();
|
|
704
734
|
const agentType = typeInput === 'human' ? 'human' : 'ai';
|
|
705
735
|
const agentName = aid.split('.')[0];
|
|
706
|
-
const agentMdContent = `---\naid: "${aid}"\nname: "${agentName}"\ntype: "${agentType}"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\n---\n`;
|
|
736
|
+
const agentMdContent = `---\naid: "${aid}"\nname: "${agentName}"\ntype: "${agentType}"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
|
|
707
737
|
try {
|
|
708
738
|
await client.auth.uploadAgentMd(agentMdContent);
|
|
709
739
|
console.log(' ✓ agent.md 已发布');
|
|
@@ -732,7 +762,24 @@ export async function setupAunAid(rl, _config) {
|
|
|
732
762
|
break;
|
|
733
763
|
// default: retry with new AID
|
|
734
764
|
}
|
|
735
|
-
|
|
765
|
+
// Owner 必填
|
|
766
|
+
console.log('\n📋 Owner 配置');
|
|
767
|
+
console.log(' Owner 将接收欢迎消息并拥有管理权限');
|
|
768
|
+
let owner = '';
|
|
769
|
+
while (!owner) {
|
|
770
|
+
const ownerInput = (await ask(rl, ' Owner AID (必填): ')).trim();
|
|
771
|
+
if (!ownerInput) {
|
|
772
|
+
console.log(' ⚠ Owner AID 不能为空');
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (!isValidAid(ownerInput)) {
|
|
776
|
+
console.log(' ⚠ Owner AID 格式无效');
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
owner = ownerInput;
|
|
780
|
+
console.log(` ✓ Owner 已设置: ${owner}`);
|
|
781
|
+
}
|
|
782
|
+
return { aid, owner };
|
|
736
783
|
}
|
|
737
784
|
export async function cmdInitAun() {
|
|
738
785
|
const p = resolvePaths();
|
|
@@ -761,6 +808,7 @@ export async function cmdInitAun() {
|
|
|
761
808
|
config.channels.aun = {
|
|
762
809
|
enabled: true,
|
|
763
810
|
aid: result.aid,
|
|
811
|
+
owner: result.owner,
|
|
764
812
|
};
|
|
765
813
|
if (!config.channels.defaultChannel)
|
|
766
814
|
config.channels.defaultChannel = 'aun';
|
package/dist/utils/init.js
CHANGED
|
@@ -376,7 +376,7 @@ async function offerRichContentRenderer(rl, config) {
|
|
|
376
376
|
// ==================== AUN AID Helpers ====================
|
|
377
377
|
// Moved to init-channel.ts
|
|
378
378
|
// ==================== Main ====================
|
|
379
|
-
export async function cmdInit() {
|
|
379
|
+
export async function cmdInit(options) {
|
|
380
380
|
const p = resolvePaths();
|
|
381
381
|
ensureDataDirs();
|
|
382
382
|
if (fs.existsSync(p.pid)) {
|
|
@@ -393,6 +393,75 @@ export async function cmdInit() {
|
|
|
393
393
|
console.log(`❌ 找不到示例配置: ${sampleSrc}`);
|
|
394
394
|
return;
|
|
395
395
|
}
|
|
396
|
+
// 非交互式模式
|
|
397
|
+
if (options?.nonInteractive) {
|
|
398
|
+
const config = JSON.parse(fs.readFileSync(sampleSrc, 'utf-8'));
|
|
399
|
+
const defaultPath = options.defaultPath || path.join(os.homedir(), 'evolclaw-project');
|
|
400
|
+
if (!fs.existsSync(defaultPath))
|
|
401
|
+
fs.mkdirSync(defaultPath, { recursive: true });
|
|
402
|
+
config.projects.defaultPath = defaultPath;
|
|
403
|
+
config.projects.list = { [path.basename(defaultPath)]: defaultPath };
|
|
404
|
+
if (options.channel === 'aun' && !options.aunAid) {
|
|
405
|
+
throw new Error('--aun-aid is required for AUN channel (e.g. --aun-aid mybot.agentid.pub)');
|
|
406
|
+
}
|
|
407
|
+
if (options.channel === 'aun' && options.aunAid) {
|
|
408
|
+
// 自动安装 AUN SDK
|
|
409
|
+
const { resolveAunCoreSdkPkg, npmInstallGlobal } = await import('./init-channel.js');
|
|
410
|
+
if (!resolveAunCoreSdkPkg()) {
|
|
411
|
+
console.log('正在安装 @eleans/aun-core-sdk...');
|
|
412
|
+
await npmInstallGlobal('@eleans/aun-core-sdk@latest');
|
|
413
|
+
}
|
|
414
|
+
// 创建 AID(如果本地不存在)
|
|
415
|
+
const aunPath = path.join(os.homedir(), '.aun');
|
|
416
|
+
const aidDir = path.join(aunPath, 'AIDs', options.aunAid);
|
|
417
|
+
if (!fs.existsSync(path.join(aidDir, 'private'))) {
|
|
418
|
+
const { AUNClient } = await import('@eleans/aun-core-sdk');
|
|
419
|
+
const client = new AUNClient({ aun_path: aunPath });
|
|
420
|
+
const domain = options.aunAid.split('.').slice(1).join('.');
|
|
421
|
+
client._gatewayUrl = `wss://gateway.${domain}:443/aun`;
|
|
422
|
+
await client.auth.createAid({ aid: options.aunAid });
|
|
423
|
+
// 下载 CA 根证书(如果本地不存在)
|
|
424
|
+
const caDir = path.join(aunPath, 'CA', 'root');
|
|
425
|
+
const caCertPath = path.join(caDir, 'root.crt');
|
|
426
|
+
if (!fs.existsSync(caCertPath)) {
|
|
427
|
+
try {
|
|
428
|
+
fs.mkdirSync(caDir, { recursive: true });
|
|
429
|
+
const resp = await fetch(`https://gateway.${domain}:20001/pki/chain`);
|
|
430
|
+
if (resp.ok) {
|
|
431
|
+
fs.writeFileSync(caCertPath, await resp.text());
|
|
432
|
+
console.log('✓ CA 根证书已下载');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
console.warn(`⚠ CA 根证书下载失败: ${e},可稍后手动下载`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// 写入初始 agent.md(initialized: false)
|
|
440
|
+
const agentName = options.aunAid.split('.')[0];
|
|
441
|
+
const agentMd = `---\naid: "${options.aunAid}"\nname: "${agentName}"\ntype: "ai"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
|
|
442
|
+
try {
|
|
443
|
+
await client.auth.uploadAgentMd(agentMd);
|
|
444
|
+
fs.writeFileSync(path.join(aidDir, 'agent.md'), agentMd, 'utf-8');
|
|
445
|
+
}
|
|
446
|
+
catch { }
|
|
447
|
+
try {
|
|
448
|
+
await client.close();
|
|
449
|
+
}
|
|
450
|
+
catch { }
|
|
451
|
+
}
|
|
452
|
+
config.channels.aun = {
|
|
453
|
+
enabled: true,
|
|
454
|
+
aid: options.aunAid,
|
|
455
|
+
...(options.aunOwner && { owner: options.aunOwner }),
|
|
456
|
+
};
|
|
457
|
+
config.channels.defaultChannel = 'aun';
|
|
458
|
+
}
|
|
459
|
+
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
460
|
+
console.log(`✓ 已创建配置文件: ${p.config}`);
|
|
461
|
+
setupEnvVar(resolveRoot());
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
// 交互式模式
|
|
396
465
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
397
466
|
try {
|
|
398
467
|
if (fs.existsSync(p.config)) {
|
|
@@ -490,6 +559,7 @@ export async function cmdInit() {
|
|
|
490
559
|
config.channels.aun = {
|
|
491
560
|
enabled: true,
|
|
492
561
|
aid: result.aid,
|
|
562
|
+
owner: result.owner,
|
|
493
563
|
};
|
|
494
564
|
channelConfigured = true;
|
|
495
565
|
config.channels.defaultChannel = 'aun';
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# EvolClaw AUN 通道安装指南
|
|
2
|
+
|
|
3
|
+
EvolClaw 是一个开源的 AI Agent 网关,将 Claude/Codex 等 AI 后端连接到 AUN(AgentUnion.Network)消息通道。
|
|
4
|
+
|
|
5
|
+
- **npm**: https://www.npmjs.com/package/evolclaw
|
|
6
|
+
- **GitHub**: https://github.com/eLeanwang/evolclaw
|
|
7
|
+
- **AUN**: https://agentunion.network
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 前置条件
|
|
12
|
+
|
|
13
|
+
- Node.js >= 18,npm >= 8
|
|
14
|
+
- 一个 AUN Agent ID(AID)——标识你的 Agent
|
|
15
|
+
- 一个 Owner AID——接收欢迎消息和管理权限
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 安装步骤
|
|
20
|
+
|
|
21
|
+
### 步骤 1:检查环境
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node --version # 需要 >= 18
|
|
25
|
+
npm --version # 需要 >= 8
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
版本不满足请先升级:https://nodejs.org
|
|
29
|
+
|
|
30
|
+
### 步骤 2:安装 EvolClaw
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g evolclaw
|
|
34
|
+
evolclaw --version
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
遇到权限错误(EACCES):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 方案 A
|
|
41
|
+
sudo npm install -g evolclaw
|
|
42
|
+
|
|
43
|
+
# 方案 B(推荐,无需 sudo)
|
|
44
|
+
npm config set prefix ~/.npm-global
|
|
45
|
+
export PATH=~/.npm-global/bin:$PATH
|
|
46
|
+
npm install -g evolclaw
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 步骤 3:准备配置信息
|
|
50
|
+
|
|
51
|
+
| 参数 | 说明 | 示例 |
|
|
52
|
+
|------|------|------|
|
|
53
|
+
| `--aun-aid` | **必填**,你的 AUN Agent ID | `mybot.agentid.pub` |
|
|
54
|
+
| `--aun-owner` | **必填**,Owner 的 AUN ID | `alice.agentid.pub` |
|
|
55
|
+
| `--default-path` | 默认项目路径(可选,默认当前目录) | `/home/user/myproject` |
|
|
56
|
+
|
|
57
|
+
如果用户在触发安装时已提供 Owner AID(例如消息中包含"将 xxx 添加为 Owner ID"),直接使用该值,否则询问用户。
|
|
58
|
+
|
|
59
|
+
### 步骤 4:非交互式初始化
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
evolclaw init --non-interactive \
|
|
63
|
+
--default-path "$PWD" \
|
|
64
|
+
--channel aun \
|
|
65
|
+
--aun-aid <AID> \
|
|
66
|
+
--aun-owner <Owner AID>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
此命令自动完成:
|
|
70
|
+
1. 创建配置文件 `~/.evolclaw/data/evolclaw.json`
|
|
71
|
+
2. 安装 `@eleans/aun-core-sdk`
|
|
72
|
+
3. 创建 AID 密钥对(如本地不存在)
|
|
73
|
+
4. 下载 CA 根证书到 `~/.aun/CA/root/root.crt`
|
|
74
|
+
5. 写入初始 `agent.md`(`initialized: false`)
|
|
75
|
+
6. 写入配置(含 owner 字段)
|
|
76
|
+
|
|
77
|
+
### 步骤 5:启动前验证
|
|
78
|
+
|
|
79
|
+
读取配置文件确认关键字段:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cat ~/.evolclaw/data/evolclaw.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
检查:
|
|
86
|
+
- `channels.aun.aid` — 你的 AID
|
|
87
|
+
- `channels.aun.owner` — Owner AID
|
|
88
|
+
- `projects.defaultPath` — 项目路径(目录需存在)
|
|
89
|
+
- `channels.defaultChannel` — 应为 `"aun"`
|
|
90
|
+
|
|
91
|
+
如发现缺失或异常,向用户说明并提供修复方案。
|
|
92
|
+
|
|
93
|
+
### 步骤 6:启动服务
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
evolclaw start
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 步骤 7:验证运行状态
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
evolclaw status
|
|
103
|
+
tail -n 30 ~/.evolclaw/logs/evolclaw.log
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
日志中应出现:`[AUN] Connected as @<aid>`
|
|
107
|
+
|
|
108
|
+
如未出现或有错误,读取完整日志分析原因并提供修复建议。
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 首次连接自动行为
|
|
113
|
+
|
|
114
|
+
EvolClaw 首次连接 AUN 网络时自动:
|
|
115
|
+
|
|
116
|
+
1. 检测 `~/.aun/AIDs/<aid>/agent.md` 中的 `initialized` 字段
|
|
117
|
+
2. 若为 `false`,生成完整 agent.md 并发布到 AUN 网络
|
|
118
|
+
3. 向 Owner 发送欢迎消息
|
|
119
|
+
|
|
120
|
+
无需手动触发,连接成功后自动完成。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 安装完成
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
✅ EvolClaw 安装完成!
|
|
128
|
+
|
|
129
|
+
服务已启动并连接到 AUN 网络。
|
|
130
|
+
首次连接后你将在 AUN 客户端收到欢迎消息。
|
|
131
|
+
使用 /bind <项目路径> 绑定工作目录即可开始。
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 常见问题
|
|
137
|
+
|
|
138
|
+
**Q: AID 已存在怎么办?**
|
|
139
|
+
`evolclaw init --non-interactive` 会检测本地密钥,已存在则跳过创建。
|
|
140
|
+
|
|
141
|
+
**Q: 启动失败怎么办?**
|
|
142
|
+
查看 `~/.evolclaw/logs/evolclaw.log` 或运行 `evolclaw diagnose`。
|
|
143
|
+
|
|
144
|
+
**Q: 如何重启/查看日志?**
|
|
145
|
+
```bash
|
|
146
|
+
evolclaw restart
|
|
147
|
+
evolclaw logs
|
|
148
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolclaw",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/",
|
|
12
12
|
"!dist/experimental/",
|
|
13
|
-
"data/evolclaw.sample.json"
|
|
13
|
+
"data/evolclaw.sample.json",
|
|
14
|
+
"evolclaw-install-aun.md"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"dev": "tsx watch src/index.ts",
|