evolcore 0.0.7 → 0.0.8
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 +19 -0
- package/README.md +3 -3
- package/dist/agents/baseagent.js +6 -5
- package/dist/agents/claude-runner.js +1 -0
- package/dist/agents/codex-app-server-client.js +6 -2
- package/dist/agents/codex-runner.js +8 -3
- package/dist/aun/aid/control-aid.js +40 -27
- package/dist/aun/aid/domain.js +23 -0
- package/dist/channels/aun.js +26 -21
- package/dist/cli/bench.js +4 -3
- package/dist/cli/daemon-commands.js +189 -64
- package/dist/cli/data-command.js +62 -35
- package/dist/cli/init-channel.js +1 -1
- package/dist/cli/init.js +9 -13
- package/dist/cli/restart-monitor.js +102 -22
- package/dist/config/config-manager.js +34 -3
- package/dist/config/gateway-config.js +80 -35
- package/dist/config-store.js +15 -3
- package/dist/core/baseagent-loader.js +5 -3
- package/dist/core/capability/providers/codex-capability-provider.js +2 -2
- package/dist/core/channel-loader.js +10 -1
- package/dist/core/command/menu-handler.js +17 -6
- package/dist/core/data-migration.js +517 -24
- package/dist/core/protected-paths.js +12 -1
- package/dist/index.js +752 -700
- package/dist/ipc.js +2 -0
- package/dist/utils/codex-cli.js +39 -0
- package/dist/utils/cross-platform.js +147 -18
- package/dist/utils/instance-registry.js +43 -6
- package/dist/utils/process-introspect.js +7 -3
- package/kits/rules/01-overview.md +3 -2
- package/kits/schemas/_meta.json +2 -1
- package/kits/schemas/daemon.schema.4.json +132 -0
- package/package.json +1 -1
|
@@ -33,7 +33,7 @@ const execFileAsync = promisify(execFile);
|
|
|
33
33
|
function printNoSelfAgentHints(options) {
|
|
34
34
|
const { daemonConfig, ecwebStarted, skipped } = options;
|
|
35
35
|
console.log('\nℹ 未配置任何 self-agent,Control Plane 已启动。');
|
|
36
|
-
console.log(' 命令行:ec agent new <aid>.
|
|
36
|
+
console.log(' 命令行:ec agent new <aid>.example.com');
|
|
37
37
|
if (daemonConfig.aid && (daemonConfig.owners?.length ?? 0) > 0) {
|
|
38
38
|
console.log(' Evol App:可通过进程级菜单创建 agent');
|
|
39
39
|
}
|
|
@@ -301,9 +301,10 @@ export async function cmdStart(opts = {}) {
|
|
|
301
301
|
if (ans.trim() === '2') {
|
|
302
302
|
const { suppressSdkLogs } = await import('../aun/aid/index.js');
|
|
303
303
|
suppressSdkLogs();
|
|
304
|
-
const { generateControlAid } = await import('../aun/aid/control-aid.js');
|
|
305
|
-
const
|
|
306
|
-
|
|
304
|
+
const { generateControlAid, resolveControlAidDomain } = await import('../aun/aid/control-aid.js');
|
|
305
|
+
const cfg = loadDaemonConfig();
|
|
306
|
+
const result = await generateControlAid(resolveControlAidDomain(cfg.aun?.defaultAidDomain));
|
|
307
|
+
saveDaemonConfig({ ...cfg, aid: result.aid });
|
|
307
308
|
console.log(`✓ 新控制 AID: ${result.aid}`);
|
|
308
309
|
}
|
|
309
310
|
}
|
|
@@ -349,8 +350,19 @@ export async function cmdStart(opts = {}) {
|
|
|
349
350
|
await sleep(2000);
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
|
-
//
|
|
353
|
-
|
|
353
|
+
// A missing/stale instance record must not turn into a duplicate daemon.
|
|
354
|
+
// On Windows/macOS the process environment may be unavailable, so an
|
|
355
|
+
// unregistered process from this package is conservatively treated as a
|
|
356
|
+
// blocker when its HOME cannot be identified.
|
|
357
|
+
const orphans = findOrphanProcesses();
|
|
358
|
+
const blockingOrphans = orphans.filter(o => isRuntimeOrphan(o));
|
|
359
|
+
if (blockingOrphans.length > 0) {
|
|
360
|
+
console.error(`❌ 检测到未登记的 EvolCore 进程(PID: ${blockingOrphans.map(o => o.pid).join(', ')}),已停止启动以避免重复连接 AUN。`);
|
|
361
|
+
console.error(' 请先停止这些进程,再重新执行 ec start。');
|
|
362
|
+
process.exitCode = 1;
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
reportOrphans(orphans);
|
|
354
366
|
console.log('🚀 Starting EvolCore...');
|
|
355
367
|
const stdoutRotation = rotateStdoutLog(p.logs);
|
|
356
368
|
if (stdoutRotation.rotatedPath) {
|
|
@@ -549,36 +561,54 @@ export async function cmdStart(opts = {}) {
|
|
|
549
561
|
async function stopPid(pid) {
|
|
550
562
|
console.log(`🛑 Stopping EvolCore (PID: ${pid})...`);
|
|
551
563
|
platform.killProcess(pid);
|
|
552
|
-
await
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
564
|
+
if (await platform.waitForProcessExit(pid, 10_000)) {
|
|
565
|
+
console.log('✓ EvolCore stopped');
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
console.log(`⚠ EvolCore PID ${pid} did not exit gracefully; forcing termination...`);
|
|
569
|
+
platform.killProcess(pid, true);
|
|
570
|
+
if (await platform.waitForProcessExit(pid, 5_000)) {
|
|
571
|
+
console.log('✓ EvolCore stopped (forced)');
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
console.error(`❌ EvolCore PID ${pid} is still running; restart aborted`);
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
function isRuntimeOrphan(orphan, currentHome = resolveRoot()) {
|
|
578
|
+
if (orphan.confirmedTestDaemon)
|
|
579
|
+
return false;
|
|
580
|
+
const homeMatches = orphan.evolcoreHome
|
|
581
|
+
&& normalizeRuntimePath(orphan.evolcoreHome) === normalizeRuntimePath(currentHome);
|
|
582
|
+
// Windows/macOS may not expose another process's environment. In that case
|
|
583
|
+
// the exact installed package path is the conservative ownership proof.
|
|
584
|
+
const packageMatches = !orphan.evolcoreHome
|
|
585
|
+
&& normalizeRuntimePath(orphan.cmdline).includes(normalizeRuntimePath(getPackageRoot()));
|
|
586
|
+
return Boolean(homeMatches || packageMatches);
|
|
570
587
|
}
|
|
571
588
|
export async function cmdStop() {
|
|
572
589
|
const status = scanInstances();
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
590
|
+
const p = resolvePaths();
|
|
591
|
+
const initialOrphans = findOrphanProcesses();
|
|
592
|
+
const runtimeOrphans = initialOrphans.filter(orphan => isRuntimeOrphan(orphan, p.root));
|
|
593
|
+
const ping = await probeDaemon(p.socket);
|
|
594
|
+
if (ping && !hasVisibleDaemonPid(status, ping) && runtimeOrphans.length === 0) {
|
|
595
|
+
if (rejectPidIsolatedLifecycle('stop', status, ping))
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
576
598
|
const aliveMains = status.mains.filter(m => m.alive);
|
|
577
|
-
if (aliveMains.length === 0) {
|
|
599
|
+
if (aliveMains.length === 0 && runtimeOrphans.length === 0) {
|
|
578
600
|
console.log('⚠ EvolCore is not running');
|
|
579
601
|
return;
|
|
580
602
|
}
|
|
581
|
-
|
|
603
|
+
const pids = new Set([...aliveMains.map(entry => entry.record.pid), ...runtimeOrphans.map(orphan => orphan.pid)]);
|
|
604
|
+
if (runtimeOrphans.length > 0) {
|
|
605
|
+
console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(orphan => orphan.pid).join(', ')}`);
|
|
606
|
+
}
|
|
607
|
+
const stopped = await Promise.all([...pids].map(pid => stopPid(pid)));
|
|
608
|
+
if (stopped.some(result => !result)) {
|
|
609
|
+
process.exitCode = 1;
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
582
612
|
await sleep(500);
|
|
583
613
|
cleanupInstances();
|
|
584
614
|
if (aliveMains.length > 1) {
|
|
@@ -589,14 +619,16 @@ export async function cmdRestart(opts = {}) {
|
|
|
589
619
|
const cmdStartedAt = Date.now();
|
|
590
620
|
console.log('🔄 Restarting EvolCore...');
|
|
591
621
|
const initialStatus = scanInstances();
|
|
622
|
+
const initialOrphans = findOrphanProcesses();
|
|
623
|
+
const initialRuntimeOrphans = initialOrphans.filter(orphan => isRuntimeOrphan(orphan));
|
|
592
624
|
const socketPath = resolvePaths().socket;
|
|
593
625
|
const ping = await probeDaemon(socketPath);
|
|
594
626
|
if (ping && !hasVisibleDaemonPid(initialStatus, ping)) {
|
|
595
627
|
if (await tryDelegatedDaemonRestart(socketPath))
|
|
596
628
|
return;
|
|
629
|
+
if (initialRuntimeOrphans.length === 0 && rejectPidIsolatedLifecycle('restart', initialStatus, ping))
|
|
630
|
+
return;
|
|
597
631
|
}
|
|
598
|
-
if (rejectPidIsolatedLifecycle('restart', initialStatus, ping))
|
|
599
|
-
return;
|
|
600
632
|
if (!requireFreshSourceBuild())
|
|
601
633
|
return;
|
|
602
634
|
// 版本检查与自动升级
|
|
@@ -651,29 +683,49 @@ export async function cmdRestart(opts = {}) {
|
|
|
651
683
|
if (aliveMains.length > 1) {
|
|
652
684
|
console.log(`⚠ 检测到 ${aliveMains.length} 个 main 实例,将一并停止: ${aliveMains.map(m => m.record.pid).join(', ')}`);
|
|
653
685
|
}
|
|
654
|
-
await Promise.all(aliveMains.map(m => stopPid(m.record.pid)));
|
|
686
|
+
const stopped = await Promise.all(aliveMains.map(m => stopPid(m.record.pid)));
|
|
687
|
+
if (stopped.some(result => !result)) {
|
|
688
|
+
console.error('❌ Restart aborted because an existing EvolCore process could not be stopped');
|
|
689
|
+
process.exitCode = 1;
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
655
692
|
await sleep(500);
|
|
656
693
|
}
|
|
657
694
|
cleanupInstances();
|
|
658
|
-
|
|
695
|
+
const remainingMainProcesses = scanInstances().mains.filter(entry => entry.alive);
|
|
696
|
+
if (remainingMainProcesses.length > 0) {
|
|
697
|
+
console.error(`❌ Restart aborted: EvolCore process still registered and alive (PID: ${remainingMainProcesses.map(entry => entry.record.pid).join(', ')})`);
|
|
698
|
+
process.exitCode = 1;
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
// 孤儿处理:同 HOME/同安装包的孤儿属于本运行时,先确认退出再替换;
|
|
659
702
|
// 跨 HOME 的孤儿只在 --clear 时 kill,否则仅警告。
|
|
660
703
|
{
|
|
661
704
|
const orphans = findOrphanProcesses();
|
|
662
705
|
const confirmedTestDaemons = orphans.filter(o => o.confirmedTestDaemon);
|
|
663
|
-
const nonTestOrphans = orphans.filter(o => !o.confirmedTestDaemon);
|
|
664
706
|
if (confirmedTestDaemons.length > 0) {
|
|
665
707
|
const killed = killOrphans(confirmedTestDaemons);
|
|
666
708
|
console.log(`☠ 已清理 ${killed.length} 个泄漏的测试 daemon: ${killed.join(', ')}`);
|
|
667
709
|
await sleep(500);
|
|
668
710
|
}
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
711
|
+
const runtimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
|
|
712
|
+
if (runtimeOrphans.length > 0) {
|
|
713
|
+
console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(o => o.pid).join(', ')}`);
|
|
714
|
+
const stopped = await Promise.all(runtimeOrphans.map(o => stopPid(o.pid)));
|
|
715
|
+
if (stopped.some(result => !result)) {
|
|
716
|
+
console.error('❌ Restart aborted because an unregistered EvolCore process could not be stopped');
|
|
717
|
+
process.exitCode = 1;
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
675
720
|
await sleep(500);
|
|
676
721
|
}
|
|
722
|
+
const remainingRuntimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
|
|
723
|
+
if (remainingRuntimeOrphans.length > 0) {
|
|
724
|
+
console.error(`❌ Restart aborted: EvolCore process still running (PID: ${remainingRuntimeOrphans.map(o => o.pid).join(', ')})`);
|
|
725
|
+
process.exitCode = 1;
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const otherHome = findOrphanProcesses().filter(o => !o.confirmedTestDaemon && !isRuntimeOrphan(o));
|
|
677
729
|
if (opts.clear && otherHome.length > 0) {
|
|
678
730
|
const killed = killOrphans(otherHome);
|
|
679
731
|
console.log(`☠ 已 SIGKILL ${killed.length} 个跨 HOME 孤儿进程: ${killed.join(', ')}`);
|
|
@@ -707,6 +759,9 @@ export async function cmdRestart(opts = {}) {
|
|
|
707
759
|
printCodeStats(getPackageRoot(), resolvePaths().logs);
|
|
708
760
|
}
|
|
709
761
|
}
|
|
762
|
+
function normalizeRuntimePath(value) {
|
|
763
|
+
return value.replace(/[\\/]+/g, '/').toLowerCase();
|
|
764
|
+
}
|
|
710
765
|
function formatTimeAgo(ms) {
|
|
711
766
|
const sec = Math.floor(ms / 1000);
|
|
712
767
|
if (sec < 60)
|
|
@@ -2382,8 +2437,22 @@ async function printEcwebStatus(p) {
|
|
|
2382
2437
|
// still provide an authoritative runtime status without local config access.
|
|
2383
2438
|
}
|
|
2384
2439
|
const entries = readEcwebInstanceEntries(p);
|
|
2385
|
-
|
|
2440
|
+
const aliveEntries = [];
|
|
2386
2441
|
for (const entry of entries) {
|
|
2442
|
+
if (platform.isProcessRunning(entry.record.pid)) {
|
|
2443
|
+
aliveEntries.push(entry);
|
|
2444
|
+
}
|
|
2445
|
+
else {
|
|
2446
|
+
// A stale EC Web record must never be used to report the PID of an
|
|
2447
|
+
// unrelated process that later inherited the port.
|
|
2448
|
+
try {
|
|
2449
|
+
fs.unlinkSync(entry.filePath);
|
|
2450
|
+
}
|
|
2451
|
+
catch { }
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
let readyEntry;
|
|
2455
|
+
for (const entry of aliveEntries) {
|
|
2387
2456
|
if (await probeEcwebReady(entry.record.port)) {
|
|
2388
2457
|
readyEntry = entry;
|
|
2389
2458
|
break;
|
|
@@ -2394,11 +2463,19 @@ async function printEcwebStatus(p) {
|
|
|
2394
2463
|
console.log(`🔭 ECWeb: 运行中 (PID: ${readyEntry.record.pid}) http://localhost:${readyEntry.record.port}`);
|
|
2395
2464
|
return;
|
|
2396
2465
|
}
|
|
2397
|
-
const
|
|
2466
|
+
const occupiedPids = new Set();
|
|
2467
|
+
for (const entry of entries) {
|
|
2468
|
+
for (const pid of platform.findProcessByPort(entry.record.port))
|
|
2469
|
+
occupiedPids.add(pid);
|
|
2470
|
+
}
|
|
2398
2471
|
console.log('');
|
|
2399
|
-
if (
|
|
2472
|
+
if (aliveEntries.length > 0) {
|
|
2473
|
+
const visibleEntry = aliveEntries[0];
|
|
2400
2474
|
console.log(`🔭 ECWeb: 进程存活 (PID: ${visibleEntry.record.pid}) 但 HTTP 未就绪 (端口 ${visibleEntry.record.port}),查看 logs/watch-web.log`);
|
|
2401
2475
|
}
|
|
2476
|
+
else if (occupiedPids.size > 0) {
|
|
2477
|
+
console.log(`🔭 ECWeb: 端口仍被未登记进程占用 (PID: ${[...occupiedPids].join(', ')}),请先执行 ec restart`);
|
|
2478
|
+
}
|
|
2402
2479
|
else if (entries.length > 0) {
|
|
2403
2480
|
console.log('🔭 ECWeb: instance 记录存在,但宿主 PID 不可见且 HTTP 未就绪');
|
|
2404
2481
|
}
|
|
@@ -2432,31 +2509,34 @@ function stopCodexAppServerOrphans() {
|
|
|
2432
2509
|
}
|
|
2433
2510
|
return killed;
|
|
2434
2511
|
}
|
|
2435
|
-
/** 若 ecweb
|
|
2436
|
-
function stopEcwebIfRunning(p) {
|
|
2512
|
+
/** 若 ecweb 在运行则杀掉并确认 pid/端口都已释放。 */
|
|
2513
|
+
async function stopEcwebIfRunning(p) {
|
|
2437
2514
|
const alive = findAliveEcweb(p);
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2515
|
+
const port = loadDaemonConfig().ecweb?.port ?? 42705;
|
|
2516
|
+
const pids = new Set([
|
|
2517
|
+
...(alive ? [alive.pid] : []),
|
|
2518
|
+
...platform.findProcessByPort(port),
|
|
2519
|
+
]);
|
|
2520
|
+
for (const pid of pids)
|
|
2521
|
+
platform.killProcess(pid, true);
|
|
2522
|
+
const exited = await Promise.all([...pids].map(pid => platform.waitForProcessExit(pid, 5_000)));
|
|
2523
|
+
const portReleased = await waitForPortRelease(port, 5_000);
|
|
2524
|
+
if (exited.some(result => !result) || !portReleased) {
|
|
2525
|
+
throw new Error(`EC Web old process or port ${port} could not be released`);
|
|
2445
2526
|
}
|
|
2446
2527
|
// 清理 pid 文件(仅 ecweb-<pid>.json / watch-web-<pid>.json,
|
|
2447
2528
|
// 不碰 ecweb-tokens.json 等非 pid 文件,否则会清空配对 token 库导致每次重启都要重新配对)
|
|
2448
2529
|
removeEcwebInstanceFiles(p);
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
catch { }
|
|
2530
|
+
return pids.size > 0;
|
|
2531
|
+
}
|
|
2532
|
+
async function waitForPortRelease(port, timeoutMs) {
|
|
2533
|
+
const deadline = Date.now() + timeoutMs;
|
|
2534
|
+
while (Date.now() < deadline) {
|
|
2535
|
+
if (platform.findProcessByPort(port).length === 0)
|
|
2536
|
+
return true;
|
|
2537
|
+
await sleep(250);
|
|
2458
2538
|
}
|
|
2459
|
-
return
|
|
2539
|
+
return platform.findProcessByPort(port).length === 0;
|
|
2460
2540
|
}
|
|
2461
2541
|
/**
|
|
2462
2542
|
* 后台 detached 启动 ecweb;若已运行则先停再启(确保加载最新代码)。
|
|
@@ -2539,7 +2619,14 @@ export async function startEcwebIfEnabled(p) {
|
|
|
2539
2619
|
const cfg = loadDaemonConfig();
|
|
2540
2620
|
if (!cfg.ecweb?.enabled)
|
|
2541
2621
|
return false;
|
|
2542
|
-
|
|
2622
|
+
let wasRunning = false;
|
|
2623
|
+
try {
|
|
2624
|
+
wasRunning = await stopEcwebIfRunning(p); // 先停旧进程(有则停),保证加载最新代码
|
|
2625
|
+
}
|
|
2626
|
+
catch (error) {
|
|
2627
|
+
console.log(`❌ EC Web 旧进程未清理干净,取消启动: ${error instanceof Error ? error.message : String(error)}`);
|
|
2628
|
+
return false;
|
|
2629
|
+
}
|
|
2543
2630
|
const port = cfg.ecweb.port ?? 42705;
|
|
2544
2631
|
const args = ['--home', p.root, '--port', String(port)];
|
|
2545
2632
|
const launch = resolveEcwebLaunchCommand(args);
|
|
@@ -2553,6 +2640,7 @@ export async function startEcwebIfEnabled(p) {
|
|
|
2553
2640
|
console.log(` command: ${launch.command}, args: ${JSON.stringify(launch.args)}`);
|
|
2554
2641
|
return false;
|
|
2555
2642
|
}
|
|
2643
|
+
let startedPid;
|
|
2556
2644
|
try {
|
|
2557
2645
|
const child = spawn(launch.command, launch.args, {
|
|
2558
2646
|
detached: true,
|
|
@@ -2565,6 +2653,7 @@ export async function startEcwebIfEnabled(p) {
|
|
|
2565
2653
|
});
|
|
2566
2654
|
child.unref();
|
|
2567
2655
|
const pid = child.pid;
|
|
2656
|
+
startedPid = pid;
|
|
2568
2657
|
if (!pid) {
|
|
2569
2658
|
console.log('❌ ECWeb 启动失败(进程创建失败)');
|
|
2570
2659
|
return false;
|
|
@@ -2592,9 +2681,17 @@ export async function startEcwebIfEnabled(p) {
|
|
|
2592
2681
|
return true;
|
|
2593
2682
|
}
|
|
2594
2683
|
console.log(`❌ ECWeb 启动失败:端口 ${port} 未就绪(进程 PID ${pid} 可能已退出,查看 logs/watch-web.log)`);
|
|
2684
|
+
platform.killProcess(pid, true);
|
|
2685
|
+
await platform.waitForProcessExit(pid, 5_000);
|
|
2686
|
+
removeEcwebInstanceFiles(p);
|
|
2595
2687
|
return false;
|
|
2596
2688
|
}
|
|
2597
2689
|
catch (err) {
|
|
2690
|
+
if (startedPid) {
|
|
2691
|
+
platform.killProcess(startedPid, true);
|
|
2692
|
+
await platform.waitForProcessExit(startedPid, 5_000);
|
|
2693
|
+
}
|
|
2694
|
+
removeEcwebInstanceFiles(p);
|
|
2598
2695
|
console.log(`⚠ ECWeb 启动失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
2599
2696
|
console.log(` command: ${launch.command}`);
|
|
2600
2697
|
console.log(` args: ${JSON.stringify(launch.args)}`);
|
|
@@ -2771,8 +2868,15 @@ export async function cmdDiagnose() {
|
|
|
2771
2868
|
try {
|
|
2772
2869
|
const instStatus = scanInstances();
|
|
2773
2870
|
const aliveMains = instStatus.mains.filter(m => m.alive);
|
|
2871
|
+
const orphanMains = findOrphanProcesses();
|
|
2774
2872
|
if (aliveMains.length > 0) {
|
|
2775
2873
|
console.log(`[diagnose] ⚠️ 已有进程运行中: PID ${aliveMains.map(m => m.record.pid).join(', ')}`);
|
|
2874
|
+
hasError = true;
|
|
2875
|
+
}
|
|
2876
|
+
else if (orphanMains.length > 0) {
|
|
2877
|
+
console.error(`[diagnose] ❌ 发现未登记的 EvolCore 进程: PID ${orphanMains.map(o => o.pid).join(', ')}`);
|
|
2878
|
+
console.error('[diagnose] 请先执行 ec stop 或手动结束这些 PID,再运行 ec start。');
|
|
2879
|
+
hasError = true;
|
|
2776
2880
|
}
|
|
2777
2881
|
else {
|
|
2778
2882
|
console.log(`[diagnose] ✓ 无残留进程`);
|
|
@@ -2781,7 +2885,28 @@ export async function cmdDiagnose() {
|
|
|
2781
2885
|
catch {
|
|
2782
2886
|
console.log(`[diagnose] ✓ 无 instance 文件`);
|
|
2783
2887
|
}
|
|
2784
|
-
// 6.
|
|
2888
|
+
// 6. 检查 EC Web 端口与实例记录是否一致。
|
|
2889
|
+
try {
|
|
2890
|
+
const cfg = loadDaemonConfig();
|
|
2891
|
+
if (cfg.ecweb?.enabled) {
|
|
2892
|
+
const port = cfg.ecweb.port ?? 42705;
|
|
2893
|
+
const portPids = platform.findProcessByPort(port);
|
|
2894
|
+
const recordedPids = new Set(readEcwebInstanceEntries(p).map(entry => entry.record.pid));
|
|
2895
|
+
const unregisteredPids = portPids.filter(pid => !recordedPids.has(pid));
|
|
2896
|
+
if (unregisteredPids.length > 0) {
|
|
2897
|
+
console.error(`[diagnose] ❌ EC Web 端口 ${port} 被未登记进程占用: PID ${unregisteredPids.join(', ')}`);
|
|
2898
|
+
console.error('[diagnose] 请执行 ec restart 清理 EC Web 后再启动。');
|
|
2899
|
+
hasError = true;
|
|
2900
|
+
}
|
|
2901
|
+
else {
|
|
2902
|
+
console.log(`[diagnose] ✓ EC Web 端口 ${port} 未发现未登记占用`);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
catch (e) {
|
|
2907
|
+
console.error(`[diagnose] ⚠️ EC Web 端口检查失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
2908
|
+
}
|
|
2909
|
+
// 7. 检查关键文件
|
|
2785
2910
|
const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
|
|
2786
2911
|
if (!fs.existsSync(appMain)) {
|
|
2787
2912
|
console.error(`[diagnose] ❌ 编译产物不存在: ${appMain}`);
|
package/dist/cli/data-command.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolvePaths, resolveRoot } from '../paths.js';
|
|
4
|
-
import { applyDataMigration, planDataMigration, readDataMigration, verifyDataMigration } from '../core/data-migration.js';
|
|
4
|
+
import { applyDataMigration, finalizeDataMigration, planDataMigration, readDataMigration, verifyDataMigration } from '../core/data-migration.js';
|
|
5
5
|
import { scanInstances } from '../utils/instance-registry.js';
|
|
6
6
|
import { isProcessRunning, killProcess } from '../utils/cross-platform.js';
|
|
7
7
|
import { ipcQuery } from '../ipc.js';
|
|
@@ -11,9 +11,11 @@ function printSummary(manifest) {
|
|
|
11
11
|
result[operation.status] = (result[operation.status] ?? 0) + 1;
|
|
12
12
|
return result;
|
|
13
13
|
}, {});
|
|
14
|
+
const pending = manifest.operations.filter(operation => !['committed', 'skipped', 'acknowledged'].includes(operation.status)).length;
|
|
14
15
|
console.log(`migration: ${manifest.id}`);
|
|
15
16
|
console.log(`state: ${manifest.state}`);
|
|
16
17
|
console.log(`operations: ${manifest.operations.length}`);
|
|
18
|
+
console.log(`pending: ${pending}`);
|
|
17
19
|
console.log(`status: ${Object.entries(counts).map(([key, value]) => `${key}=${value}`).join(', ') || 'none'}`);
|
|
18
20
|
if (manifest.warnings.length) {
|
|
19
21
|
console.log('warnings:');
|
|
@@ -77,19 +79,72 @@ async function waitForMaintenanceWindow() {
|
|
|
77
79
|
}
|
|
78
80
|
export async function cmdData(args) {
|
|
79
81
|
if (args[0] !== 'migrate' || args.includes('--help')) {
|
|
80
|
-
console.log('用法: ec data migrate --dry-run | --apply
|
|
82
|
+
console.log('用法: ec data migrate --dry-run | --apply [migration-id] | --verify [migration-id]');
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
85
|
const root = resolveRoot();
|
|
84
86
|
if (args.includes('--dry-run')) {
|
|
87
|
+
if (args.length !== 2)
|
|
88
|
+
throw new Error('--dry-run cannot be combined with another migration action');
|
|
85
89
|
printSummary(planDataMigration(root));
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
88
|
-
const resumeIndex = args.indexOf('--resume');
|
|
89
92
|
const verify = args.includes('--verify');
|
|
90
|
-
const requestedId =
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
+
const requestedId = args.find(value => value.startsWith('data-'));
|
|
94
|
+
if (args.includes('--apply')) {
|
|
95
|
+
if (verify) {
|
|
96
|
+
throw new Error('--apply cannot be combined with another migration action');
|
|
97
|
+
}
|
|
98
|
+
// Planning is read-only, so avoid an unnecessary service interruption
|
|
99
|
+
// when there is no legacy source to process.
|
|
100
|
+
const existing = requestedId ? readDataMigration(root, requestedId) : undefined;
|
|
101
|
+
let manifest = existing ?? planDataMigration(root);
|
|
102
|
+
if (manifest.operations.length === 0 && Object.keys(manifest.supersededSourceHashes ?? {}).length === 0) {
|
|
103
|
+
printSummary(manifest);
|
|
104
|
+
console.log('No legacy data requires migration.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const initialRuntime = liveRuntime();
|
|
108
|
+
const wasRunning = initialRuntime.mainCount > 0
|
|
109
|
+
|| initialRuntime.monitorPids.length > 0
|
|
110
|
+
|| await daemonIpcReachable();
|
|
111
|
+
if (wasRunning) {
|
|
112
|
+
console.log('Stopping daemon and all Agent runtimes for data migration...');
|
|
113
|
+
await stopRestartMonitors(initialRuntime.monitorPids);
|
|
114
|
+
await cmdStop();
|
|
115
|
+
await waitForMaintenanceWindow();
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
if (existing?.state !== 'completed') {
|
|
119
|
+
manifest = applyDataMigration(manifest);
|
|
120
|
+
manifest = verifyDataMigration(manifest, { strictTargetContent: true });
|
|
121
|
+
printSummary(manifest);
|
|
122
|
+
if (manifest.state !== 'completed') {
|
|
123
|
+
throw new Error(`migration is not fully verified; fix the listed operations and run ec data migrate --apply ${manifest.id}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// A completed manifest may come from the previous copy-only workflow,
|
|
128
|
+
// or from an earlier successful --apply. The finalization routine is
|
|
129
|
+
// idempotent and verifies the archive receipt before returning it.
|
|
130
|
+
printSummary(manifest);
|
|
131
|
+
}
|
|
132
|
+
const finalization = finalizeDataMigration(root, manifest);
|
|
133
|
+
console.log(`archive: ${finalization.archivePath}`);
|
|
134
|
+
console.log(`archive-sha256: ${finalization.archiveSha256}`);
|
|
135
|
+
console.log(`removed legacy sources: ${finalization.sourcePaths.length}`);
|
|
136
|
+
if (wasRunning) {
|
|
137
|
+
console.log('Starting daemon and Agent runtimes after data migration...');
|
|
138
|
+
await cmdStart();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (wasRunning)
|
|
143
|
+
console.error('Data migration failed; daemon remains stopped.');
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
93
148
|
if (verify) {
|
|
94
149
|
// A completed migration's destination becomes live runtime data. Operator
|
|
95
150
|
// verification therefore validates source integrity and target structure
|
|
@@ -100,33 +155,5 @@ export async function cmdData(args) {
|
|
|
100
155
|
process.exitCode = 1;
|
|
101
156
|
return;
|
|
102
157
|
}
|
|
103
|
-
|
|
104
|
-
throw new Error('select --dry-run, --apply, --resume, or --verify');
|
|
105
|
-
const initialRuntime = liveRuntime();
|
|
106
|
-
const wasRunning = initialRuntime.mainCount > 0
|
|
107
|
-
|| initialRuntime.monitorPids.length > 0
|
|
108
|
-
|| await daemonIpcReachable();
|
|
109
|
-
if (wasRunning) {
|
|
110
|
-
console.log('Stopping daemon and all Agent runtimes for data migration...');
|
|
111
|
-
// A restart monitor would otherwise relaunch the daemon in the middle of
|
|
112
|
-
// a copy transaction, so it must leave the maintenance window first.
|
|
113
|
-
await stopRestartMonitors(initialRuntime.monitorPids);
|
|
114
|
-
await cmdStop();
|
|
115
|
-
await waitForMaintenanceWindow();
|
|
116
|
-
}
|
|
117
|
-
let manifest = resumeIndex >= 0
|
|
118
|
-
? applyDataMigration(readDataMigration(root, requestedId))
|
|
119
|
-
: applyDataMigration(planDataMigration(root));
|
|
120
|
-
manifest = verifyDataMigration(manifest);
|
|
121
|
-
printSummary(manifest);
|
|
122
|
-
// A partial migration must be inspected before any runtime can write the new
|
|
123
|
-
// layout. All independent operations have still been attempted and recorded.
|
|
124
|
-
if (wasRunning && manifest.state === 'completed') {
|
|
125
|
-
console.log('Starting daemon and Agent runtimes after verified migration...');
|
|
126
|
-
await cmdStart();
|
|
127
|
-
}
|
|
128
|
-
else if (wasRunning) {
|
|
129
|
-
console.error('Migration has errors; daemon remains stopped. Fix or resume the listed operations before restarting.');
|
|
130
|
-
process.exitCode = 1;
|
|
131
|
-
}
|
|
158
|
+
throw new Error('select --dry-run, --apply, or --verify');
|
|
132
159
|
}
|
package/dist/cli/init-channel.js
CHANGED
|
@@ -1250,7 +1250,7 @@ async function pickAgentForChannel(rl) {
|
|
|
1250
1250
|
const { agents } = loadAllAgents();
|
|
1251
1251
|
if (agents.length === 0) {
|
|
1252
1252
|
console.log('❌ 暂无 agent,请先创建:');
|
|
1253
|
-
console.log(' ec agent new <aid>.
|
|
1253
|
+
console.log(' ec agent new <aid>.example.com');
|
|
1254
1254
|
return null;
|
|
1255
1255
|
}
|
|
1256
1256
|
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
package/dist/cli/init.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolvePaths, ensureDataDirs } from '../paths.js';
|
|
|
6
6
|
import { commandExists } from '../utils/cross-platform.js';
|
|
7
7
|
import { scanInstances } from '../utils/instance-registry.js';
|
|
8
8
|
import { saveDefaultsSafe, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
|
|
9
|
-
import { generateControlAid,
|
|
9
|
+
import { generateControlAid, resolveControlAidDomain } from '../aun/aid/control-aid.js';
|
|
10
10
|
import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
|
|
11
11
|
import { resolveEcagentConfig } from '../agents/baseagent.js';
|
|
12
12
|
import { defaultProjectsRoot } from '../utils/project-path.js';
|
|
@@ -313,29 +313,24 @@ export async function initTail(options = {}) {
|
|
|
313
313
|
const { agents } = loadAllAgents();
|
|
314
314
|
if (agents.length === 0 && !options.invokedByStart) {
|
|
315
315
|
console.log('\n提示:尚无 agent,运行以下命令创建:');
|
|
316
|
-
console.log(' ec agent new <aid>.
|
|
316
|
+
console.log(' ec agent new <aid>.example.com');
|
|
317
317
|
}
|
|
318
318
|
// 控制 AID:daemon 进程身份。缺失则生成并写回 daemon.json(幂等:已存在则跳过)。
|
|
319
319
|
const daemonConfig = loadDaemonConfig();
|
|
320
320
|
let controlAidReady = !!daemonConfig.aid;
|
|
321
321
|
if (daemonConfig.aid) {
|
|
322
|
-
const targetIssuer = resolveControlIssuer();
|
|
323
|
-
const currentIssuer = daemonConfig.aid.split('.').slice(1).join('.');
|
|
324
|
-
if (currentIssuer !== targetIssuer) {
|
|
325
|
-
console.log(`⚠️ 控制 AID issuer (${currentIssuer}) 与目标 issuer (${targetIssuer}) 不一致`);
|
|
326
|
-
console.log(` 如需切换,请删除 daemon.json 中的 aid 字段后重新运行 init`);
|
|
327
|
-
}
|
|
328
322
|
console.log(`✓ 控制 AID 已存在: ${daemonConfig.aid}`);
|
|
329
323
|
}
|
|
330
324
|
else {
|
|
331
325
|
try {
|
|
332
|
-
const
|
|
326
|
+
const aidDomain = resolveControlAidDomain(daemonConfig.aun?.defaultAidDomain);
|
|
327
|
+
const { aid } = await generateControlAid(aidDomain);
|
|
333
328
|
saveDaemonConfig({ ...daemonConfig, $schema_version: daemonConfig.$schema_version ?? 1, aid });
|
|
334
329
|
controlAidReady = true;
|
|
335
330
|
console.log(`✓ 已生成控制 AID: ${aid}`);
|
|
336
331
|
}
|
|
337
332
|
catch (e) {
|
|
338
|
-
console.error(`⚠️ 控制 AID
|
|
333
|
+
console.error(`⚠️ 控制 AID 生成失败(请检查 AID domain 配置或 Gateway 连通性): ${e?.message || e}`);
|
|
339
334
|
controlAidReady = false;
|
|
340
335
|
}
|
|
341
336
|
}
|
|
@@ -446,7 +441,7 @@ export async function initTail(options = {}) {
|
|
|
446
441
|
const { agents: finalAgents } = loadAllAgents();
|
|
447
442
|
if (finalAgents.length === 0) {
|
|
448
443
|
console.log('\n📌 下一步:创建 agent');
|
|
449
|
-
console.log(' ec agent new <your-aid>.
|
|
444
|
+
console.log(' ec agent new <your-aid>.example.com');
|
|
450
445
|
console.log(' ec init feishu # 绑定飞书');
|
|
451
446
|
console.log(' ec start # 启动服务');
|
|
452
447
|
}
|
|
@@ -632,11 +627,12 @@ export async function cmdInitNonInteractive(opts, dependencies = {}) {
|
|
|
632
627
|
let controlAid = existingCfg.aid;
|
|
633
628
|
if (!controlAid) {
|
|
634
629
|
try {
|
|
635
|
-
const
|
|
630
|
+
const aidDomain = resolveControlAidDomain(existingCfg.aun?.defaultAidDomain);
|
|
631
|
+
const { aid } = await generateControlAid(aidDomain);
|
|
636
632
|
controlAid = aid;
|
|
637
633
|
}
|
|
638
634
|
catch (e) {
|
|
639
|
-
fail('CONTROL_AID_CREATE_FAILED', `
|
|
635
|
+
fail('CONTROL_AID_CREATE_FAILED', `control AID creation failed: ${e?.message || e}`, EXIT_RUNTIME, format);
|
|
640
636
|
}
|
|
641
637
|
}
|
|
642
638
|
// ── 9. 写 daemon.json(aid + owners + ecweb)──
|