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
package/dist/ipc.js
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { isWindows, resolveCommandPath } from './cross-platform.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the installed Codex CLI entrypoint. npm creates a `.cmd` shim on
|
|
5
|
+
* Windows, which is not the same executable as the extensionless Unix shim.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveCodexCliPath() {
|
|
8
|
+
return resolveCommandPath('codex');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build a child-process invocation that works for npm's Windows command shim.
|
|
12
|
+
* The options are injectable so platform-specific behavior can be tested on
|
|
13
|
+
* non-Windows CI hosts.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveCodexLaunchCommand(args, options = {}) {
|
|
16
|
+
const windows = options.isWindows ?? isWindows;
|
|
17
|
+
const commandPath = options.commandPath !== undefined
|
|
18
|
+
? options.commandPath
|
|
19
|
+
: resolveCodexCliPath();
|
|
20
|
+
if (!commandPath)
|
|
21
|
+
return null;
|
|
22
|
+
if (windows && /\.(cmd|bat)$/i.test(commandPath)) {
|
|
23
|
+
return {
|
|
24
|
+
command: options.comSpec ?? process.env.ComSpec ?? 'cmd.exe',
|
|
25
|
+
args: ['/d', '/s', '/c', commandPath, ...args],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return { command: commandPath, args };
|
|
29
|
+
}
|
|
30
|
+
/** Execute a Codex CLI command using the resolved npm shim/path. */
|
|
31
|
+
export function execCodexCliSync(args, options) {
|
|
32
|
+
const commandPath = resolveCodexCliPath();
|
|
33
|
+
if (!commandPath)
|
|
34
|
+
throw new Error('Codex CLI not found');
|
|
35
|
+
const launchCommand = resolveCodexLaunchCommand(args, { commandPath });
|
|
36
|
+
if (!launchCommand)
|
|
37
|
+
throw new Error('Codex CLI not found');
|
|
38
|
+
return execFileSync(launchCommand.command, launchCommand.args, options);
|
|
39
|
+
}
|
|
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
import { execFileSync, execFile, spawn, spawnSync } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
-
import { getProcessStartTime } from './process-introspect.js';
|
|
6
|
+
import { getProcessStartTime, parseCimDate } from './process-introspect.js';
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
export const isWindows = process.platform === 'win32';
|
|
9
9
|
const ENCODE_PATH_MAX = 200;
|
|
@@ -38,6 +38,10 @@ export function encodePath(projectPath) {
|
|
|
38
38
|
* Cross-platform process liveness check.
|
|
39
39
|
*/
|
|
40
40
|
export function isProcessRunning(pid) {
|
|
41
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
42
|
+
return false;
|
|
43
|
+
if (isWindows)
|
|
44
|
+
return isWindowsProcessRunning(pid);
|
|
41
45
|
try {
|
|
42
46
|
process.kill(pid, 0);
|
|
43
47
|
return true;
|
|
@@ -47,23 +51,65 @@ export function isProcessRunning(pid) {
|
|
|
47
51
|
return e.code === 'EPERM';
|
|
48
52
|
}
|
|
49
53
|
}
|
|
54
|
+
function isWindowsProcessRunning(pid) {
|
|
55
|
+
try {
|
|
56
|
+
const result = spawnSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], { encoding: 'utf-8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
57
|
+
if (result.status === 0 && new RegExp(`,"${pid}",`, 'i').test(result.stdout || ''))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
// Keep a conservative fallback for restricted Windows environments where
|
|
62
|
+
// tasklist is unavailable. A failed probe must never be treated as proof of
|
|
63
|
+
// process exit by lifecycle code.
|
|
64
|
+
try {
|
|
65
|
+
process.kill(pid, 0);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return e?.code === 'EPERM';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
50
72
|
/**
|
|
51
73
|
* Cross-platform process termination.
|
|
52
74
|
*/
|
|
53
75
|
export function killProcess(pid, force = false) {
|
|
54
|
-
if (
|
|
76
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
77
|
+
return false;
|
|
78
|
+
if (isWindows) {
|
|
55
79
|
try {
|
|
56
|
-
|
|
80
|
+
const args = ['/PID', String(pid), '/T'];
|
|
81
|
+
if (force)
|
|
82
|
+
args.push('/F');
|
|
83
|
+
const result = spawnSync('taskkill', args, {
|
|
84
|
+
encoding: 'utf-8',
|
|
85
|
+
timeout: 10_000,
|
|
86
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
87
|
+
windowsHide: true,
|
|
88
|
+
});
|
|
89
|
+
return result.status === 0 || !isProcessRunning(pid);
|
|
57
90
|
}
|
|
58
|
-
catch {
|
|
59
|
-
|
|
60
|
-
else {
|
|
61
|
-
try {
|
|
62
|
-
process.kill(pid, force ? 'SIGKILL' : 'SIGTERM');
|
|
91
|
+
catch {
|
|
92
|
+
return !isProcessRunning(pid);
|
|
63
93
|
}
|
|
64
|
-
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
process.kill(pid, force ? 'SIGKILL' : 'SIGTERM');
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return !isProcessRunning(pid);
|
|
65
101
|
}
|
|
66
102
|
}
|
|
103
|
+
/** Wait until a process has really disappeared from the host process table. */
|
|
104
|
+
export async function waitForProcessExit(pid, timeoutMs = 10_000, intervalMs = 250) {
|
|
105
|
+
const deadline = Date.now() + timeoutMs;
|
|
106
|
+
while (Date.now() < deadline) {
|
|
107
|
+
if (!isProcessRunning(pid))
|
|
108
|
+
return true;
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
110
|
+
}
|
|
111
|
+
return !isProcessRunning(pid);
|
|
112
|
+
}
|
|
67
113
|
/**
|
|
68
114
|
* Cross-platform process search by command line pattern.
|
|
69
115
|
* Returns list of matching PIDs.
|
|
@@ -71,11 +117,37 @@ export function killProcess(pid, force = false) {
|
|
|
71
117
|
export function findProcesses(pattern) {
|
|
72
118
|
try {
|
|
73
119
|
if (isWindows) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
120
|
+
// WMI LIKE is wildcard matching, not JavaScript regex matching. Query a
|
|
121
|
+
// stable literal anchor first, then apply the requested regex locally.
|
|
122
|
+
const anchor = windowsProcessSearchAnchor(pattern);
|
|
123
|
+
const escapedAnchor = anchor.replace(/'/g, "''");
|
|
124
|
+
const script = `$a='${escapedAnchor}'; Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -like ('*' + $a + '*') } | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress`;
|
|
125
|
+
const result = spawnSync('powershell', ['-NoProfile', '-Command', script], { encoding: null, timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
126
|
+
const raw = decodeWindowsOutput(result.stdout).trim();
|
|
127
|
+
if (raw) {
|
|
128
|
+
const parsed = JSON.parse(raw);
|
|
129
|
+
const rows = !parsed ? [] : Array.isArray(parsed) ? parsed : [parsed];
|
|
130
|
+
const matcherCandidates = [pattern, pattern.replace(/\\\\/g, '\\')]
|
|
131
|
+
.filter((value, index, values) => values.indexOf(value) === index)
|
|
132
|
+
.map((value) => {
|
|
133
|
+
try {
|
|
134
|
+
return new RegExp(value, 'i');
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.filter((value) => value !== null);
|
|
141
|
+
return rows
|
|
142
|
+
.filter(row => Number.isInteger(row.ProcessId) && row.ProcessId !== process.pid)
|
|
143
|
+
.filter(row => {
|
|
144
|
+
const commandLine = row.CommandLine || '';
|
|
145
|
+
const normalized = commandLine.replace(/\\/g, '/');
|
|
146
|
+
return matcherCandidates.some(matcher => matcher.test(commandLine) || matcher.test(normalized));
|
|
147
|
+
})
|
|
148
|
+
.map(row => row.ProcessId);
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
79
151
|
}
|
|
80
152
|
else {
|
|
81
153
|
const output = execFileSync('pgrep', ['-f', pattern], { encoding: 'utf-8' }).trim();
|
|
@@ -86,6 +158,10 @@ export function findProcesses(pattern) {
|
|
|
86
158
|
return [];
|
|
87
159
|
}
|
|
88
160
|
}
|
|
161
|
+
function windowsProcessSearchAnchor(pattern) {
|
|
162
|
+
const tokens = pattern.match(/[A-Za-z0-9][A-Za-z0-9._-]{2,}/g) || [];
|
|
163
|
+
return tokens.sort((a, b) => b.length - a.length)[0] || pattern;
|
|
164
|
+
}
|
|
89
165
|
/**
|
|
90
166
|
* Cross-platform: find PIDs listening on a TCP port.
|
|
91
167
|
* Used to clean up stale/orphaned listeners (e.g. manually-spawned ecweb
|
|
@@ -124,13 +200,35 @@ export function findProcessByPort(port) {
|
|
|
124
200
|
export function getProcessInfo(pid) {
|
|
125
201
|
try {
|
|
126
202
|
if (isWindows) {
|
|
203
|
+
// WMIC was removed from recent Windows installations. Prefer the
|
|
204
|
+
// supported CIM API and keep WMIC as a compatibility fallback.
|
|
205
|
+
const psScript = `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}' | Select-Object WorkingSetSize,CreationDate | ConvertTo-Json -Compress)`;
|
|
206
|
+
const ps = spawnSync('powershell', ['-NoProfile', '-Command', psScript], {
|
|
207
|
+
encoding: null, timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
|
|
208
|
+
});
|
|
209
|
+
const raw = decodeWindowsOutput(ps.stdout).trim();
|
|
210
|
+
if (raw) {
|
|
211
|
+
const processInfo = JSON.parse(raw);
|
|
212
|
+
const memory = Number(processInfo.WorkingSetSize);
|
|
213
|
+
const createdAt = processInfo.CreationDate
|
|
214
|
+
? parseCimDate(processInfo.CreationDate) ?? parseDateString(processInfo.CreationDate)
|
|
215
|
+
: null;
|
|
216
|
+
return {
|
|
217
|
+
...(Number.isFinite(memory) && memory > 0 ? { memory: String(Math.round(memory / 1024)) } : {}),
|
|
218
|
+
...(createdAt !== null ? { uptime: formatUptime(Math.max(0, Math.floor((Date.now() - createdAt) / 1000))) } : {}),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
127
221
|
const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'WorkingSetSize,CreationDate'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
128
222
|
const output = result.stdout || '';
|
|
129
223
|
const lines = output.trim().split('\n').filter(l => l.trim());
|
|
130
224
|
if (lines.length >= 2) {
|
|
131
225
|
const parts = lines[1].trim().split(/\s+/);
|
|
132
226
|
const memKB = parts[1] ? Math.round(parseInt(parts[1], 10) / 1024) : undefined;
|
|
133
|
-
|
|
227
|
+
const createdAt = parts[0] ? parseCimDate(parts[0]) ?? parseDateString(parts[0]) : null;
|
|
228
|
+
return {
|
|
229
|
+
...(memKB ? { memory: `${memKB}` } : {}),
|
|
230
|
+
...(createdAt !== null ? { uptime: formatUptime(Math.max(0, Math.floor((Date.now() - createdAt) / 1000))) } : {}),
|
|
231
|
+
};
|
|
134
232
|
}
|
|
135
233
|
}
|
|
136
234
|
else {
|
|
@@ -167,6 +265,37 @@ function formatUptime(totalSeconds) {
|
|
|
167
265
|
parts.push(`${seconds}s`);
|
|
168
266
|
return parts.join(' ');
|
|
169
267
|
}
|
|
268
|
+
function parseDateString(value) {
|
|
269
|
+
const microsoftJsonDate = value.match(/^\\?\/Date\(([-+]?\d+)\)\\?\/$/);
|
|
270
|
+
if (microsoftJsonDate)
|
|
271
|
+
return Number(microsoftJsonDate[1]);
|
|
272
|
+
const parsed = Date.parse(value);
|
|
273
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
274
|
+
}
|
|
275
|
+
/** Decode PowerShell output consistently across Windows 5.1 and pwsh. */
|
|
276
|
+
function decodeWindowsOutput(value) {
|
|
277
|
+
if (!value)
|
|
278
|
+
return '';
|
|
279
|
+
const bytes = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
280
|
+
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) {
|
|
281
|
+
return bytes.subarray(2).toString('utf16le');
|
|
282
|
+
}
|
|
283
|
+
if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
284
|
+
const swapped = Buffer.allocUnsafe(bytes.length - 2);
|
|
285
|
+
for (let i = 2; i + 1 < bytes.length; i += 2) {
|
|
286
|
+
swapped[i - 2] = bytes[i + 1];
|
|
287
|
+
swapped[i - 1] = bytes[i];
|
|
288
|
+
}
|
|
289
|
+
return swapped.toString('utf16le');
|
|
290
|
+
}
|
|
291
|
+
const utf8 = bytes.toString('utf8').replace(/^\uFEFF/, '');
|
|
292
|
+
if (utf8.includes('\u0000')) {
|
|
293
|
+
const utf16 = bytes.toString('utf16le').replace(/^\uFEFF/, '');
|
|
294
|
+
if (utf16.includes('{') || utf16.includes('[') || utf16.includes('CommandLine'))
|
|
295
|
+
return utf16;
|
|
296
|
+
}
|
|
297
|
+
return utf8;
|
|
298
|
+
}
|
|
170
299
|
/**
|
|
171
300
|
* Cross-platform command existence check.
|
|
172
301
|
*/
|
|
@@ -178,7 +307,7 @@ export function commandExists(cmd) {
|
|
|
178
307
|
let exists = false;
|
|
179
308
|
try {
|
|
180
309
|
if (isWindows) {
|
|
181
|
-
const r = spawnSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
|
|
310
|
+
const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
|
|
182
311
|
exists = r.status === 0;
|
|
183
312
|
}
|
|
184
313
|
else {
|
|
@@ -194,7 +323,7 @@ export function commandExists(cmd) {
|
|
|
194
323
|
}
|
|
195
324
|
/**
|
|
196
325
|
* 解析命令的真实可执行文件绝对路径。
|
|
197
|
-
* Windows: `where` 会列出全部同名文件(npm 全局 bin 同时生成无后缀 sh 包装、.cmd、.ps1),
|
|
326
|
+
* Windows: `where.exe` 会列出全部同名文件(npm 全局 bin 同时生成无后缀 sh 包装、.cmd、.ps1),
|
|
198
327
|
* 其中无后缀的那个是 Unix sh 脚本,Windows 无法直接 spawn(ENOENT)。
|
|
199
328
|
* 因此优先选 PATHEXT 可执行后缀(.cmd/.exe/.bat/.com),都没有才退回首行。
|
|
200
329
|
* 失败返回 null。不缓存——刚安装的命令需要重新探测。
|
|
@@ -202,7 +331,7 @@ export function commandExists(cmd) {
|
|
|
202
331
|
export function resolveCommandPath(cmd) {
|
|
203
332
|
try {
|
|
204
333
|
if (isWindows) {
|
|
205
|
-
const r = spawnSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
|
|
334
|
+
const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
|
|
206
335
|
if (r.status !== 0 || !r.stdout)
|
|
207
336
|
return null;
|
|
208
337
|
const candidates = r.stdout.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
@@ -350,12 +350,20 @@ export function findOrphanProcesses() {
|
|
|
350
350
|
// 1. 已登记 PID(自己 HOME 下的 main + 自己进程)
|
|
351
351
|
const known = new Set([process.pid]);
|
|
352
352
|
const status = scanInstances();
|
|
353
|
-
for (const m of status.mains)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
for (const m of status.mains) {
|
|
354
|
+
if (m.alive)
|
|
355
|
+
known.add(m.record.pid);
|
|
356
|
+
}
|
|
357
|
+
for (const m of status.restartMonitors) {
|
|
358
|
+
if (m.alive)
|
|
359
|
+
known.add(m.record.pid);
|
|
360
|
+
}
|
|
357
361
|
// 2. 系统中所有跑 dist/index.js 的 node 进程
|
|
358
|
-
|
|
362
|
+
// Use a stable filename anchor for the Windows CIM query. The full path
|
|
363
|
+
// regex contains escaped separators and is applied after command lines have
|
|
364
|
+
// been fetched; embedding it in the CIM pre-filter can silently miss a
|
|
365
|
+
// backslash-delimited `dist\\index.js` command on some PowerShell builds.
|
|
366
|
+
const candidates = findProcesses('index\\.js');
|
|
359
367
|
const orphans = [];
|
|
360
368
|
for (const pid of candidates) {
|
|
361
369
|
if (known.has(pid))
|
|
@@ -366,8 +374,8 @@ export function findOrphanProcesses() {
|
|
|
366
374
|
// 二次验证:确实是 evolcore 的 dist/index.js
|
|
367
375
|
if (!/dist[\\/]index\.js/.test(cmdline))
|
|
368
376
|
continue;
|
|
369
|
-
//
|
|
370
|
-
if (/[\\/]ecweb[\\/]dist[\\/]index\.js/.test(cmdline))
|
|
377
|
+
// 三次验证:排除内嵌 ecweb 与独立 npm 包 ec-web。
|
|
378
|
+
if (/[\\/](?:ecweb|ec-web)[\\/]dist[\\/]index\.js/.test(cmdline))
|
|
371
379
|
continue;
|
|
372
380
|
const processEnv = readProcessEnvironment(pid);
|
|
373
381
|
orphans.push({
|
|
@@ -395,11 +403,21 @@ export function killOrphans(orphans) {
|
|
|
395
403
|
}
|
|
396
404
|
function readCmdline(pid) {
|
|
397
405
|
if (isWindows) {
|
|
406
|
+
// WMIC is removed from recent Windows installations. Keep it as the
|
|
407
|
+
// first probe for older hosts, then use the supported CIM API when it is
|
|
408
|
+
// unavailable or returns no row.
|
|
398
409
|
try {
|
|
399
410
|
const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/value'], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
400
411
|
const out = result.stdout || '';
|
|
401
412
|
const m = out.match(/CommandLine=([^\r\n]+)/);
|
|
402
|
-
|
|
413
|
+
if (m?.[1]?.trim())
|
|
414
|
+
return m[1].trim();
|
|
415
|
+
}
|
|
416
|
+
catch { }
|
|
417
|
+
try {
|
|
418
|
+
const script = `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine`;
|
|
419
|
+
const result = spawnSync('powershell', ['-NoProfile', '-Command', script], { encoding: null, timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
420
|
+
return decodeWindowsOutput(result.stdout).trim();
|
|
403
421
|
}
|
|
404
422
|
catch {
|
|
405
423
|
return '';
|
|
@@ -422,6 +440,25 @@ function readCmdline(pid) {
|
|
|
422
440
|
}
|
|
423
441
|
}
|
|
424
442
|
}
|
|
443
|
+
function decodeWindowsOutput(value) {
|
|
444
|
+
if (!value)
|
|
445
|
+
return '';
|
|
446
|
+
const bytes = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
447
|
+
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe)
|
|
448
|
+
return bytes.subarray(2).toString('utf16le');
|
|
449
|
+
if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
450
|
+
const swapped = Buffer.allocUnsafe(bytes.length - 2);
|
|
451
|
+
for (let i = 2; i + 1 < bytes.length; i += 2) {
|
|
452
|
+
swapped[i - 2] = bytes[i + 1];
|
|
453
|
+
swapped[i - 1] = bytes[i];
|
|
454
|
+
}
|
|
455
|
+
return swapped.toString('utf16le');
|
|
456
|
+
}
|
|
457
|
+
const utf8 = bytes.toString('utf8').replace(/^\uFEFF/, '');
|
|
458
|
+
if (utf8.includes('\u0000'))
|
|
459
|
+
return bytes.toString('utf16le').replace(/^\uFEFF/, '');
|
|
460
|
+
return utf8;
|
|
461
|
+
}
|
|
425
462
|
function readProcessEnvironment(pid) {
|
|
426
463
|
// Linux: /proc/<pid>/environ
|
|
427
464
|
if (!isWindows && process.platform !== 'darwin') {
|
|
@@ -115,12 +115,12 @@ function winPowerShellCreationDate(pid) {
|
|
|
115
115
|
const result = spawnSync('powershell', [
|
|
116
116
|
'-NoProfile',
|
|
117
117
|
'-Command',
|
|
118
|
-
`(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate`,
|
|
118
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate.ToUniversalTime().ToString('o')`,
|
|
119
119
|
], { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
120
120
|
const out = result.stdout?.trim();
|
|
121
121
|
if (!out)
|
|
122
122
|
return null;
|
|
123
|
-
return parseCimDate(out);
|
|
123
|
+
return parseCimDate(out) ?? parseDateString(out);
|
|
124
124
|
}
|
|
125
125
|
function winWmicCreationDate(pid) {
|
|
126
126
|
const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CreationDate', '/value'], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
@@ -128,7 +128,11 @@ function winWmicCreationDate(pid) {
|
|
|
128
128
|
const m = out.match(/CreationDate=([^\r\n]+)/);
|
|
129
129
|
if (!m)
|
|
130
130
|
return null;
|
|
131
|
-
return parseCimDate(m[1].trim());
|
|
131
|
+
return parseCimDate(m[1].trim()) ?? parseDateString(m[1].trim());
|
|
132
|
+
}
|
|
133
|
+
function parseDateString(value) {
|
|
134
|
+
const parsed = Date.parse(value);
|
|
135
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
132
136
|
}
|
|
133
137
|
/**
|
|
134
138
|
* 解析 CIM/WMI 日期格式:yyyyMMddHHmmss.ffffff±TZZZ
|
|
@@ -14,8 +14,9 @@ AUN(Agent Union Network)是 agent 间安全通信的网络协议,SDK 的 n
|
|
|
14
14
|
|
|
15
15
|
AID(Agent Identifier)是主体在 AUN 网络中的唯一身份标识,格式为 `{name}.{issuer}`(如 `alice.agentid.pub`)。任何拥有域名的组织都可以作为 Issuer 签发 AID——去中心化,无需中央权威。AID 同时也是通信地址:身份即入口。
|
|
16
16
|
|
|
17
|
-
> 本机 evolcore
|
|
18
|
-
> `
|
|
17
|
+
> 本机 evolcore 自动生成控制 AID 时的默认 AID domain 是 `agentid.cn`(`src/aun/aid/control-aid.ts` 的
|
|
18
|
+
> `resolveControlAidDomain()`;可用 `daemon.json.aun.defaultAidDomain` 或 `EVOLCORE_AID_DOMAIN` 覆盖)。
|
|
19
|
+
> 已存在的 AID 保持原域名不变;`EVOLCORE_ISSUER` 仍作为废弃兼容别名支持。
|
|
19
20
|
|
|
20
21
|
访问 `https://{aid}` 可获取该主体的个人主页。
|
|
21
22
|
|
package/kits/schemas/_meta.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemas": {
|
|
3
|
-
"daemon": { "currentVersion":
|
|
3
|
+
"daemon": { "currentVersion": 4 },
|
|
4
4
|
"defaults": { "currentVersion": 4 },
|
|
5
5
|
"agent-config": { "currentVersion": 10 },
|
|
6
6
|
"relation-config": { "currentVersion": 8 },
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
{ "schema": "relation-config", "version": 6, "date": "2026-07-27", "description": "remove relation Session Renew effort; auxiliary inference is response-mode owned" },
|
|
35
35
|
{ "schema": "single-session", "version": 2, "date": "2026-07-27", "description": "add optional baseagent-aware auxiliaryModel and auxiliaryEffort without factory defaults" },
|
|
36
36
|
{ "schema": "daemon", "version": 3, "date": "2026-07-29", "description": "make daemon.json the exclusive source for all process debug settings" },
|
|
37
|
+
{ "schema": "daemon", "version": 4, "date": "2026-08-04", "description": "add configurable default AID domain for generated identities" },
|
|
37
38
|
{ "schema": "defaults", "version": 3, "date": "2026-07-29", "description": "remove debug from the Agent configuration inheritance chain" },
|
|
38
39
|
{ "schema": "agent-config", "version": 8, "date": "2026-07-29", "description": "reject Agent-level debug because debug is process-owned" },
|
|
39
40
|
{ "schema": "agent-config", "version": 9, "date": "2026-07-29", "description": "add owner-controlled AUN group rules loading and Agent message access policy" },
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "daemon.schema.4.json",
|
|
4
|
+
"title": "ProcessConfig v4 (daemon.json)",
|
|
5
|
+
"description": "进程级配置:daemon 自身运行配置,链外独立作用域,不进覆盖链。",
|
|
6
|
+
"x-logical-name": "daemon",
|
|
7
|
+
"x-scope": "process",
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"$schema_version": { "type": "number", "const": 4, "default": 4, "x-merge": "scalar" },
|
|
12
|
+
"aid": { "type": "string", "description": "daemon 默认身份 AID", "x-merge": "scalar" },
|
|
13
|
+
"owners": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" },
|
|
16
|
+
"description": "进程控制面鉴权名单(谁能远程管 daemon)",
|
|
17
|
+
"x-merge": "list"
|
|
18
|
+
},
|
|
19
|
+
"debug": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "进程级调试配置;唯一合法来源为 daemon.json,不参与 Agent 覆盖链",
|
|
22
|
+
"x-merge": "dict",
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"properties": {
|
|
25
|
+
"logLevel": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": ["DEBUG", "INFO", "WARN", "ERROR"],
|
|
28
|
+
"description": "daemon 日志级别"
|
|
29
|
+
},
|
|
30
|
+
"flusherDiag": {
|
|
31
|
+
"type": "boolean",
|
|
32
|
+
"description": "是否启用消息 flusher 诊断"
|
|
33
|
+
},
|
|
34
|
+
"aunTrace": {
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"description": "是否记录 AUN 协议 trace"
|
|
37
|
+
},
|
|
38
|
+
"aunSdkLog": {
|
|
39
|
+
"type": "boolean",
|
|
40
|
+
"description": "是否启用 AUN SDK 调试日志"
|
|
41
|
+
},
|
|
42
|
+
"upmsg": {
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"description": "是否向 Agent owner 发送 daemon 上线通知"
|
|
45
|
+
},
|
|
46
|
+
"eckSnapshots": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"default": false,
|
|
49
|
+
"description": "是否写入可能包含敏感上下文的 ECK 旁路调试快照"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"tunnel": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"description": "内网穿透配置",
|
|
56
|
+
"x-merge": "dict",
|
|
57
|
+
"properties": {
|
|
58
|
+
"targets": { "type": "array", "description": "穿透目标列表", "items": { "type": "object" } }
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"aun": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"description": "AUN 进程配置",
|
|
64
|
+
"x-merge": "dict",
|
|
65
|
+
"properties": {
|
|
66
|
+
"encryptionSeed": { "type": ["string", "null"], "description": "已废弃兼容字段;EvolCore 固定使用内置值,检测到其他值时拒绝启动并提示联系管理人员" },
|
|
67
|
+
"gatewayUrl": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "控制 AID 使用的显式 AUN WebSocket 网关;省略时由 SDK 缓存和 AID 发现决定"
|
|
70
|
+
},
|
|
71
|
+
"defaultAidDomain": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"pattern": "^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$",
|
|
74
|
+
"description": "自动生成控制 AID 与 benchmark AID 使用的后缀域名;不影响已有 AID"
|
|
75
|
+
},
|
|
76
|
+
"minEvolVersion": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$",
|
|
79
|
+
"description": "AUN Menu 允许的最低 Evol 客户端版本;省略时不限制"
|
|
80
|
+
},
|
|
81
|
+
"menuTokenRequired": {
|
|
82
|
+
"type": "boolean",
|
|
83
|
+
"default": false,
|
|
84
|
+
"description": "是否要求普通 AUN Menu 请求携带有效 menu_token;默认 false"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"serviceProxy": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"description": "AUN Service Proxy:把一个本地 HTTP/WS 服务暴露到 AUN 网络",
|
|
91
|
+
"x-merge": "dict",
|
|
92
|
+
"properties": {
|
|
93
|
+
"enabled": { "type": "boolean", "description": "是否启用 Service Proxy" },
|
|
94
|
+
"services": { "type": "array", "description": "暴露到 AUN 的本地服务列表", "items": { "type": "object" } }
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"configSnapshots": {
|
|
98
|
+
"type": "boolean",
|
|
99
|
+
"default": true,
|
|
100
|
+
"description": "是否自动维护配置快照、启动日志和自检回落",
|
|
101
|
+
"x-merge": "scalar"
|
|
102
|
+
},
|
|
103
|
+
"idleMonitor": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"description": "流式输出空闲监控配置",
|
|
106
|
+
"x-merge": "dict",
|
|
107
|
+
"properties": {
|
|
108
|
+
"enabled": { "type": "boolean", "default": true, "description": "是否启用空闲监控" },
|
|
109
|
+
"timeout": { "type": "number", "default": 120, "description": "空闲超时秒数" },
|
|
110
|
+
"retryAttemptTimeout": { "type": "number", "description": "API 重试尝试连续无事件时的超时秒数;未设置时继承 timeout" },
|
|
111
|
+
"maxExecutionTime": { "type": "number", "default": 3600, "description": "单个任务总执行时限(秒),等待审批期间仍继续计时" }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"ecweb": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"description": "web 控制台开关/端口",
|
|
117
|
+
"x-merge": "dict",
|
|
118
|
+
"properties": {
|
|
119
|
+
"enabled": { "type": "boolean", "description": "是否启用 web 控制台" },
|
|
120
|
+
"port": { "type": "number", "description": "web 控制台监听端口" }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"watch": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"description": "日志监听配置",
|
|
126
|
+
"x-merge": "dict",
|
|
127
|
+
"properties": {
|
|
128
|
+
"logTypes": { "type": "array", "description": "要监听的日志类型", "items": { "type": "string" } }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolcore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "AI Agent gateway connecting Claude, Codex, Gemini, and the bundled ecagent runner to messaging channels with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|