evolcore 0.0.19 → 0.0.20
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 +21 -1
- package/README.md +2 -0
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/claude-runner.js +4 -3
- package/dist/agents/codex-runner.js +3 -2
- package/dist/agents/ecagent-runner.js +3 -2
- package/dist/aun/aid/agentmd.js +7 -0
- package/dist/aun/msg/group.js +14 -3
- package/dist/aun/msg/p2p.js +21 -11
- package/dist/aun/outbox.js +144 -19
- package/dist/channels/aun.js +621 -211
- package/dist/cli/daemon-commands.js +12 -6
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +55 -15
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/config/aun-gateway-config.js +2 -0
- package/dist/config/config-manager.js +92 -8
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/gateway-config.js +9 -7
- package/dist/config/lifecycle.js +16 -5
- package/dist/config-store.js +13 -6
- package/dist/core/auth/authorization-audit.js +5 -2
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +21 -36
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/evolagent.js +5 -4
- package/dist/core/message/response-engine.js +9 -4
- package/dist/core/permission/ec-command-parser.js +56 -3
- package/dist/core/permission/sandbox-runtime.js +46 -12
- package/dist/core/permission/tool-policy.js +98 -41
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/eck/kit-renderer.js +17 -8
- package/dist/index.js +30 -19
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +408 -0
- package/kits/schemas/daemon.schema.5.json +136 -0
- package/kits/schemas/defaults.schema.5.json +107 -0
- package/package.json +2 -1
|
@@ -58,15 +58,21 @@ function printNoSelfAgentHints(options) {
|
|
|
58
58
|
console.log(` - ${skippedAgent.dirName}: ${skippedAgent.reason}`);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
// Windows named-pipe connection setup can take a couple of seconds on a busy
|
|
62
|
+
// host (especially on the first request after the daemon starts). A one-second
|
|
63
|
+
// probe causes `ec stop` to skip the IPC shutdown path and fall back to
|
|
64
|
+
// taskkill, even though the daemon is healthy and able to shut down gracefully.
|
|
65
|
+
const DAEMON_PROBE_TIMEOUT_MS = platform.isWindows ? 5_000 : 1_000;
|
|
66
|
+
export const DAEMON_SHUTDOWN_TIMEOUT_MS = platform.isWindows ? 10_000 : 3_000;
|
|
67
|
+
async function probeDaemon(socketPath, timeoutMs = DAEMON_PROBE_TIMEOUT_MS) {
|
|
62
68
|
const response = await ipcQuery(socketPath, { type: 'ping' }, timeoutMs);
|
|
63
69
|
if (response?.pong !== true || !Number.isInteger(response.pid) || response.pid <= 0)
|
|
64
70
|
return null;
|
|
65
71
|
return response;
|
|
66
72
|
}
|
|
67
73
|
/** Ask the daemon to run its own graceful shutdown lifecycle. */
|
|
68
|
-
export async function requestDaemonShutdown(socketPath, timeoutMs =
|
|
69
|
-
const daemon = await probeDaemon(socketPath, Math.min(timeoutMs,
|
|
74
|
+
export async function requestDaemonShutdown(socketPath, timeoutMs = DAEMON_SHUTDOWN_TIMEOUT_MS, reason = 'cli', expectedPids) {
|
|
75
|
+
const daemon = await probeDaemon(socketPath, Math.min(timeoutMs, DAEMON_PROBE_TIMEOUT_MS));
|
|
70
76
|
if (!daemon || (expectedPids && !expectedPids.has(daemon.pid)))
|
|
71
77
|
return null;
|
|
72
78
|
const response = await ipcQuery(socketPath, {
|
|
@@ -654,7 +660,7 @@ export async function cmdStop() {
|
|
|
654
660
|
console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(orphan => orphan.pid).join(', ')}`);
|
|
655
661
|
}
|
|
656
662
|
const gracefulPid = ping && pids.has(ping.pid)
|
|
657
|
-
? await requestDaemonShutdown(p.socket,
|
|
663
|
+
? await requestDaemonShutdown(p.socket, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec stop', pids)
|
|
658
664
|
: null;
|
|
659
665
|
const stopped = await Promise.all([...pids].map(pid => stopPid(pid, pid === gracefulPid)));
|
|
660
666
|
if (stopped.some(result => !result)) {
|
|
@@ -726,7 +732,7 @@ export async function cmdRestart(opts = {}) {
|
|
|
726
732
|
const mainPids = new Set(aliveMains.map(entry => entry.record.pid));
|
|
727
733
|
const currentPing = await probeDaemon(socketPath);
|
|
728
734
|
const gracefulPid = currentPing && mainPids.has(currentPing.pid)
|
|
729
|
-
? await requestDaemonShutdown(socketPath,
|
|
735
|
+
? await requestDaemonShutdown(socketPath, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec restart', mainPids)
|
|
730
736
|
: null;
|
|
731
737
|
const stopped = await Promise.all(aliveMains.map(m => stopPid(m.record.pid, m.record.pid === gracefulPid)));
|
|
732
738
|
if (stopped.some(result => !result)) {
|
|
@@ -759,7 +765,7 @@ export async function cmdRestart(opts = {}) {
|
|
|
759
765
|
const orphanPids = new Set(runtimeOrphans.map(orphan => orphan.pid));
|
|
760
766
|
const currentPing = await probeDaemon(socketPath);
|
|
761
767
|
const gracefulPid = currentPing && orphanPids.has(currentPing.pid)
|
|
762
|
-
? await requestDaemonShutdown(socketPath,
|
|
768
|
+
? await requestDaemonShutdown(socketPath, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec restart', orphanPids)
|
|
763
769
|
: null;
|
|
764
770
|
const stopped = await Promise.all(runtimeOrphans.map(o => stopPid(o.pid, o.pid === gracefulPid)));
|
|
765
771
|
if (stopped.some(result => !result)) {
|
package/dist/cli/index.js
CHANGED
|
@@ -118,6 +118,7 @@ export async function main(args) {
|
|
|
118
118
|
|
|
119
119
|
仅初始化 defaults.json:
|
|
120
120
|
ec init 交互式(写完 defaults.json 后嵌套 agent new)
|
|
121
|
+
选择 ecagent 时会继续询问 API Key 和 Base URL
|
|
121
122
|
ec init --non-interactive [选项]
|
|
122
123
|
--baseagent <claude|codex|gemini|ecagent> 默认: PATH 中第一个可用项;ecagent 需显式选择
|
|
123
124
|
--force 已存在 defaults.json 时覆盖
|
package/dist/cli/init.js
CHANGED
|
@@ -8,7 +8,7 @@ import { scanInstances } from '../utils/instance-registry.js';
|
|
|
8
8
|
import { saveDefaultsSafe, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
|
|
9
9
|
import { generateControlAid, resolveControlAidDomain } from '../aun/aid/control-aid.js';
|
|
10
10
|
import { getCodexAppServerAvailability } from '../agents/codex-runner.js';
|
|
11
|
-
import { resolveEcagentConfig } from '../agents/baseagent.js';
|
|
11
|
+
import { DEFAULT_ECAGENT_BASE_URL, resolveEcagentConfig } from '../agents/baseagent.js';
|
|
12
12
|
import { defaultProjectsRoot } from '../utils/project-path.js';
|
|
13
13
|
import { WEB_CLI_BIN, WEB_PACKAGE_LATEST } from '../product.js';
|
|
14
14
|
import { autostartInstalled, autostartPlatformLabel, configureAutostart } from '../utils/autostart.js';
|
|
@@ -79,14 +79,14 @@ function ecagentUnavailableReason() {
|
|
|
79
79
|
return error instanceof Error ? error.message : String(error);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
function buildDefaults(chosen, available, projectsDefaultPath) {
|
|
82
|
+
function buildDefaults(chosen, available, projectsDefaultPath, ecagentConfig) {
|
|
83
83
|
const baseagents = {};
|
|
84
84
|
for (const b of available) {
|
|
85
85
|
baseagents[b] = {};
|
|
86
86
|
}
|
|
87
87
|
// ecagent is bundled with EvolCore, so declare it even when the caller
|
|
88
88
|
// supplies a reduced availability list (for example, a test probe).
|
|
89
|
-
baseagents.ecagent
|
|
89
|
+
baseagents.ecagent = ecagentConfig ?? baseagents.ecagent ?? {};
|
|
90
90
|
return {
|
|
91
91
|
$schema_version: 1,
|
|
92
92
|
active_baseagent: chosen,
|
|
@@ -94,8 +94,8 @@ function buildDefaults(chosen, available, projectsDefaultPath) {
|
|
|
94
94
|
...(projectsDefaultPath ? { projects: { defaultPath: projectsDefaultPath } } : {}),
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
-
function writeDefaults(chosen, available, projectsDefaultPath) {
|
|
98
|
-
saveDefaultsSafe(buildDefaults(chosen, available, projectsDefaultPath));
|
|
97
|
+
function writeDefaults(chosen, available, projectsDefaultPath, ecagentConfig) {
|
|
98
|
+
saveDefaultsSafe(buildDefaults(chosen, available, projectsDefaultPath, ecagentConfig));
|
|
99
99
|
}
|
|
100
100
|
function hasCompletedInitConfig() {
|
|
101
101
|
const p = resolvePaths();
|
|
@@ -263,7 +263,7 @@ export async function cmdInit(options) {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
const available = detectAvailable(codexAvailability);
|
|
266
|
-
if (available.length === 0
|
|
266
|
+
if (available.length === 0) {
|
|
267
267
|
console.log('❌ 未检测到可用 baseagent。请安装至少一款:');
|
|
268
268
|
console.log(' - claude CLI');
|
|
269
269
|
console.log(' - gemini CLI');
|
|
@@ -361,13 +361,6 @@ export async function cmdInit(options) {
|
|
|
361
361
|
console.log(` ${input} 当前环境不可用(可用: ${available.join('/')})`);
|
|
362
362
|
continue;
|
|
363
363
|
}
|
|
364
|
-
if (input === 'ecagent') {
|
|
365
|
-
const reason = ecagentUnavailableReason();
|
|
366
|
-
if (reason) {
|
|
367
|
-
console.log(` ecagent 当前环境不可用:${reason}`);
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
364
|
chosen = input;
|
|
372
365
|
}
|
|
373
366
|
return chosen;
|
|
@@ -405,13 +398,59 @@ export async function cmdInit(options) {
|
|
|
405
398
|
return resolved;
|
|
406
399
|
}
|
|
407
400
|
}
|
|
401
|
+
async function askEcagentConfig() {
|
|
402
|
+
let configured;
|
|
403
|
+
try {
|
|
404
|
+
const raw = JSON.parse(fs.readFileSync(defaultsPath, 'utf8'));
|
|
405
|
+
const candidate = raw?.baseagents?.ecagent;
|
|
406
|
+
if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
|
|
407
|
+
configured = candidate;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// A malformed or missing defaults file should not prevent the user
|
|
412
|
+
// from supplying a fresh ecagent configuration during init.
|
|
413
|
+
configured = undefined;
|
|
414
|
+
}
|
|
415
|
+
const usable = (value) => {
|
|
416
|
+
if (typeof value !== 'string')
|
|
417
|
+
return false;
|
|
418
|
+
const normalized = value.trim();
|
|
419
|
+
if (!normalized)
|
|
420
|
+
return false;
|
|
421
|
+
const lower = normalized.toLowerCase();
|
|
422
|
+
return !lower.includes('your-') && !lower.includes('placeholder');
|
|
423
|
+
};
|
|
424
|
+
const configuredApiKey = usable(configured?.apiKey) ? configured.apiKey.trim() : undefined;
|
|
425
|
+
const environmentApiKeyName = !configuredApiKey
|
|
426
|
+
? (usable(process.env.ECAGENT_API_KEY)
|
|
427
|
+
? 'ECAGENT_API_KEY'
|
|
428
|
+
: usable(process.env.OPENAI_API_KEY) ? 'OPENAI_API_KEY' : undefined)
|
|
429
|
+
: undefined;
|
|
430
|
+
let apiKey = '';
|
|
431
|
+
while (!apiKey) {
|
|
432
|
+
const suffix = configuredApiKey || environmentApiKeyName ? '(回车保留当前配置)' : '(必填)';
|
|
433
|
+
const input = (await ask(rl, `ecagent API Key ${suffix}: `)).trim();
|
|
434
|
+
apiKey = input || configuredApiKey || (environmentApiKeyName ? '${' + environmentApiKeyName + '}' : '');
|
|
435
|
+
if (!apiKey)
|
|
436
|
+
console.log(' API Key 不能为空,请重新输入');
|
|
437
|
+
}
|
|
438
|
+
const configuredBaseUrl = [
|
|
439
|
+
configured?.baseUrl,
|
|
440
|
+
process.env.ECAGENT_BASE_URL,
|
|
441
|
+
DEFAULT_ECAGENT_BASE_URL,
|
|
442
|
+
].find(usable) || DEFAULT_ECAGENT_BASE_URL;
|
|
443
|
+
const baseUrl = (await ask(rl, `ecagent Base URL [${configuredBaseUrl}]: `)).trim() || configuredBaseUrl;
|
|
444
|
+
return { apiKey, baseUrl };
|
|
445
|
+
}
|
|
408
446
|
try {
|
|
409
447
|
if (defaultsExisted) {
|
|
410
448
|
const ans = (await ask(rl, `配置文件已存在: ${defaultsPath}\n 是否覆盖?[y/N] `)).trim().toLowerCase();
|
|
411
449
|
if (ans === 'y' || ans === 'yes') {
|
|
412
450
|
const chosen = await askBaseagent();
|
|
451
|
+
const ecagentConfig = chosen === 'ecagent' ? await askEcagentConfig() : undefined;
|
|
413
452
|
const projectsDefaultPath = await askProjectsDefaultPath();
|
|
414
|
-
writeDefaults(chosen, available, projectsDefaultPath);
|
|
453
|
+
writeDefaults(chosen, available, projectsDefaultPath, ecagentConfig);
|
|
415
454
|
console.log(`\n✓ 已覆盖: ${defaultsPath}`);
|
|
416
455
|
console.log(` active_baseagent: ${chosen}\n`);
|
|
417
456
|
}
|
|
@@ -421,8 +460,9 @@ export async function cmdInit(options) {
|
|
|
421
460
|
}
|
|
422
461
|
else {
|
|
423
462
|
const chosen = await askBaseagent();
|
|
463
|
+
const ecagentConfig = chosen === 'ecagent' ? await askEcagentConfig() : undefined;
|
|
424
464
|
const projectsDefaultPath = await askProjectsDefaultPath();
|
|
425
|
-
writeDefaults(chosen, available, projectsDefaultPath);
|
|
465
|
+
writeDefaults(chosen, available, projectsDefaultPath, ecagentConfig);
|
|
426
466
|
console.log(`\n✓ 已创建: ${defaultsPath}`);
|
|
427
467
|
console.log(` active_baseagent: ${chosen}\n`);
|
|
428
468
|
}
|
|
@@ -14,7 +14,7 @@ import { WEB_PACKAGE_NAME } from '../product.js';
|
|
|
14
14
|
import { shouldSuppressRealRestart } from '../utils/restart-safety.js';
|
|
15
15
|
import { rotateStdoutLog } from '../utils/log-writer.js';
|
|
16
16
|
import { inspectDataMigrationRequirement } from '../core/data-migration.js';
|
|
17
|
-
import { requestDaemonShutdown } from './daemon-commands.js';
|
|
17
|
+
import { DAEMON_SHUTDOWN_TIMEOUT_MS, requestDaemonShutdown } from './daemon-commands.js';
|
|
18
18
|
const execFileAsync = promisify(execFile);
|
|
19
19
|
// 清理 Claude Code 环境变量,防止 SDK 认为是嵌套会话
|
|
20
20
|
function cleanEnv() {
|
|
@@ -107,7 +107,7 @@ export async function cmdRestartMonitor() {
|
|
|
107
107
|
const pids = runtimeOrphans.map(o => o.pid).join(', ');
|
|
108
108
|
log(`Stopping unregistered EvolCore process(es): ${pids}`);
|
|
109
109
|
const orphanPids = new Set(runtimeOrphans.map(orphan => orphan.pid));
|
|
110
|
-
const ping = await requestDaemonShutdown(p.socket,
|
|
110
|
+
const ping = await requestDaemonShutdown(p.socket, DAEMON_SHUTDOWN_TIMEOUT_MS, 'restart-monitor orphan cleanup', orphanPids);
|
|
111
111
|
for (const orphan of runtimeOrphans) {
|
|
112
112
|
if (orphan.pid !== ping)
|
|
113
113
|
platform.killProcess(orphan.pid, false);
|
|
@@ -134,7 +134,7 @@ export async function cmdRestartMonitor() {
|
|
|
134
134
|
if (aliveMains.length > 0) {
|
|
135
135
|
const oldPids = aliveMains.map(m => m.record.pid);
|
|
136
136
|
log(`Monitoring ${oldPids.length} main process(es): ${oldPids.join(', ')}`);
|
|
137
|
-
const gracefulPid = await requestDaemonShutdown(p.socket,
|
|
137
|
+
const gracefulPid = await requestDaemonShutdown(p.socket, DAEMON_SHUTDOWN_TIMEOUT_MS, 'restart-monitor', new Set(oldPids));
|
|
138
138
|
// 没有收到 IPC shutdown 确认的进程才走信号兜底
|
|
139
139
|
for (const pid of oldPids) {
|
|
140
140
|
if (pid !== gracefulPid) {
|
|
@@ -29,6 +29,74 @@ import { clearRoleStoreCache, isRoleName } from './role-store.js';
|
|
|
29
29
|
import { writeRoleDefinition, writeRoleRegistry } from './role-service.js';
|
|
30
30
|
import { mutateContactBookTransactionSync } from './contact-book-store.js';
|
|
31
31
|
import { DAEMON_SCHEMA_NAME } from '../product.js';
|
|
32
|
+
// AUN gateway discovery belongs to fastaun. Keep the retired gatewayUrl
|
|
33
|
+
// compatibility handling beside the rest of the configuration read/write
|
|
34
|
+
// policy so all config callers share one implementation.
|
|
35
|
+
const warnedLegacyGatewayFiles = new Set();
|
|
36
|
+
export function legacyAunGatewayConfigPaths(value) {
|
|
37
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
38
|
+
return [];
|
|
39
|
+
const record = value;
|
|
40
|
+
const paths = [];
|
|
41
|
+
if (record.aun && typeof record.aun === 'object' && !Array.isArray(record.aun)
|
|
42
|
+
&& Object.prototype.hasOwnProperty.call(record.aun, 'gatewayUrl')) {
|
|
43
|
+
paths.push('/aun/gatewayUrl');
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(record.channels)) {
|
|
46
|
+
record.channels.forEach((channel, index) => {
|
|
47
|
+
if (channel && typeof channel === 'object'
|
|
48
|
+
&& channel.type === 'aun'
|
|
49
|
+
&& Object.prototype.hasOwnProperty.call(channel, 'gatewayUrl')) {
|
|
50
|
+
paths.push(`/channels/${index}/gatewayUrl`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return paths;
|
|
55
|
+
}
|
|
56
|
+
export function legacyAunGatewayConfigError(file, paths) {
|
|
57
|
+
return `${file}: AUN gatewayUrl at ${paths.join(', ')} ${paths.length === 1 ? 'is' : 'are'} no longer supported; `
|
|
58
|
+
+ 'fastaun owns gateway discovery';
|
|
59
|
+
}
|
|
60
|
+
/** Reject newly submitted gateway overrides. Legacy persisted values are handled on read. */
|
|
61
|
+
export function assertNoLegacyAunGatewayConfig(value, file) {
|
|
62
|
+
const paths = legacyAunGatewayConfigPaths(value);
|
|
63
|
+
if (paths.length > 0)
|
|
64
|
+
throw new Error(legacyAunGatewayConfigError(file, paths));
|
|
65
|
+
}
|
|
66
|
+
/** Remove retired AUN gateway overrides without mutating the input. */
|
|
67
|
+
export function stripLegacyAunGatewayConfig(value, file, warn) {
|
|
68
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
69
|
+
return value;
|
|
70
|
+
const record = value;
|
|
71
|
+
const next = { ...record };
|
|
72
|
+
let changed = false;
|
|
73
|
+
if (record.aun && typeof record.aun === 'object' && !Array.isArray(record.aun)
|
|
74
|
+
&& Object.prototype.hasOwnProperty.call(record.aun, 'gatewayUrl')) {
|
|
75
|
+
const { gatewayUrl: _legacyGatewayUrl, ...aun } = record.aun;
|
|
76
|
+
next.aun = aun;
|
|
77
|
+
changed = true;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(record.channels)) {
|
|
80
|
+
const channels = record.channels.map(channel => {
|
|
81
|
+
if (!channel || typeof channel !== 'object'
|
|
82
|
+
|| channel.type !== 'aun'
|
|
83
|
+
|| !Object.prototype.hasOwnProperty.call(channel, 'gatewayUrl'))
|
|
84
|
+
return channel;
|
|
85
|
+
const { gatewayUrl: _legacyGatewayUrl, ...rest } = channel;
|
|
86
|
+
changed = true;
|
|
87
|
+
return rest;
|
|
88
|
+
});
|
|
89
|
+
if (changed)
|
|
90
|
+
next.channels = channels;
|
|
91
|
+
}
|
|
92
|
+
if (!changed)
|
|
93
|
+
return value;
|
|
94
|
+
if (!warnedLegacyGatewayFiles.has(file)) {
|
|
95
|
+
warnedLegacyGatewayFiles.add(file);
|
|
96
|
+
warn(`[config] ${file}: AUN gatewayUrl is no longer supported and will be ignored; fastaun owns gateway discovery`);
|
|
97
|
+
}
|
|
98
|
+
return next;
|
|
99
|
+
}
|
|
32
100
|
export function shouldFailFastForMissingOwners(env = process.env) {
|
|
33
101
|
const value = (env.EVOLCORE_REQUIRE_OWNERS || '').trim().toLowerCase();
|
|
34
102
|
return value === '1' || value === 'true' || value === 'yes';
|
|
@@ -114,6 +182,7 @@ export function assertRoleConfigV4Ready() {
|
|
|
114
182
|
issues.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
|
|
115
183
|
return;
|
|
116
184
|
}
|
|
185
|
+
value = stripLegacyAunGatewayConfig(value, file, message => configWarn(message));
|
|
117
186
|
const actualVersion = value && typeof value === 'object'
|
|
118
187
|
? value.$schema_version
|
|
119
188
|
: undefined;
|
|
@@ -207,7 +276,10 @@ export function assertRoleConfigV5Ready() {
|
|
|
207
276
|
if (!fs.existsSync(agentFile))
|
|
208
277
|
continue;
|
|
209
278
|
const agent = readJson(agentFile);
|
|
210
|
-
const
|
|
279
|
+
const sanitizedAgent = agent
|
|
280
|
+
? stripLegacyAunGatewayConfig(agent, agentFile, message => configWarn(message))
|
|
281
|
+
: agent;
|
|
282
|
+
const agentVersion = sanitizedAgent?.$schema_version;
|
|
211
283
|
let agentSchema = null;
|
|
212
284
|
if (typeof agentVersion === 'number' && agentVersion >= 5) {
|
|
213
285
|
try {
|
|
@@ -215,8 +287,8 @@ export function assertRoleConfigV5Ready() {
|
|
|
215
287
|
}
|
|
216
288
|
catch { }
|
|
217
289
|
}
|
|
218
|
-
if (!
|
|
219
|
-
if (
|
|
290
|
+
if (!sanitizedAgent || !agentSchema || !agentSchema.validate(sanitizedAgent)) {
|
|
291
|
+
if (sanitizedAgent)
|
|
220
292
|
issues.push({
|
|
221
293
|
file: agentFile,
|
|
222
294
|
detail: agentSchema
|
|
@@ -266,6 +338,7 @@ export function assertContactBookV2Ready() {
|
|
|
266
338
|
issues.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
|
|
267
339
|
return;
|
|
268
340
|
}
|
|
341
|
+
value = stripLegacyAunGatewayConfig(value, file, message => configWarn(message));
|
|
269
342
|
const candidates = Array.isArray(schemas) ? schemas : [schemas];
|
|
270
343
|
if (!candidates.some(schema => schema.validate(value))) {
|
|
271
344
|
const detail = (candidates.at(-1)?.validate.errors ?? [])
|
|
@@ -519,6 +592,7 @@ export function read(target, sel, opts = {}) {
|
|
|
519
592
|
}
|
|
520
593
|
if (raw === null)
|
|
521
594
|
return null;
|
|
595
|
+
raw = stripLegacyAunGatewayConfig(raw, file, message => configWarn(message));
|
|
522
596
|
// schema 版本迁移(read 时若 $schema_version < current)
|
|
523
597
|
const migrated = migrateIfNeeded(target, raw, file);
|
|
524
598
|
const normalized = target === ConfigTarget.Agent
|
|
@@ -702,9 +776,12 @@ export function write(target, value, sel, opts = {}) {
|
|
|
702
776
|
const schema = target === ConfigTarget.Contact && requestedVersion === 2
|
|
703
777
|
? loadSchema('contact-book', 2)
|
|
704
778
|
: loadSchema(TARGET_SCHEMA[target]);
|
|
779
|
+
const retiredGatewayPaths = legacyAunGatewayConfigPaths(value);
|
|
780
|
+
if (retiredGatewayPaths.length > 0) {
|
|
781
|
+
throw new ConfigError('AUN_GATEWAY_URL_REMOVED', legacyAunGatewayConfigError(file, retiredGatewayPaths), { paths: retiredGatewayPaths });
|
|
782
|
+
}
|
|
705
783
|
const withVer = ensureSchemaVersion(value, schema.version);
|
|
706
|
-
const
|
|
707
|
-
const canonicalized = normalizeBaseagentEffortCompat(migrated);
|
|
784
|
+
const canonicalized = normalizeBaseagentEffortCompat(withVer);
|
|
708
785
|
// Agent config 写入规范化(aid 校验、projects 字段清理)
|
|
709
786
|
const normalized = target === ConfigTarget.Agent
|
|
710
787
|
? normalizeAgentConfigForWrite(canonicalized)
|
|
@@ -1014,10 +1091,14 @@ export function validateConfig(target, value) {
|
|
|
1014
1091
|
];
|
|
1015
1092
|
}
|
|
1016
1093
|
export function validateConfigFile(target, sel) {
|
|
1017
|
-
const
|
|
1094
|
+
const file = targetPath(target, sel);
|
|
1095
|
+
const value = atomicReadJson(file);
|
|
1018
1096
|
if (value === null)
|
|
1019
1097
|
return { exists: false, errors: [] };
|
|
1020
|
-
|
|
1098
|
+
// Persisted pre-removal files remain readable: validate the same sanitized
|
|
1099
|
+
// view used by runtime reads, while new writes are still rejected by write().
|
|
1100
|
+
const sanitized = stripLegacyAunGatewayConfig(value, file, message => configWarn(message));
|
|
1101
|
+
return { exists: true, errors: validateConfig(target, sanitized) };
|
|
1021
1102
|
}
|
|
1022
1103
|
function fileCacheAvailable() {
|
|
1023
1104
|
try {
|
|
@@ -1108,6 +1189,7 @@ function stripRelationRoleData(config) {
|
|
|
1108
1189
|
*/
|
|
1109
1190
|
export function resolveEffective(sel, opts = {}) {
|
|
1110
1191
|
const config = resolveAgentConfig(sel, opts);
|
|
1192
|
+
const observableDefault = loadSchema('agent-config').fields.get('observable')?.default;
|
|
1111
1193
|
const effective = {
|
|
1112
1194
|
$schema_version: config.$schema_version ?? currentVersion('agent-config'),
|
|
1113
1195
|
aid: config.aid ?? sel.self ?? '',
|
|
@@ -1121,7 +1203,9 @@ export function resolveEffective(sel, opts = {}) {
|
|
|
1121
1203
|
projects: config.projects,
|
|
1122
1204
|
capabilities: config.capabilities,
|
|
1123
1205
|
readonlySourceDiagnostics: config.readonlySourceDiagnostics,
|
|
1124
|
-
|
|
1206
|
+
// Schema defaults are not applied by AJV during reads; apply the current
|
|
1207
|
+
// factory default here so omitted observable fields are enabled at runtime.
|
|
1208
|
+
observable: config.observable ?? (typeof observableDefault === 'boolean' ? observableDefault : true),
|
|
1125
1209
|
extra_backup: config.extra_backup,
|
|
1126
1210
|
// Runtime configuration parameters
|
|
1127
1211
|
active_baseagent: config.active_baseagent,
|
|
@@ -287,8 +287,7 @@ function relPath(absolute) {
|
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
289
|
* 各存储层都无值时的兜底:若 schema 为该字段声明了 `default`,展示出厂默认并标记
|
|
290
|
-
* schemaDefault=true
|
|
291
|
-
* 未设值仍为 undefined,以保留协议层回退等既有语义)。
|
|
290
|
+
* schemaDefault=true。运行时 resolveEffective 也会应用 Agent schema 的 observable 默认值。
|
|
292
291
|
*/
|
|
293
292
|
function getWithSchemaDefault(op, absent, note) {
|
|
294
293
|
const nt = note ? { note } : {};
|
|
@@ -20,7 +20,7 @@ import { buildModelRequestHeaders } from '../agents/request-identity.js';
|
|
|
20
20
|
import { resolvePriceRow } from '../stats/billing.js';
|
|
21
21
|
import { ipcQuery } from '../ipc.js';
|
|
22
22
|
import { logger } from '../utils/logger.js';
|
|
23
|
-
import { ConfigTarget, read as readConfig } from './config-manager.js';
|
|
23
|
+
import { ConfigTarget, read as readConfig, stripLegacyAunGatewayConfig } from './config-manager.js';
|
|
24
24
|
/** 已知 baseagent 类型(网关可管理范围)。 */
|
|
25
25
|
const GATEWAY_TYPES = ['claude', 'codex', 'gemini', 'ecagent'];
|
|
26
26
|
const DISPLAY_NAMES = {
|
|
@@ -84,8 +84,9 @@ function readDefaultsRaw() {
|
|
|
84
84
|
// loadDefaults 会展开 $ENV,掩码逻辑需看原始引用,故直接读盘
|
|
85
85
|
try {
|
|
86
86
|
const p = path.join(resolvePaths().agentsDir, 'defaults.json');
|
|
87
|
-
if (fs.existsSync(p))
|
|
88
|
-
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
87
|
+
if (fs.existsSync(p)) {
|
|
88
|
+
return stripLegacyAunGatewayConfig(JSON.parse(fs.readFileSync(p, 'utf-8')), p, message => logger.warn(message));
|
|
89
|
+
}
|
|
89
90
|
}
|
|
90
91
|
catch (e) {
|
|
91
92
|
logger.warn(`[gateway] read defaults.json failed: ${e}`);
|
|
@@ -95,8 +96,9 @@ function readDefaultsRaw() {
|
|
|
95
96
|
function readAgentRaw(aid) {
|
|
96
97
|
try {
|
|
97
98
|
const p = path.join(resolvePaths().agentsDir, aid, 'config.json');
|
|
98
|
-
if (fs.existsSync(p))
|
|
99
|
-
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
99
|
+
if (fs.existsSync(p)) {
|
|
100
|
+
return stripLegacyAunGatewayConfig(JSON.parse(fs.readFileSync(p, 'utf-8')), p, message => logger.warn(message));
|
|
101
|
+
}
|
|
100
102
|
}
|
|
101
103
|
catch (e) {
|
|
102
104
|
logger.warn(`[gateway] read agents/${aid}/config.json failed: ${e}`);
|
|
@@ -780,7 +782,7 @@ export async function gatewaySyncEnv(args) {
|
|
|
780
782
|
}
|
|
781
783
|
if (hasChanges) {
|
|
782
784
|
const defaultsPath = path.join(resolvePaths().agentsDir, 'defaults.json');
|
|
783
|
-
|
|
785
|
+
saveDefaultsSafe(stripLegacyAunGatewayConfig(defaults, defaultsPath, message => logger.warn(message)));
|
|
784
786
|
synced.push('全局配置 (defaults.json)');
|
|
785
787
|
}
|
|
786
788
|
}
|
|
@@ -826,7 +828,7 @@ export async function gatewaySyncEnv(args) {
|
|
|
826
828
|
}
|
|
827
829
|
if (hasChanges) {
|
|
828
830
|
const agentConfigPath = path.join(resolvePaths().agentsDir, aid, 'config.json');
|
|
829
|
-
|
|
831
|
+
saveAgent(stripLegacyAunGatewayConfig(agentRaw, agentConfigPath, message => logger.warn(message)));
|
|
830
832
|
synced.push(`${aid} (config.json)`);
|
|
831
833
|
}
|
|
832
834
|
}
|
package/dist/config/lifecycle.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { CONFIG_SCHEMA_VERSION } from '../types.js';
|
|
2
2
|
const VALID_LIFECYCLES = new Set(['created', 'bootstrapping', 'active']);
|
|
3
|
-
export function
|
|
3
|
+
export function isAgentLifecycle(value) {
|
|
4
|
+
return typeof value === 'string' && VALID_LIFECYCLES.has(value);
|
|
5
|
+
}
|
|
6
|
+
/** Resolve the effective lifecycle. Null means an explicit invalid value. */
|
|
7
|
+
export function resolveAgentLifecycle(config) {
|
|
4
8
|
const current = config.lifecycle;
|
|
5
|
-
if (
|
|
9
|
+
if (isAgentLifecycle(current))
|
|
10
|
+
return current;
|
|
11
|
+
if (current !== undefined)
|
|
12
|
+
return null;
|
|
13
|
+
return config.initialized === false ? 'created' : 'active';
|
|
14
|
+
}
|
|
15
|
+
export function normalizeAgentLifecycle(config) {
|
|
16
|
+
const lifecycle = resolveAgentLifecycle(config);
|
|
17
|
+
// Preserve explicit invalid values so validation and runtime gates can fail
|
|
18
|
+
// closed. Only an omitted lifecycle receives the compatibility default.
|
|
19
|
+
if (lifecycle === null || config.lifecycle === lifecycle) {
|
|
6
20
|
return config;
|
|
7
21
|
}
|
|
8
|
-
const lifecycle = config.initialized === true ? 'active'
|
|
9
|
-
: config.initialized === false ? 'created'
|
|
10
|
-
: 'active';
|
|
11
22
|
return { ...config, lifecycle };
|
|
12
23
|
}
|
|
13
24
|
export function withLifecycleForWrite(config, lifecycle) {
|
package/dist/config-store.js
CHANGED
|
@@ -23,14 +23,15 @@ import { checkAgentDir, isValidAid } from './aun/aid/validation.js';
|
|
|
23
23
|
import { normalizeAidDomain } from './aun/aid/domain.js';
|
|
24
24
|
import { isValidChannelName } from './core/channel-loader.js';
|
|
25
25
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
26
|
-
import { ConfigTarget, read as cfgRead, write as cfgWrite } from './config/config-manager.js';
|
|
26
|
+
import { ConfigTarget, assertNoLegacyAunGatewayConfig, read as cfgRead, stripLegacyAunGatewayConfig, write as cfgWrite, } from './config/config-manager.js';
|
|
27
27
|
import { expandVars, buildEnvResolver } from './config/merge.js';
|
|
28
|
+
import { isAgentLifecycle } from './config/lifecycle.js';
|
|
28
29
|
import { logger } from './utils/logger.js';
|
|
29
30
|
import { parseStableSemver } from './utils/stable-semver.js';
|
|
30
31
|
/** 读 {root}/daemon.json。文件不存在返回 {},不报错。 */
|
|
31
32
|
export function loadDaemonConfig() {
|
|
32
33
|
const raw = atomicReadJson(resolvePaths().daemonConfig);
|
|
33
|
-
return validateDaemonConfig(raw ?? {}, false);
|
|
34
|
+
return validateDaemonConfig(stripLegacyAunGatewayConfig(raw ?? {}, resolvePaths().daemonConfig, message => logger.warn(message)), false);
|
|
34
35
|
}
|
|
35
36
|
let eckSnapshotsConfigCache;
|
|
36
37
|
/** Parse the process-level snapshot gate once for the current daemon lifecycle. */
|
|
@@ -52,13 +53,14 @@ export function isEckSnapshotsEnabled() {
|
|
|
52
53
|
/** 原子写入 {root}/daemon.json。调用方负责传完整对象(含要保留的字段)。 */
|
|
53
54
|
export function saveDaemonConfig(value) {
|
|
54
55
|
const configPath = resolvePaths().daemonConfig;
|
|
56
|
+
assertNoLegacyAunGatewayConfig(value, configPath);
|
|
55
57
|
const current = atomicReadJson(configPath);
|
|
56
58
|
// A legacy default is irrelevant while an existing control AID is retained.
|
|
57
59
|
// Validate the field when it is created or changed, but do not make unrelated
|
|
58
60
|
// daemon.json updates fail because of a persisted, unused legacy value.
|
|
59
61
|
const validateAidDomain = current === null
|
|
60
62
|
|| current.aun?.defaultAidDomain !== value.aun?.defaultAidDomain;
|
|
61
|
-
atomicWriteJson(configPath, validateDaemonConfig(value, validateAidDomain));
|
|
63
|
+
atomicWriteJson(configPath, validateDaemonConfig(stripLegacyAunGatewayConfig(value, configPath, message => logger.warn(message)), validateAidDomain));
|
|
62
64
|
}
|
|
63
65
|
function validateDaemonConfig(value, validateAidDomain) {
|
|
64
66
|
const minEvolVersion = value.aun?.minEvolVersion;
|
|
@@ -105,11 +107,12 @@ export function loadDefaults() {
|
|
|
105
107
|
if (typeof raw.$schema_version !== 'number') {
|
|
106
108
|
logger.warn(`[config] ${p}: missing $schema_version, treating as ${CONFIG_SCHEMA_VERSION}`);
|
|
107
109
|
}
|
|
108
|
-
return expandEnvRefs(raw);
|
|
110
|
+
return expandEnvRefs(stripLegacyAunGatewayConfig(raw, p, message => logger.warn(message)));
|
|
109
111
|
}
|
|
110
112
|
export function saveDefaults(value) {
|
|
113
|
+
assertNoLegacyAunGatewayConfig(value, resolvePaths().defaultsConfig);
|
|
111
114
|
backupDefaults(resolvePaths().defaultsConfig);
|
|
112
|
-
atomicWriteJson(resolvePaths().defaultsConfig, value);
|
|
115
|
+
atomicWriteJson(resolvePaths().defaultsConfig, stripLegacyAunGatewayConfig(value, resolvePaths().defaultsConfig, message => logger.warn(message)));
|
|
113
116
|
}
|
|
114
117
|
/**
|
|
115
118
|
* 备份 defaults.json 为 defaults_YYYYMMDDhhmmss.json。文件不存在时为 no-op。
|
|
@@ -141,6 +144,7 @@ function backupDefaults(filePath) {
|
|
|
141
144
|
*/
|
|
142
145
|
export function saveDefaultsSafe(patch) {
|
|
143
146
|
const p = resolvePaths().defaultsConfig;
|
|
147
|
+
assertNoLegacyAunGatewayConfig(patch, p);
|
|
144
148
|
let existing = null;
|
|
145
149
|
try {
|
|
146
150
|
existing = atomicReadJson(p);
|
|
@@ -153,7 +157,7 @@ export function saveDefaultsSafe(patch) {
|
|
|
153
157
|
const merged = existing
|
|
154
158
|
? deepMergeObject(existing, patch)
|
|
155
159
|
: { $schema_version: CONFIG_SCHEMA_VERSION, ...patch };
|
|
156
|
-
atomicWriteJson(p, merged);
|
|
160
|
+
atomicWriteJson(p, stripLegacyAunGatewayConfig(merged, p, message => logger.warn(message)));
|
|
157
161
|
}
|
|
158
162
|
/** 递归对象合并:overlay 覆盖 base;标量与数组按 overlay 替换;plain object 递归。
|
|
159
163
|
* saveDefaultsSafe 内部用(保留现有"补丁式写 defaults"语义,与覆盖链合并无关)。 */
|
|
@@ -300,6 +304,9 @@ export function validateAgentConfig(cfg) {
|
|
|
300
304
|
const errs = [];
|
|
301
305
|
if (!cfg.aid || !isValidAid(cfg.aid))
|
|
302
306
|
errs.push(`invalid aid: ${cfg.aid}`);
|
|
307
|
+
if (cfg.lifecycle !== undefined && !isAgentLifecycle(cfg.lifecycle)) {
|
|
308
|
+
errs.push(`invalid lifecycle: ${String(cfg.lifecycle)}`);
|
|
309
|
+
}
|
|
303
310
|
if (!Array.isArray(cfg.channels)) {
|
|
304
311
|
errs.push('channels must be an array');
|
|
305
312
|
return errs;
|
|
@@ -37,7 +37,10 @@ export function auditToolPreflightDenial(input) {
|
|
|
37
37
|
toolName: input.toolName,
|
|
38
38
|
policyCode: input.policyCode,
|
|
39
39
|
protectionClass: input.protectionClass ?? protectionClassForPolicy(input.policyCode),
|
|
40
|
-
|
|
40
|
+
// Path provenance must come from the policy parser. Do not infer it from
|
|
41
|
+
// localized summaries: words such as `daemon.json` or `.lock` may only be
|
|
42
|
+
// regex/string literals in an otherwise harmless diagnostic command.
|
|
43
|
+
matchedPath: input.matchedPath,
|
|
41
44
|
reason: input.reason,
|
|
42
45
|
argsSummary: input.summary ? { summary: input.summary } : undefined,
|
|
43
46
|
sessionId: input.sessionId ?? 'unknown',
|
|
@@ -341,7 +344,7 @@ function protectionClassForPolicy(policyCode) {
|
|
|
341
344
|
return 'L';
|
|
342
345
|
return undefined;
|
|
343
346
|
}
|
|
344
|
-
function
|
|
347
|
+
function extractAuditPath_UNUSED(summary) {
|
|
345
348
|
if (!summary)
|
|
346
349
|
return undefined;
|
|
347
350
|
const match = summary.match(/(?:^|\s)(\/[^\s'"`;]+|[A-Za-z]:[\\/][^\s'"`;]+|(?:src|ecagent|ecweb|scripts|tests?)\/[^\s'"`;]+)/);
|
|
@@ -70,11 +70,11 @@ export function bindPostBootstrapWelcomeOutboxSession(aid, sessionId) {
|
|
|
70
70
|
return false;
|
|
71
71
|
if (entry.context?.sessionId === sessionId)
|
|
72
72
|
return true;
|
|
73
|
-
return outbox.
|
|
73
|
+
return outbox.replaceIfRouteMatches(aid, entry, {
|
|
74
74
|
...entry,
|
|
75
75
|
context: {
|
|
76
76
|
...(entry.context ?? {}),
|
|
77
77
|
sessionId,
|
|
78
78
|
},
|
|
79
|
-
});
|
|
79
|
+
}) === 'replaced';
|
|
80
80
|
}
|