evolclaw 3.1.5 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -3
- package/dist/agents/claude-runner.js +69 -24
- package/dist/agents/kit-renderer.js +15 -4
- package/dist/aun/aid/agentmd.js +10 -3
- package/dist/aun/msg/group.js +2 -2
- package/dist/channels/aun.js +98 -12
- package/dist/channels/dingtalk.js +1 -1
- package/dist/channels/feishu.js +31 -9
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/channels/wecom.js +1 -1
- package/dist/cli/agent.js +10 -11
- package/dist/cli/bench.js +1 -5
- package/dist/cli/help.js +8 -0
- package/dist/cli/index.js +91 -128
- package/dist/cli/init.js +37 -21
- package/dist/cli/link-rules.js +1 -7
- package/dist/cli/model.js +231 -6
- package/dist/config-store.js +1 -22
- package/dist/core/command-handler.js +181 -48
- package/dist/core/evolagent.js +0 -18
- package/dist/core/message/im-renderer.js +9 -20
- package/dist/core/message/message-bridge.js +7 -3
- package/dist/core/message/message-processor.js +138 -35
- package/dist/core/relation/peer-identity.js +23 -11
- package/dist/core/trigger/parser.js +4 -4
- package/dist/core/trigger/scheduler.js +20 -6
- package/dist/index.js +55 -5
- package/dist/ipc.js +1 -1
- package/dist/utils/error-utils.js +6 -0
- package/dist/utils/process-introspect.js +7 -5
- package/kits/docs/INDEX.md +4 -8
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolclaw/INDEX.md +43 -0
- package/kits/docs/evolclaw/group.md +13 -6
- package/kits/docs/evolclaw/model.md +51 -0
- package/kits/docs/evolclaw/msg.md +5 -0
- package/kits/docs/venues/group.md +13 -1
- package/kits/eck_manifest.json +9 -0
- package/kits/rules/06-channel.md +5 -1
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +7 -5
- package/kits/templates/system-fragments/commands.md +19 -0
- package/kits/templates/system-fragments/session.md +9 -0
- package/kits/templates/system-fragments/venue.md +15 -0
- package/package.json +3 -3
package/dist/channels/feishu.js
CHANGED
|
@@ -283,7 +283,8 @@ export class FeishuChannel {
|
|
|
283
283
|
else if (msg.message_type === 'merge_forward') {
|
|
284
284
|
const { text: mergedText, images: mergedImages } = await this.extractMergeForwardContent(msg.message_id, msg.chat_id);
|
|
285
285
|
if (mergedText) {
|
|
286
|
-
|
|
286
|
+
// 直接发送合并转发时,parent_id 指向自己,引用解析会把相同内容填入 quotedText 导致重复,丢弃
|
|
287
|
+
const finalContent = mergedText;
|
|
287
288
|
const allImages = [...quotedImages, ...mergedImages];
|
|
288
289
|
await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: allImages.length > 0 ? allImages : undefined, peerId, peerName, messageId: msg.message_id, threadId, rootId, chatType });
|
|
289
290
|
}
|
|
@@ -513,10 +514,15 @@ export class FeishuChannel {
|
|
|
513
514
|
if (options.replyInThread) {
|
|
514
515
|
replyData.reply_in_thread = true;
|
|
515
516
|
}
|
|
516
|
-
await this.client.im.message.reply({
|
|
517
|
+
const replyRes = await this.client.im.message.reply({
|
|
517
518
|
path: { message_id: options.replyToMessageId },
|
|
518
519
|
data: replyData
|
|
519
520
|
});
|
|
521
|
+
if (options.replyInThread && options.onThreadCreated) {
|
|
522
|
+
const newThreadId = replyRes?.data?.thread_id;
|
|
523
|
+
if (newThreadId)
|
|
524
|
+
options.onThreadCreated(newThreadId);
|
|
525
|
+
}
|
|
520
526
|
}
|
|
521
527
|
else {
|
|
522
528
|
await this.client.im.message.create({
|
|
@@ -710,13 +716,18 @@ export class FeishuChannel {
|
|
|
710
716
|
// seenThreads 无时间戳,仅限容量(话题持久存在,不按时间清理)
|
|
711
717
|
if (this.seenThreads.size > 1000)
|
|
712
718
|
this.seenThreads.clear();
|
|
713
|
-
//
|
|
714
|
-
if (
|
|
719
|
+
// 重写文件,去掉过期条目(仅在有记录被清理时才写)
|
|
720
|
+
if (cleaned > 0 && this.config.seenMsgFile) {
|
|
715
721
|
try {
|
|
716
|
-
|
|
717
|
-
.
|
|
718
|
-
|
|
719
|
-
|
|
722
|
+
if (this.seenMessages.size === 0) {
|
|
723
|
+
fs.unlinkSync(this.config.seenMsgFile);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
const lines = [...this.seenMessages.entries()]
|
|
727
|
+
.map(([id, ts]) => JSON.stringify({ id, ts }))
|
|
728
|
+
.join('\n') + '\n';
|
|
729
|
+
fs.writeFileSync(this.config.seenMsgFile, lines);
|
|
730
|
+
}
|
|
720
731
|
}
|
|
721
732
|
catch { }
|
|
722
733
|
}
|
|
@@ -1196,6 +1207,15 @@ export function buildResolvedV2(interaction, response) {
|
|
|
1196
1207
|
});
|
|
1197
1208
|
bodyElements.push({ tag: 'markdown', content: lines.join('\n') });
|
|
1198
1209
|
}
|
|
1210
|
+
// CommandCard: 显示原有按钮列表(保留上下文)
|
|
1211
|
+
if (kind.kind === 'command-card' && kind.buttons?.length) {
|
|
1212
|
+
const lines = kind.buttons.map(btn => {
|
|
1213
|
+
const prefix = btn.command === action ? '✓' : '•';
|
|
1214
|
+
const cleanLabel = btn.label.replace(/^✓\s*/, '');
|
|
1215
|
+
return `${prefix} ${cleanLabel}`;
|
|
1216
|
+
});
|
|
1217
|
+
bodyElements.push({ tag: 'markdown', content: lines.join('\n') });
|
|
1218
|
+
}
|
|
1199
1219
|
return {
|
|
1200
1220
|
toast: { type: 'success', content: statusText },
|
|
1201
1221
|
card: {
|
|
@@ -1403,7 +1423,7 @@ export class FeishuChannelPlugin {
|
|
|
1403
1423
|
const adapter = {
|
|
1404
1424
|
channelName: inst.name,
|
|
1405
1425
|
channelKey: inst.name,
|
|
1406
|
-
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true },
|
|
1426
|
+
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true, thread: true },
|
|
1407
1427
|
send: async (envelope, payload) => {
|
|
1408
1428
|
const ctx = envelope.replyContext;
|
|
1409
1429
|
const channelId = envelope.channelId;
|
|
@@ -1417,6 +1437,8 @@ export class FeishuChannelPlugin {
|
|
|
1417
1437
|
const sendCtx = { ...(ctx ?? {}) };
|
|
1418
1438
|
if (payload.kind === 'result.text' && payload.isFinal)
|
|
1419
1439
|
sendCtx.title = '✅ 最终回复:';
|
|
1440
|
+
if (ctx?.metadata?.onThreadCreated)
|
|
1441
|
+
sendCtx.onThreadCreated = ctx.metadata.onThreadCreated;
|
|
1420
1442
|
await channel.sendMessage(channelId, payload.text, sendCtx);
|
|
1421
1443
|
return;
|
|
1422
1444
|
}
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -335,7 +335,7 @@ export class QQBotChannelPlugin {
|
|
|
335
335
|
const adapter = {
|
|
336
336
|
channelName: inst.name,
|
|
337
337
|
channelKey: inst.name,
|
|
338
|
-
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
338
|
+
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
|
|
339
339
|
send: async (envelope, payload) => {
|
|
340
340
|
const ctx = envelope.replyContext;
|
|
341
341
|
const channelId = envelope.channelId;
|
package/dist/channels/wechat.js
CHANGED
|
@@ -731,7 +731,7 @@ export class WechatChannelPlugin {
|
|
|
731
731
|
const adapter = {
|
|
732
732
|
channelName: inst.name,
|
|
733
733
|
channelKey: inst.name,
|
|
734
|
-
capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true },
|
|
734
|
+
capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true, thread: false },
|
|
735
735
|
send: async (envelope, payload) => {
|
|
736
736
|
const channelId = envelope.channelId;
|
|
737
737
|
switch (payload.kind) {
|
package/dist/channels/wecom.js
CHANGED
|
@@ -491,7 +491,7 @@ export class WecomChannelPlugin {
|
|
|
491
491
|
const adapter = {
|
|
492
492
|
channelName: inst.name,
|
|
493
493
|
channelKey: inst.name,
|
|
494
|
-
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
494
|
+
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
|
|
495
495
|
send: async (envelope, payload) => {
|
|
496
496
|
const ctx = envelope.replyContext;
|
|
497
497
|
const channelId = envelope.channelId;
|
package/dist/cli/agent.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import readline from 'readline';
|
|
5
4
|
import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
|
|
6
5
|
import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
|
|
@@ -11,11 +10,6 @@ import { commandExists } from '../utils/cross-platform.js';
|
|
|
11
10
|
import { isCodexSdkAvailable } from '../agents/codex-runner.js';
|
|
12
11
|
// ==================== Helpers ====================
|
|
13
12
|
const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
|
|
14
|
-
const BASEAGENT_ENV_KEY = {
|
|
15
|
-
claude: 'ANTHROPIC_API_KEY',
|
|
16
|
-
codex: 'OPENAI_API_KEY',
|
|
17
|
-
gemini: 'GEMINI_API_KEY',
|
|
18
|
-
};
|
|
19
13
|
function isBaseagentAvailable(baseagent) {
|
|
20
14
|
if (baseagent === 'codex')
|
|
21
15
|
return isCodexSdkAvailable();
|
|
@@ -30,8 +24,7 @@ function pickDefaultBaseagent(available) {
|
|
|
30
24
|
return available.includes('claude') ? 'claude' : available[0];
|
|
31
25
|
}
|
|
32
26
|
function buildBaseagentsBlock(chosen) {
|
|
33
|
-
|
|
34
|
-
return { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} };
|
|
27
|
+
return { [chosen]: {} };
|
|
35
28
|
}
|
|
36
29
|
const DEFAULT_CHATMODE = { private: 'interactive', group: 'proactive', nothuman: 'proactive' };
|
|
37
30
|
const DEFAULT_DISPATCH = 'mention';
|
|
@@ -296,11 +289,11 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
296
289
|
const defaults = loadDefaults();
|
|
297
290
|
const rootPath = defaults?.projects?.rootPath
|
|
298
291
|
|| (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
|
|
299
|
-
||
|
|
292
|
+
|| resolvePaths().root + '/projects';
|
|
300
293
|
suggestedProjectPath = deriveAgentProjectPath(rootPath, aid);
|
|
301
294
|
}
|
|
302
295
|
catch {
|
|
303
|
-
suggestedProjectPath = deriveAgentProjectPath(
|
|
296
|
+
suggestedProjectPath = deriveAgentProjectPath(resolvePaths().root + '/projects', aid);
|
|
304
297
|
}
|
|
305
298
|
const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
|
|
306
299
|
const projectPath = projectInput || suggestedProjectPath;
|
|
@@ -633,7 +626,7 @@ export async function agentSyncAids() {
|
|
|
633
626
|
const defaults = loadDefaults();
|
|
634
627
|
const rootPath = defaults?.projects?.rootPath
|
|
635
628
|
|| (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
|
|
636
|
-
||
|
|
629
|
+
|| resolvePaths().root + '/projects';
|
|
637
630
|
const created = [];
|
|
638
631
|
for (const aid of localAids) {
|
|
639
632
|
if (existingAids.has(aid))
|
|
@@ -761,6 +754,12 @@ export async function agentSet(aid, key, rawValue) {
|
|
|
761
754
|
return { ok: false, error: `Failed to read config: ${e?.message || e}` };
|
|
762
755
|
}
|
|
763
756
|
const value = parseJsonValue(rawValue);
|
|
757
|
+
// active_baseagent 白名单校验:只允许已知 baseagent,挡住把模型名(如 deepseek)误设为后端
|
|
758
|
+
if (key === 'active_baseagent') {
|
|
759
|
+
if (typeof value !== 'string' || !BASEAGENT_CANDIDATES.includes(value)) {
|
|
760
|
+
return { ok: false, error: `无效 active_baseagent: ${JSON.stringify(value)}(可选: ${BASEAGENT_CANDIDATES.join(' / ')})` };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
764
763
|
setNestedValue(config, key, value);
|
|
765
764
|
try {
|
|
766
765
|
saveAgent(config);
|
package/dist/cli/bench.js
CHANGED
|
@@ -8,7 +8,7 @@ import { aidList, aidCreate } from '../aun/aid/identity.js';
|
|
|
8
8
|
import { msgSend, msgPull } from '../aun/msg/index.js';
|
|
9
9
|
import { getPackageRoot, aunPath as defaultAunPath } from '../paths.js';
|
|
10
10
|
import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
11
|
-
import { isHelpFlag } from './help.js';
|
|
11
|
+
import { isHelpFlag, getArgValue } from './help.js';
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
// ==================== ANSI ====================
|
|
14
14
|
const GREEN = '\x1b[32m';
|
|
@@ -132,10 +132,6 @@ function percentile(sorted, p) {
|
|
|
132
132
|
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
133
133
|
return sorted[Math.max(0, idx)];
|
|
134
134
|
}
|
|
135
|
-
function getArgValue(args, flag) {
|
|
136
|
-
const idx = args.indexOf(flag);
|
|
137
|
-
return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
138
|
-
}
|
|
139
135
|
// ==================== Promise Pool ====================
|
|
140
136
|
function withTimeout(promise, ms, label) {
|
|
141
137
|
return new Promise((resolve, reject) => {
|
package/dist/cli/help.js
CHANGED
|
@@ -21,3 +21,11 @@ export function wantsHelp(args) {
|
|
|
21
21
|
return true;
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 取出 `--flag <value>` 形式的参数值。
|
|
26
|
+
* flag 不存在或其后无值时返回 undefined。
|
|
27
|
+
*/
|
|
28
|
+
export function getArgValue(args, flag) {
|
|
29
|
+
const idx = args.indexOf(flag);
|
|
30
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
31
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { migrateProject } from '../config-store.js';
|
|
|
11
11
|
import { cmdInit } from './init.js';
|
|
12
12
|
import { ipcQuery } from '../ipc.js';
|
|
13
13
|
import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom } from './init-channel.js';
|
|
14
|
-
import { isHelpFlag, wantsHelp } from './help.js';
|
|
14
|
+
import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
|
|
15
15
|
import * as platform from '../utils/cross-platform.js';
|
|
16
16
|
import { EventBus } from '../core/event-bus.js';
|
|
17
17
|
import { tryUpgrade, tryUpgradeAunSdk } from '../utils/npm-ops.js';
|
|
@@ -292,6 +292,13 @@ async function cmdStart() {
|
|
|
292
292
|
// 旧配置自动迁移(evolclaw.json → 新结构)
|
|
293
293
|
const { autoMigrateIfNeeded } = await import('../config-store.js');
|
|
294
294
|
autoMigrateIfNeeded();
|
|
295
|
+
// 未初始化时自动引导
|
|
296
|
+
const defaults = loadDefaults();
|
|
297
|
+
if (!defaults || !defaults.baseagents || Object.keys(defaults.baseagents).length === 0) {
|
|
298
|
+
console.log('⚡ 未检测到初始化配置,自动启动初始化向导...\n');
|
|
299
|
+
await cmdInit();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
295
302
|
// 检查至少有一个 self-agent
|
|
296
303
|
const { agents, skipped } = loadAllAgents();
|
|
297
304
|
if (agents.length === 0) {
|
|
@@ -313,7 +320,7 @@ async function cmdStart() {
|
|
|
313
320
|
const aliveMains = status.mains.filter(m => m.alive);
|
|
314
321
|
if (aliveMains.length > 0) {
|
|
315
322
|
const first = aliveMains[0];
|
|
316
|
-
console.log(
|
|
323
|
+
console.log(` EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
|
|
317
324
|
console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
|
|
318
325
|
console.log(` 启动方式: ${first.record.launchedBy}`);
|
|
319
326
|
// 报告 AID 状态
|
|
@@ -371,14 +378,7 @@ async function cmdStart() {
|
|
|
371
378
|
const checkReady = () => {
|
|
372
379
|
// ready signal 出现(优先检查,避免 Windows 上误判进程状态)
|
|
373
380
|
if (fs.existsSync(p.readySignal)) {
|
|
374
|
-
|
|
375
|
-
let aunVer = 'unknown';
|
|
376
|
-
try {
|
|
377
|
-
const aunPkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json'), 'utf-8'));
|
|
378
|
-
aunVer = aunPkg.version;
|
|
379
|
-
}
|
|
380
|
-
catch { /* ignore */ }
|
|
381
|
-
console.log(`✓ EvolClaw v${pkg.version} started successfully (PID: ${childPid}) fastaun v${aunVer}`);
|
|
381
|
+
console.log(`✓ EvolClaw started successfully (PID: ${childPid})`);
|
|
382
382
|
console.log(` EVOLCLAW_HOME: ${resolveRoot()}`);
|
|
383
383
|
console.log(` Logs: ${p.logs}/`);
|
|
384
384
|
// 从主日志提取渠道连接摘要
|
|
@@ -495,7 +495,6 @@ async function cmdStop() {
|
|
|
495
495
|
}
|
|
496
496
|
async function cmdRestart(opts = {}) {
|
|
497
497
|
const cmdStartedAt = Date.now();
|
|
498
|
-
printStartupInfo();
|
|
499
498
|
console.log('🔄 Restarting EvolClaw...');
|
|
500
499
|
// 版本检查与自动升级
|
|
501
500
|
console.log('📦 Checking for updates...');
|
|
@@ -2103,95 +2102,22 @@ async function cmdWatchAid() {
|
|
|
2103
2102
|
platform.onShutdown(cleanup);
|
|
2104
2103
|
}
|
|
2105
2104
|
async function cmdWatchWeb() {
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
const
|
|
2109
|
-
const
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
const GREEN = useColor ? '\x1b[32m' : '';
|
|
2114
|
-
const YELLOW = useColor ? '\x1b[33m' : '';
|
|
2115
|
-
const logLine = (line) => {
|
|
2116
|
-
const t = new Date();
|
|
2117
|
-
const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}`;
|
|
2118
|
-
process.stdout.write(`${DIM}${ts}${RST} ${line}\n`);
|
|
2119
|
-
};
|
|
2120
|
-
// 调试日志文件:每次运行 watch web 时清空,便于建立调试闭环
|
|
2121
|
-
// 查看 sessions 调试日志 → 读这个文件
|
|
2122
|
-
const logFile = path.join(p.logs, 'watch-web.log');
|
|
2123
|
-
try {
|
|
2124
|
-
fs.mkdirSync(p.logs, { recursive: true });
|
|
2125
|
-
fs.writeFileSync(logFile, `# watch-web debug log\n# started ${new Date().toISOString()} pid=${process.pid}\n`);
|
|
2126
|
-
}
|
|
2127
|
-
catch { /* best effort */ }
|
|
2128
|
-
const fileLog = (line) => {
|
|
2129
|
-
const t = new Date();
|
|
2130
|
-
const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}.${String(t.getMilliseconds()).padStart(3, '0')}`;
|
|
2105
|
+
// ecweb 是独立插件包(可执行命令),按需安装。
|
|
2106
|
+
// 复用 npm-ops.npmInstallGlobal(含 EACCES→sudo 回退、Windows npm.cmd、超时)。
|
|
2107
|
+
const { execFileSync } = await import('child_process');
|
|
2108
|
+
const home = resolvePaths().root;
|
|
2109
|
+
if (!platform.commandExists('ecweb')) {
|
|
2110
|
+
process.stdout.write('📦 ecweb 未安装,正在从 npm 安装...\n');
|
|
2111
|
+
const { npmInstallGlobal } = await import('../utils/npm-ops.js');
|
|
2131
2112
|
try {
|
|
2132
|
-
|
|
2113
|
+
await npmInstallGlobal('ecweb');
|
|
2133
2114
|
}
|
|
2134
|
-
catch {
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
const log = (line) => { logLine(line); fileLog(line); };
|
|
2138
|
-
const { startWatchWebServer } = await import('./watch-web/server.js');
|
|
2139
|
-
let handle;
|
|
2140
|
-
try {
|
|
2141
|
-
handle = await startWatchWebServer({ log });
|
|
2142
|
-
}
|
|
2143
|
-
catch (e) {
|
|
2144
|
-
console.error(`❌ 启动 Web 服务失败: ${e?.message || e}`);
|
|
2145
|
-
process.exit(1);
|
|
2146
|
-
}
|
|
2147
|
-
// 注册 instance 文件
|
|
2148
|
-
const instanceFile = path.join(p.instanceDir, `watch-web-${process.pid}.json`);
|
|
2149
|
-
fs.writeFileSync(instanceFile, JSON.stringify({
|
|
2150
|
-
pid: process.pid, startedAt: Date.now(), startedAtIso: new Date().toISOString(),
|
|
2151
|
-
type: 'watch-web', port: handle.port,
|
|
2152
|
-
}, null, 2));
|
|
2153
|
-
// 列出本机访问地址
|
|
2154
|
-
const os = await import('os');
|
|
2155
|
-
const ifaces = os.networkInterfaces();
|
|
2156
|
-
const lanIps = [];
|
|
2157
|
-
for (const list of Object.values(ifaces)) {
|
|
2158
|
-
for (const ni of list || []) {
|
|
2159
|
-
if (ni.family === 'IPv4' && !ni.internal)
|
|
2160
|
-
lanIps.push(ni.address);
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
process.stdout.write(`\n${BOLD}${CYAN}🔭 EvolClaw Watch Web${RST}\n\n`);
|
|
2164
|
-
process.stdout.write(` ${BOLD}配对码:${RST} ${GREEN}${BOLD}${handle.pairingCode}${RST} ${DIM}(5 分钟内有效,配对后 token 缓存 24h 自动续期)${RST}\n\n`);
|
|
2165
|
-
process.stdout.write(` ${BOLD}本机:${RST} http://localhost:${handle.port}\n`);
|
|
2166
|
-
for (const ip of lanIps) {
|
|
2167
|
-
process.stdout.write(` ${BOLD}局域网:${RST} http://${ip}:${handle.port}\n`);
|
|
2168
|
-
}
|
|
2169
|
-
process.stdout.write(`\n ${DIM}绑定 0.0.0.0,远程可访问。按任意键退出。${RST}\n`);
|
|
2170
|
-
process.stdout.write(` ${DIM}调试日志: ${logFile}${RST}\n\n`);
|
|
2171
|
-
const cleanup = () => {
|
|
2172
|
-
try {
|
|
2173
|
-
fs.unlinkSync(instanceFile);
|
|
2115
|
+
catch (e) {
|
|
2116
|
+
process.stderr.write(`❌ 安装 ecweb 失败: ${e?.stderr || e?.message || e}\n 可手动安装: npm install -g ecweb\n`);
|
|
2117
|
+
process.exit(1);
|
|
2174
2118
|
}
|
|
2175
|
-
catch { }
|
|
2176
|
-
handle.close().finally(() => process.exit(0));
|
|
2177
|
-
};
|
|
2178
|
-
process.on('exit', () => { try {
|
|
2179
|
-
fs.unlinkSync(instanceFile);
|
|
2180
|
-
}
|
|
2181
|
-
catch { } });
|
|
2182
|
-
process.on('SIGINT', cleanup);
|
|
2183
|
-
process.on('SIGTERM', cleanup);
|
|
2184
|
-
platform.onShutdown(cleanup);
|
|
2185
|
-
// 按任意键退出
|
|
2186
|
-
if (process.stdin.isTTY) {
|
|
2187
|
-
process.stdin.setRawMode(true);
|
|
2188
|
-
process.stdin.resume();
|
|
2189
|
-
process.stdin.on('data', (key) => {
|
|
2190
|
-
logLine(`${YELLOW}收到退出指令,关闭服务…${RST}`);
|
|
2191
|
-
cleanup();
|
|
2192
|
-
});
|
|
2193
2119
|
}
|
|
2194
|
-
|
|
2120
|
+
execFileSync('ecweb', ['--home', home], { stdio: 'inherit' });
|
|
2195
2121
|
}
|
|
2196
2122
|
async function cmdRestartMonitor() {
|
|
2197
2123
|
const p = resolvePaths();
|
|
@@ -2719,8 +2645,6 @@ async function cmdMv(oldDir, newDir) {
|
|
|
2719
2645
|
console.log('✓ 项目目录已移动');
|
|
2720
2646
|
if (r.evolclawDbUpdated > 0)
|
|
2721
2647
|
console.log(`✓ EvolClaw 会话存储已更新 (${r.evolclawDbUpdated} 条记录)`);
|
|
2722
|
-
if (r.evolclawConfigUpdated)
|
|
2723
|
-
console.log('✓ agent config projects.list 已更新');
|
|
2724
2648
|
console.log('\n迁移完成!');
|
|
2725
2649
|
}
|
|
2726
2650
|
catch (e) {
|
|
@@ -2894,7 +2818,7 @@ Agent:
|
|
|
2894
2818
|
// ==================== Agent ====================
|
|
2895
2819
|
async function cmdAgent(args) {
|
|
2896
2820
|
const sub = args[0];
|
|
2897
|
-
const formatJson = args
|
|
2821
|
+
const formatJson = getArgValue(args, '--format') === 'json';
|
|
2898
2822
|
if (!sub || isHelpFlag(sub)) {
|
|
2899
2823
|
console.log(`用法: evolclaw agent <command>
|
|
2900
2824
|
|
|
@@ -3469,7 +3393,7 @@ function resolveAunPath(args) {
|
|
|
3469
3393
|
}
|
|
3470
3394
|
async function cmdAid(args) {
|
|
3471
3395
|
const sub = args[0];
|
|
3472
|
-
const formatJson = args
|
|
3396
|
+
const formatJson = getArgValue(args, '--format') === 'json';
|
|
3473
3397
|
const aunPath = resolveAunPath(args);
|
|
3474
3398
|
if (!sub || isHelpFlag(sub)) {
|
|
3475
3399
|
console.log(`用法: evolclaw aid <command>
|
|
@@ -3970,6 +3894,10 @@ async function cmdRpc(args) {
|
|
|
3970
3894
|
|
|
3971
3895
|
每行 JSON 格式: {"method":"<namespace.method>","params":{...}}
|
|
3972
3896
|
|
|
3897
|
+
Options:
|
|
3898
|
+
--app <name> 指定应用 slot(独立消费通道)。仅对 message.pull / group.pull
|
|
3899
|
+
等消费类方法有意义——隔离 seq 游标与消息过滤;默认与 daemon 共享通道。
|
|
3900
|
+
|
|
3973
3901
|
示例:
|
|
3974
3902
|
evolclaw rpc --as alice.agentid.pub --params '{"method":"message.send","params":{"to":"bob.agentid.pub","payload":{"type":"text","text":"hello"}}}'
|
|
3975
3903
|
evolclaw rpc --as alice.agentid.pub --params calls.jsonl`);
|
|
@@ -3978,6 +3906,7 @@ async function cmdRpc(args) {
|
|
|
3978
3906
|
const asIdx = args.indexOf('--as');
|
|
3979
3907
|
const paramsIdx = args.indexOf('--params');
|
|
3980
3908
|
const aunPath = resolveAunPath(args);
|
|
3909
|
+
const appSlot = getArgValue(args, '--app');
|
|
3981
3910
|
if (asIdx === -1 || asIdx + 1 >= args.length) {
|
|
3982
3911
|
console.error('❌ 缺少 --as <aid>');
|
|
3983
3912
|
process.exit(1);
|
|
@@ -4022,11 +3951,11 @@ async function cmdRpc(args) {
|
|
|
4022
3951
|
}
|
|
4023
3952
|
const { rpcCall, rpcBatch } = await import('../aun/rpc/index.js');
|
|
4024
3953
|
if (calls.length === 1) {
|
|
4025
|
-
const result = await rpcCall(aid, calls[0].method, calls[0].params, { aunPath });
|
|
3954
|
+
const result = await rpcCall(aid, calls[0].method, calls[0].params, { aunPath, slotId: appSlot });
|
|
4026
3955
|
console.log(JSON.stringify(result));
|
|
4027
3956
|
}
|
|
4028
3957
|
else {
|
|
4029
|
-
const results = await rpcBatch(aid, calls, { aunPath });
|
|
3958
|
+
const results = await rpcBatch(aid, calls, { aunPath, slotId: appSlot });
|
|
4030
3959
|
for (const r of results) {
|
|
4031
3960
|
console.log(JSON.stringify(r));
|
|
4032
3961
|
}
|
|
@@ -4036,7 +3965,7 @@ async function cmdRpc(args) {
|
|
|
4036
3965
|
async function cmdStorage(args) {
|
|
4037
3966
|
const sub = args[0];
|
|
4038
3967
|
const aunPath = resolveAunPath(args);
|
|
4039
|
-
const formatJson = args
|
|
3968
|
+
const formatJson = getArgValue(args, '--format') === 'json';
|
|
4040
3969
|
if (!sub || isHelpFlag(sub)) {
|
|
4041
3970
|
console.log(`用法: evolclaw storage <command> <aid> [options]
|
|
4042
3971
|
|
|
@@ -4183,7 +4112,7 @@ Commands:
|
|
|
4183
4112
|
async function cmdMsg(args) {
|
|
4184
4113
|
const sub = args[0];
|
|
4185
4114
|
const aunPath = resolveAunPath(args);
|
|
4186
|
-
const formatJson = args
|
|
4115
|
+
const formatJson = getArgValue(args, '--format') === 'json';
|
|
4187
4116
|
const appIdx = args.indexOf('--app');
|
|
4188
4117
|
const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
|
|
4189
4118
|
if (!sub || isHelpFlag(sub)) {
|
|
@@ -4207,6 +4136,8 @@ Options:
|
|
|
4207
4136
|
--content-type <mime> 显式覆盖 MIME(仅 --file 模式)
|
|
4208
4137
|
--text <说明> 附件说明文字(仅 --file 模式)
|
|
4209
4138
|
--transcript <text> 语音转写(仅 --as voice)
|
|
4139
|
+
-- end-of-options:其后所有参数按正文处理
|
|
4140
|
+
(用于发送恰好等于某 flag 的文本,如 send a b -- --encrypt)
|
|
4210
4141
|
|
|
4211
4142
|
示例:
|
|
4212
4143
|
evolclaw msg send alice.agentid.pub bob.agentid.pub "hello"
|
|
@@ -4465,7 +4396,7 @@ Options:
|
|
|
4465
4396
|
async function cmdGroup(args) {
|
|
4466
4397
|
const sub = args[0];
|
|
4467
4398
|
const aunPath = resolveAunPath(args);
|
|
4468
|
-
const formatJson = args
|
|
4399
|
+
const formatJson = getArgValue(args, '--format') === 'json';
|
|
4469
4400
|
const appIdx = args.indexOf('--app');
|
|
4470
4401
|
const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
|
|
4471
4402
|
if (!sub || isHelpFlag(sub)) {
|
|
@@ -4496,8 +4427,11 @@ async function cmdGroup(args) {
|
|
|
4496
4427
|
Options:
|
|
4497
4428
|
--app <name> 指定应用 slot(独立消费通道,不影响 daemon)
|
|
4498
4429
|
--format json 输出 JSON 格式
|
|
4499
|
-
--
|
|
4430
|
+
--encrypt 启用端到端加密(仅 send)
|
|
4431
|
+
--mention <aid> 发送时 @ 某个成员(可多次,或用逗号分隔多个 aid)
|
|
4500
4432
|
--mention-all 发送时 @ 所有人
|
|
4433
|
+
-- end-of-options:其后所有参数按正文处理
|
|
4434
|
+
(用于发送恰好等于某 flag 的文本,如 send a g -- --encrypt)
|
|
4501
4435
|
|
|
4502
4436
|
示例:
|
|
4503
4437
|
evolclaw group create alice.agentid.pub "Dev Team" --visibility private
|
|
@@ -4529,12 +4463,23 @@ Options:
|
|
|
4529
4463
|
}
|
|
4530
4464
|
return gid;
|
|
4531
4465
|
};
|
|
4532
|
-
// 收集 --mention
|
|
4466
|
+
// 收集 --mention(可多次;每次的值支持逗号分隔多个 aid)
|
|
4533
4467
|
const collectMentions = () => {
|
|
4534
4468
|
const mentions = [];
|
|
4535
|
-
for (let i = 0; i < args.length
|
|
4536
|
-
if (args[i]
|
|
4537
|
-
|
|
4469
|
+
for (let i = 0; i < args.length; i++) {
|
|
4470
|
+
if (args[i] !== '--mention')
|
|
4471
|
+
continue;
|
|
4472
|
+
const val = args[i + 1];
|
|
4473
|
+
if (val === undefined || val.startsWith('--')) {
|
|
4474
|
+
console.error(`❌ --mention 后面缺少 <aid>`);
|
|
4475
|
+
process.exit(1);
|
|
4476
|
+
}
|
|
4477
|
+
for (const aid of val.split(',').map(s => s.trim()).filter(Boolean)) {
|
|
4478
|
+
if (!isValidAid(aid)) {
|
|
4479
|
+
console.error(`❌ --mention 的 aid 无效: ${aid}`);
|
|
4480
|
+
process.exit(1);
|
|
4481
|
+
}
|
|
4482
|
+
mentions.push({ aid });
|
|
4538
4483
|
}
|
|
4539
4484
|
}
|
|
4540
4485
|
if (args.includes('--mention-all')) {
|
|
@@ -4828,32 +4773,50 @@ Options:
|
|
|
4828
4773
|
process.exit(1);
|
|
4829
4774
|
}
|
|
4830
4775
|
// ==================== Main ====================
|
|
4831
|
-
function getArgValue(args, flag) {
|
|
4832
|
-
const idx = args.indexOf(flag);
|
|
4833
|
-
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
4834
|
-
}
|
|
4835
4776
|
/**
|
|
4836
|
-
* 收集位置参数(从 startIdx
|
|
4837
|
-
*
|
|
4777
|
+
* 收集位置参数(从 startIdx 开始)。
|
|
4778
|
+
*
|
|
4779
|
+
* flag 判定采用**精确匹配已知 flag 集合**,而非 `startsWith('--')`——
|
|
4780
|
+
* 这样"正文恰好以 -- 开头"(如消息文本 `--file 坏了`)不会被误当 flag 吞掉。
|
|
4781
|
+
* 仅当 token 精确等于某个已知 flag 时才按 flag 处理:
|
|
4782
|
+
* - VALUE_FLAGS:消耗自身 + 下一个 arg(flag 的值)
|
|
4783
|
+
* - BOOLEAN_FLAGS:仅消耗自身
|
|
4784
|
+
* 其余以 -- 开头但不在集合中的 token,一律视为正文。
|
|
4785
|
+
*
|
|
4786
|
+
* 另支持 POSIX `--` end-of-options 分隔符:遇到单独的 `--` 后,
|
|
4787
|
+
* 其后所有 token 无条件按正文处理(用于发送精确等于某 flag 的文本,如 `-- --encrypt`)。
|
|
4838
4788
|
*/
|
|
4789
|
+
const VALUE_FLAGS = new Set([
|
|
4790
|
+
'--format', '--app', '--after-seq', '--limit', '--file', '--link',
|
|
4791
|
+
'--payload', '--title', '--description', '--text', '--transcript',
|
|
4792
|
+
'--as', '--content-type', '--mention', '--visibility', '--join-mode',
|
|
4793
|
+
'--group-id', '--name', '--message', '--answer', '--page', '--size',
|
|
4794
|
+
'--aun-path', '--thread',
|
|
4795
|
+
]);
|
|
4796
|
+
const BOOLEAN_FLAGS = new Set([
|
|
4797
|
+
'--encrypt', '--mention-all',
|
|
4798
|
+
]);
|
|
4839
4799
|
function collectPositional(args, startIdx) {
|
|
4840
|
-
const VALUE_FLAGS = new Set([
|
|
4841
|
-
'--format', '--app', '--after-seq', '--limit', '--file', '--link',
|
|
4842
|
-
'--payload', '--title', '--description', '--text', '--transcript',
|
|
4843
|
-
'--as', '--content-type', '--mention', '--visibility', '--join-mode',
|
|
4844
|
-
'--group-id', '--name', '--message', '--answer', '--page', '--size',
|
|
4845
|
-
'--aun-path',
|
|
4846
|
-
]);
|
|
4847
4800
|
const out = [];
|
|
4801
|
+
let endOfFlags = false;
|
|
4848
4802
|
for (let i = startIdx; i < args.length; i++) {
|
|
4849
4803
|
const a = args[i];
|
|
4850
|
-
if (
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4804
|
+
if (endOfFlags) {
|
|
4805
|
+
out.push(a);
|
|
4806
|
+
continue;
|
|
4807
|
+
}
|
|
4808
|
+
if (a === '--') {
|
|
4809
|
+
endOfFlags = true;
|
|
4854
4810
|
continue;
|
|
4855
4811
|
}
|
|
4856
|
-
|
|
4812
|
+
if (VALUE_FLAGS.has(a)) {
|
|
4813
|
+
i++;
|
|
4814
|
+
continue;
|
|
4815
|
+
} // 精确匹配取值 flag:跳过其值
|
|
4816
|
+
if (BOOLEAN_FLAGS.has(a)) {
|
|
4817
|
+
continue;
|
|
4818
|
+
} // 精确匹配开关 flag:仅跳过自身
|
|
4819
|
+
out.push(a); // 其余(含以 -- 开头的未知 token)= 正文
|
|
4857
4820
|
}
|
|
4858
4821
|
return out;
|
|
4859
4822
|
}
|