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.
Files changed (89) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +27 -2
  3. package/dist/agents/{resolve.js → baseagent.js} +34 -5
  4. package/dist/agents/claude-runner.js +120 -27
  5. package/dist/agents/codex-app-server-client.js +364 -0
  6. package/dist/agents/codex-runner.js +1069 -141
  7. package/dist/agents/gemini-runner.js +2 -2
  8. package/dist/agents/runner-types.js +28 -0
  9. package/dist/aun/aid/control-aid.js +67 -0
  10. package/dist/aun/aid/identity.js +20 -7
  11. package/dist/aun/aid/store.js +2 -2
  12. package/dist/aun/storage/download.js +1 -1
  13. package/dist/aun/storage/upload.js +13 -1
  14. package/dist/channels/aun.js +538 -325
  15. package/dist/channels/dingtalk.js +77 -140
  16. package/dist/channels/feishu.js +98 -151
  17. package/dist/channels/qqbot.js +75 -138
  18. package/dist/channels/wechat.js +75 -136
  19. package/dist/channels/wecom.js +75 -138
  20. package/dist/cli/agent.js +44 -13
  21. package/dist/cli/index.js +207 -46
  22. package/dist/cli/init-channel.js +38 -148
  23. package/dist/cli/init.js +192 -85
  24. package/dist/cli/model.js +1 -1
  25. package/dist/cli/stats.js +558 -0
  26. package/dist/cli/version.js +87 -0
  27. package/dist/cli/watch-msg.js +5 -2
  28. package/dist/config-store.js +48 -11
  29. package/dist/core/channel-loader.js +84 -82
  30. package/dist/core/command-handler.js +754 -172
  31. package/dist/core/daemon-file-cache.js +216 -0
  32. package/dist/core/evolagent-registry.js +4 -0
  33. package/dist/core/evolagent.js +28 -23
  34. package/dist/core/interaction-router.js +8 -0
  35. package/dist/core/message/command-handler-agent-control.js +215 -0
  36. package/dist/core/message/create-status.js +67 -0
  37. package/dist/core/message/im-renderer.js +35 -13
  38. package/dist/core/message/items-formatter.js +9 -1
  39. package/dist/core/message/message-bridge.js +52 -22
  40. package/dist/core/message/message-log.js +1 -0
  41. package/dist/core/message/message-processor.js +336 -68
  42. package/dist/core/message/message-queue.js +15 -8
  43. package/dist/core/message/pending-hints.js +232 -0
  44. package/dist/core/message/response-depth.js +56 -0
  45. package/dist/core/model/model-catalog.js +1 -1
  46. package/dist/core/model/model-scope.js +40 -7
  47. package/dist/core/permission.js +9 -12
  48. package/dist/core/relation/peer-identity.js +16 -1
  49. package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
  50. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  51. package/dist/core/session/session-manager.js +27 -13
  52. package/dist/core/session/session-title.js +26 -0
  53. package/dist/core/stats/billing.js +151 -0
  54. package/dist/core/stats/budget.js +93 -0
  55. package/dist/core/stats/db.js +314 -0
  56. package/dist/core/stats/eck-vars.js +84 -0
  57. package/dist/core/stats/index.js +10 -0
  58. package/dist/core/stats/normalizer.js +78 -0
  59. package/dist/core/stats/query.js +760 -0
  60. package/dist/core/stats/writer.js +115 -0
  61. package/dist/core/trigger/manager.js +34 -0
  62. package/dist/core/trigger/parser.js +9 -3
  63. package/dist/core/trigger/scheduler.js +20 -17
  64. package/dist/{agents → eck}/kit-renderer.js +5 -1
  65. package/dist/{agents → eck}/manifest-engine.js +127 -35
  66. package/dist/{agents → eck}/message-renderer.js +26 -1
  67. package/dist/index.js +185 -8
  68. package/dist/ipc.js +22 -0
  69. package/dist/paths.js +7 -3
  70. package/dist/utils/cross-platform.js +23 -5
  71. package/dist/utils/ecweb-pair.js +20 -0
  72. package/dist/utils/stats.js +14 -0
  73. package/kits/docs/evolclaw/INDEX.md +3 -1
  74. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  75. package/kits/docs/evolclaw/fs.md +131 -0
  76. package/kits/docs/evolclaw/group-fs.md +209 -0
  77. package/kits/docs/evolclaw/stats.md +70 -0
  78. package/kits/docs/venues/aun-group.md +29 -6
  79. package/kits/docs/venues/group.md +5 -4
  80. package/kits/eck_manifest.json +12 -0
  81. package/kits/eck_message_manifest.json +30 -3
  82. package/kits/rules/05-venue.md +1 -1
  83. package/kits/templates/message-fragments/inject-default.md +2 -0
  84. package/kits/templates/message-fragments/item.md +1 -1
  85. package/kits/templates/system-fragments/response-depth.md +16 -0
  86. package/package.json +4 -4
  87. package/dist/agents/baseagent-normalize.js +0 -19
  88. package/dist/core/relation/peer-key.js +0 -16
  89. 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/resolve.js';
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 './agents/kit-renderer.js';
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?.hasActiveStream(sessionKey)) {
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(err => logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${err}`));
557
+ .catch(onEnqueueFailed);
502
558
  });
503
559
  return;
504
560
  }
505
561
  messageQueue.enqueue(`${msg.channel}:${msg.channelId}`, msg, primaryProjectPath, { interruptible: false })
506
- .catch(err => logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${err}`));
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/ 模板拷贝 config.json 和 .env 到 $EVOLCLAW_HOME。
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
- * Replace all path separators with '-'.
11
- * e.g. /home/user/project -> -home-user-project
12
- * C:\Users\project -> C--Users-project
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 normalized = projectPath.replace(/[/\\]+$/, '');
16
- return normalized.replace(/[/\\:]/g, '-');
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
+ }
@@ -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 storage` | 文件存储 | 上传/下载/配额 | 任意有渠道场景 | `storage.md` |
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