evolclaw 3.1.11 → 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 +41 -0
- package/README.md +27 -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/control-aid.js +67 -0
- package/dist/aun/aid/identity.js +20 -7
- package/dist/aun/aid/store.js +2 -2
- package/dist/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +538 -325
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +98 -151
- 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 +44 -13
- package/dist/cli/index.js +207 -46
- package/dist/cli/init-channel.js +38 -148
- package/dist/cli/init.js +192 -85
- 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 +48 -11
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +754 -172
- package/dist/core/daemon-file-cache.js +216 -0
- package/dist/core/evolagent-registry.js +4 -0
- package/dist/core/evolagent.js +28 -23
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +215 -0
- package/dist/core/message/create-status.js +67 -0
- 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 +52 -22
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +336 -68
- package/dist/core/message/message-queue.js +15 -8
- 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 +40 -7
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
- 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}/kit-renderer.js +5 -1
- package/dist/{agents → eck}/manifest-engine.js +127 -35
- package/dist/{agents → eck}/message-renderer.js +26 -1
- package/dist/index.js +185 -8
- package/dist/ipc.js +22 -0
- package/dist/paths.js +7 -3
- 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/message-fragments/item.md +1 -1
- 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/utils/channel-helpers.js +0 -46
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import { ClaudeSessionFileAdapter } from './core/session/adapters/claude-session
|
|
|
2
2
|
import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-file-adapter.js';
|
|
3
3
|
import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
|
|
4
4
|
import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath } from './paths.js';
|
|
5
|
-
import { resolveAnthropicConfig } from './agents/
|
|
6
|
-
import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded } from './config-store.js';
|
|
5
|
+
import { resolveAnthropicConfig } from './agents/baseagent.js';
|
|
6
|
+
import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded, migrateProcessConfigIfNeeded, loadEvolclawConfig } from './config-store.js';
|
|
7
7
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
9
|
import { SessionManager } from './core/session/session-manager.js';
|
|
@@ -12,7 +12,7 @@ import { CodexAgentPlugin } from './agents/codex-runner.js';
|
|
|
12
12
|
import { GeminiAgentPlugin } from './agents/gemini-runner.js';
|
|
13
13
|
import { FeishuChannelPlugin } from './channels/feishu.js';
|
|
14
14
|
import { WechatChannelPlugin } from './channels/wechat.js';
|
|
15
|
-
import { AUNChannelPlugin } from './channels/aun.js';
|
|
15
|
+
import { AUNChannel, AUNChannelPlugin } from './channels/aun.js';
|
|
16
16
|
import { DingtalkChannelPlugin } from './channels/dingtalk.js';
|
|
17
17
|
import { QQBotChannelPlugin } from './channels/qqbot.js';
|
|
18
18
|
import { WecomChannelPlugin } from './channels/wecom.js';
|
|
@@ -20,7 +20,7 @@ import { MessageProcessor, buildEnvelope } from './core/message/message-processo
|
|
|
20
20
|
import { MessageQueue } from './core/message/message-queue.js';
|
|
21
21
|
import { MessageBridge } from './core/message/message-bridge.js';
|
|
22
22
|
import { MessageCache } from './core/message/message-cache.js';
|
|
23
|
-
import { CommandHandler } from './core/command-handler.js';
|
|
23
|
+
import { CommandHandler, isProcessLevelOwner } from './core/command-handler.js';
|
|
24
24
|
import { EventBus } from './core/event-bus.js';
|
|
25
25
|
import { StatsCollector } from './utils/stats.js';
|
|
26
26
|
import { AidStatsCollector } from './utils/stats.js';
|
|
@@ -32,9 +32,10 @@ import { EvolAgentRegistry } from './core/evolagent-registry.js';
|
|
|
32
32
|
import { buildReloadHooks } from './core/channel-loader.js';
|
|
33
33
|
import { IpcServer } from './ipc.js';
|
|
34
34
|
import { logger, setLogLevel } from './utils/logger.js';
|
|
35
|
+
import { fetchEcwebPairCode } from './utils/ecweb-pair.js';
|
|
35
36
|
import { writeMain, removeAll, isMainWinner, scanInstances } from './utils/instance-registry.js';
|
|
36
37
|
import { detectDuplicates } from './core/evolagent-registry.js';
|
|
37
|
-
import { loadKitManifest, cleanEckDebug, invalidateKitCache } from './
|
|
38
|
+
import { loadKitManifest, cleanEckDebug, invalidateKitCache } from './eck/kit-renderer.js';
|
|
38
39
|
import { initEck } from './eck/init.js';
|
|
39
40
|
import { TriggerManager } from './core/trigger/manager.js';
|
|
40
41
|
import { TriggerScheduler, calcNextFireAt } from './core/trigger/scheduler.js';
|
|
@@ -216,6 +217,9 @@ async function main() {
|
|
|
216
217
|
// ── 自动迁移 ──
|
|
217
218
|
migrateIdentitiesIfNeeded();
|
|
218
219
|
autoMigrateIfNeeded();
|
|
220
|
+
// config.json(ProcessConfig)→ evolclaw.json:必须在任何 getAidStore(AUN 连接)之前,
|
|
221
|
+
// 否则首次读 encryptionSeed 时迁移还没发生。
|
|
222
|
+
migrateProcessConfigIfNeeded();
|
|
219
223
|
// ── ECK 运行时初始化 ──
|
|
220
224
|
initEck();
|
|
221
225
|
// 加载 ECK manifest + 清理旧调试文件
|
|
@@ -223,6 +227,13 @@ async function main() {
|
|
|
223
227
|
loadKitManifest();
|
|
224
228
|
// 加载配置(新结构:defaults.json + per-agent config.json)
|
|
225
229
|
const defaults = loadDefaults() ?? { $schema_version: CONFIG_SCHEMA_VERSION };
|
|
230
|
+
const evolclawCfg = loadEvolclawConfig();
|
|
231
|
+
// 进程级 menu 操作(/system /agent)鉴权:owners 来自 evolclaw.json 顶层。
|
|
232
|
+
// owners 为空时这些操作一律 FORBIDDEN,启动时提示如何配置。
|
|
233
|
+
if (!evolclawCfg.owners || evolclawCfg.owners.length === 0) {
|
|
234
|
+
logger.warn('[startup] evolclaw.json.owners 未配置:进程级 menu 操作(/system /agent)将一律拒绝。' +
|
|
235
|
+
'如需远程管理,请在 evolclaw.json 配置 owners: [<你的 AID>]');
|
|
236
|
+
}
|
|
226
237
|
// 应用配置中的日志级别(优先于环境变量)
|
|
227
238
|
// logLevel 现在不在新结构中——若要保留,将来可加 defaults.debug.logLevel
|
|
228
239
|
// 阶段 2c 暂跳过
|
|
@@ -305,6 +316,40 @@ async function main() {
|
|
|
305
316
|
// Per-AID 消息统计收集器(累计,供 watch aid 实时展示)
|
|
306
317
|
const aidStatsCollector = new AidStatsCollector(eventBus);
|
|
307
318
|
aidStatsCollector.setSessionsDir(paths.sessionsDir);
|
|
319
|
+
// 持久化网络流量到 message_events 表
|
|
320
|
+
aidStatsCollector.onMessage = (ev) => {
|
|
321
|
+
import('./core/stats/writer.js').then(({ insertMessageEvent }) => {
|
|
322
|
+
insertMessageEvent(paths.root, ev);
|
|
323
|
+
}).catch(() => { });
|
|
324
|
+
};
|
|
325
|
+
// 日聚合表 usage_daily:首次启动回填 + 每日自愈。
|
|
326
|
+
// 首次:表为空但明细非空时全量回填历史数据;之后靠 writer 写时增量维护。
|
|
327
|
+
// 自愈:每日全量重建一次,纠正任何写时漂移。
|
|
328
|
+
import('./core/stats/db.js').then(({ getDb, rebuildDailyRollup }) => {
|
|
329
|
+
const db = getDb(paths.root);
|
|
330
|
+
if (!db)
|
|
331
|
+
return;
|
|
332
|
+
try {
|
|
333
|
+
const daily = db.prepare('SELECT COUNT(*) AS n FROM usage_daily').get();
|
|
334
|
+
const events = db.prepare('SELECT COUNT(*) AS n FROM usage_events').get();
|
|
335
|
+
if (daily.n === 0 && events.n > 0) {
|
|
336
|
+
logger.info('[Stats] usage_daily 为空,回填历史数据…');
|
|
337
|
+
rebuildDailyRollup(paths.root);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
logger.warn(`[Stats] usage_daily 回填检测失败(非致命): ${e}`);
|
|
342
|
+
}
|
|
343
|
+
// 每日自愈(24h),纠正写时增量漂移。
|
|
344
|
+
setInterval(() => {
|
|
345
|
+
try {
|
|
346
|
+
rebuildDailyRollup(paths.root);
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
logger.warn(`[Stats] usage_daily 自愈失败(非致命): ${e}`);
|
|
350
|
+
}
|
|
351
|
+
}, 24 * 60 * 60 * 1000);
|
|
352
|
+
}).catch(() => { });
|
|
308
353
|
// 初始化 SessionManager(文件系统后端)
|
|
309
354
|
const sessionManager = new SessionManager(paths.sessionsDir, eventBus, (channel, userId) => agentRegistry.isOwner(channel, userId), (channel, userId) => agentRegistry.isAdmin(channel, userId));
|
|
310
355
|
// sessionMode 解析:从 channel 路由到具体 agent,按 agent.config.chatmode
|
|
@@ -438,7 +483,7 @@ async function main() {
|
|
|
438
483
|
const evol = evolagentName || primaryAgent.aid;
|
|
439
484
|
const agent = agentMap.get(`${evol}::${baseagent}`)
|
|
440
485
|
|| agentMap.get(primaryRunnerKey);
|
|
441
|
-
if (agent
|
|
486
|
+
if (agent) {
|
|
442
487
|
await agent.interrupt(sessionKey);
|
|
443
488
|
}
|
|
444
489
|
});
|
|
@@ -486,6 +531,17 @@ async function main() {
|
|
|
486
531
|
}
|
|
487
532
|
}
|
|
488
533
|
scheduler.setFireCallback((msg, trigger) => {
|
|
534
|
+
const onEnqueueFailed = (err) => {
|
|
535
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
536
|
+
logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${error}`);
|
|
537
|
+
eventBus.publish({
|
|
538
|
+
type: 'trigger:failed', triggerId: trigger.id, name: trigger.name,
|
|
539
|
+
messageId: msg.messageId || '', error,
|
|
540
|
+
targetChannel: trigger.targetChannel, targetChannelId: trigger.targetChannelId,
|
|
541
|
+
fireTime: msg.triggerMeta?.fireTime ?? Date.now(), phase: 'enqueue',
|
|
542
|
+
});
|
|
543
|
+
scheduler.onTriggerComplete(trigger.id, 'failed');
|
|
544
|
+
};
|
|
489
545
|
if (trigger.targetSessionStrategy === 'current' && trigger.boundSessionId) {
|
|
490
546
|
const boundId = trigger.boundSessionId;
|
|
491
547
|
if (messageQueue.isProcessing(boundId)) {
|
|
@@ -498,12 +554,12 @@ async function main() {
|
|
|
498
554
|
return;
|
|
499
555
|
}
|
|
500
556
|
messageQueue.enqueue(boundId, msg, bound.projectPath, { interruptible: false })
|
|
501
|
-
.catch(
|
|
557
|
+
.catch(onEnqueueFailed);
|
|
502
558
|
});
|
|
503
559
|
return;
|
|
504
560
|
}
|
|
505
561
|
messageQueue.enqueue(`${msg.channel}:${msg.channelId}`, msg, primaryProjectPath, { interruptible: false })
|
|
506
|
-
.catch(
|
|
562
|
+
.catch(onEnqueueFailed);
|
|
507
563
|
});
|
|
508
564
|
// Subscribe to trigger:completed/failed/skipped to update cron inflight state
|
|
509
565
|
eventBus.subscribe('trigger:completed', (ev) => scheduler.onTriggerComplete(ev.triggerId, 'completed'));
|
|
@@ -512,6 +568,41 @@ async function main() {
|
|
|
512
568
|
if (ev.reason === 'interrupted')
|
|
513
569
|
scheduler.onTriggerComplete(ev.triggerId, 'interrupted');
|
|
514
570
|
});
|
|
571
|
+
// ── Trigger 失败/跳过通知:向 targetChannel 发送告警消息 ──
|
|
572
|
+
eventBus.subscribe('trigger:failed', (ev) => {
|
|
573
|
+
const adapter = processor.getAdapter(ev.targetChannel);
|
|
574
|
+
if (!adapter)
|
|
575
|
+
return;
|
|
576
|
+
const phaseLabel = ev.phase === 'enqueue' ? '入队' : '执行';
|
|
577
|
+
const timeStr = ev.fireTime ? new Date(ev.fireTime).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) : '未知';
|
|
578
|
+
const text = `⚠️ 定时任务 [${ev.name || ev.triggerId}] 执行失败\n阶段:${phaseLabel}\n原因:${ev.error}\n触发时间:${timeStr}`;
|
|
579
|
+
const envelope = {
|
|
580
|
+
taskId: `trigger-notify:${ev.triggerId}`,
|
|
581
|
+
channel: ev.targetChannel,
|
|
582
|
+
channelId: ev.targetChannelId,
|
|
583
|
+
agentName: 'system',
|
|
584
|
+
chatmode: 'interactive',
|
|
585
|
+
timestamp: Date.now(),
|
|
586
|
+
};
|
|
587
|
+
adapter.send(envelope, { kind: 'result.text', text, isFinal: true, format: 'plain' }).catch(() => { });
|
|
588
|
+
});
|
|
589
|
+
eventBus.subscribe('trigger:skipped', (ev) => {
|
|
590
|
+
if (ev.reason !== 'overlap')
|
|
591
|
+
return;
|
|
592
|
+
const adapter = processor.getAdapter(ev.targetChannel);
|
|
593
|
+
if (!adapter)
|
|
594
|
+
return;
|
|
595
|
+
const text = `⚠️ 定时任务 [${ev.name || ev.triggerId}] 本次跳过(上次执行仍在进行中)`;
|
|
596
|
+
const envelope = {
|
|
597
|
+
taskId: `trigger-notify:${ev.triggerId}`,
|
|
598
|
+
channel: ev.targetChannel,
|
|
599
|
+
channelId: ev.targetChannelId,
|
|
600
|
+
agentName: 'system',
|
|
601
|
+
chatmode: 'interactive',
|
|
602
|
+
timestamp: Date.now(),
|
|
603
|
+
};
|
|
604
|
+
adapter.send(envelope, { kind: 'result.text', text, isFinal: true, format: 'plain' }).catch(() => { });
|
|
605
|
+
});
|
|
515
606
|
// Note: only the primary agent's scheduler is wired to cmdHandler.
|
|
516
607
|
// Non-primary agent channels will receive "⚠️ 触发器功能未启用" when using /trigger.
|
|
517
608
|
// Full per-channel scheduler routing is a future improvement.
|
|
@@ -584,6 +675,19 @@ async function main() {
|
|
|
584
675
|
if (inst.registerHooks) {
|
|
585
676
|
inst.registerHooks({ eventBus, sessionManager });
|
|
586
677
|
}
|
|
678
|
+
// 4c. 观察者模式配置读取器(AUN):从 EvolAgent 的 merged config 读 observable/owners,
|
|
679
|
+
// 不另建缓存——EvolAgent 那份在启动/重启/热重载时统一更新,是唯一真相源。
|
|
680
|
+
const channelForObserver = inst.channel;
|
|
681
|
+
if (typeof channelForObserver.setObserverConfigResolver === 'function') {
|
|
682
|
+
const channelKey = inst.adapter.channelKey;
|
|
683
|
+
channelForObserver.setObserverConfigResolver(() => {
|
|
684
|
+
const owningAgent = agentRegistry.resolveByChannel(channelKey);
|
|
685
|
+
return {
|
|
686
|
+
observable: owningAgent?.getObservable() ?? false,
|
|
687
|
+
owners: owningAgent?.config.owners ?? [],
|
|
688
|
+
};
|
|
689
|
+
});
|
|
690
|
+
}
|
|
587
691
|
// 5. 撤回消息 → 中断执行中任务
|
|
588
692
|
inst.channel.onRecall?.((messageId) => {
|
|
589
693
|
msgBridge.cancel(messageId);
|
|
@@ -627,6 +731,7 @@ async function main() {
|
|
|
627
731
|
createdByPeerId: '__system__',
|
|
628
732
|
createdByChannel: '__system__',
|
|
629
733
|
fireCount: 0,
|
|
734
|
+
failCount: 0,
|
|
630
735
|
createdAt: Date.now(),
|
|
631
736
|
updatedAt: Date.now(),
|
|
632
737
|
};
|
|
@@ -675,6 +780,67 @@ async function main() {
|
|
|
675
780
|
timestamp: Date.now()
|
|
676
781
|
});
|
|
677
782
|
}
|
|
783
|
+
// ── 控制 AID(daemon 进程身份):pureIdentity 接入 AUN,独立于 evolagent ──
|
|
784
|
+
// 证书缺失检测/生成在 CLI 侧(evolclaw start)完成。daemon 是后台进程无终端,
|
|
785
|
+
// 这里只做兜底:证书缺失时 warn 并继续(AUNChannel 内部后台重连),绝不阻塞。
|
|
786
|
+
if (evolclawCfg.aid) {
|
|
787
|
+
const aunPath = resolvePaths().root;
|
|
788
|
+
const certKey = path.join(aunPath, 'AIDs', evolclawCfg.aid, 'private', 'key.json');
|
|
789
|
+
if (!fs.existsSync(certKey)) {
|
|
790
|
+
logger.warn(`控制 AID 证书缺失:${evolclawCfg.aid}(AUN 控制通道后台重连;如需重建运行 evolclaw init)`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
let controlChannel;
|
|
794
|
+
if (evolclawCfg.aid) {
|
|
795
|
+
controlChannel = new AUNChannel({
|
|
796
|
+
aid: evolclawCfg.aid,
|
|
797
|
+
agentName: evolclawCfg.aid,
|
|
798
|
+
channelName: 'control',
|
|
799
|
+
pureIdentity: true,
|
|
800
|
+
aunTrace: evolclawCfg.debug?.aunTrace ?? defaults.debug?.aunTrace,
|
|
801
|
+
aunSdkLog: evolclawCfg.debug?.aunSdkLog ?? defaults.debug?.aunSdkLog,
|
|
802
|
+
});
|
|
803
|
+
// connect() 失败不置空实例:AUNChannel 内部有无限重连(SDK auto_reconnect +
|
|
804
|
+
// scheduleReconnect),首连失败后台会自愈;保留实例供 status 显示 disconnected。
|
|
805
|
+
try {
|
|
806
|
+
await controlChannel.connect();
|
|
807
|
+
logger.info(`✓ 控制 AID 已连接: ${evolclawCfg.aid}`);
|
|
808
|
+
}
|
|
809
|
+
catch (e) {
|
|
810
|
+
logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
|
|
811
|
+
}
|
|
812
|
+
// 控制 AID 接收 owner 指令:本轮只做 ECWeb 登录入口(/pair 取配对码)。
|
|
813
|
+
// 发送方身份由 AUN X.509 证书链验证,非 owner 完全静默。daemon 直接处理,
|
|
814
|
+
// 不转回 ecweb——仅「配对码是 ecweb 持有的状态」需经 localhost 取一次。
|
|
815
|
+
controlChannel.onMessage(async (opts) => {
|
|
816
|
+
try {
|
|
817
|
+
if (!isProcessLevelOwner(opts.peerId, evolclawCfg.owners)) {
|
|
818
|
+
logger.debug(`控制 AID 收到非 owner 消息,忽略: from=${opts.peerId}`);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const text = (opts.content || '').trim();
|
|
822
|
+
if (text.toLowerCase() === '/pair') {
|
|
823
|
+
const port = evolclawCfg.ecweb?.port ?? 42705;
|
|
824
|
+
const pair = await fetchEcwebPairCode(port);
|
|
825
|
+
let reply;
|
|
826
|
+
if (pair) {
|
|
827
|
+
const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
|
|
828
|
+
reply = `ECWeb 配对码:${pair.code}(约 ${mins} 分钟内有效)\n在浏览器打开 ECWeb 后输入此码登录`;
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
reply = 'ECWeb 未运行或暂不可达。请在主机运行 ec watch web 启动后重试。';
|
|
832
|
+
}
|
|
833
|
+
await controlChannel.sendMessage(opts.channelId, reply);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
// owner 发的其他内容:提示可用指令,避免无响应让 owner 困惑
|
|
837
|
+
await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)');
|
|
838
|
+
}
|
|
839
|
+
catch (e) {
|
|
840
|
+
logger.warn(`控制 AID 消息处理失败: ${e?.message || e}`);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
}
|
|
678
844
|
// 上线通知:延迟 1-3 秒后向 owner 发送上线消息(带 name + 工作目录)
|
|
679
845
|
// 需在配置中 debug.upmsg: true 手动开启
|
|
680
846
|
setTimeout(() => {
|
|
@@ -882,10 +1048,14 @@ async function main() {
|
|
|
882
1048
|
errors: snap.lastHour.errors,
|
|
883
1049
|
avgResponseMs: snap.lastHour.avgResponseMs,
|
|
884
1050
|
},
|
|
1051
|
+
controlAid: evolclawCfg.aid
|
|
1052
|
+
? { aid: evolclawCfg.aid, connected: controlChannel?.getAidState().status === 'connected' }
|
|
1053
|
+
: undefined,
|
|
885
1054
|
};
|
|
886
1055
|
}, async (cmd, sessionId) => cmdHandler.handleCtl(cmd, sessionId));
|
|
887
1056
|
// M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
|
|
888
1057
|
ipcServer.setAgentRegistry(agentRegistry);
|
|
1058
|
+
ipcServer.setMenuExecutor((payload) => cmdHandler.execMenuForEcweb(payload));
|
|
889
1059
|
// 注入 AUN AID 状态聚合器:遍历所有 aun 类型 channel,调 getAidState() 收集
|
|
890
1060
|
ipcServer.setAunAidProvider(() => {
|
|
891
1061
|
const out = [];
|
|
@@ -1036,6 +1206,13 @@ async function main() {
|
|
|
1036
1206
|
const type = inst.channelType || inst.adapter.channelName;
|
|
1037
1207
|
eventBus.publish({ type: 'channel:disconnected', channel: type, channelName: inst.adapter.channelName, reason: 'shutdown' });
|
|
1038
1208
|
}
|
|
1209
|
+
// 断开控制 AID(daemon 进程身份)
|
|
1210
|
+
if (controlChannel) {
|
|
1211
|
+
try {
|
|
1212
|
+
await controlChannel.disconnect();
|
|
1213
|
+
}
|
|
1214
|
+
catch { /* ignore */ }
|
|
1215
|
+
}
|
|
1039
1216
|
sessionManager.close();
|
|
1040
1217
|
removeAll();
|
|
1041
1218
|
logger.info('✓ Shutdown complete');
|
package/dist/ipc.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { logger } from './utils/logger.js';
|
|
4
|
+
import { fileCache } from './core/daemon-file-cache.js';
|
|
4
5
|
const isWindows = process.platform === 'win32';
|
|
5
6
|
const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
|
|
6
7
|
export class IpcServer {
|
|
@@ -12,6 +13,7 @@ export class IpcServer {
|
|
|
12
13
|
aunAidProvider;
|
|
13
14
|
aunAidStatsProvider;
|
|
14
15
|
aunAidStatsRecorder;
|
|
16
|
+
menuExecutor;
|
|
15
17
|
constructor(socketPath, getStatus, commandExecutor) {
|
|
16
18
|
this.socketPath = socketPath;
|
|
17
19
|
this.getStatus = getStatus;
|
|
@@ -21,6 +23,10 @@ export class IpcServer {
|
|
|
21
23
|
setAgentRegistry(registry) {
|
|
22
24
|
this.agentRegistry = registry;
|
|
23
25
|
}
|
|
26
|
+
/** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
|
|
27
|
+
setMenuExecutor(executor) {
|
|
28
|
+
this.menuExecutor = executor;
|
|
29
|
+
}
|
|
24
30
|
/** Inject AUN AID state aggregator for aun-aids IPC handler */
|
|
25
31
|
setAunAidProvider(provider) {
|
|
26
32
|
this.aunAidProvider = provider;
|
|
@@ -102,6 +108,11 @@ export class IpcServer {
|
|
|
102
108
|
const stats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
|
|
103
109
|
return { ok: true, stats };
|
|
104
110
|
}
|
|
111
|
+
case 'cache-stats': {
|
|
112
|
+
// daemon 统一 FileCache 的只读运行统计(watch web Cache 页用)。
|
|
113
|
+
// 直接读单例,无需 provider 注入(与 manifest-engine 等 import 单例同款)。
|
|
114
|
+
return { ok: true, stats: fileCache.stats() };
|
|
115
|
+
}
|
|
105
116
|
case 'aun-aid-stats-record-outbound': {
|
|
106
117
|
if (!this.aunAidStatsRecorder)
|
|
107
118
|
return { ok: false, error: 'recorder not configured' };
|
|
@@ -200,6 +211,17 @@ export class IpcServer {
|
|
|
200
211
|
return { ok: false, error: e?.message || String(e) };
|
|
201
212
|
}
|
|
202
213
|
}
|
|
214
|
+
case 'menu.exec': {
|
|
215
|
+
if (!this.menuExecutor)
|
|
216
|
+
return { ok: false, error: 'menu.exec not configured' };
|
|
217
|
+
try {
|
|
218
|
+
const response = await this.menuExecutor(cmd.payload);
|
|
219
|
+
return { ok: true, response };
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
return { ok: false, error: e?.message ?? String(e) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
203
225
|
default:
|
|
204
226
|
return { error: `unknown command: ${cmd.type}` };
|
|
205
227
|
}
|
package/dist/paths.js
CHANGED
|
@@ -38,7 +38,8 @@ export function resolvePaths() {
|
|
|
38
38
|
socket: resolveInstanceSocketPath(root),
|
|
39
39
|
// ── 新结构(evolclaw-directory-design.md)────────────────
|
|
40
40
|
defaultsConfig: path.join(root, 'agents', 'defaults.json'),
|
|
41
|
-
processConfig: path.join(root, 'config.json'),
|
|
41
|
+
processConfig: path.join(root, 'config.json'), // legacy ProcessConfig path — only for migration source
|
|
42
|
+
evolclawJson: path.join(root, 'evolclaw.json'),
|
|
42
43
|
eckDir: path.join(root, 'eck'),
|
|
43
44
|
instanceReadySignal: path.join(root, 'data', 'instance', 'ready.signal'),
|
|
44
45
|
instanceSocket: resolveInstanceSocketPath(root),
|
|
@@ -182,14 +183,17 @@ function migrateFromAun() {
|
|
|
182
183
|
catch { }
|
|
183
184
|
}
|
|
184
185
|
/**
|
|
185
|
-
* 首次运行时从 assets/ 模板拷贝
|
|
186
|
+
* 首次运行时从 assets/ 模板拷贝 .env 到 $EVOLCLAW_HOME。
|
|
186
187
|
* 已存在则跳过,不覆盖用户修改。
|
|
188
|
+
*
|
|
189
|
+
* 注:config.json(旧 ProcessConfig)已废弃——不再 seed,否则会与
|
|
190
|
+
* migrateProcessConfigIfNeeded 的归档形成每次启动重建+重迁移的循环。
|
|
191
|
+
* 进程级配置统一走 evolclaw.json。
|
|
187
192
|
*/
|
|
188
193
|
function seedConfigTemplates() {
|
|
189
194
|
const root = resolveRoot();
|
|
190
195
|
const assetsDir = path.join(getPackageRoot(), 'assets');
|
|
191
196
|
const templates = [
|
|
192
|
-
{ src: 'config.json.template', dst: 'config.json' },
|
|
193
197
|
{ src: '.env.template', dst: '.env' },
|
|
194
198
|
];
|
|
195
199
|
for (const { src, dst } of templates) {
|
|
@@ -5,15 +5,33 @@ import { promisify } from 'util';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
export const isWindows = process.platform === 'win32';
|
|
8
|
+
const ENCODE_PATH_MAX = 200;
|
|
9
|
+
function encodePathHash(s) {
|
|
10
|
+
let h = 0;
|
|
11
|
+
for (let i = 0; i < s.length; i++)
|
|
12
|
+
h = (h << 5) - h + s.charCodeAt(i) | 0;
|
|
13
|
+
return Math.abs(h).toString(36);
|
|
14
|
+
}
|
|
8
15
|
/**
|
|
9
16
|
* Encode project path as directory name (Claude SDK convention).
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
17
|
+
* Mirrors the SDK's F0(L_(path)) logic:
|
|
18
|
+
* 1. path.resolve → realpath (if exists) → Unicode NFC
|
|
19
|
+
* 2. replace every non-alphanumeric character with '-'
|
|
20
|
+
* 3. truncate to 200 chars + hash suffix for long paths
|
|
21
|
+
* This must stay in sync with the SDK so evolclaw can locate session files.
|
|
13
22
|
*/
|
|
14
23
|
export function encodePath(projectPath) {
|
|
15
|
-
const
|
|
16
|
-
|
|
24
|
+
const resolved = path.resolve(projectPath);
|
|
25
|
+
let real = resolved;
|
|
26
|
+
try {
|
|
27
|
+
real = fs.realpathSync(resolved);
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
const nfc = real.normalize('NFC');
|
|
31
|
+
const encoded = nfc.replace(/[^a-zA-Z0-9]/g, '-');
|
|
32
|
+
if (encoded.length <= ENCODE_PATH_MAX)
|
|
33
|
+
return encoded;
|
|
34
|
+
return `${encoded.slice(0, ENCODE_PATH_MAX)}-${encodePathHash(nfc)}`;
|
|
17
35
|
}
|
|
18
36
|
/**
|
|
19
37
|
* Cross-platform process liveness check.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECWeb 配对码取码 helper(共享给 daemon 和 CLI)。
|
|
3
|
+
*
|
|
4
|
+
* 配对码是 ecweb 进程自己生成并持有的内部状态。daemon/CLI 通过 ecweb 的
|
|
5
|
+
* localhost-only HTTP 接口 GET /api/pair-code 取当前码(远程访问被 ecweb 403 拒绝)。
|
|
6
|
+
*/
|
|
7
|
+
/** 经 localhost 拉取 ecweb 当前配对码(仅本机可取)。失败返回 null。 */
|
|
8
|
+
export async function fetchEcwebPairCode(port) {
|
|
9
|
+
try {
|
|
10
|
+
const resp = await fetch(`http://127.0.0.1:${port}/api/pair-code`, {
|
|
11
|
+
signal: AbortSignal.timeout(2000),
|
|
12
|
+
});
|
|
13
|
+
if (!resp.ok)
|
|
14
|
+
return null;
|
|
15
|
+
return await resp.json();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/utils/stats.js
CHANGED
|
@@ -104,6 +104,8 @@ export class AidStatsCollector {
|
|
|
104
104
|
queueStatsProvider;
|
|
105
105
|
/** sessionId → 当前正在跑该 session 的 agent,task:started 写入,task:completed/error 清除 */
|
|
106
106
|
sessionToAgent = new Map();
|
|
107
|
+
/** 外部注入的持久化回调(写入 message_events 表) */
|
|
108
|
+
onMessage;
|
|
107
109
|
constructor(eventBus) {
|
|
108
110
|
if (!eventBus)
|
|
109
111
|
return;
|
|
@@ -342,6 +344,12 @@ export class AidStatsCollector {
|
|
|
342
344
|
}
|
|
343
345
|
entry.bytesReceived += byteLength;
|
|
344
346
|
entry.uniquePeers.add(fromPeer);
|
|
347
|
+
// 持久化
|
|
348
|
+
this.onMessage?.({
|
|
349
|
+
ts: Date.now(), agent_aid: aid, peer_key: `aun#${fromPeer}`,
|
|
350
|
+
direction: 'in', msg_type: isSystem ? 'system' : 'private',
|
|
351
|
+
bytes: byteLength, encrypted: encrypt, chatmode,
|
|
352
|
+
});
|
|
345
353
|
}
|
|
346
354
|
recordOutbound(aid, toPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
|
|
347
355
|
const entry = this.getOrCreate(aid);
|
|
@@ -369,6 +377,12 @@ export class AidStatsCollector {
|
|
|
369
377
|
}
|
|
370
378
|
entry.bytesSent += byteLength;
|
|
371
379
|
entry.uniquePeers.add(toPeer);
|
|
380
|
+
// 持久化
|
|
381
|
+
this.onMessage?.({
|
|
382
|
+
ts: Date.now(), agent_aid: aid, peer_key: `aun#${toPeer}`,
|
|
383
|
+
direction: 'out', msg_type: isSystem ? 'system' : 'private',
|
|
384
|
+
bytes: byteLength, encrypted: encrypt, chatmode,
|
|
385
|
+
});
|
|
372
386
|
}
|
|
373
387
|
getAllSnapshots() {
|
|
374
388
|
const out = [];
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
| `ec msg` | 私聊收发消息 | 回复/发消息/拉取/撤回/查在线 | 有对端(peerId) | `msg.md` |
|
|
15
15
|
| `ec group` | 群聊收发与群管理 | 群发/建群/邀请/踢人/退群/群成员 | 群聊(groupId) | `group.md` |
|
|
16
16
|
| `ec aid` | AID 身份管理 | 身份/证书/名片/探测对端 | 任意有渠道场景 | `aid.md` |
|
|
17
|
-
| `ec
|
|
17
|
+
| `ec fs` | 网络文件系统(统一前端) | 上传/下载/看文件/列目录/分享/共享/挂载/配额 | 任意有渠道场景 | `fs.md`(架构全景 `fs-architecture.md`) |
|
|
18
|
+
| `ec storage` | 文件存储(底层调试) | 上传/下载/配额 | 任意有渠道场景 | `storage.md` |
|
|
18
19
|
| `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md` |
|
|
19
20
|
| `ec rpc` | 底层 AUN RPC(逃生通道) | 直接调协议方法 | 高级/兜底 | `rpc.md` |
|
|
20
21
|
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
| 命令集 | 用途 | 触发词 | 文档 |
|
|
30
31
|
|--------|------|--------|------|
|
|
31
32
|
| `ec model` | 模型管理(按作用域持久化) | 切模型/列模型/看当前/改强度 | `model.md` |
|
|
33
|
+
| `ec stats` | Token 用量与费用统计 | 用量/费用/统计/预算/token/cost | `stats.md` |
|
|
32
34
|
|
|
33
35
|
## 开发者工具(非 agent 会话能力)
|
|
34
36
|
|