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.
Files changed (123) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +58 -9
  3. package/dist/agents/baseagent.js +10 -6
  4. package/dist/agents/claude-runner.js +379 -108
  5. package/dist/agents/codex-app-server-client.js +10 -2
  6. package/dist/agents/codex-runner.js +402 -135
  7. package/dist/agents/ecagent-runner.js +171 -61
  8. package/dist/agents/gemini-runner.js +130 -30
  9. package/dist/agents/request-identity.js +25 -0
  10. package/dist/agents/runner-types.js +19 -0
  11. package/dist/aun/aid/agentmd.js +59 -2
  12. package/dist/aun/aid/identity.js +4 -1
  13. package/dist/aun/aid/index.js +1 -1
  14. package/dist/aun/msg/group.js +72 -6
  15. package/dist/aun/msg/history.js +213 -36
  16. package/dist/aun/msg/managed-operation.js +58 -9
  17. package/dist/aun/msg/p2p.js +5 -0
  18. package/dist/aun/outbox.js +182 -80
  19. package/dist/aun/service-proxy.js +43 -25
  20. package/dist/channels/aun.js +409 -88
  21. package/dist/channels/daemon.js +6 -1
  22. package/dist/cli/agent-command.js +4 -3
  23. package/dist/cli/agent.js +66 -56
  24. package/dist/cli/aun-commands.js +177 -42
  25. package/dist/cli/command-log.js +10 -11
  26. package/dist/cli/contact.js +1 -0
  27. package/dist/cli/daemon-commands.js +69 -115
  28. package/dist/cli/init.js +27 -15
  29. package/dist/cli/task-context.js +46 -0
  30. package/dist/cli/trigger-command.js +1 -1
  31. package/dist/cli/watch-logs.js +10 -3
  32. package/dist/config/builtin-roles.js +1 -0
  33. package/dist/config/config-field-policy.js +16 -5
  34. package/dist/config/config-manager.js +135 -17
  35. package/dist/config/contact-operation-service.js +32 -1
  36. package/dist/config/contact-request-service.js +44 -0
  37. package/dist/config/daemon-services.js +186 -0
  38. package/dist/config/gateway-config.js +20 -9
  39. package/dist/config/role-service.js +54 -3
  40. package/dist/config/schema-migration.js +550 -0
  41. package/dist/config-store.js +151 -9
  42. package/dist/core/agent-application-service.js +279 -0
  43. package/dist/core/audit/log-integrity.js +102 -0
  44. package/dist/core/auth/agent-delegation.js +31 -1
  45. package/dist/core/auth/auth-gateway.js +33 -4
  46. package/dist/core/auth/authorization-audit.js +150 -2
  47. package/dist/core/auth/operation-authorizer.js +41 -1
  48. package/dist/core/auth/operation-catalog.js +9 -1
  49. package/dist/core/bootstrap-service.js +6 -2
  50. package/dist/core/causation/aun-association.js +7 -4
  51. package/dist/core/command/agent-control.js +56 -16
  52. package/dist/core/command/command-handler.js +290 -44
  53. package/dist/core/command/connect-menu.js +3 -4
  54. package/dist/core/command/group-menu.js +5 -7
  55. package/dist/core/command/menu-handler.js +279 -80
  56. package/dist/core/command/role-menu.js +21 -11
  57. package/dist/core/command/slash-gate.js +85 -18
  58. package/dist/core/command/slash-handler.js +350 -32
  59. package/dist/core/event-catalog.js +5 -0
  60. package/dist/core/evolagent.js +4 -0
  61. package/dist/core/handoff/dispatcher.js +4 -0
  62. package/dist/core/handoff/runtime.js +10 -0
  63. package/dist/core/handoff/store.js +32 -9
  64. package/dist/core/inference/text-inference.js +7 -15
  65. package/dist/core/message/im-renderer.js +83 -84
  66. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  67. package/dist/core/message/message-bridge.js +124 -10
  68. package/dist/core/message/message-log.js +14 -7
  69. package/dist/core/message/message-queue.js +206 -16
  70. package/dist/core/message/message-utils.js +12 -5
  71. package/dist/core/message/response-engine.js +486 -68
  72. package/dist/core/message/send-receipt.js +1 -0
  73. package/dist/core/message/stream-debouncer.js +9 -2
  74. package/dist/core/model/model-catalog.js +23 -15
  75. package/dist/core/model/model-diagnostics.js +28 -10
  76. package/dist/core/permission/approval-gateway.js +180 -6
  77. package/dist/core/permission/ec-command-parser.js +410 -54
  78. package/dist/core/permission/mode.js +18 -3
  79. package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
  80. package/dist/core/permission/readonly-shell-query.js +263 -9
  81. package/dist/core/permission/sandbox-runtime.js +159 -1
  82. package/dist/core/permission/tool-policy.js +575 -21
  83. package/dist/core/session/session-fs-store.js +154 -5
  84. package/dist/core/session/session-manager.js +299 -30
  85. package/dist/core/session/session-renew.js +19 -12
  86. package/dist/core/session/session-turn-coordinator.js +11 -4
  87. package/dist/eck/kit-renderer.js +1 -1
  88. package/dist/index.js +253 -46
  89. package/dist/ipc.js +374 -24
  90. package/dist/paths.js +64 -7
  91. package/dist/response-system/context-builder.js +1 -7
  92. package/dist/trigger/anomaly-store.js +1 -0
  93. package/dist/trigger/feedback.js +56 -5
  94. package/dist/trigger/history.js +79 -4
  95. package/dist/trigger/legacy-session-history.js +2 -2
  96. package/dist/trigger/parser.js +3 -2
  97. package/dist/trigger/validation.js +6 -1
  98. package/dist/utils/atomic-write.js +27 -0
  99. package/dist/utils/ecweb-utils.js +16 -2
  100. package/dist/utils/error-utils.js +4 -1
  101. package/dist/utils/logger.js +21 -2
  102. package/dist/utils/process-tree-stats.js +24 -4
  103. package/dist/utils/process-tree-worker.js +31 -0
  104. package/dist/utils/project-path.js +1 -2
  105. package/kits/docs/INDEX.md +1 -1
  106. package/kits/docs/evolcore/INDEX.md +1 -1
  107. package/kits/docs/evolcore/contact.md +7 -1
  108. package/kits/docs/evolcore/msg.md +16 -0
  109. package/kits/schemas/_meta.json +4 -2
  110. package/kits/schemas/agent-config.schema.11.json +13 -0
  111. package/kits/schemas/daemon.schema.5.json +0 -1
  112. package/kits/schemas/daemon.schema.6.json +131 -0
  113. package/kits/schemas/defaults.schema.5.json +15 -3
  114. package/kits/schemas/migrations/README.md +3 -1
  115. package/kits/schemas/relation-config.schema.8.json +13 -0
  116. package/kits/schemas/role-config.schema.1.json +1 -2
  117. package/kits/schemas/single-session.schema.3.json +32 -0
  118. package/kits/templates/roles/admin.json +1 -0
  119. package/kits/templates/roles/member.json +1 -0
  120. package/kits/templates/roles/visitor.json +1 -0
  121. package/package.json +6 -3
  122. package/skills/eclink/SKILL.md +2 -0
  123. 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 1s background CPU sampling loop (for monitor-snapshot). Call after start(). */
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
- this.sampleProcessTree();
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 loop. */
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
- const roots = this.readAssociatedProcessRoots();
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.processTreeSnapshot = null;
304
- this.associatedProcessKinds.clear();
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 = getProcessStartTime(pid);
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
- actorAid: null,
1022
- localDirect: true,
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
- resolve(JSON.parse(buf.slice(0, idx)));
1507
+ response = JSON.parse(buf.slice(0, idx));
1164
1508
  }
1165
1509
  catch {
1166
- resolve(null);
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
  });