evolclaw 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +1 -2
- package/dist/agents/{resolve.js → baseagent.js} +34 -5
- package/dist/agents/claude-runner.js +120 -27
- package/dist/agents/codex-app-server-client.js +364 -0
- package/dist/agents/codex-runner.js +1069 -141
- package/dist/agents/gemini-runner.js +2 -2
- package/dist/agents/runner-types.js +28 -0
- package/dist/aun/aid/store.js +1 -1
- package/dist/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +406 -293
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +97 -150
- package/dist/channels/qqbot.js +75 -138
- package/dist/channels/wechat.js +75 -136
- package/dist/channels/wecom.js +75 -138
- package/dist/cli/agent.js +8 -5
- package/dist/cli/index.js +177 -44
- package/dist/cli/init.js +33 -6
- package/dist/cli/model.js +1 -1
- package/dist/cli/stats.js +558 -0
- package/dist/cli/version.js +87 -0
- package/dist/cli/watch-msg.js +5 -2
- package/dist/config-store.js +12 -6
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +473 -114
- package/dist/core/evolagent-registry.js +1 -0
- package/dist/core/evolagent.js +1 -1
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +63 -1
- package/dist/core/message/im-renderer.js +35 -13
- package/dist/core/message/items-formatter.js +9 -1
- package/dist/core/message/message-bridge.js +49 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +295 -35
- package/dist/core/message/message-queue.js +2 -2
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +56 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-scope.js +2 -2
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
- package/dist/core/session/session-manager.js +27 -13
- package/dist/core/session/session-title.js +26 -0
- package/dist/core/stats/billing.js +151 -0
- package/dist/core/stats/budget.js +93 -0
- package/dist/core/stats/db.js +314 -0
- package/dist/core/stats/eck-vars.js +84 -0
- package/dist/core/stats/index.js +10 -0
- package/dist/core/stats/normalizer.js +78 -0
- package/dist/core/stats/query.js +760 -0
- package/dist/core/stats/writer.js +115 -0
- package/dist/core/trigger/manager.js +34 -0
- package/dist/core/trigger/parser.js +9 -3
- package/dist/core/trigger/scheduler.js +20 -17
- package/dist/{agents → eck}/manifest-engine.js +20 -1
- package/dist/{agents → eck}/message-renderer.js +24 -1
- package/dist/index.js +130 -8
- package/dist/ipc.js +17 -1
- package/dist/utils/cross-platform.js +23 -5
- package/dist/utils/ecweb-pair.js +20 -0
- package/dist/utils/stats.js +14 -0
- package/kits/docs/evolclaw/INDEX.md +3 -1
- package/kits/docs/evolclaw/fs-architecture.md +1215 -0
- package/kits/docs/evolclaw/fs.md +131 -0
- package/kits/docs/evolclaw/group-fs.md +209 -0
- package/kits/docs/evolclaw/stats.md +70 -0
- package/kits/docs/venues/aun-group.md +29 -6
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +30 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/system-fragments/response-depth.md +16 -0
- package/package.json +4 -4
- package/dist/agents/baseagent-normalize.js +0 -19
- package/dist/core/relation/peer-key.js +0 -16
- package/dist/evolclaw-config.js +0 -11
- package/dist/utils/channel-helpers.js +0 -46
- /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
- /package/dist/{agents → eck}/kit-renderer.js +0 -0
package/dist/channels/wecom.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { logger } from '../utils/logger.js';
|
|
3
3
|
import { requireOptional } from '../utils/npm-ops.js';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
|
|
5
5
|
import { formatItemsAsText } from '../core/message/items-formatter.js';
|
|
6
6
|
// ── WecomChannel ───────────────────────────────────────────────────────────────
|
|
7
7
|
export class WecomChannel {
|
|
@@ -465,143 +465,80 @@ function isValidCredential(value) {
|
|
|
465
465
|
}
|
|
466
466
|
export class WecomChannelPlugin {
|
|
467
467
|
name = 'wecom';
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
await channel.sendMessage(channelId, payload.text);
|
|
506
|
-
return;
|
|
507
|
-
case 'result.file':
|
|
508
|
-
await channel.sendFile(channelId, payload.filePath);
|
|
509
|
-
return;
|
|
510
|
-
case 'result.image':
|
|
511
|
-
await channel.sendImage(channelId, payload.data);
|
|
512
|
-
return;
|
|
513
|
-
case 'activity.batch': {
|
|
514
|
-
const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
|
|
515
|
-
const text = formatItemsAsText(filtered);
|
|
516
|
-
if (text)
|
|
517
|
-
await channel.sendMessage(channelId, text);
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
case 'interaction':
|
|
521
|
-
if (payload.fallbackText)
|
|
522
|
-
await channel.sendMessage(channelId, payload.fallbackText);
|
|
523
|
-
return;
|
|
524
|
-
case 'status.started':
|
|
525
|
-
case 'status.completed':
|
|
526
|
-
case 'status.interrupted':
|
|
527
|
-
case 'status.error':
|
|
528
|
-
case 'status.timeout':
|
|
529
|
-
case 'status.progress':
|
|
530
|
-
case 'custom':
|
|
531
|
-
return;
|
|
532
|
-
default:
|
|
533
|
-
logger.warn(`[WeCom] Unhandled payload kind: ${payload.kind}`);
|
|
468
|
+
async createInstance(inst, ctx) {
|
|
469
|
+
if (inst.enabled === false)
|
|
470
|
+
return null;
|
|
471
|
+
if (!isValidCredential(inst.botId) || !isValidCredential(inst.secret))
|
|
472
|
+
return null;
|
|
473
|
+
const channel = new WecomChannel({
|
|
474
|
+
botId: inst.botId,
|
|
475
|
+
secret: inst.secret,
|
|
476
|
+
});
|
|
477
|
+
const mode = resolveShowActivities(inst);
|
|
478
|
+
const adapter = {
|
|
479
|
+
channelName: inst.name,
|
|
480
|
+
channelKey: inst.name,
|
|
481
|
+
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
|
|
482
|
+
send: async (envelope, payload) => {
|
|
483
|
+
const channelId = envelope.channelId;
|
|
484
|
+
switch (payload.kind) {
|
|
485
|
+
case 'result.text':
|
|
486
|
+
case 'command.result':
|
|
487
|
+
case 'command.error':
|
|
488
|
+
case 'system.notice':
|
|
489
|
+
case 'system.error':
|
|
490
|
+
case 'result.error':
|
|
491
|
+
await channel.sendMessage(channelId, payload.text);
|
|
492
|
+
return;
|
|
493
|
+
case 'result.file':
|
|
494
|
+
await channel.sendFile(channelId, payload.filePath);
|
|
495
|
+
return;
|
|
496
|
+
case 'result.image':
|
|
497
|
+
await channel.sendImage(channelId, payload.data);
|
|
498
|
+
return;
|
|
499
|
+
case 'activity.batch': {
|
|
500
|
+
const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
|
|
501
|
+
const text = formatItemsAsText(filtered);
|
|
502
|
+
if (text)
|
|
503
|
+
await channel.sendMessage(channelId, text);
|
|
504
|
+
return;
|
|
534
505
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
channelType: 'wecom',
|
|
573
|
-
adapter,
|
|
574
|
-
channel,
|
|
575
|
-
policy,
|
|
576
|
-
options,
|
|
577
|
-
connect: () => channel.connect(),
|
|
578
|
-
disconnect: () => channel.disconnect(),
|
|
579
|
-
onProjectPathRequest: () => Promise.resolve(config.projects?.defaultPath || process.cwd()),
|
|
580
|
-
registerBridge(bridge, channelType) {
|
|
581
|
-
bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
|
|
582
|
-
handler({
|
|
583
|
-
channel: adapter.channelName,
|
|
584
|
-
channelType,
|
|
585
|
-
channelId: event.channelId,
|
|
586
|
-
selfAID: inst.agentName,
|
|
587
|
-
content: event.content,
|
|
588
|
-
images: event.images,
|
|
589
|
-
chatType: event.chatType || 'private',
|
|
590
|
-
peerId: event.peerId || '',
|
|
591
|
-
peerName: event.peerName,
|
|
592
|
-
messageId: event.messageId,
|
|
593
|
-
});
|
|
594
|
-
}), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
|
|
595
|
-
},
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
return result;
|
|
599
|
-
}
|
|
600
|
-
async createChannel(config) {
|
|
601
|
-
const instances = await this.createChannels(config);
|
|
602
|
-
if (instances.length === 0) {
|
|
603
|
-
throw new Error('WeCom config missing or invalid');
|
|
604
|
-
}
|
|
605
|
-
return instances[0];
|
|
506
|
+
case 'interaction':
|
|
507
|
+
if (payload.fallbackText)
|
|
508
|
+
await channel.sendMessage(channelId, payload.fallbackText);
|
|
509
|
+
return;
|
|
510
|
+
default: return;
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
const policy = {
|
|
515
|
+
canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
|
|
516
|
+
canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
|
|
517
|
+
canCreateSession: () => true,
|
|
518
|
+
canDeleteSession: () => true,
|
|
519
|
+
canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
|
|
520
|
+
messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
|
|
521
|
+
showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
|
|
522
|
+
showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
|
|
523
|
+
accumulateErrors: () => true,
|
|
524
|
+
};
|
|
525
|
+
return {
|
|
526
|
+
channelType: 'wecom', adapter, channel,
|
|
527
|
+
policy,
|
|
528
|
+
options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
|
|
529
|
+
connect: () => channel.connect(),
|
|
530
|
+
disconnect: () => channel.disconnect(),
|
|
531
|
+
onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
|
|
532
|
+
registerBridge(bridge, channelType) {
|
|
533
|
+
bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
|
|
534
|
+
handler({
|
|
535
|
+
channel: adapter.channelName, channelType, channelId: event.channelId,
|
|
536
|
+
selfAID: ctx.agentName, content: event.content, images: event.images,
|
|
537
|
+
chatType: event.chatType || 'private', peerId: event.peerId || '',
|
|
538
|
+
peerName: event.peerName, messageId: event.messageId,
|
|
539
|
+
});
|
|
540
|
+
}), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
|
|
541
|
+
},
|
|
542
|
+
};
|
|
606
543
|
}
|
|
607
544
|
}
|
package/dist/cli/agent.js
CHANGED
|
@@ -7,12 +7,12 @@ import { ipcQuery } from '../ipc.js';
|
|
|
7
7
|
import { CONFIG_SCHEMA_VERSION } from '../types.js';
|
|
8
8
|
import { isValidChannelName } from '../core/channel-loader.js';
|
|
9
9
|
import { commandExists } from '../utils/cross-platform.js';
|
|
10
|
-
import {
|
|
10
|
+
import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
|
|
11
11
|
// ==================== Helpers ====================
|
|
12
12
|
const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
|
|
13
13
|
function isBaseagentAvailable(baseagent) {
|
|
14
14
|
if (baseagent === 'codex')
|
|
15
|
-
return
|
|
15
|
+
return isCodexAppServerAvailable();
|
|
16
16
|
return commandExists(baseagent);
|
|
17
17
|
}
|
|
18
18
|
function detectAvailableBaseagents() {
|
|
@@ -313,7 +313,7 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
313
313
|
// Baseagent
|
|
314
314
|
const available = detectAvailableBaseagents();
|
|
315
315
|
if (available.length === 0) {
|
|
316
|
-
return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or
|
|
316
|
+
return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server.` };
|
|
317
317
|
}
|
|
318
318
|
const defaultBa = pickDefaultBaseagent(available);
|
|
319
319
|
let baseagent;
|
|
@@ -467,7 +467,7 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
467
467
|
// Baseagent
|
|
468
468
|
const available = detectAvailableBaseagents();
|
|
469
469
|
if (available.length === 0) {
|
|
470
|
-
return failValidating(`No usable baseagent detected. Install claude/gemini CLI or
|
|
470
|
+
return failValidating(`No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server.`);
|
|
471
471
|
}
|
|
472
472
|
let baseagent;
|
|
473
473
|
if (opts.baseagent) {
|
|
@@ -475,7 +475,10 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
475
475
|
return failValidating(`Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`);
|
|
476
476
|
}
|
|
477
477
|
if (!available.includes(opts.baseagent)) {
|
|
478
|
-
|
|
478
|
+
const reason = opts.baseagent === 'codex'
|
|
479
|
+
? getCodexAppServerAvailability().reason
|
|
480
|
+
: undefined;
|
|
481
|
+
return failValidating(reason || `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})`);
|
|
479
482
|
}
|
|
480
483
|
baseagent = opts.baseagent;
|
|
481
484
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -5,10 +5,10 @@ import os from 'os';
|
|
|
5
5
|
import { spawn, execFileSync, execFile } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath } from '../paths.js';
|
|
8
|
-
import { loadDefaults, loadAllAgents, mergeForAgent } from '../config-store.js';
|
|
9
|
-
import {
|
|
10
|
-
import { resolveAnthropicConfig } from '../agents/resolve.js';
|
|
8
|
+
import { loadDefaults, loadAllAgents, mergeForAgent, loadEvolclawConfig, saveEvolclawConfig } from '../config-store.js';
|
|
9
|
+
import { resolveAnthropicConfig } from '../agents/baseagent.js';
|
|
11
10
|
import { migrateProject } from '../config-store.js';
|
|
11
|
+
import readline from 'readline';
|
|
12
12
|
import { cmdInit, needsControlAidInit, initTail } from './init.js';
|
|
13
13
|
import { ipcQuery } from '../ipc.js';
|
|
14
14
|
import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom, cmdInitAun } from './init-channel.js';
|
|
@@ -16,8 +16,10 @@ import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
|
|
|
16
16
|
import * as platform from '../utils/cross-platform.js';
|
|
17
17
|
import { EventBus } from '../core/event-bus.js';
|
|
18
18
|
import { tryUpgrade, tryUpgradeAunSdk } from '../utils/npm-ops.js';
|
|
19
|
+
import { fetchEcwebPairCode } from '../utils/ecweb-pair.js';
|
|
19
20
|
import { resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG } from '../aun/aid/client.js';
|
|
20
21
|
import { scanInstances, cleanupInstances, writeRestartMonitor, removeRestartMonitor, isRestartMonitorWinner, findOrphanProcesses, killOrphans } from '../utils/instance-registry.js';
|
|
22
|
+
import { displaySessionTitle } from '../core/session/session-title.js';
|
|
21
23
|
// Suppress Node.js ExperimentalWarning (e.g. SQLite) from cluttering CLI output
|
|
22
24
|
process.removeAllListeners('warning');
|
|
23
25
|
process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
|
|
@@ -313,6 +315,27 @@ async function cmdStart() {
|
|
|
313
315
|
if (!evolclawCfgStart.aid) {
|
|
314
316
|
console.log('⚠ 控制 AID 未配置(非交互式启动,跳过补全)。如需进程身份/远程管理,请运行 evolclaw init');
|
|
315
317
|
}
|
|
318
|
+
else if (process.stdin.isTTY) {
|
|
319
|
+
// 证书缺失时在 CLI 侧提示,daemon 是后台进程无终端不做交互
|
|
320
|
+
const certKey = path.join(resolvePaths().root, 'AIDs', evolclawCfgStart.aid, 'private', 'key.json');
|
|
321
|
+
if (!fs.existsSync(certKey)) {
|
|
322
|
+
console.log(`⚠ 控制 AID 证书缺失:${evolclawCfgStart.aid}`);
|
|
323
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
324
|
+
const ans = await new Promise(res => rl.question(' [1] 继续启动 [2] 重新生成 AID [3] 退出 [1/2/3]: ', res));
|
|
325
|
+
rl.close();
|
|
326
|
+
if (ans.trim() === '3') {
|
|
327
|
+
process.exit(0);
|
|
328
|
+
}
|
|
329
|
+
if (ans.trim() === '2') {
|
|
330
|
+
const { suppressSdkLogs } = await import('../aun/aid/index.js');
|
|
331
|
+
suppressSdkLogs();
|
|
332
|
+
const { generateControlAid } = await import('../aun/aid/control-aid.js');
|
|
333
|
+
const result = await generateControlAid();
|
|
334
|
+
saveEvolclawConfig({ ...loadEvolclawConfig(), aid: result.aid });
|
|
335
|
+
console.log(`✓ 新控制 AID: ${result.aid}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
316
339
|
// 检查至少有一个 self-agent
|
|
317
340
|
const { agents, skipped } = loadAllAgents();
|
|
318
341
|
if (agents.length === 0) {
|
|
@@ -436,6 +459,8 @@ async function cmdStart() {
|
|
|
436
459
|
countLines(getPackageRoot(), p.logs);
|
|
437
460
|
}
|
|
438
461
|
console.log(`⏱ done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
|
|
462
|
+
// ECWeb 自动后台启动
|
|
463
|
+
startEcwebIfEnabled(p);
|
|
439
464
|
return;
|
|
440
465
|
}
|
|
441
466
|
// 超时
|
|
@@ -914,7 +939,7 @@ async function cmdStatus() {
|
|
|
914
939
|
const projectName = path.basename(s.projectPath);
|
|
915
940
|
const sessionType = s.threadId ? '话题会话' : '主会话';
|
|
916
941
|
const chatType = s.chatType === 'group' ? '群聊' : '单聊';
|
|
917
|
-
const sessionName = s.name
|
|
942
|
+
const sessionName = displaySessionTitle(s.name);
|
|
918
943
|
const timeAgo = formatTimeAgo(Date.now() - s.updatedAt);
|
|
919
944
|
const dot = s.isActive ? '•' : '○';
|
|
920
945
|
const agentSidLabel = s.agentSessionId ? ` [${s.agentSessionId}]` : '';
|
|
@@ -2123,54 +2148,129 @@ async function cmdWatchAid() {
|
|
|
2123
2148
|
}
|
|
2124
2149
|
platform.onShutdown(cleanup);
|
|
2125
2150
|
}
|
|
2151
|
+
/** 扫描 instance/ 目录,返回存活的 ecweb 实例(ecweb-<pid>.json)。 */
|
|
2152
|
+
function findAliveEcweb(p) {
|
|
2153
|
+
if (!fs.existsSync(p.instanceDir))
|
|
2154
|
+
return null;
|
|
2155
|
+
for (const file of fs.readdirSync(p.instanceDir)) {
|
|
2156
|
+
if (!file.startsWith('ecweb-') || !file.endsWith('.json'))
|
|
2157
|
+
continue;
|
|
2158
|
+
try {
|
|
2159
|
+
const rec = JSON.parse(fs.readFileSync(path.join(p.instanceDir, file), 'utf-8'));
|
|
2160
|
+
if (rec.pid && platform.isProcessRunning(rec.pid))
|
|
2161
|
+
return { pid: rec.pid, port: rec.port ?? 42705 };
|
|
2162
|
+
fs.unlinkSync(path.join(p.instanceDir, file));
|
|
2163
|
+
}
|
|
2164
|
+
catch { }
|
|
2165
|
+
}
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2168
|
+
/** 若 ecweb 在运行则杀掉并清理 pid 文件,返回是否成功 kill。 */
|
|
2169
|
+
function stopEcwebIfRunning(p) {
|
|
2170
|
+
const alive = findAliveEcweb(p);
|
|
2171
|
+
if (!alive)
|
|
2172
|
+
return false;
|
|
2173
|
+
try {
|
|
2174
|
+
platform.killProcess(alive.pid);
|
|
2175
|
+
}
|
|
2176
|
+
catch { }
|
|
2177
|
+
// 清理 pid 文件
|
|
2178
|
+
try {
|
|
2179
|
+
for (const file of fs.readdirSync(p.instanceDir)) {
|
|
2180
|
+
if (file.startsWith('ecweb-') && file.endsWith('.json')) {
|
|
2181
|
+
fs.unlinkSync(path.join(p.instanceDir, file));
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
catch { }
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
/** 后台 detached 启动 ecweb;若已运行则先停再启(确保加载最新代码)。 */
|
|
2189
|
+
function startEcwebIfEnabled(p) {
|
|
2190
|
+
const cfg = loadEvolclawConfig();
|
|
2191
|
+
if (!cfg.ecweb?.enabled)
|
|
2192
|
+
return;
|
|
2193
|
+
stopEcwebIfRunning(p); // 先停旧进程(有则停),保证加载最新代码
|
|
2194
|
+
const exe = platform.resolveCommandPath('evolclaw-web');
|
|
2195
|
+
if (!exe)
|
|
2196
|
+
return; // 未安装,静默跳过
|
|
2197
|
+
const port = cfg.ecweb.port ?? 42705;
|
|
2198
|
+
const isBatch = /\.(cmd|bat)$/i.test(exe);
|
|
2199
|
+
const args = ['--home', p.root, '--port', String(port)];
|
|
2200
|
+
const child = isBatch
|
|
2201
|
+
? spawn(`"${exe}"`, args.map(a => `"${a}"`), { detached: true, stdio: 'ignore', shell: true, windowsHide: true })
|
|
2202
|
+
: spawn(exe, args, { detached: true, stdio: 'ignore', windowsHide: true });
|
|
2203
|
+
child.unref();
|
|
2204
|
+
const pid = child.pid;
|
|
2205
|
+
if (!pid)
|
|
2206
|
+
return;
|
|
2207
|
+
fs.mkdirSync(p.instanceDir, { recursive: true });
|
|
2208
|
+
fs.writeFileSync(path.join(p.instanceDir, `ecweb-${pid}.json`), JSON.stringify({ pid, port, startedAt: Date.now() }, null, 2));
|
|
2209
|
+
console.log(`🔭 ECWeb 已在后台启动 (PID: ${pid}) http://localhost:${port}`);
|
|
2210
|
+
console.log(` 运行 ec watch web 查看配对码`);
|
|
2211
|
+
}
|
|
2212
|
+
/** 显示 ecweb 访问信息 + 配对码(启动后 ecweb 需要一点时间起 HTTP,故重试几次)。 */
|
|
2213
|
+
async function printEcwebAccess(port) {
|
|
2214
|
+
console.log(`🔭 ECWeb http://localhost:${port}`);
|
|
2215
|
+
let pair = null;
|
|
2216
|
+
for (let i = 0; i < 10 && !pair; i++) {
|
|
2217
|
+
pair = await fetchEcwebPairCode(port);
|
|
2218
|
+
if (!pair)
|
|
2219
|
+
await sleep(300);
|
|
2220
|
+
}
|
|
2221
|
+
if (pair) {
|
|
2222
|
+
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
2223
|
+
console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,配对后 token 缓存 24h)`);
|
|
2224
|
+
}
|
|
2225
|
+
else {
|
|
2226
|
+
console.log(' 配对码: 暂不可用(稍后重试 ec watch web,或查看 logs/watch-web.log)');
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2126
2229
|
async function cmdWatchWeb() {
|
|
2127
|
-
|
|
2128
|
-
//
|
|
2129
|
-
const { execFileSync } = await import('child_process');
|
|
2130
|
-
const home = resolvePaths().root;
|
|
2230
|
+
const p = resolvePaths();
|
|
2231
|
+
// 1. 检查安装
|
|
2131
2232
|
if (!platform.commandExists('evolclaw-web')) {
|
|
2132
|
-
process.stdout.write('📦 evolclaw-web
|
|
2233
|
+
process.stdout.write('📦 evolclaw-web 未安装。');
|
|
2234
|
+
if (!process.stdin.isTTY) {
|
|
2235
|
+
process.stdout.write(' 请手动安装: npm install -g evolclaw-web\n');
|
|
2236
|
+
process.exit(1);
|
|
2237
|
+
}
|
|
2238
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2239
|
+
const ans = await new Promise(res => rl.question(' 立即安装?[Y/n] ', res));
|
|
2240
|
+
rl.close();
|
|
2241
|
+
if (ans.trim().toLowerCase() === 'n') {
|
|
2242
|
+
process.exit(0);
|
|
2243
|
+
}
|
|
2244
|
+
process.stdout.write('\n');
|
|
2133
2245
|
const { npmInstallGlobal } = await import('../utils/npm-ops.js');
|
|
2134
2246
|
try {
|
|
2135
2247
|
await npmInstallGlobal('evolclaw-web');
|
|
2136
2248
|
}
|
|
2137
2249
|
catch (e) {
|
|
2138
|
-
process.stderr.write(`❌
|
|
2250
|
+
process.stderr.write(`❌ 安装失败: ${e?.stderr || e?.message || e}\n`);
|
|
2139
2251
|
process.exit(1);
|
|
2140
2252
|
}
|
|
2141
2253
|
}
|
|
2142
|
-
//
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
process.stderr.write('❌ 已安装 evolclaw-web 但无法定位可执行文件。\n 请重新打开终端后再次运行,或手动执行: evolclaw-web --home ' + home + '\n');
|
|
2148
|
-
process.exit(1);
|
|
2149
|
-
}
|
|
2150
|
-
// Node 18.20+/20+/22 起,execFile 拒绝直接 spawn .cmd/.bat(CVE-2024-27980),必须 shell:true。
|
|
2151
|
-
// shell 模式下含空格的路径/参数需加引号。
|
|
2152
|
-
// evolclaw-web 是前台长驻服务:用户 Ctrl-C、被新实例的单实例保护 SIGKILL、或正常退出,
|
|
2153
|
-
// execFileSync 都会抛错(signal 终止时 status=null)。这些都是正常生命周期,
|
|
2154
|
-
// 不应让父进程 evolclaw 带堆栈崩溃。只有真正的非信号失败才提示。
|
|
2155
|
-
const isBatch = /\.(cmd|bat)$/i.test(exe);
|
|
2156
|
-
try {
|
|
2157
|
-
if (isBatch) {
|
|
2158
|
-
const q = (s) => `"${s}"`;
|
|
2159
|
-
execFileSync(q(exe), ['--home', q(home)], { stdio: 'inherit', shell: true });
|
|
2160
|
-
}
|
|
2161
|
-
else {
|
|
2162
|
-
execFileSync(exe, ['--home', home], { stdio: 'inherit' });
|
|
2163
|
-
}
|
|
2254
|
+
// 2. 检查是否已运行
|
|
2255
|
+
const alive = findAliveEcweb(p);
|
|
2256
|
+
if (alive) {
|
|
2257
|
+
await printEcwebAccess(alive.port);
|
|
2258
|
+
return;
|
|
2164
2259
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
//
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2260
|
+
// 3. 启动(后台)并同步配置
|
|
2261
|
+
const cfg = loadEvolclawConfig();
|
|
2262
|
+
const port = cfg.ecweb?.port ?? 42705;
|
|
2263
|
+
if (cfg.ecweb?.enabled === undefined) {
|
|
2264
|
+
// 首次手动启动时自动写入 enabled:true
|
|
2265
|
+
saveEvolclawConfig({ ...cfg, ecweb: { enabled: true, port } });
|
|
2266
|
+
}
|
|
2267
|
+
startEcwebIfEnabled(p);
|
|
2268
|
+
const started = findAliveEcweb(p);
|
|
2269
|
+
if (!started) {
|
|
2270
|
+
process.stderr.write('❌ 启动失败,请检查 evolclaw-web 是否正确安装\n');
|
|
2271
|
+
process.exit(1);
|
|
2173
2272
|
}
|
|
2273
|
+
await printEcwebAccess(started.port);
|
|
2174
2274
|
}
|
|
2175
2275
|
async function cmdRestartMonitor() {
|
|
2176
2276
|
const p = resolvePaths();
|
|
@@ -2827,13 +2927,23 @@ async function cmdCtl(args) {
|
|
|
2827
2927
|
Agent:
|
|
2828
2928
|
agent <subcommand> EvolAgent 管理(list/show/new/enable/disable/reload/delete)
|
|
2829
2929
|
|
|
2930
|
+
触发器:
|
|
2931
|
+
trigger 查看活跃触发器
|
|
2932
|
+
trigger list 查看所有触发器(含历史)
|
|
2933
|
+
trigger set --delay <时长> --prompt <内容> 延迟触发(如 15m、2h)
|
|
2934
|
+
trigger set --at <ISO时间> --prompt <内容> 定时触发(如 2026-06-10T09:00)
|
|
2935
|
+
trigger set --cron '<表达式>' --prompt <内容> 周期触发(如 '*/15 * * * *')
|
|
2936
|
+
trigger cancel <名称> 取消触发器
|
|
2937
|
+
trigger update <名称> ... 修改触发器参数
|
|
2938
|
+
|
|
2830
2939
|
运维:
|
|
2831
2940
|
restart [channel] 重启服务或重连指定渠道
|
|
2832
2941
|
|
|
2833
2942
|
示例:
|
|
2834
2943
|
evolclaw ctl model sonnet
|
|
2835
2944
|
evolclaw ctl effort high
|
|
2836
|
-
evolclaw ctl compact
|
|
2945
|
+
evolclaw ctl compact
|
|
2946
|
+
evolclaw ctl "trigger set --cron '*/15 * * * *' --prompt '现在时间?'"`);
|
|
2837
2947
|
process.exit(1);
|
|
2838
2948
|
}
|
|
2839
2949
|
// help 不需要连接服务,直接复用无参数时的帮助输出
|
|
@@ -4075,11 +4185,16 @@ Commands:
|
|
|
4075
4185
|
process.exit(1);
|
|
4076
4186
|
}
|
|
4077
4187
|
if (formatJson) {
|
|
4078
|
-
console.log(JSON.stringify({ ok: true, objectKey: remotePath, isPublic, ref: `${aid}/${remotePath}
|
|
4188
|
+
console.log(JSON.stringify({ ok: true, objectKey: remotePath, isPublic, ref: `${aid}/${remotePath}`, publicUrl: result.publicUrl ?? null }));
|
|
4079
4189
|
}
|
|
4080
4190
|
else {
|
|
4081
4191
|
console.log(`✓ 已上传: ${remotePath}${isPublic ? ' (公开)' : ''}`);
|
|
4082
|
-
|
|
4192
|
+
if (result.publicUrl) {
|
|
4193
|
+
console.log(` 🔗 访问: ${result.publicUrl}`);
|
|
4194
|
+
}
|
|
4195
|
+
else {
|
|
4196
|
+
console.log(` 引用: ${aid}/${remotePath}`);
|
|
4197
|
+
}
|
|
4083
4198
|
console.log(` 下载: evolclaw storage download ${aid} ${aid}/${remotePath}`);
|
|
4084
4199
|
}
|
|
4085
4200
|
return;
|
|
@@ -4959,7 +5074,12 @@ export async function main(args) {
|
|
|
4959
5074
|
case 'logs':
|
|
4960
5075
|
cmdLogs(args.slice(1));
|
|
4961
5076
|
break;
|
|
4962
|
-
case 'watch':
|
|
5077
|
+
case 'watch': {
|
|
5078
|
+
// watch 子命令(aid/msg)会调 AUN SDK(aidLookup 刷名片、对端探测等),
|
|
5079
|
+
// 与 aid/msg/group 等命令一致:进 case 先关掉 SDK 的 [aun_core] 日志,
|
|
5080
|
+
// 否则 SDK debug 日志会直喷终端、糊住 watch 的 TUI 面板。
|
|
5081
|
+
const { suppressSdkLogs } = await import('../aun/aid/index.js');
|
|
5082
|
+
suppressSdkLogs();
|
|
4963
5083
|
if (args[1] === 'aid') {
|
|
4964
5084
|
await cmdWatchAid();
|
|
4965
5085
|
}
|
|
@@ -4999,6 +5119,7 @@ export async function main(args) {
|
|
|
4999
5119
|
cmdWatch();
|
|
5000
5120
|
}
|
|
5001
5121
|
break;
|
|
5122
|
+
}
|
|
5002
5123
|
case 'restart-monitor':
|
|
5003
5124
|
await cmdRestartMonitor();
|
|
5004
5125
|
break;
|
|
@@ -5060,6 +5181,18 @@ export async function main(args) {
|
|
|
5060
5181
|
await cmdModel(args.slice(1));
|
|
5061
5182
|
break;
|
|
5062
5183
|
}
|
|
5184
|
+
case 'stats': {
|
|
5185
|
+
const { handleStats } = await import('./stats.js');
|
|
5186
|
+
await handleStats(args.slice(1));
|
|
5187
|
+
break;
|
|
5188
|
+
}
|
|
5189
|
+
case 'version':
|
|
5190
|
+
case '-v':
|
|
5191
|
+
case '--version': {
|
|
5192
|
+
const { handleVersion } = await import('./version.js');
|
|
5193
|
+
handleVersion(args.slice(1));
|
|
5194
|
+
break;
|
|
5195
|
+
}
|
|
5063
5196
|
case 'bench': {
|
|
5064
5197
|
const { suppressSdkLogs } = await import('../aun/aid/index.js');
|
|
5065
5198
|
suppressSdkLogs();
|