evolcore 0.0.33 → 0.0.34
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 +37 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +12 -1
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +82 -13
- package/dist/agents/codex/diagnostics.js +37 -11
- package/dist/agents/codex-runner.js +218 -44
- package/dist/agents/ecagent-runner.js +73 -20
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/channels/aun.js +16 -6
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +3 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +120 -19
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/index.js +20 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/memory-command.js +158 -145
- package/dist/cli/thread-command.js +5 -3
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +1 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +138 -4
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +22 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +36 -1
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +35 -14
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation.js +91 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +2 -1
- package/dist/core/message/response-engine.js +584 -47
- package/dist/core/permission/ec-command-parser.js +93 -4
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +75 -5
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/thread-service.js +13 -20
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +2 -2
- package/kits/docs/evolcore/group.md +10 -10
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +18 -14
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/thread-chain.md +5 -5
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.json +0 -20
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +12 -15
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +4 -4
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
|
@@ -4,7 +4,7 @@ import os from 'os';
|
|
|
4
4
|
import { spawn, execFile } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import readline from 'readline';
|
|
7
|
-
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath } from '../paths.js';
|
|
7
|
+
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath, asModeOverridePath } from '../paths.js';
|
|
8
8
|
import { loadDefaults, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
|
|
9
9
|
import { resolveEffective } from '../config/config-manager.js';
|
|
10
10
|
import { resolveAnthropicConfig } from '../agents/baseagent.js';
|
|
@@ -458,7 +458,73 @@ function resolveRunningDuration(pid, uptime) {
|
|
|
458
458
|
const instance = scanInstances().mains.find(entry => entry.alive && entry.record.pid === pid);
|
|
459
459
|
return instance ? formatRunningDuration(Date.now() - instance.record.startedAt) : '未知';
|
|
460
460
|
}
|
|
461
|
-
|
|
461
|
+
/**
|
|
462
|
+
* 渲染"运行模式"那一行。
|
|
463
|
+
*
|
|
464
|
+
* `daemonMode` 有值时用 daemon 实例记录里的模式(`ec status` 看的是正在跑的进程);
|
|
465
|
+
* 没有时按当前进程判断(`ec start` 时就是这一份 CLI 即将拉起的模式)。
|
|
466
|
+
*/
|
|
467
|
+
/**
|
|
468
|
+
* CLI 侧按包路径判断模式。**不能用 isDevMode()**:它首次求值会读走并删除
|
|
469
|
+
* `--as-mode` 覆盖文件,那份文件只该由 daemon 消费。
|
|
470
|
+
*/
|
|
471
|
+
function packageIsDevMode() {
|
|
472
|
+
return !getPackageRoot().includes('node_modules');
|
|
473
|
+
}
|
|
474
|
+
export function describeRunMode(daemonMode, cliIsDevMode = packageIsDevMode()) {
|
|
475
|
+
const effective = daemonMode ?? (cliIsDevMode ? 'dev' : 'install');
|
|
476
|
+
const label = effective === 'install' ? '安装模式' : '开发模式';
|
|
477
|
+
const note = effective === 'install'
|
|
478
|
+
? '(校验随包文件,篡改自动恢复)'
|
|
479
|
+
: '(不校验随包文件,可随意改动)';
|
|
480
|
+
return `运行模式: ${label}${note}`;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* 把本次 `--as-mode` 写进一次性覆盖文件,daemon 启动时读走并删除。
|
|
484
|
+
*
|
|
485
|
+
* 不走环境变量:环境变量会被 `ec restart` 全量继承,模式会在操作者不知情的
|
|
486
|
+
* 情况下被带到新进程。文件是一次性的,daemon 读过即删;若 daemon 最终没起来,
|
|
487
|
+
* 文件还留着,会在下一次启动时被读到——所以启动真实失败时此处清理。
|
|
488
|
+
*/
|
|
489
|
+
function writeAsModeOverride(asMode) {
|
|
490
|
+
const file = asModeOverridePath();
|
|
491
|
+
if (!asMode) {
|
|
492
|
+
// 没带 --as-mode:清掉上次失败启动可能残留的文件,保证这次按包路径判断
|
|
493
|
+
clearAsModeOverride(file);
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
497
|
+
fs.writeFileSync(file, `${asMode}\n`, 'utf-8');
|
|
498
|
+
return file;
|
|
499
|
+
}
|
|
500
|
+
function clearAsModeOverride(file) {
|
|
501
|
+
if (!file)
|
|
502
|
+
return;
|
|
503
|
+
try {
|
|
504
|
+
fs.rmSync(file, { force: true });
|
|
505
|
+
}
|
|
506
|
+
catch { /* ignore */ }
|
|
507
|
+
}
|
|
508
|
+
/** daemon 已在运行时 `--as-mode` 不会生效,明说,别让人以为切过去了。 */
|
|
509
|
+
function warnAsModeNotApplied(asMode) {
|
|
510
|
+
if (!asMode)
|
|
511
|
+
return;
|
|
512
|
+
console.log(` ⚠ --as-mode ${asMode} 未生效:daemon 已在运行,模式只在启动时确定。`);
|
|
513
|
+
console.log(` 需要切换请先 ec stop,再 ec start --as-mode ${asMode}。`);
|
|
514
|
+
}
|
|
515
|
+
function printAsModeNotice(asMode) {
|
|
516
|
+
if (!asMode)
|
|
517
|
+
return;
|
|
518
|
+
console.log('');
|
|
519
|
+
console.log(`🧪 本次运行模式已设为${asMode === 'install' ? '安装模式' : '开发模式'}(ec start --as-mode ${asMode})`);
|
|
520
|
+
if (asMode === 'install') {
|
|
521
|
+
console.log(' 随包发布的 kits/技能文件将按台账校验,带外改动会被自动恢复。');
|
|
522
|
+
console.log(' 开发仓里请勿长期使用:本机 kits 文件会被登记进真实台账,');
|
|
523
|
+
console.log(' 之后再按开发模式修改它们,会在下次以安装模式启动时被回滚。');
|
|
524
|
+
}
|
|
525
|
+
console.log(' 只影响这一次启动;ec restart 不带该参数,会恢复按包路径判断。');
|
|
526
|
+
}
|
|
527
|
+
function printStartupInfo(opts = {}, asMode) {
|
|
462
528
|
const pkgRoot = getPackageRoot();
|
|
463
529
|
const isNpmInstall = pkgRoot.includes('node_modules');
|
|
464
530
|
const cliRunsSource = !import.meta.url.includes('/dist/');
|
|
@@ -502,13 +568,18 @@ function printStartupInfo(opts = {}) {
|
|
|
502
568
|
console.log(`${prefix}${version}${pidPart}${aunPart}`);
|
|
503
569
|
console.log(` 包路径: ${pkgRoot}`);
|
|
504
570
|
console.log(` 安装类型: ${isNpmInstall ? 'npm全局安装' : '开发仓(link)'}`);
|
|
571
|
+
// asMode 只是"本次请求",是否生效要看 daemon 是否真的由这次启动拉起,标清楚别和实际模式混淆
|
|
572
|
+
const modeLine = describeRunMode(opts.runMode ?? asMode, !isNpmInstall);
|
|
573
|
+
console.log(` ${asMode && !opts.runMode ? `${modeLine}(--as-mode 请求)` : modeLine}`);
|
|
505
574
|
console.log(` CLI执行: ${cliRunsSource ? '源码(tsx)' : '编译产物(dist)'}`);
|
|
506
575
|
console.log(` Daemon执行: ${daemonRunsDist ? '编译产物(dist)' : '未知'}`);
|
|
507
576
|
console.log(` 代码时间: ${latestMtime ? formatLocalTime(latestMtime) : '?'}`);
|
|
508
577
|
}
|
|
509
578
|
export async function cmdStart(opts = {}) {
|
|
510
|
-
|
|
511
|
-
|
|
579
|
+
// Measure from process start so the reported time includes CLI bootstrap
|
|
580
|
+
// (tsx compilation in a dev checkout), which is what the operator waits for.
|
|
581
|
+
const cmdStartedAt = Math.min(Date.now(), Math.round(performance.timeOrigin));
|
|
582
|
+
printStartupInfo(undefined, opts.asMode);
|
|
512
583
|
const p = resolvePaths();
|
|
513
584
|
const existingDaemon = await probeDaemon(p.socket);
|
|
514
585
|
if (existingDaemon) {
|
|
@@ -521,6 +592,9 @@ export async function cmdStart(opts = {}) {
|
|
|
521
592
|
return;
|
|
522
593
|
}
|
|
523
594
|
console.log(` EvolCore is already running (PID: ${existingDaemon.pid}, IPC reachable)`);
|
|
595
|
+
const runningRecord = scanInstances().mains.find(m => m.alive && m.record.pid === existingDaemon.pid)?.record;
|
|
596
|
+
console.log(` 当前 daemon ${describeRunMode(runningRecord?.runMode)}`);
|
|
597
|
+
warnAsModeNotApplied(opts.asMode);
|
|
524
598
|
console.log(` 已在运行;已运行时长: ${resolveRunningDuration(existingDaemon.pid, existingDaemon.uptime)};部署/脚本可直接复用当前实例`);
|
|
525
599
|
return;
|
|
526
600
|
}
|
|
@@ -621,6 +695,8 @@ export async function cmdStart(opts = {}) {
|
|
|
621
695
|
console.log(` EvolCore is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
|
|
622
696
|
console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
|
|
623
697
|
console.log(` 启动方式: ${first.record.launchedBy}`);
|
|
698
|
+
console.log(` 当前 daemon ${describeRunMode(first.record.runMode)}`);
|
|
699
|
+
warnAsModeNotApplied(opts.asMode);
|
|
624
700
|
// 报告 AID 状态
|
|
625
701
|
if (status.aidLastActivity.size > 0) {
|
|
626
702
|
console.log(' AID 状态:');
|
|
@@ -647,7 +723,9 @@ export async function cmdStart(opts = {}) {
|
|
|
647
723
|
// On Windows/macOS the process environment may be unavailable, so an
|
|
648
724
|
// unregistered process from this package is conservatively treated as a
|
|
649
725
|
// blocker when its HOME cannot be identified.
|
|
650
|
-
|
|
726
|
+
// No live main reached this point, so the registry snapshot above is still
|
|
727
|
+
// the authoritative "known PID" set; avoid a second scan.
|
|
728
|
+
const orphans = findOrphanProcesses(status);
|
|
651
729
|
const blockingOrphans = orphans.filter(o => isRuntimeOrphan(o));
|
|
652
730
|
if (blockingOrphans.length > 0) {
|
|
653
731
|
console.error(`❌ 检测到未登记的 EvolCore 进程(PID: ${blockingOrphans.map(o => o.pid).join(', ')}),已停止启动以避免重复连接 AUN。`);
|
|
@@ -658,10 +736,13 @@ export async function cmdStart(opts = {}) {
|
|
|
658
736
|
reportOrphans(orphans);
|
|
659
737
|
if (versionInfo.some(result => result.hasUpdate)) {
|
|
660
738
|
cleanEnv();
|
|
739
|
+
// 这条路由 restart-monitor 拉起 daemon,同样靠覆盖文件把 --as-mode 带过去
|
|
740
|
+
const overrideFile = writeAsModeOverride(opts.asMode);
|
|
661
741
|
const launched = await launchPackageLifecycle('start', console.log, {
|
|
662
742
|
prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts),
|
|
663
743
|
});
|
|
664
744
|
if (!launched.ok) {
|
|
745
|
+
clearAsModeOverride(overrideFile);
|
|
665
746
|
console.error(`❌ 启动事务未能建立:${launched.reason}`);
|
|
666
747
|
process.exitCode = 1;
|
|
667
748
|
}
|
|
@@ -684,6 +765,7 @@ export async function cmdStart(opts = {}) {
|
|
|
684
765
|
const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
|
|
685
766
|
const daemonEnv = buildDaemonEnv(p, opts);
|
|
686
767
|
if (opts.foreground) {
|
|
768
|
+
const overrideFile = writeAsModeOverride(opts.asMode);
|
|
687
769
|
const child = spawn('node', ['--no-warnings=ExperimentalWarning', appMain], {
|
|
688
770
|
detached: false,
|
|
689
771
|
stdio: 'inherit',
|
|
@@ -692,6 +774,7 @@ export async function cmdStart(opts = {}) {
|
|
|
692
774
|
});
|
|
693
775
|
const childPid = child.pid;
|
|
694
776
|
if (!childPid) {
|
|
777
|
+
clearAsModeOverride(overrideFile);
|
|
695
778
|
console.error('❌ Failed to start EvolCore (process creation failed)');
|
|
696
779
|
process.exit(1);
|
|
697
780
|
return;
|
|
@@ -710,16 +793,17 @@ export async function cmdStart(opts = {}) {
|
|
|
710
793
|
console.log('🔧 调试模式已启用(EVOLCORE_DEBUG_MODE=1)');
|
|
711
794
|
console.log(' 在宿主终端以任意 session 身份执行命令:');
|
|
712
795
|
console.log('');
|
|
713
|
-
console.log(
|
|
796
|
+
console.log(` ec msg send '<消息内容>' --as-session <sessionId>`);
|
|
714
797
|
console.log(' ec trigger list --as-session <sessionId>');
|
|
715
798
|
console.log(' ec memory list --as-session <sessionId>');
|
|
716
799
|
console.log('');
|
|
717
800
|
console.log(' 查看可用会话(运行后将在此处列出):');
|
|
718
|
-
console.log(
|
|
801
|
+
console.log(` ec msg send 'test' --as-session # 不带 sessionId 自动列出`);
|
|
719
802
|
console.log('');
|
|
720
803
|
console.log(' 注意:重启 daemon(ec restart)会清除调试模式,');
|
|
721
804
|
console.log(' 需重新执行 ec start --debug 才能恢复。');
|
|
722
805
|
}
|
|
806
|
+
printAsModeNotice(opts.asMode);
|
|
723
807
|
void waitForExistingEcweb(p).then((ecweb) => {
|
|
724
808
|
if (shouldPrintNoSelfAgentHints) {
|
|
725
809
|
printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted: ecweb !== null, skipped });
|
|
@@ -766,6 +850,7 @@ export async function cmdStart(opts = {}) {
|
|
|
766
850
|
const stdoutLog = path.join(p.logs, 'stdout.log');
|
|
767
851
|
const out = fs.openSync(stdoutLog, 'a');
|
|
768
852
|
const err = fs.openSync(stdoutLog, 'a');
|
|
853
|
+
const overrideFile = writeAsModeOverride(opts.asMode);
|
|
769
854
|
const child = spawn('node', ['--no-warnings=ExperimentalWarning', appMain], {
|
|
770
855
|
detached: true,
|
|
771
856
|
stdio: ['ignore', out, err],
|
|
@@ -773,6 +858,12 @@ export async function cmdStart(opts = {}) {
|
|
|
773
858
|
env: daemonEnv,
|
|
774
859
|
});
|
|
775
860
|
const childPid = child.pid;
|
|
861
|
+
// daemon 已在启动时读走该文件;若未能起来(pid 丢失/提前退出),清掉避免影响下次启动
|
|
862
|
+
child.once('error', () => clearAsModeOverride(overrideFile));
|
|
863
|
+
// Track liveness from the spawn handle instead of launching tasklist on
|
|
864
|
+
// every poll; the poll interval below is short enough that it matters.
|
|
865
|
+
let childExited = false;
|
|
866
|
+
child.once('exit', () => { childExited = true; });
|
|
776
867
|
child.unref();
|
|
777
868
|
// 等待 ready signal(最多 30 秒,AUN sidecar 超时 15s + 其他通道连接)
|
|
778
869
|
const startTime = Date.now();
|
|
@@ -823,25 +914,27 @@ export async function cmdStart(opts = {}) {
|
|
|
823
914
|
printCodeStats(getPackageRoot(), p.logs);
|
|
824
915
|
}
|
|
825
916
|
console.log(`⏱ done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
|
|
917
|
+
printAsModeNotice(opts.asMode);
|
|
826
918
|
if (opts.debug) {
|
|
827
919
|
console.log('');
|
|
828
920
|
console.log('🔧 调试模式已启用(EVOLCORE_DEBUG_MODE=1)');
|
|
829
921
|
console.log(' 在宿主终端以任意 session 身份执行命令:');
|
|
830
922
|
console.log('');
|
|
831
|
-
console.log(
|
|
923
|
+
console.log(` ec msg send '<消息内容>' --as-session <sessionId>`);
|
|
832
924
|
console.log(' ec trigger list --as-session <sessionId>');
|
|
833
925
|
console.log(' ec memory list --as-session <sessionId>');
|
|
834
926
|
console.log('');
|
|
835
927
|
console.log(' 查看可用会话(运行后将在此处列出):');
|
|
836
|
-
console.log(
|
|
928
|
+
console.log(` ec msg send 'test' --as-session # 不带 sessionId 自动列出`);
|
|
837
929
|
console.log('');
|
|
838
930
|
console.log(' 注意:重启 daemon(ec restart)会清除调试模式,');
|
|
839
931
|
console.log(' 需重新执行 ec start --debug 才能恢复。');
|
|
840
932
|
}
|
|
841
|
-
// ECWeb lifecycle belongs to the daemon; the CLI only
|
|
842
|
-
// the
|
|
843
|
-
|
|
933
|
+
// ECWeb lifecycle belongs to the daemon; the CLI only needs its health
|
|
934
|
+
// for the no-agent hints. Waiting otherwise just holds the prompt for
|
|
935
|
+
// up to the probe timeout after the daemon is already ready.
|
|
844
936
|
if (shouldPrintNoSelfAgentHints) {
|
|
937
|
+
const ecwebStarted = await waitForExistingEcweb(p) !== null;
|
|
845
938
|
printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted, skipped });
|
|
846
939
|
}
|
|
847
940
|
return;
|
|
@@ -860,7 +953,7 @@ export async function cmdStart(opts = {}) {
|
|
|
860
953
|
return;
|
|
861
954
|
}
|
|
862
955
|
// 进程已退出且无 ready signal
|
|
863
|
-
if (
|
|
956
|
+
if (childExited) {
|
|
864
957
|
// 给进程一点时间写 ready signal(可能刚好在写入中)
|
|
865
958
|
if (Date.now() - startTime > 3000) {
|
|
866
959
|
console.log('❌ Failed to start EvolCore');
|
|
@@ -875,9 +968,9 @@ export async function cmdStart(opts = {}) {
|
|
|
875
968
|
return;
|
|
876
969
|
}
|
|
877
970
|
}
|
|
878
|
-
setTimeout(checkReady,
|
|
971
|
+
setTimeout(checkReady, 200);
|
|
879
972
|
};
|
|
880
|
-
setTimeout(checkReady,
|
|
973
|
+
setTimeout(checkReady, 200);
|
|
881
974
|
}
|
|
882
975
|
function isPidReused(pid, expectedStartedAt) {
|
|
883
976
|
if (expectedStartedAt === undefined)
|
|
@@ -890,7 +983,10 @@ function reportPidReused(pid) {
|
|
|
890
983
|
}
|
|
891
984
|
async function stopPid(pid, gracefulRequested = false, expectedStartedAt) {
|
|
892
985
|
console.log(`🛑 Stopping EvolCore (PID: ${pid})...`);
|
|
893
|
-
|
|
986
|
+
// A graceful request was accepted by the daemon that answered for this exact
|
|
987
|
+
// PID over IPC, so its identity is already proven; the start-time probe (a
|
|
988
|
+
// PowerShell launch on Windows) only guards the kill path.
|
|
989
|
+
if (!gracefulRequested && isPidReused(pid, expectedStartedAt)) {
|
|
894
990
|
reportPidReused(pid);
|
|
895
991
|
return true;
|
|
896
992
|
}
|
|
@@ -939,7 +1035,7 @@ function isRuntimeOrphan(orphan, currentHome = resolveRoot()) {
|
|
|
939
1035
|
export async function cmdStop() {
|
|
940
1036
|
const status = scanInstances();
|
|
941
1037
|
const p = resolvePaths();
|
|
942
|
-
const initialOrphans = findOrphanProcesses();
|
|
1038
|
+
const initialOrphans = findOrphanProcesses(status);
|
|
943
1039
|
const runtimeOrphans = initialOrphans.filter(orphan => isRuntimeOrphan(orphan, p.root));
|
|
944
1040
|
const ping = await probeDaemon(p.socket);
|
|
945
1041
|
if (ping && !hasVisibleDaemonPid(status, ping) && runtimeOrphans.length === 0) {
|
|
@@ -964,7 +1060,8 @@ export async function cmdStop() {
|
|
|
964
1060
|
process.exitCode = 1;
|
|
965
1061
|
return;
|
|
966
1062
|
}
|
|
967
|
-
|
|
1063
|
+
// stopPid already observed every process leave the process table, so the
|
|
1064
|
+
// records can be cleaned immediately (same reasoning as restart-monitor).
|
|
968
1065
|
cleanupInstances();
|
|
969
1066
|
if (aliveMains.length > 1) {
|
|
970
1067
|
console.log(`⚠ 停止了 ${aliveMains.length} 个 main 实例: ${aliveMains.map(m => m.record.pid).join(', ')}`);
|
|
@@ -973,6 +1070,9 @@ export async function cmdStop() {
|
|
|
973
1070
|
export async function cmdRestart(opts = {}) {
|
|
974
1071
|
const cmdStartedAt = Date.now();
|
|
975
1072
|
console.log('🔄 Restarting EvolCore...');
|
|
1073
|
+
// 重启不带 --as-mode,模式按包路径重新判定;清掉可能残留的覆盖文件,并显示出来
|
|
1074
|
+
writeAsModeOverride(undefined);
|
|
1075
|
+
console.log(` ${describeRunMode()}`);
|
|
976
1076
|
if (!await ensureCodexHookBeforeLifecycle())
|
|
977
1077
|
return;
|
|
978
1078
|
const initialStatus = scanInstances();
|
|
@@ -1186,7 +1286,8 @@ export async function cmdStatus() {
|
|
|
1186
1286
|
console.log('');
|
|
1187
1287
|
}
|
|
1188
1288
|
if (pid) {
|
|
1189
|
-
|
|
1289
|
+
const mainRecord = aliveMains.find(m => m.record.pid === pid)?.record;
|
|
1290
|
+
printStartupInfo({ pid, running: true, runMode: mainRecord?.runMode });
|
|
1190
1291
|
if (!ping) {
|
|
1191
1292
|
console.log('⚠ 仅确认 daemon PID 存活,IPC 不可用,不能据此判断服务健康。');
|
|
1192
1293
|
if (ipcFailure)
|
package/dist/cli/fs-command.js
CHANGED
|
@@ -56,7 +56,8 @@ Options:
|
|
|
56
56
|
--content-type <mime> 上传时指定内容类型
|
|
57
57
|
--public 上传后公开可读(personal storage)
|
|
58
58
|
--visibility <public|private> chmod 可见性
|
|
59
|
-
--allow-roles <roles> chmod
|
|
59
|
+
--allow-roles <roles> chmod 角色约束,写法固定为单引号包住、逗号分隔,
|
|
60
|
+
如 --allow-roles 'owner,admin'
|
|
60
61
|
-m aid:<aid>:<perms> setfacl 新增/更新 ACL
|
|
61
62
|
-x aid:<aid> setfacl 移除 ACL
|
|
62
63
|
--source <AID>:<path> mount 虚拟卷来源
|
package/dist/cli/index.js
CHANGED
|
@@ -78,7 +78,7 @@ AUN:
|
|
|
78
78
|
EVENT_LOG 事件日志(默认: true)`;
|
|
79
79
|
function printLifecycleHelp(command) {
|
|
80
80
|
const help = {
|
|
81
|
-
start: `用法: ec start [--foreground] [--diagnose|-d]\n\n --foreground 前台运行,适合 systemd 或容器直接管理\n --diagnose, -d 以诊断模式启动 daemon
|
|
81
|
+
start: `用法: ec start [--foreground] [--diagnose|-d] [--debug] [--as-mode dev|install]\n\n --foreground 前台运行,适合 systemd 或容器直接管理\n --diagnose, -d 以诊断模式启动 daemon\n --debug 调试模式:注册调试 IPC,可在宿主终端以任意 session 身份发命令\n --as-mode 覆盖运行模式判定(仅本次启动生效,不写配置):\n install = 按安装模式(校验随包文件完整性,篡改自动恢复)\n dev = 按开发模式(不校验随包文件)`,
|
|
82
82
|
stop: '用法: ec stop\n\n停止当前 EVOLCORE_HOME 下运行的 EvolCore 服务。',
|
|
83
83
|
restart: `用法: ec restart [--clear] [--diagnose|-d]\n\n --clear 同时清理跨 HOME 残留的 EvolCore 主进程\n --diagnose, -d 以诊断模式启动新 daemon`,
|
|
84
84
|
status: '用法: ec status\n\n查看 daemon、agent、渠道与会话运行状态。',
|
|
@@ -237,11 +237,20 @@ export async function main(args) {
|
|
|
237
237
|
printLifecycleHelp('start');
|
|
238
238
|
break;
|
|
239
239
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
{
|
|
241
|
+
const asModeRaw = getArgValue(strippedArgs, '--as-mode');
|
|
242
|
+
if (asModeRaw !== undefined && asModeRaw !== 'dev' && asModeRaw !== 'install') {
|
|
243
|
+
console.error(`❌ --as-mode 只接受 dev 或 install,收到: ${asModeRaw}`);
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
await cmdStart({
|
|
248
|
+
diagnose: strippedArgs.includes('--diagnose') || strippedArgs.includes('-d'),
|
|
249
|
+
foreground: strippedArgs.includes('--foreground'),
|
|
250
|
+
debug: strippedArgs.includes('--debug'),
|
|
251
|
+
asMode: asModeRaw,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
245
254
|
break;
|
|
246
255
|
case 'stop':
|
|
247
256
|
if (wantsHelp(strippedArgs.slice(1))) {
|
|
@@ -339,6 +348,11 @@ export async function main(args) {
|
|
|
339
348
|
await cmdNet(strippedArgs.slice(1));
|
|
340
349
|
break;
|
|
341
350
|
}
|
|
351
|
+
case 'integrity': {
|
|
352
|
+
const { cmdIntegrity } = await import('./integrity-command.js');
|
|
353
|
+
await cmdIntegrity(strippedArgs.slice(1));
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
342
356
|
case 'ctl':
|
|
343
357
|
await cmdCtl(strippedArgs.slice(1));
|
|
344
358
|
break;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* integrity-command.ts — `ec integrity` 子命令
|
|
3
|
+
*
|
|
4
|
+
* 子命令:
|
|
5
|
+
* ec integrity check <path> — 检查单个文件的台账状态(不修改,不恢复)
|
|
6
|
+
* ec integrity seal <path> — 把当前磁盘内容作为新基线登记(确认一次合法的带外修改)
|
|
7
|
+
* ec integrity query <path> — 打印台账记录(sha256、时间、登记方)
|
|
8
|
+
* ec integrity list — 列出所有已登记文件的状态摘要
|
|
9
|
+
*
|
|
10
|
+
* 所有操作都是本地 CLI,不经过 daemon IPC。
|
|
11
|
+
* 注意:ec integrity 不在 managed 白名单里,agent 会话内执行会被拒绝。
|
|
12
|
+
*/
|
|
13
|
+
import { createRequire } from 'module';
|
|
14
|
+
import { checkFile, queryRecord, registerFile } from '../core/file-integrity/store.js';
|
|
15
|
+
import { integrityDbPath, resolveRoot } from '../paths.js';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
19
|
+
const HELP = `用法: ec integrity <subcommand> [path]
|
|
20
|
+
|
|
21
|
+
子命令:
|
|
22
|
+
check <path> 检查文件台账状态(不修改,不恢复文件)
|
|
23
|
+
seal <path> 把当前磁盘内容登记为新基线(确认合法的带外修改)
|
|
24
|
+
query <path> 查看台账记录(hash、时间、登记方)
|
|
25
|
+
list 列出所有已登记文件的摘要状态
|
|
26
|
+
|
|
27
|
+
说明:
|
|
28
|
+
台账库位于 $EVOLCORE_HOME/data/integrity/file-integrity.db
|
|
29
|
+
"seal" 操作相当于手动确认一次合法修改,使其成为新的校验基线。
|
|
30
|
+
在开发模式下(非 node_modules 安装),系统文件不会自动校验,
|
|
31
|
+
但可以用 seal 主动建立基线,便于后续对比。
|
|
32
|
+
`;
|
|
33
|
+
function fmtTime(unix) {
|
|
34
|
+
if (!unix)
|
|
35
|
+
return '—';
|
|
36
|
+
return new Date(unix * 1000).toLocaleString('zh-CN', {
|
|
37
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
38
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
39
|
+
hour12: false,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function fmtHash(h) {
|
|
43
|
+
return h.slice(0, 16) + '…';
|
|
44
|
+
}
|
|
45
|
+
function resolveTarget(arg) {
|
|
46
|
+
return path.isAbsolute(arg) ? arg : path.resolve(process.cwd(), arg);
|
|
47
|
+
}
|
|
48
|
+
function cmdCheck(target) {
|
|
49
|
+
const result = checkFile(target);
|
|
50
|
+
const rel = path.relative(resolveRoot(), target) || path.basename(target);
|
|
51
|
+
switch (result.status) {
|
|
52
|
+
case 'missing':
|
|
53
|
+
console.log(`❓ 文件不存在: ${rel}`);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
break;
|
|
56
|
+
case 'unregistered':
|
|
57
|
+
console.log(`🆕 未登记(使用 seal 建立基线): ${rel}`);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
break;
|
|
60
|
+
case 'verified':
|
|
61
|
+
console.log(`✅ 校验通过: ${rel}`);
|
|
62
|
+
console.log(` sha256: ${fmtHash(result.actual)}`);
|
|
63
|
+
break;
|
|
64
|
+
case 'tampered':
|
|
65
|
+
console.log(`❌ 已被修改: ${rel}`);
|
|
66
|
+
console.log(` 预期: ${fmtHash(result.expected)}`);
|
|
67
|
+
console.log(` 实际: ${fmtHash(result.actual)}`);
|
|
68
|
+
console.log(` 可恢复: ${result.restorable ? '是(快照完好)' : '否(快照已损坏)'}`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function cmdSeal(target) {
|
|
74
|
+
if (!fs.existsSync(target)) {
|
|
75
|
+
console.error(`❌ 文件不存在: ${target}`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
registerFile(target);
|
|
80
|
+
const rel = path.relative(resolveRoot(), target) || path.basename(target);
|
|
81
|
+
console.log(`✅ 已登记为新基线: ${rel}`);
|
|
82
|
+
}
|
|
83
|
+
function cmdQuery(target) {
|
|
84
|
+
const rec = queryRecord(target);
|
|
85
|
+
const rel = path.relative(resolveRoot(), target) || path.basename(target);
|
|
86
|
+
if (!rec) {
|
|
87
|
+
console.log(`🆕 台账无记录: ${rel}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(`文件: ${rel}`);
|
|
91
|
+
console.log(` sha256: ${rec.sha256}`);
|
|
92
|
+
console.log(` 登记时间: ${fmtTime(rec.registered_at)}`);
|
|
93
|
+
console.log(` 最后校验: ${fmtTime(rec.last_verified_at)}`);
|
|
94
|
+
console.log(` 登记方: ${rec.registrar_aid ?? '(系统)'}`);
|
|
95
|
+
}
|
|
96
|
+
function cmdList() {
|
|
97
|
+
if (!fs.existsSync(integrityDbPath())) {
|
|
98
|
+
console.log('台账库尚未创建(还没有任何文件被登记过)');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let DatabaseSync;
|
|
102
|
+
try {
|
|
103
|
+
({ DatabaseSync } = requireFromHere('node:sqlite'));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
console.error('❌ node:sqlite 不可用(需要 Node.js 22.5+)');
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const db = new DatabaseSync(integrityDbPath(), { readonly: true });
|
|
111
|
+
const rows = db.prepare('SELECT file_path, sha256, registered_at, last_verified_at FROM file_integrity ORDER BY file_path').all();
|
|
112
|
+
if (rows.length === 0) {
|
|
113
|
+
console.log('台账为空');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// 台账主键在 Windows 下是小写,root 也要按同样规则归一化才能前缀匹配
|
|
117
|
+
const rootSlash = resolveRoot().replace(/\\/g, '/');
|
|
118
|
+
const root = (process.platform === 'win32' ? rootSlash.toLowerCase() : rootSlash) + '/';
|
|
119
|
+
let ok = 0, tampered = 0, missing = 0;
|
|
120
|
+
console.log(`台账共 ${rows.length} 条记录(${integrityDbPath()})\n`);
|
|
121
|
+
for (const row of rows) {
|
|
122
|
+
const rel = row.file_path.replace(/\\/g, '/').startsWith(root)
|
|
123
|
+
? row.file_path.replace(/\\/g, '/').slice(root.length)
|
|
124
|
+
: row.file_path;
|
|
125
|
+
const chk = checkFile(row.file_path);
|
|
126
|
+
let icon;
|
|
127
|
+
switch (chk.status) {
|
|
128
|
+
case 'verified':
|
|
129
|
+
icon = '✅';
|
|
130
|
+
ok++;
|
|
131
|
+
break;
|
|
132
|
+
case 'tampered':
|
|
133
|
+
icon = '❌';
|
|
134
|
+
tampered++;
|
|
135
|
+
break;
|
|
136
|
+
case 'missing':
|
|
137
|
+
icon = '❓';
|
|
138
|
+
missing++;
|
|
139
|
+
break;
|
|
140
|
+
case 'unregistered':
|
|
141
|
+
icon = '🆕';
|
|
142
|
+
break;
|
|
143
|
+
default: icon = ' ';
|
|
144
|
+
}
|
|
145
|
+
console.log(`${icon} ${rel}`);
|
|
146
|
+
if (chk.status === 'tampered') {
|
|
147
|
+
console.log(` 预期: ${fmtHash(row.sha256)} 实际: ${fmtHash(chk.actual)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
console.log(`\n摘要:通过 ${ok},已改 ${tampered},不存在 ${missing}`);
|
|
151
|
+
if (tampered > 0 || missing > 0)
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
}
|
|
154
|
+
export async function cmdIntegrity(args) {
|
|
155
|
+
const sub = args[0];
|
|
156
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
157
|
+
console.log(HELP);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
switch (sub) {
|
|
161
|
+
case 'check': {
|
|
162
|
+
if (!args[1]) {
|
|
163
|
+
console.error('用法: ec integrity check <path>');
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
cmdCheck(resolveTarget(args[1]));
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'seal': {
|
|
171
|
+
if (!args[1]) {
|
|
172
|
+
console.error('用法: ec integrity seal <path>');
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
cmdSeal(resolveTarget(args[1]));
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case 'query': {
|
|
180
|
+
if (!args[1]) {
|
|
181
|
+
console.error('用法: ec integrity query <path>');
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
cmdQuery(resolveTarget(args[1]));
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'list':
|
|
189
|
+
cmdList();
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
console.error(`未知子命令: ${sub}`);
|
|
193
|
+
console.log(HELP);
|
|
194
|
+
process.exitCode = 1;
|
|
195
|
+
}
|
|
196
|
+
}
|