evolcore 0.0.20 → 0.0.21
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 +22 -0
- package/README.md +58 -9
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +379 -108
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +402 -135
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +182 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +409 -88
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +69 -115
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +135 -17
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +150 -2
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-service.js +6 -2
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +486 -68
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +410 -54
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-policy.js +575 -21
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +253 -46
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/schemas/_meta.json +4 -2
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
package/dist/ipc.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { Worker } from 'node:worker_threads';
|
|
3
4
|
import fs from 'fs';
|
|
4
5
|
import crypto from 'crypto';
|
|
5
6
|
import os from 'os';
|
|
@@ -9,14 +10,20 @@ import { withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived
|
|
|
9
10
|
import { fileCache } from './core/daemon-file-cache.js';
|
|
10
11
|
import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
|
|
11
12
|
import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
|
|
12
|
-
import { resolvePaths } from './paths.js';
|
|
13
|
+
import { getWindowsInstanceSocketPathCandidates, resolvePaths } from './paths.js';
|
|
13
14
|
import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
|
|
14
|
-
import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
|
|
15
|
+
import { classifyProcessTree, ProcessTreeSampler, readProcessMetrics } from './utils/process-tree-stats.js';
|
|
15
16
|
import { readSystemMemoryUsage } from './utils/system-memory.js';
|
|
16
17
|
import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
|
|
17
18
|
import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
18
19
|
const isWindows = process.platform === 'win32';
|
|
19
20
|
const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
|
|
21
|
+
const CPU_SAMPLE_INTERVAL_MS = 1000;
|
|
22
|
+
const PROCESS_TREE_SAMPLE_INTERVAL_MS = 5000;
|
|
23
|
+
const PROCESS_TREE_SAMPLE_WARN_MS = 250;
|
|
24
|
+
const EVENT_LOOP_LAG_WARN_MS = 100;
|
|
25
|
+
const SAMPLE_WARN_LOG_INTERVAL_MS = 60_000;
|
|
26
|
+
const PROCESS_TREE_WORKER_REQUEST_TIMEOUT_MS = 15_000;
|
|
20
27
|
const finiteTimestamp = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
|
|
21
28
|
const LOCAL_CONTROL_COMMANDS = new Set([
|
|
22
29
|
'bind.begin',
|
|
@@ -38,6 +45,22 @@ const LOCAL_CONTROL_COMMANDS = new Set([
|
|
|
38
45
|
'monitor-snapshot',
|
|
39
46
|
'shutdown',
|
|
40
47
|
]);
|
|
48
|
+
let ipcQuerySequence = 0;
|
|
49
|
+
function ipcLogToken(value, fallback = 'unknown') {
|
|
50
|
+
const token = String(value ?? '').replace(/[^A-Za-z0-9_.:-]/g, '_').slice(0, 128);
|
|
51
|
+
return token || fallback;
|
|
52
|
+
}
|
|
53
|
+
function nextIpcQueryId(cmd) {
|
|
54
|
+
const payload = cmd.payload;
|
|
55
|
+
const nestedId = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
56
|
+
? payload.requestId ?? payload.id
|
|
57
|
+
: undefined;
|
|
58
|
+
const supplied = cmd.requestId ?? cmd.id ?? nestedId;
|
|
59
|
+
if (typeof supplied === 'string' || typeof supplied === 'number')
|
|
60
|
+
return ipcLogToken(supplied, 'unknown');
|
|
61
|
+
ipcQuerySequence = (ipcQuerySequence + 1) % Number.MAX_SAFE_INTEGER;
|
|
62
|
+
return `ipc-${ipcQuerySequence}`;
|
|
63
|
+
}
|
|
41
64
|
function requiresLocalControlToken(cmd) {
|
|
42
65
|
// Managed bootstrap completion is authenticated by the session delegation
|
|
43
66
|
// ticket. A standalone CLI invocation still needs the daemon control token.
|
|
@@ -62,6 +85,7 @@ export class IpcServer {
|
|
|
62
85
|
commandExecutor;
|
|
63
86
|
server = null;
|
|
64
87
|
agentRegistry;
|
|
88
|
+
agentApplicationService;
|
|
65
89
|
aunAidProvider;
|
|
66
90
|
aunAidStatsProvider;
|
|
67
91
|
aunAidStatsRecorder;
|
|
@@ -108,9 +132,28 @@ export class IpcServer {
|
|
|
108
132
|
sysCpuPercent = 0; // 系统级
|
|
109
133
|
lastCpuTimes = null;
|
|
110
134
|
cpuTimer = null;
|
|
135
|
+
processTreeTimer = null;
|
|
136
|
+
processTreeWorker = null;
|
|
137
|
+
processTreeWorkerRequestId = 0;
|
|
138
|
+
processTreeWorkerRequestStartedAt = 0;
|
|
139
|
+
processTreeWorkerRequestTimer = null;
|
|
140
|
+
processTreeSamplingInFlight = false;
|
|
141
|
+
lastCpuTickMono = null;
|
|
142
|
+
eventLoopLagMs = 0;
|
|
143
|
+
maxEventLoopLagMs = 0;
|
|
144
|
+
lastSampleWarningAt = 0;
|
|
111
145
|
processTreeSampler = new ProcessTreeSampler(process.pid);
|
|
112
146
|
processTreeSnapshot = null;
|
|
113
147
|
associatedProcessKinds = new Map();
|
|
148
|
+
processTreeSampling = {
|
|
149
|
+
lastDurationMs: 0,
|
|
150
|
+
maxDurationMs: 0,
|
|
151
|
+
lastSampleAt: 0,
|
|
152
|
+
associatedRootCount: 0,
|
|
153
|
+
processCount: 0,
|
|
154
|
+
inFlight: false,
|
|
155
|
+
lastError: null,
|
|
156
|
+
};
|
|
114
157
|
constructor(socketPath, getStatus, commandExecutor) {
|
|
115
158
|
this.socketPath = socketPath;
|
|
116
159
|
this.getStatus = getStatus;
|
|
@@ -120,6 +163,10 @@ export class IpcServer {
|
|
|
120
163
|
setAgentRegistry(registry) {
|
|
121
164
|
this.agentRegistry = registry;
|
|
122
165
|
}
|
|
166
|
+
/** Inject the Core-owned agent application facade for in-process handlers. */
|
|
167
|
+
setAgentApplicationService(service) {
|
|
168
|
+
this.agentApplicationService = service;
|
|
169
|
+
}
|
|
123
170
|
/** Authenticate local control-plane callers that cannot use task delegation. */
|
|
124
171
|
setControlToken(token) {
|
|
125
172
|
this.controlToken = Buffer.from(token);
|
|
@@ -249,12 +296,23 @@ export class IpcServer {
|
|
|
249
296
|
setWechatContactBindExecutor(executor) {
|
|
250
297
|
this.wechatContactBindExecutor = executor;
|
|
251
298
|
}
|
|
252
|
-
/** Start the
|
|
299
|
+
/** Start the background CPU and process-tree sampling loops. Call after start(). */
|
|
253
300
|
startCpuTracking() {
|
|
254
301
|
if (this.cpuTimer)
|
|
255
302
|
return;
|
|
256
303
|
this.sampleProcessTree();
|
|
304
|
+
this.lastCpuTickMono = performance.now();
|
|
257
305
|
this.cpuTimer = setInterval(() => {
|
|
306
|
+
const tickMono = performance.now();
|
|
307
|
+
if (this.lastCpuTickMono !== null) {
|
|
308
|
+
const lagMs = Math.max(0, tickMono - this.lastCpuTickMono - CPU_SAMPLE_INTERVAL_MS);
|
|
309
|
+
this.eventLoopLagMs = Math.round(lagMs * 10) / 10;
|
|
310
|
+
this.maxEventLoopLagMs = Math.max(this.maxEventLoopLagMs, this.eventLoopLagMs);
|
|
311
|
+
if (this.eventLoopLagMs >= EVENT_LOOP_LAG_WARN_MS) {
|
|
312
|
+
logger.debug(`[IPC] cpu_sample event_loop_lag_ms=${this.eventLoopLagMs}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
this.lastCpuTickMono = tickMono;
|
|
258
316
|
const now = Date.now();
|
|
259
317
|
const elapsedUs = (now - this.lastCpuTs) * 1000; // wall time in microseconds
|
|
260
318
|
if (elapsedUs > 0) {
|
|
@@ -281,30 +339,213 @@ export class IpcServer {
|
|
|
281
339
|
this.lastCpuTimes = { idle, total };
|
|
282
340
|
}
|
|
283
341
|
catch { /* os.cpus() 理论上不抛 */ }
|
|
284
|
-
|
|
285
|
-
}, 1000);
|
|
342
|
+
}, CPU_SAMPLE_INTERVAL_MS);
|
|
286
343
|
// Don't keep the event loop alive for sampling alone.
|
|
287
344
|
this.cpuTimer.unref?.();
|
|
345
|
+
// Process enumeration is substantially more expensive on Windows than
|
|
346
|
+
// process.cpuUsage()/os.cpus(), so keep it at a lower refresh rate.
|
|
347
|
+
this.processTreeTimer = setInterval(() => this.sampleProcessTree(), PROCESS_TREE_SAMPLE_INTERVAL_MS);
|
|
348
|
+
this.processTreeTimer.unref?.();
|
|
288
349
|
}
|
|
289
|
-
/** Stop the CPU sampling
|
|
350
|
+
/** Stop the CPU and process-tree sampling loops. */
|
|
290
351
|
stopCpuTracking() {
|
|
291
352
|
if (this.cpuTimer) {
|
|
292
353
|
clearInterval(this.cpuTimer);
|
|
293
354
|
this.cpuTimer = null;
|
|
294
355
|
}
|
|
356
|
+
if (this.processTreeTimer) {
|
|
357
|
+
clearInterval(this.processTreeTimer);
|
|
358
|
+
this.processTreeTimer = null;
|
|
359
|
+
}
|
|
360
|
+
if (this.processTreeWorkerRequestTimer) {
|
|
361
|
+
clearTimeout(this.processTreeWorkerRequestTimer);
|
|
362
|
+
this.processTreeWorkerRequestTimer = null;
|
|
363
|
+
}
|
|
364
|
+
this.processTreeSamplingInFlight = false;
|
|
365
|
+
this.processTreeSampling.inFlight = false;
|
|
366
|
+
// Invalidate callbacks from a worker that is being stopped. Its error and
|
|
367
|
+
// exit events are asynchronous and must not turn an intentional shutdown
|
|
368
|
+
// into a failed sample.
|
|
369
|
+
this.processTreeWorkerRequestId++;
|
|
370
|
+
this.processTreeWorkerRequestStartedAt = 0;
|
|
371
|
+
const worker = this.processTreeWorker;
|
|
372
|
+
this.processTreeWorker = null;
|
|
373
|
+
if (worker)
|
|
374
|
+
void worker.terminate();
|
|
375
|
+
this.lastCpuTickMono = null;
|
|
295
376
|
}
|
|
296
377
|
sampleProcessTree() {
|
|
378
|
+
if (isWindows) {
|
|
379
|
+
this.sampleProcessTreeInWorker();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const startedAt = performance.now();
|
|
383
|
+
try {
|
|
384
|
+
this.commitProcessTreeSample(readProcessMetrics(), performance.now() - startedAt);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
this.recordProcessTreeSampleError(error, performance.now() - startedAt);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
sampleProcessTreeInWorker() {
|
|
391
|
+
if (this.processTreeSamplingInFlight)
|
|
392
|
+
return;
|
|
393
|
+
const worker = this.getProcessTreeWorker();
|
|
394
|
+
if (!worker)
|
|
395
|
+
return;
|
|
396
|
+
this.processTreeSamplingInFlight = true;
|
|
397
|
+
this.processTreeSampling.inFlight = true;
|
|
398
|
+
this.processTreeWorkerRequestStartedAt = performance.now();
|
|
399
|
+
const requestId = ++this.processTreeWorkerRequestId;
|
|
400
|
+
const startedAt = this.processTreeWorkerRequestStartedAt;
|
|
401
|
+
this.processTreeWorkerRequestTimer = setTimeout(() => {
|
|
402
|
+
if (this.processTreeWorker !== worker
|
|
403
|
+
|| !this.processTreeSamplingInFlight
|
|
404
|
+
|| this.processTreeWorkerRequestId !== requestId)
|
|
405
|
+
return;
|
|
406
|
+
this.processTreeSamplingInFlight = false;
|
|
407
|
+
this.processTreeSampling.inFlight = false;
|
|
408
|
+
this.processTreeWorkerRequestTimer = null;
|
|
409
|
+
this.processTreeWorkerRequestId++;
|
|
410
|
+
this.processTreeWorkerRequestStartedAt = 0;
|
|
411
|
+
this.processTreeWorker = null;
|
|
412
|
+
void worker.terminate();
|
|
413
|
+
this.recordProcessTreeSampleError(`process-tree worker request timed out after ${PROCESS_TREE_WORKER_REQUEST_TIMEOUT_MS}ms`, performance.now() - startedAt);
|
|
414
|
+
}, PROCESS_TREE_WORKER_REQUEST_TIMEOUT_MS);
|
|
415
|
+
this.processTreeWorkerRequestTimer.unref?.();
|
|
416
|
+
try {
|
|
417
|
+
worker.postMessage({ type: 'sample', requestId });
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (this.processTreeWorker !== worker || this.processTreeWorkerRequestId !== requestId)
|
|
421
|
+
return;
|
|
422
|
+
if (this.processTreeWorkerRequestTimer) {
|
|
423
|
+
clearTimeout(this.processTreeWorkerRequestTimer);
|
|
424
|
+
this.processTreeWorkerRequestTimer = null;
|
|
425
|
+
}
|
|
426
|
+
this.processTreeSamplingInFlight = false;
|
|
427
|
+
this.processTreeSampling.inFlight = false;
|
|
428
|
+
this.processTreeWorkerRequestId++;
|
|
429
|
+
this.processTreeWorkerRequestStartedAt = 0;
|
|
430
|
+
this.processTreeWorker = null;
|
|
431
|
+
void worker.terminate();
|
|
432
|
+
this.recordProcessTreeSampleError(error, performance.now() - startedAt);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
getProcessTreeWorker() {
|
|
436
|
+
if (this.processTreeWorker)
|
|
437
|
+
return this.processTreeWorker;
|
|
438
|
+
try {
|
|
439
|
+
const worker = new Worker(new URL('./utils/process-tree-worker.js', import.meta.url));
|
|
440
|
+
worker.unref();
|
|
441
|
+
worker.on('message', (message) => {
|
|
442
|
+
if (this.processTreeWorker !== worker
|
|
443
|
+
|| !this.processTreeSamplingInFlight
|
|
444
|
+
|| message.requestId !== this.processTreeWorkerRequestId)
|
|
445
|
+
return;
|
|
446
|
+
if (this.processTreeWorkerRequestTimer) {
|
|
447
|
+
clearTimeout(this.processTreeWorkerRequestTimer);
|
|
448
|
+
this.processTreeWorkerRequestTimer = null;
|
|
449
|
+
}
|
|
450
|
+
this.processTreeSamplingInFlight = false;
|
|
451
|
+
this.processTreeSampling.inFlight = false;
|
|
452
|
+
const durationMs = typeof message.durationMs === 'number'
|
|
453
|
+
? message.durationMs
|
|
454
|
+
: performance.now() - this.processTreeWorkerRequestStartedAt;
|
|
455
|
+
if (message.type === 'sample' && Array.isArray(message.metrics)) {
|
|
456
|
+
this.commitProcessTreeSample(message.metrics, durationMs);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
this.recordProcessTreeSampleError(message.error ?? 'process-tree worker failed', durationMs);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
worker.on('error', error => {
|
|
463
|
+
if (this.processTreeWorker !== worker)
|
|
464
|
+
return;
|
|
465
|
+
const wasInFlight = this.processTreeSamplingInFlight;
|
|
466
|
+
if (this.processTreeWorkerRequestTimer) {
|
|
467
|
+
clearTimeout(this.processTreeWorkerRequestTimer);
|
|
468
|
+
this.processTreeWorkerRequestTimer = null;
|
|
469
|
+
}
|
|
470
|
+
this.processTreeWorker = null;
|
|
471
|
+
if (!wasInFlight)
|
|
472
|
+
return;
|
|
473
|
+
this.processTreeSamplingInFlight = false;
|
|
474
|
+
this.processTreeSampling.inFlight = false;
|
|
475
|
+
this.processTreeWorkerRequestId++;
|
|
476
|
+
this.recordProcessTreeSampleError(error, performance.now() - this.processTreeWorkerRequestStartedAt);
|
|
477
|
+
});
|
|
478
|
+
worker.on('exit', code => {
|
|
479
|
+
if (this.processTreeWorker !== worker)
|
|
480
|
+
return;
|
|
481
|
+
const wasInFlight = this.processTreeSamplingInFlight;
|
|
482
|
+
if (this.processTreeWorkerRequestTimer) {
|
|
483
|
+
clearTimeout(this.processTreeWorkerRequestTimer);
|
|
484
|
+
this.processTreeWorkerRequestTimer = null;
|
|
485
|
+
}
|
|
486
|
+
this.processTreeWorker = null;
|
|
487
|
+
if (code !== 0 && wasInFlight) {
|
|
488
|
+
this.processTreeSamplingInFlight = false;
|
|
489
|
+
this.processTreeSampling.inFlight = false;
|
|
490
|
+
this.processTreeWorkerRequestId++;
|
|
491
|
+
this.recordProcessTreeSampleError(`process-tree worker exited with code ${code}`, performance.now() - this.processTreeWorkerRequestStartedAt);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
this.processTreeWorker = worker;
|
|
495
|
+
return worker;
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
this.recordProcessTreeSampleError(error, 0);
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
commitProcessTreeSample(metrics, durationMs) {
|
|
503
|
+
let roots = [];
|
|
504
|
+
let processCount = 0;
|
|
297
505
|
try {
|
|
298
|
-
|
|
506
|
+
let processStartTimes;
|
|
507
|
+
if (isWindows) {
|
|
508
|
+
const starts = new Map();
|
|
509
|
+
for (const metric of metrics) {
|
|
510
|
+
starts.set(metric.pid, typeof metric.startId === 'number' && Number.isFinite(metric.startId) ? metric.startId : null);
|
|
511
|
+
}
|
|
512
|
+
processStartTimes = starts;
|
|
513
|
+
}
|
|
514
|
+
roots = this.readAssociatedProcessRoots(processStartTimes);
|
|
299
515
|
this.associatedProcessKinds = new Map(roots.map(root => [root.pid, root.kind]));
|
|
300
|
-
this.processTreeSnapshot = this.processTreeSampler.sample(roots.map(root => root.pid));
|
|
516
|
+
this.processTreeSnapshot = this.processTreeSampler.sample(roots.map(root => root.pid), metrics);
|
|
517
|
+
processCount = this.processTreeSnapshot.processCount;
|
|
518
|
+
this.processTreeSampling.lastError = null;
|
|
301
519
|
}
|
|
302
|
-
catch {
|
|
303
|
-
this.
|
|
304
|
-
|
|
520
|
+
catch (error) {
|
|
521
|
+
this.recordProcessTreeSampleError(error, durationMs);
|
|
522
|
+
return;
|
|
305
523
|
}
|
|
524
|
+
const roundedDurationMs = Math.round(durationMs * 10) / 10;
|
|
525
|
+
this.processTreeSampling.lastDurationMs = roundedDurationMs;
|
|
526
|
+
this.processTreeSampling.maxDurationMs = Math.max(this.processTreeSampling.maxDurationMs, durationMs);
|
|
527
|
+
this.processTreeSampling.lastSampleAt = Date.now();
|
|
528
|
+
this.processTreeSampling.associatedRootCount = roots.length;
|
|
529
|
+
this.processTreeSampling.processCount = processCount;
|
|
530
|
+
const sampleLog = `[IPC] process_tree_sample duration_ms=${roundedDurationMs}`
|
|
531
|
+
+ ` event_loop_lag_ms=${this.eventLoopLagMs}`
|
|
532
|
+
+ ` associated_roots=${roots.length}`
|
|
533
|
+
+ ` process_count=${processCount}`;
|
|
534
|
+
logger.debug(sampleLog);
|
|
535
|
+
const shouldWarn = roundedDurationMs >= PROCESS_TREE_SAMPLE_WARN_MS || this.eventLoopLagMs >= EVENT_LOOP_LAG_WARN_MS;
|
|
536
|
+
if (shouldWarn && this.processTreeSampling.lastSampleAt - this.lastSampleWarningAt >= SAMPLE_WARN_LOG_INTERVAL_MS) {
|
|
537
|
+
this.lastSampleWarningAt = this.processTreeSampling.lastSampleAt;
|
|
538
|
+
logger.warn(sampleLog);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
recordProcessTreeSampleError(error, durationMs) {
|
|
542
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
543
|
+
this.processTreeSampling.lastDurationMs = Math.round(durationMs * 10) / 10;
|
|
544
|
+
this.processTreeSampling.maxDurationMs = Math.max(this.processTreeSampling.maxDurationMs, durationMs);
|
|
545
|
+
this.processTreeSampling.lastError = message;
|
|
546
|
+
logger.warn(`[IPC] process_tree_sample failed duration_ms=${this.processTreeSampling.lastDurationMs} error=${message}`);
|
|
306
547
|
}
|
|
307
|
-
readAssociatedProcessRoots() {
|
|
548
|
+
readAssociatedProcessRoots(processStartTimes) {
|
|
308
549
|
const roots = [];
|
|
309
550
|
let files;
|
|
310
551
|
try {
|
|
@@ -323,7 +564,9 @@ export class IpcServer {
|
|
|
323
564
|
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid)
|
|
324
565
|
continue;
|
|
325
566
|
if (record.startedAt) {
|
|
326
|
-
const actual =
|
|
567
|
+
const actual = processStartTimes
|
|
568
|
+
? processStartTimes.get(pid) ?? null
|
|
569
|
+
: getProcessStartTime(pid);
|
|
327
570
|
if (!isSameOrOlderProcess(Number(record.startedAt), actual))
|
|
328
571
|
continue;
|
|
329
572
|
}
|
|
@@ -910,16 +1153,25 @@ export class IpcServer {
|
|
|
910
1153
|
}
|
|
911
1154
|
}
|
|
912
1155
|
case 'evolagent.list': {
|
|
1156
|
+
if (this.agentApplicationService) {
|
|
1157
|
+
return { ok: true, agents: this.agentApplicationService.list() };
|
|
1158
|
+
}
|
|
913
1159
|
if (!this.agentRegistry)
|
|
914
1160
|
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
915
1161
|
return { ok: true, agents: this.agentRegistry.list() };
|
|
916
1162
|
}
|
|
917
1163
|
case 'evolagent.show': {
|
|
918
|
-
if (!this.agentRegistry)
|
|
919
|
-
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
920
1164
|
const name = cmd.name;
|
|
921
1165
|
if (!name || typeof name !== 'string')
|
|
922
1166
|
return { ok: false, error: 'missing name' };
|
|
1167
|
+
if (this.agentApplicationService) {
|
|
1168
|
+
const info = this.agentApplicationService.info(name);
|
|
1169
|
+
if (!info)
|
|
1170
|
+
return { ok: false, error: `Agent "${name}" not found` };
|
|
1171
|
+
return { ok: true, agent: info };
|
|
1172
|
+
}
|
|
1173
|
+
if (!this.agentRegistry)
|
|
1174
|
+
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
923
1175
|
const agent = this.agentRegistry.get(name);
|
|
924
1176
|
if (!agent)
|
|
925
1177
|
return { ok: false, error: `Agent "${name}" not found` };
|
|
@@ -948,11 +1200,23 @@ export class IpcServer {
|
|
|
948
1200
|
}
|
|
949
1201
|
}
|
|
950
1202
|
case 'evolagent.reload': {
|
|
951
|
-
if (!this.agentRegistry)
|
|
952
|
-
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
953
1203
|
const name = cmd.name;
|
|
954
1204
|
if (!name || typeof name !== 'string')
|
|
955
1205
|
return { ok: false, error: 'missing name' };
|
|
1206
|
+
if (this.agentApplicationService) {
|
|
1207
|
+
const result = await this.agentApplicationService.reload(name, { force: cmd.force === true });
|
|
1208
|
+
if (!result.ok) {
|
|
1209
|
+
return {
|
|
1210
|
+
ok: false,
|
|
1211
|
+
...(result.code ? { code: result.code } : {}),
|
|
1212
|
+
...(result.busyCount === undefined ? {} : { busyCount: result.busyCount }),
|
|
1213
|
+
error: result.error,
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
return { ok: true, result: `Agent "${name}" reloaded` };
|
|
1217
|
+
}
|
|
1218
|
+
if (!this.agentRegistry)
|
|
1219
|
+
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
956
1220
|
if (!this.agentReloadExecutor)
|
|
957
1221
|
return { ok: false, error: 'Agent reload coordinator not initialized' };
|
|
958
1222
|
try {
|
|
@@ -970,11 +1234,17 @@ export class IpcServer {
|
|
|
970
1234
|
}
|
|
971
1235
|
}
|
|
972
1236
|
case 'evolagent.load': {
|
|
973
|
-
if (!this.agentRegistry)
|
|
974
|
-
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
975
1237
|
const aid = cmd.aid;
|
|
976
1238
|
if (!aid || typeof aid !== 'string')
|
|
977
1239
|
return { ok: false, error: 'missing aid' };
|
|
1240
|
+
if (this.agentApplicationService) {
|
|
1241
|
+
const result = await this.agentApplicationService.load(aid);
|
|
1242
|
+
return result.ok
|
|
1243
|
+
? { ok: true, result: `Agent "${aid}" loaded and online` }
|
|
1244
|
+
: { ok: false, ...(result.code ? { code: result.code } : {}), error: result.error };
|
|
1245
|
+
}
|
|
1246
|
+
if (!this.agentRegistry)
|
|
1247
|
+
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
978
1248
|
const hotLoad = globalThis.__evolcore_hotLoadAgent;
|
|
979
1249
|
if (!hotLoad)
|
|
980
1250
|
return { ok: false, error: 'Hot-load handler not initialized' };
|
|
@@ -987,6 +1257,12 @@ export class IpcServer {
|
|
|
987
1257
|
}
|
|
988
1258
|
}
|
|
989
1259
|
case 'evolagent.resync': {
|
|
1260
|
+
if (this.agentApplicationService) {
|
|
1261
|
+
const result = await this.agentApplicationService.resync();
|
|
1262
|
+
return result.ok
|
|
1263
|
+
? { ok: true, results: result.results }
|
|
1264
|
+
: { ok: false, ...(result.code ? { code: result.code } : {}), error: result.error };
|
|
1265
|
+
}
|
|
990
1266
|
if (!this.agentRegistry)
|
|
991
1267
|
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
992
1268
|
const resync = globalThis.__evolcore_resyncAgents;
|
|
@@ -1016,10 +1292,27 @@ export class IpcServer {
|
|
|
1016
1292
|
return { ok: false, error: 'menu.exec not configured' };
|
|
1017
1293
|
}
|
|
1018
1294
|
try {
|
|
1295
|
+
const suppliedAuth = cmd.auth && typeof cmd.auth === 'object' && !Array.isArray(cmd.auth)
|
|
1296
|
+
? cmd.auth
|
|
1297
|
+
: undefined;
|
|
1298
|
+
const actorAid = typeof suppliedAuth?.actorAid === 'string' && suppliedAuth.actorAid.trim()
|
|
1299
|
+
? suppliedAuth.actorAid.trim()
|
|
1300
|
+
: null;
|
|
1019
1301
|
const response = await this.menuExecutor(cmd.payload, {
|
|
1020
1302
|
source: 'ecweb',
|
|
1021
|
-
|
|
1022
|
-
|
|
1303
|
+
// The ECWeb process resolved this actor from its bearer token. The
|
|
1304
|
+
// IPC control-token gate authenticates the local ECWeb process; the
|
|
1305
|
+
// menu handler still checks that actorAid remains a configured owner.
|
|
1306
|
+
actorAid,
|
|
1307
|
+
localDirect: suppliedAuth?.localDirect === true,
|
|
1308
|
+
// Reaching this handler means the control token gate succeeded.
|
|
1309
|
+
// Never trust an authenticated flag supplied by the caller.
|
|
1310
|
+
authenticated: true,
|
|
1311
|
+
// Reaching this handler means the ECWeb control token gate
|
|
1312
|
+
// succeeded. ECWeb management is daemon-owner-authorized; the
|
|
1313
|
+
// menu layer independently revalidates remote actorAid against
|
|
1314
|
+
// the current daemon owners before applying that authority.
|
|
1315
|
+
canApprovePersistentFullAccess: true,
|
|
1023
1316
|
});
|
|
1024
1317
|
const timedResponse = withMenuProcessingTime(response, { receivedAtMono });
|
|
1025
1318
|
logMenuRequestCompleted(request, timedResponse, flowContext, { delivery: 'returned' });
|
|
@@ -1087,6 +1380,13 @@ export class IpcServer {
|
|
|
1087
1380
|
memory: mem,
|
|
1088
1381
|
},
|
|
1089
1382
|
processTree: processTreeDetails,
|
|
1383
|
+
sampling: {
|
|
1384
|
+
cpuIntervalMs: CPU_SAMPLE_INTERVAL_MS,
|
|
1385
|
+
processTreeIntervalMs: PROCESS_TREE_SAMPLE_INTERVAL_MS,
|
|
1386
|
+
eventLoopLagMs: this.eventLoopLagMs,
|
|
1387
|
+
maxEventLoopLagMs: this.maxEventLoopLagMs,
|
|
1388
|
+
processTree: { ...this.processTreeSampling },
|
|
1389
|
+
},
|
|
1090
1390
|
// 系统级:整机
|
|
1091
1391
|
system: {
|
|
1092
1392
|
...systemMemory,
|
|
@@ -1125,6 +1425,30 @@ export class IpcServer {
|
|
|
1125
1425
|
*/
|
|
1126
1426
|
/** Pass `null` as timeoutMs to wait without a client-side deadline. */
|
|
1127
1427
|
export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
1428
|
+
let socketCandidates = [socketPath];
|
|
1429
|
+
if (isNamedPipe(socketPath)) {
|
|
1430
|
+
const paths = resolvePaths();
|
|
1431
|
+
if (socketPath === paths.socket)
|
|
1432
|
+
socketCandidates = getWindowsInstanceSocketPathCandidates(paths.root);
|
|
1433
|
+
}
|
|
1434
|
+
if (socketCandidates.length <= 1)
|
|
1435
|
+
return ipcQueryOnce(socketPath, cmd, timeoutMs);
|
|
1436
|
+
return (async () => {
|
|
1437
|
+
const startedAt = Date.now();
|
|
1438
|
+
for (const candidate of socketCandidates) {
|
|
1439
|
+
const remainingTimeout = timeoutMs === null
|
|
1440
|
+
? null
|
|
1441
|
+
: Math.max(0, timeoutMs - (Date.now() - startedAt));
|
|
1442
|
+
if (remainingTimeout === 0)
|
|
1443
|
+
return null;
|
|
1444
|
+
const response = await ipcQueryOnce(candidate, cmd, remainingTimeout);
|
|
1445
|
+
if (response !== null)
|
|
1446
|
+
return response;
|
|
1447
|
+
}
|
|
1448
|
+
return null;
|
|
1449
|
+
})();
|
|
1450
|
+
}
|
|
1451
|
+
function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
1128
1452
|
let authenticatedCmd = cmd;
|
|
1129
1453
|
if (!process.env.EVOLCORE_SESSION_ID
|
|
1130
1454
|
&& requiresLocalControlToken(cmd)
|
|
@@ -1142,35 +1466,61 @@ export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
|
1142
1466
|
: undefined;
|
|
1143
1467
|
const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
|
|
1144
1468
|
return new Promise((resolve) => {
|
|
1469
|
+
const startedAtMono = performance.now();
|
|
1470
|
+
const queryId = nextIpcQueryId(request);
|
|
1471
|
+
const commandType = ipcLogToken(request.type);
|
|
1472
|
+
const elapsedMs = () => Math.round(performance.now() - startedAtMono);
|
|
1473
|
+
const logPhase = (phase, details = '') => {
|
|
1474
|
+
logger.debug(`[IPC] query=${queryId} phase=${phase} type=${commandType} elapsed_ms=${elapsedMs()}${details}`);
|
|
1475
|
+
};
|
|
1145
1476
|
const conn = net.connect(socketPath);
|
|
1146
1477
|
let buf = '';
|
|
1478
|
+
let settled = false;
|
|
1147
1479
|
const timer = timeoutMs === null
|
|
1148
1480
|
? undefined
|
|
1149
1481
|
: setTimeout(() => {
|
|
1482
|
+
if (settled)
|
|
1483
|
+
return;
|
|
1484
|
+
settled = true;
|
|
1485
|
+
logPhase('timeout', ` timeout_ms=${timeoutMs}`);
|
|
1150
1486
|
conn.destroy();
|
|
1151
1487
|
resolve(null);
|
|
1152
1488
|
}, timeoutMs);
|
|
1153
1489
|
conn.on('connect', () => {
|
|
1490
|
+
if (settled)
|
|
1491
|
+
return;
|
|
1492
|
+
logPhase('connect');
|
|
1154
1493
|
conn.write(JSON.stringify(request) + '\n');
|
|
1155
1494
|
});
|
|
1156
1495
|
conn.on('data', (data) => {
|
|
1496
|
+
if (settled)
|
|
1497
|
+
return;
|
|
1157
1498
|
buf += data.toString();
|
|
1158
1499
|
const idx = buf.indexOf('\n');
|
|
1159
1500
|
if (idx !== -1) {
|
|
1501
|
+
settled = true;
|
|
1160
1502
|
if (timer)
|
|
1161
1503
|
clearTimeout(timer);
|
|
1504
|
+
let response = null;
|
|
1505
|
+
let parse = 'ok';
|
|
1162
1506
|
try {
|
|
1163
|
-
|
|
1507
|
+
response = JSON.parse(buf.slice(0, idx));
|
|
1164
1508
|
}
|
|
1165
1509
|
catch {
|
|
1166
|
-
|
|
1510
|
+
parse = 'invalid_json';
|
|
1167
1511
|
}
|
|
1512
|
+
logPhase('response', ` bytes=${idx + 1} parse=${parse}`);
|
|
1513
|
+
resolve(response);
|
|
1168
1514
|
conn.destroy();
|
|
1169
1515
|
}
|
|
1170
1516
|
});
|
|
1171
|
-
conn.on('error', () => {
|
|
1517
|
+
conn.on('error', (error) => {
|
|
1518
|
+
if (settled)
|
|
1519
|
+
return;
|
|
1520
|
+
settled = true;
|
|
1172
1521
|
if (timer)
|
|
1173
1522
|
clearTimeout(timer);
|
|
1523
|
+
logPhase('error', ` code=${error.code ?? 'unknown'}`);
|
|
1174
1524
|
resolve(null);
|
|
1175
1525
|
});
|
|
1176
1526
|
});
|