evolcore 0.0.6 → 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 +25 -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 +196 -66
- 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 +116 -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 +755 -701
- 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 +45 -8
- 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
|
}
|
|
@@ -111,7 +111,7 @@ function buildDaemonEnv(p, opts) {
|
|
|
111
111
|
...(opts.bindBootstrap ? { EVOLCORE_BIND_BOOTSTRAP: '1' } : {}),
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
|
-
function serviceProxyNeedsEcweb(cfg) {
|
|
114
|
+
export function serviceProxyNeedsEcweb(cfg) {
|
|
115
115
|
if (!cfg.serviceProxy?.enabled)
|
|
116
116
|
return false;
|
|
117
117
|
return (cfg.serviceProxy.services ?? []).some(s => s.enabled !== false && s.source === 'ecweb');
|
|
@@ -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;若已运行则先停再启(确保加载最新代码)。
|
|
@@ -2530,11 +2610,23 @@ function ensureServiceProxyConfig(cfg, port) {
|
|
|
2530
2610
|
console.log(` 自动配置 Service Proxy: https://${cfg.aid}/proxy/ecweb/`);
|
|
2531
2611
|
}
|
|
2532
2612
|
}
|
|
2533
|
-
|
|
2613
|
+
/**
|
|
2614
|
+
* Start the separately installed `ec-web` process for a configured runtime.
|
|
2615
|
+
* Kept public for restart-monitor, which launches the daemon directly and
|
|
2616
|
+
* therefore cannot rely on cmdStart's post-ready ECWeb startup path.
|
|
2617
|
+
*/
|
|
2618
|
+
export async function startEcwebIfEnabled(p) {
|
|
2534
2619
|
const cfg = loadDaemonConfig();
|
|
2535
2620
|
if (!cfg.ecweb?.enabled)
|
|
2536
2621
|
return false;
|
|
2537
|
-
|
|
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
|
+
}
|
|
2538
2630
|
const port = cfg.ecweb.port ?? 42705;
|
|
2539
2631
|
const args = ['--home', p.root, '--port', String(port)];
|
|
2540
2632
|
const launch = resolveEcwebLaunchCommand(args);
|
|
@@ -2548,6 +2640,7 @@ async function startEcwebIfEnabled(p) {
|
|
|
2548
2640
|
console.log(` command: ${launch.command}, args: ${JSON.stringify(launch.args)}`);
|
|
2549
2641
|
return false;
|
|
2550
2642
|
}
|
|
2643
|
+
let startedPid;
|
|
2551
2644
|
try {
|
|
2552
2645
|
const child = spawn(launch.command, launch.args, {
|
|
2553
2646
|
detached: true,
|
|
@@ -2560,6 +2653,7 @@ async function startEcwebIfEnabled(p) {
|
|
|
2560
2653
|
});
|
|
2561
2654
|
child.unref();
|
|
2562
2655
|
const pid = child.pid;
|
|
2656
|
+
startedPid = pid;
|
|
2563
2657
|
if (!pid) {
|
|
2564
2658
|
console.log('❌ ECWeb 启动失败(进程创建失败)');
|
|
2565
2659
|
return false;
|
|
@@ -2587,9 +2681,17 @@ async function startEcwebIfEnabled(p) {
|
|
|
2587
2681
|
return true;
|
|
2588
2682
|
}
|
|
2589
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);
|
|
2590
2687
|
return false;
|
|
2591
2688
|
}
|
|
2592
2689
|
catch (err) {
|
|
2690
|
+
if (startedPid) {
|
|
2691
|
+
platform.killProcess(startedPid, true);
|
|
2692
|
+
await platform.waitForProcessExit(startedPid, 5_000);
|
|
2693
|
+
}
|
|
2694
|
+
removeEcwebInstanceFiles(p);
|
|
2593
2695
|
console.log(`⚠ ECWeb 启动失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
2594
2696
|
console.log(` command: ${launch.command}`);
|
|
2595
2697
|
console.log(` args: ${JSON.stringify(launch.args)}`);
|
|
@@ -2766,8 +2868,15 @@ export async function cmdDiagnose() {
|
|
|
2766
2868
|
try {
|
|
2767
2869
|
const instStatus = scanInstances();
|
|
2768
2870
|
const aliveMains = instStatus.mains.filter(m => m.alive);
|
|
2871
|
+
const orphanMains = findOrphanProcesses();
|
|
2769
2872
|
if (aliveMains.length > 0) {
|
|
2770
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;
|
|
2771
2880
|
}
|
|
2772
2881
|
else {
|
|
2773
2882
|
console.log(`[diagnose] ✓ 无残留进程`);
|
|
@@ -2776,7 +2885,28 @@ export async function cmdDiagnose() {
|
|
|
2776
2885
|
catch {
|
|
2777
2886
|
console.log(`[diagnose] ✓ 无 instance 文件`);
|
|
2778
2887
|
}
|
|
2779
|
-
// 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. 检查关键文件
|
|
2780
2910
|
const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
|
|
2781
2911
|
if (!fs.existsSync(appMain)) {
|
|
2782
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)──
|