evolcore 0.0.20 → 0.0.22

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 (147) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +58 -9
  3. package/bin/codex-managed-hook.mjs +3 -0
  4. package/bin/install-codex-managed-hooks.mjs +3 -1
  5. package/dist/agents/baseagent.js +10 -6
  6. package/dist/agents/claude-runner.js +393 -108
  7. package/dist/agents/codex-app-server-client.js +41 -7
  8. package/dist/agents/codex-runner.js +1292 -220
  9. package/dist/agents/ecagent-runner.js +171 -61
  10. package/dist/agents/gemini-runner.js +130 -30
  11. package/dist/agents/request-identity.js +25 -0
  12. package/dist/agents/runner-types.js +19 -0
  13. package/dist/aun/aid/agentmd.js +59 -2
  14. package/dist/aun/aid/identity.js +4 -1
  15. package/dist/aun/aid/index.js +1 -1
  16. package/dist/aun/msg/group.js +72 -6
  17. package/dist/aun/msg/history.js +213 -36
  18. package/dist/aun/msg/managed-operation.js +58 -9
  19. package/dist/aun/msg/p2p.js +5 -0
  20. package/dist/aun/outbox.js +189 -80
  21. package/dist/aun/service-proxy.js +43 -25
  22. package/dist/channels/aun.js +618 -123
  23. package/dist/channels/daemon.js +6 -1
  24. package/dist/cli/agent-command.js +4 -3
  25. package/dist/cli/agent.js +66 -56
  26. package/dist/cli/aun-commands.js +177 -42
  27. package/dist/cli/command-log.js +10 -11
  28. package/dist/cli/contact.js +1 -0
  29. package/dist/cli/daemon-commands.js +98 -123
  30. package/dist/cli/init.js +27 -15
  31. package/dist/cli/task-context.js +50 -0
  32. package/dist/cli/trigger-command.js +14 -5
  33. package/dist/cli/watch-logs.js +10 -3
  34. package/dist/config/builtin-roles.js +1 -0
  35. package/dist/config/config-field-policy.js +19 -5
  36. package/dist/config/config-manager.js +167 -22
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/contact-operation-service.js +32 -1
  39. package/dist/config/contact-request-service.js +44 -0
  40. package/dist/config/daemon-services.js +186 -0
  41. package/dist/config/gateway-config.js +20 -9
  42. package/dist/config/role-service.js +54 -3
  43. package/dist/config/schema-migration.js +550 -0
  44. package/dist/config-store.js +151 -9
  45. package/dist/core/agent-application-service.js +279 -0
  46. package/dist/core/audit/log-integrity.js +102 -0
  47. package/dist/core/auth/agent-delegation.js +43 -1
  48. package/dist/core/auth/auth-gateway.js +41 -4
  49. package/dist/core/auth/authorization-audit.js +216 -8
  50. package/dist/core/auth/operation-authorizer.js +41 -1
  51. package/dist/core/auth/operation-catalog.js +9 -1
  52. package/dist/core/bootstrap-messages.js +8 -0
  53. package/dist/core/bootstrap-service.js +99 -27
  54. package/dist/core/causation/aun-association.js +7 -4
  55. package/dist/core/command/agent-control.js +56 -16
  56. package/dist/core/command/command-handler.js +311 -44
  57. package/dist/core/command/connect-menu.js +3 -4
  58. package/dist/core/command/group-menu.js +5 -7
  59. package/dist/core/command/menu-handler.js +288 -80
  60. package/dist/core/command/menu-protocol.js +1 -1
  61. package/dist/core/command/role-menu.js +21 -11
  62. package/dist/core/command/slash-gate.js +85 -18
  63. package/dist/core/command/slash-handler.js +377 -36
  64. package/dist/core/data-migration.js +11 -1
  65. package/dist/core/event-catalog.js +37 -0
  66. package/dist/core/evolagent.js +4 -0
  67. package/dist/core/handoff/dispatcher.js +4 -0
  68. package/dist/core/handoff/runtime.js +33 -3
  69. package/dist/core/handoff/store.js +32 -9
  70. package/dist/core/inference/text-inference.js +7 -15
  71. package/dist/core/message/im-renderer.js +90 -87
  72. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  73. package/dist/core/message/message-bridge.js +184 -12
  74. package/dist/core/message/message-log.js +47 -7
  75. package/dist/core/message/message-queue.js +227 -16
  76. package/dist/core/message/message-utils.js +12 -5
  77. package/dist/core/message/response-engine.js +658 -109
  78. package/dist/core/message/send-receipt.js +1 -0
  79. package/dist/core/message/stream-debouncer.js +9 -2
  80. package/dist/core/model/model-catalog.js +23 -15
  81. package/dist/core/model/model-diagnostics.js +28 -10
  82. package/dist/core/permission/approval-gateway.js +180 -6
  83. package/dist/core/permission/ec-command-parser.js +627 -69
  84. package/dist/core/permission/mode.js +18 -3
  85. package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
  86. package/dist/core/permission/readonly-shell-query.js +263 -9
  87. package/dist/core/permission/sandbox-runtime.js +159 -1
  88. package/dist/core/permission/tool-error-code.js +12 -0
  89. package/dist/core/permission/tool-policy.js +618 -23
  90. package/dist/core/session/session-fs-store.js +154 -5
  91. package/dist/core/session/session-manager.js +329 -30
  92. package/dist/core/session/session-renew.js +37 -13
  93. package/dist/core/session/session-turn-coordinator.js +16 -5
  94. package/dist/eck/kit-renderer.js +1 -1
  95. package/dist/index.js +316 -50
  96. package/dist/ipc.js +459 -29
  97. package/dist/paths.js +82 -7
  98. package/dist/response-system/context-builder.js +1 -7
  99. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  100. package/dist/stats/price-resolver.js +4 -0
  101. package/dist/trigger/anomaly-store.js +1 -0
  102. package/dist/trigger/feedback.js +70 -7
  103. package/dist/trigger/history.js +79 -4
  104. package/dist/trigger/legacy-session-history.js +2 -2
  105. package/dist/trigger/parser.js +13 -3
  106. package/dist/trigger/scheduler.js +20 -3
  107. package/dist/trigger/validation.js +6 -1
  108. package/dist/utils/atomic-write.js +27 -0
  109. package/dist/utils/ecweb-utils.js +16 -2
  110. package/dist/utils/error-utils.js +4 -1
  111. package/dist/utils/logger.js +30 -6
  112. package/dist/utils/process-tree-stats.js +24 -4
  113. package/dist/utils/process-tree-worker.js +31 -0
  114. package/dist/utils/project-path.js +1 -2
  115. package/dist/utils/tool-summary.js +59 -0
  116. package/dist/utils/windows-shell-trust.js +201 -0
  117. package/kits/docs/INDEX.md +1 -1
  118. package/kits/docs/evolcore/INDEX.md +3 -3
  119. package/kits/docs/evolcore/agent-create.md +146 -0
  120. package/kits/docs/evolcore/agent.md +6 -0
  121. package/kits/docs/evolcore/contact.md +7 -1
  122. package/kits/docs/evolcore/group-collaboration.md +251 -0
  123. package/kits/docs/evolcore/group-rules.md +1 -19
  124. package/kits/docs/evolcore/group.md +3 -1
  125. package/kits/docs/evolcore/msg.md +16 -0
  126. package/kits/docs/evolcore/trigger.md +6 -3
  127. package/kits/docs/prompt-loading-architecture.md +6 -0
  128. package/kits/eck_message_manifest.json +6 -6
  129. package/kits/schemas/_meta.json +7 -4
  130. package/kits/schemas/agent-config.schema.11.json +13 -0
  131. package/kits/schemas/agent-config.schema.12.json +427 -0
  132. package/kits/schemas/daemon.schema.5.json +0 -1
  133. package/kits/schemas/daemon.schema.6.json +131 -0
  134. package/kits/schemas/defaults.schema.5.json +15 -3
  135. package/kits/schemas/migrations/README.md +3 -1
  136. package/kits/schemas/relation-config.schema.8.json +13 -0
  137. package/kits/schemas/role-config.schema.1.json +1 -2
  138. package/kits/schemas/single-session.schema.3.json +32 -0
  139. package/kits/templates/message-fragments/item.md +1 -1
  140. package/kits/templates/roles/admin.json +1 -0
  141. package/kits/templates/roles/member.json +1 -0
  142. package/kits/templates/roles/visitor.json +1 -0
  143. package/kits/templates/system-fragments/bootstrap.md +2 -1
  144. package/kits/templates/system-fragments/commands.md +2 -2
  145. package/package.json +6 -3
  146. package/skills/eclink/SKILL.md +2 -0
  147. 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';
@@ -7,16 +8,24 @@ import path from 'path';
7
8
  import { logger } from './utils/logger.js';
8
9
  import { withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
9
10
  import { fileCache } from './core/daemon-file-cache.js';
11
+ import { DAEMON_RUNTIME_EPOCH_ENV } from './cli/task-context.js';
10
12
  import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
11
13
  import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
12
- import { resolvePaths } from './paths.js';
14
+ import { getWindowsInstanceSocketPathCandidates, normalizeIpcEndpoint, resolvePaths } from './paths.js';
13
15
  import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
14
- import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
16
+ import { classifyProcessTree, ProcessTreeSampler, readProcessMetrics } from './utils/process-tree-stats.js';
15
17
  import { readSystemMemoryUsage } from './utils/system-memory.js';
16
18
  import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
17
19
  import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
18
20
  const isWindows = process.platform === 'win32';
19
- const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
21
+ const isNamedPipe = (p) => isWindows && p.toLowerCase().startsWith('\\\\.\\pipe\\');
22
+ const CPU_SAMPLE_INTERVAL_MS = 1000;
23
+ const PROCESS_TREE_SAMPLE_INTERVAL_MS = 5000;
24
+ const MONITOR_LEASE_TTL_MS = 15_000;
25
+ const PROCESS_TREE_SAMPLE_WARN_MS = 250;
26
+ const EVENT_LOOP_LAG_WARN_MS = 100;
27
+ const SAMPLE_WARN_LOG_INTERVAL_MS = 60_000;
28
+ const PROCESS_TREE_WORKER_REQUEST_TIMEOUT_MS = 15_000;
20
29
  const finiteTimestamp = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
21
30
  const LOCAL_CONTROL_COMMANDS = new Set([
22
31
  'bind.begin',
@@ -36,8 +45,26 @@ const LOCAL_CONTROL_COMMANDS = new Set([
36
45
  'evolagent.resync',
37
46
  'menu.exec',
38
47
  'monitor-snapshot',
48
+ 'monitor-subscribe',
49
+ 'monitor-unsubscribe',
39
50
  'shutdown',
40
51
  ]);
52
+ let ipcQuerySequence = 0;
53
+ function ipcLogToken(value, fallback = 'unknown') {
54
+ const token = String(value ?? '').replace(/[^A-Za-z0-9_.:-]/g, '_').slice(0, 128);
55
+ return token || fallback;
56
+ }
57
+ function nextIpcQueryId(cmd) {
58
+ const payload = cmd.payload;
59
+ const nestedId = payload && typeof payload === 'object' && !Array.isArray(payload)
60
+ ? payload.requestId ?? payload.id
61
+ : undefined;
62
+ const supplied = cmd.requestId ?? cmd.id ?? nestedId;
63
+ if (typeof supplied === 'string' || typeof supplied === 'number')
64
+ return ipcLogToken(supplied, 'unknown');
65
+ ipcQuerySequence = (ipcQuerySequence + 1) % Number.MAX_SAFE_INTEGER;
66
+ return `ipc-${ipcQuerySequence}`;
67
+ }
41
68
  function requiresLocalControlToken(cmd) {
42
69
  // Managed bootstrap completion is authenticated by the session delegation
43
70
  // ticket. A standalone CLI invocation still needs the daemon control token.
@@ -57,11 +84,12 @@ export function summarizeMonitorAgents(agents, aids) {
57
84
  };
58
85
  }
59
86
  export class IpcServer {
60
- socketPath;
61
87
  getStatus;
62
88
  commandExecutor;
89
+ socketPath;
63
90
  server = null;
64
91
  agentRegistry;
92
+ agentApplicationService;
65
93
  aunAidProvider;
66
94
  aunAidStatsProvider;
67
95
  aunAidStatsRecorder;
@@ -98,6 +126,7 @@ export class IpcServer {
98
126
  wecomContactBindExecutor;
99
127
  wechatContactBindExecutor;
100
128
  controlToken;
129
+ daemonRuntimeEpoch;
101
130
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
102
131
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
103
132
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -108,22 +137,50 @@ export class IpcServer {
108
137
  sysCpuPercent = 0; // 系统级
109
138
  lastCpuTimes = null;
110
139
  cpuTimer = null;
140
+ processTreeTimer = null;
141
+ processTreeWorker = null;
142
+ processTreeWorkerRequestId = 0;
143
+ processTreeWorkerRequestStartedAt = 0;
144
+ processTreeWorkerRequestTimer = null;
145
+ processTreeSamplingInFlight = false;
146
+ processTreeMonitorLeases = new Map();
147
+ lastCpuTickMono = null;
148
+ eventLoopLagMs = 0;
149
+ maxEventLoopLagMs = 0;
150
+ lastSampleWarningAt = 0;
111
151
  processTreeSampler = new ProcessTreeSampler(process.pid);
112
152
  processTreeSnapshot = null;
113
153
  associatedProcessKinds = new Map();
154
+ processTreeSampling = {
155
+ lastDurationMs: 0,
156
+ maxDurationMs: 0,
157
+ lastSampleAt: 0,
158
+ associatedRootCount: 0,
159
+ processCount: 0,
160
+ inFlight: false,
161
+ lastError: null,
162
+ };
114
163
  constructor(socketPath, getStatus, commandExecutor) {
115
- this.socketPath = socketPath;
116
164
  this.getStatus = getStatus;
117
165
  this.commandExecutor = commandExecutor;
166
+ this.socketPath = normalizeIpcEndpoint(socketPath);
118
167
  }
119
168
  /** Inject EvolAgentRegistry for evolagent.* IPC handlers */
120
169
  setAgentRegistry(registry) {
121
170
  this.agentRegistry = registry;
122
171
  }
172
+ /** Inject the Core-owned agent application facade for in-process handlers. */
173
+ setAgentApplicationService(service) {
174
+ this.agentApplicationService = service;
175
+ }
123
176
  /** Authenticate local control-plane callers that cannot use task delegation. */
124
177
  setControlToken(token) {
125
178
  this.controlToken = Buffer.from(token);
126
179
  }
180
+ /** Set the process-lifetime marker used to reject stale managed children. */
181
+ setDaemonRuntimeEpoch(epoch) {
182
+ this.daemonRuntimeEpoch = epoch;
183
+ }
127
184
  /** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
128
185
  setMenuExecutor(executor) {
129
186
  this.menuExecutor = executor;
@@ -249,12 +306,22 @@ export class IpcServer {
249
306
  setWechatContactBindExecutor(executor) {
250
307
  this.wechatContactBindExecutor = executor;
251
308
  }
252
- /** Start the 1s background CPU sampling loop (for monitor-snapshot). Call after start(). */
309
+ /** Start the lightweight background CPU sampling loop. Call after start(). */
253
310
  startCpuTracking() {
254
311
  if (this.cpuTimer)
255
312
  return;
256
- this.sampleProcessTree();
313
+ this.lastCpuTickMono = performance.now();
257
314
  this.cpuTimer = setInterval(() => {
315
+ const tickMono = performance.now();
316
+ if (this.lastCpuTickMono !== null) {
317
+ const lagMs = Math.max(0, tickMono - this.lastCpuTickMono - CPU_SAMPLE_INTERVAL_MS);
318
+ this.eventLoopLagMs = Math.round(lagMs * 10) / 10;
319
+ this.maxEventLoopLagMs = Math.max(this.maxEventLoopLagMs, this.eventLoopLagMs);
320
+ if (this.eventLoopLagMs >= EVENT_LOOP_LAG_WARN_MS) {
321
+ logger.debug(`[IPC] cpu_sample event_loop_lag_ms=${this.eventLoopLagMs}`);
322
+ }
323
+ }
324
+ this.lastCpuTickMono = tickMono;
258
325
  const now = Date.now();
259
326
  const elapsedUs = (now - this.lastCpuTs) * 1000; // wall time in microseconds
260
327
  if (elapsedUs > 0) {
@@ -281,30 +348,250 @@ export class IpcServer {
281
348
  this.lastCpuTimes = { idle, total };
282
349
  }
283
350
  catch { /* os.cpus() 理论上不抛 */ }
284
- this.sampleProcessTree();
285
- }, 1000);
351
+ }, CPU_SAMPLE_INTERVAL_MS);
286
352
  // Don't keep the event loop alive for sampling alone.
287
353
  this.cpuTimer.unref?.();
288
354
  }
289
- /** Stop the CPU sampling loop. */
355
+ /** Stop all CPU and process-tree sampling loops. */
290
356
  stopCpuTracking() {
291
357
  if (this.cpuTimer) {
292
358
  clearInterval(this.cpuTimer);
293
359
  this.cpuTimer = null;
294
360
  }
361
+ this.processTreeMonitorLeases.clear();
362
+ this.stopProcessTreeTracking();
363
+ this.lastCpuTickMono = null;
364
+ }
365
+ startProcessTreeTracking() {
366
+ if (this.processTreeTimer)
367
+ return;
368
+ this.sampleProcessTree();
369
+ this.processTreeTimer = setInterval(() => {
370
+ this.pruneExpiredMonitorLeases();
371
+ if (this.processTreeMonitorLeases.size === 0)
372
+ return;
373
+ this.sampleProcessTree();
374
+ }, PROCESS_TREE_SAMPLE_INTERVAL_MS);
375
+ this.processTreeTimer.unref?.();
376
+ }
377
+ stopProcessTreeTracking() {
378
+ if (this.processTreeTimer) {
379
+ clearInterval(this.processTreeTimer);
380
+ this.processTreeTimer = null;
381
+ }
382
+ if (this.processTreeWorkerRequestTimer) {
383
+ clearTimeout(this.processTreeWorkerRequestTimer);
384
+ this.processTreeWorkerRequestTimer = null;
385
+ }
386
+ this.processTreeSamplingInFlight = false;
387
+ this.processTreeSampling.inFlight = false;
388
+ // Invalidate callbacks from a worker that is being stopped. Its error and
389
+ // exit events are asynchronous and must not turn an intentional shutdown
390
+ // into a failed sample.
391
+ this.processTreeWorkerRequestId++;
392
+ this.processTreeWorkerRequestStartedAt = 0;
393
+ const worker = this.processTreeWorker;
394
+ this.processTreeWorker = null;
395
+ if (worker)
396
+ void worker.terminate();
397
+ this.processTreeSnapshot = null;
398
+ this.associatedProcessKinds.clear();
399
+ }
400
+ acquireProcessTreeMonitor(leaseId) {
401
+ this.processTreeMonitorLeases.set(leaseId, Date.now() + MONITOR_LEASE_TTL_MS);
402
+ this.startProcessTreeTracking();
403
+ }
404
+ refreshProcessTreeMonitor(leaseId) {
405
+ if (typeof leaseId !== 'string' || !this.processTreeMonitorLeases.has(leaseId))
406
+ return;
407
+ this.processTreeMonitorLeases.set(leaseId, Date.now() + MONITOR_LEASE_TTL_MS);
408
+ }
409
+ releaseProcessTreeMonitor(leaseId) {
410
+ this.processTreeMonitorLeases.delete(leaseId);
411
+ if (this.processTreeMonitorLeases.size === 0)
412
+ this.stopProcessTreeTracking();
413
+ }
414
+ pruneExpiredMonitorLeases() {
415
+ const now = Date.now();
416
+ for (const [leaseId, expiresAt] of this.processTreeMonitorLeases) {
417
+ if (expiresAt <= now)
418
+ this.processTreeMonitorLeases.delete(leaseId);
419
+ }
420
+ if (this.processTreeMonitorLeases.size === 0)
421
+ this.stopProcessTreeTracking();
295
422
  }
296
423
  sampleProcessTree() {
424
+ if (isWindows) {
425
+ this.sampleProcessTreeInWorker();
426
+ return;
427
+ }
428
+ const startedAt = performance.now();
429
+ try {
430
+ this.commitProcessTreeSample(readProcessMetrics(), performance.now() - startedAt);
431
+ }
432
+ catch (error) {
433
+ this.recordProcessTreeSampleError(error, performance.now() - startedAt);
434
+ }
435
+ }
436
+ sampleProcessTreeInWorker() {
437
+ if (this.processTreeSamplingInFlight)
438
+ return;
439
+ const worker = this.getProcessTreeWorker();
440
+ if (!worker)
441
+ return;
442
+ this.processTreeSamplingInFlight = true;
443
+ this.processTreeSampling.inFlight = true;
444
+ this.processTreeWorkerRequestStartedAt = performance.now();
445
+ const requestId = ++this.processTreeWorkerRequestId;
446
+ const startedAt = this.processTreeWorkerRequestStartedAt;
447
+ this.processTreeWorkerRequestTimer = setTimeout(() => {
448
+ if (this.processTreeWorker !== worker
449
+ || !this.processTreeSamplingInFlight
450
+ || this.processTreeWorkerRequestId !== requestId)
451
+ return;
452
+ this.processTreeSamplingInFlight = false;
453
+ this.processTreeSampling.inFlight = false;
454
+ this.processTreeWorkerRequestTimer = null;
455
+ this.processTreeWorkerRequestId++;
456
+ this.processTreeWorkerRequestStartedAt = 0;
457
+ this.processTreeWorker = null;
458
+ void worker.terminate();
459
+ this.recordProcessTreeSampleError(`process-tree worker request timed out after ${PROCESS_TREE_WORKER_REQUEST_TIMEOUT_MS}ms`, performance.now() - startedAt);
460
+ }, PROCESS_TREE_WORKER_REQUEST_TIMEOUT_MS);
461
+ this.processTreeWorkerRequestTimer.unref?.();
462
+ try {
463
+ worker.postMessage({ type: 'sample', requestId });
464
+ }
465
+ catch (error) {
466
+ if (this.processTreeWorker !== worker || this.processTreeWorkerRequestId !== requestId)
467
+ return;
468
+ if (this.processTreeWorkerRequestTimer) {
469
+ clearTimeout(this.processTreeWorkerRequestTimer);
470
+ this.processTreeWorkerRequestTimer = null;
471
+ }
472
+ this.processTreeSamplingInFlight = false;
473
+ this.processTreeSampling.inFlight = false;
474
+ this.processTreeWorkerRequestId++;
475
+ this.processTreeWorkerRequestStartedAt = 0;
476
+ this.processTreeWorker = null;
477
+ void worker.terminate();
478
+ this.recordProcessTreeSampleError(error, performance.now() - startedAt);
479
+ }
480
+ }
481
+ getProcessTreeWorker() {
482
+ if (this.processTreeWorker)
483
+ return this.processTreeWorker;
484
+ try {
485
+ const worker = new Worker(new URL('./utils/process-tree-worker.js', import.meta.url));
486
+ worker.unref();
487
+ worker.on('message', (message) => {
488
+ if (this.processTreeWorker !== worker
489
+ || !this.processTreeSamplingInFlight
490
+ || message.requestId !== this.processTreeWorkerRequestId)
491
+ return;
492
+ if (this.processTreeWorkerRequestTimer) {
493
+ clearTimeout(this.processTreeWorkerRequestTimer);
494
+ this.processTreeWorkerRequestTimer = null;
495
+ }
496
+ this.processTreeSamplingInFlight = false;
497
+ this.processTreeSampling.inFlight = false;
498
+ const durationMs = typeof message.durationMs === 'number'
499
+ ? message.durationMs
500
+ : performance.now() - this.processTreeWorkerRequestStartedAt;
501
+ if (message.type === 'sample' && Array.isArray(message.metrics)) {
502
+ this.commitProcessTreeSample(message.metrics, durationMs);
503
+ }
504
+ else {
505
+ this.recordProcessTreeSampleError(message.error ?? 'process-tree worker failed', durationMs);
506
+ }
507
+ });
508
+ worker.on('error', error => {
509
+ if (this.processTreeWorker !== worker)
510
+ return;
511
+ const wasInFlight = this.processTreeSamplingInFlight;
512
+ if (this.processTreeWorkerRequestTimer) {
513
+ clearTimeout(this.processTreeWorkerRequestTimer);
514
+ this.processTreeWorkerRequestTimer = null;
515
+ }
516
+ this.processTreeWorker = null;
517
+ if (!wasInFlight)
518
+ return;
519
+ this.processTreeSamplingInFlight = false;
520
+ this.processTreeSampling.inFlight = false;
521
+ this.processTreeWorkerRequestId++;
522
+ this.recordProcessTreeSampleError(error, performance.now() - this.processTreeWorkerRequestStartedAt);
523
+ });
524
+ worker.on('exit', code => {
525
+ if (this.processTreeWorker !== worker)
526
+ return;
527
+ const wasInFlight = this.processTreeSamplingInFlight;
528
+ if (this.processTreeWorkerRequestTimer) {
529
+ clearTimeout(this.processTreeWorkerRequestTimer);
530
+ this.processTreeWorkerRequestTimer = null;
531
+ }
532
+ this.processTreeWorker = null;
533
+ if (code !== 0 && wasInFlight) {
534
+ this.processTreeSamplingInFlight = false;
535
+ this.processTreeSampling.inFlight = false;
536
+ this.processTreeWorkerRequestId++;
537
+ this.recordProcessTreeSampleError(`process-tree worker exited with code ${code}`, performance.now() - this.processTreeWorkerRequestStartedAt);
538
+ }
539
+ });
540
+ this.processTreeWorker = worker;
541
+ return worker;
542
+ }
543
+ catch (error) {
544
+ this.recordProcessTreeSampleError(error, 0);
545
+ return null;
546
+ }
547
+ }
548
+ commitProcessTreeSample(metrics, durationMs) {
549
+ let roots = [];
550
+ let processCount = 0;
297
551
  try {
298
- const roots = this.readAssociatedProcessRoots();
552
+ let processStartTimes;
553
+ if (isWindows) {
554
+ const starts = new Map();
555
+ for (const metric of metrics) {
556
+ starts.set(metric.pid, typeof metric.startId === 'number' && Number.isFinite(metric.startId) ? metric.startId : null);
557
+ }
558
+ processStartTimes = starts;
559
+ }
560
+ roots = this.readAssociatedProcessRoots(processStartTimes);
299
561
  this.associatedProcessKinds = new Map(roots.map(root => [root.pid, root.kind]));
300
- this.processTreeSnapshot = this.processTreeSampler.sample(roots.map(root => root.pid));
562
+ this.processTreeSnapshot = this.processTreeSampler.sample(roots.map(root => root.pid), metrics);
563
+ processCount = this.processTreeSnapshot.processCount;
564
+ this.processTreeSampling.lastError = null;
301
565
  }
302
- catch {
303
- this.processTreeSnapshot = null;
304
- this.associatedProcessKinds.clear();
566
+ catch (error) {
567
+ this.recordProcessTreeSampleError(error, durationMs);
568
+ return;
569
+ }
570
+ const roundedDurationMs = Math.round(durationMs * 10) / 10;
571
+ this.processTreeSampling.lastDurationMs = roundedDurationMs;
572
+ this.processTreeSampling.maxDurationMs = Math.max(this.processTreeSampling.maxDurationMs, durationMs);
573
+ this.processTreeSampling.lastSampleAt = Date.now();
574
+ this.processTreeSampling.associatedRootCount = roots.length;
575
+ this.processTreeSampling.processCount = processCount;
576
+ const sampleLog = `[IPC] process_tree_sample duration_ms=${roundedDurationMs}`
577
+ + ` event_loop_lag_ms=${this.eventLoopLagMs}`
578
+ + ` associated_roots=${roots.length}`
579
+ + ` process_count=${processCount}`;
580
+ logger.debug(sampleLog);
581
+ const shouldWarn = roundedDurationMs >= PROCESS_TREE_SAMPLE_WARN_MS || this.eventLoopLagMs >= EVENT_LOOP_LAG_WARN_MS;
582
+ if (shouldWarn && this.processTreeSampling.lastSampleAt - this.lastSampleWarningAt >= SAMPLE_WARN_LOG_INTERVAL_MS) {
583
+ this.lastSampleWarningAt = this.processTreeSampling.lastSampleAt;
584
+ logger.warn(sampleLog);
305
585
  }
306
586
  }
307
- readAssociatedProcessRoots() {
587
+ recordProcessTreeSampleError(error, durationMs) {
588
+ const message = error instanceof Error ? error.message : String(error);
589
+ this.processTreeSampling.lastDurationMs = Math.round(durationMs * 10) / 10;
590
+ this.processTreeSampling.maxDurationMs = Math.max(this.processTreeSampling.maxDurationMs, durationMs);
591
+ this.processTreeSampling.lastError = message;
592
+ logger.warn(`[IPC] process_tree_sample failed duration_ms=${this.processTreeSampling.lastDurationMs} error=${message}`);
593
+ }
594
+ readAssociatedProcessRoots(processStartTimes) {
308
595
  const roots = [];
309
596
  let files;
310
597
  try {
@@ -323,7 +610,9 @@ export class IpcServer {
323
610
  if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid)
324
611
  continue;
325
612
  if (record.startedAt) {
326
- const actual = getProcessStartTime(pid);
613
+ const actual = processStartTimes
614
+ ? processStartTimes.get(pid) ?? null
615
+ : getProcessStartTime(pid);
327
616
  if (!isSameOrOlderProcess(Number(record.startedAt), actual))
328
617
  continue;
329
618
  }
@@ -411,6 +700,17 @@ export class IpcServer {
411
700
  if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
412
701
  return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
413
702
  }
703
+ if (typeof cmd.sessionId === 'string' && cmd.sessionId
704
+ && typeof cmd.delegationToken === 'string' && cmd.delegationToken
705
+ && this.daemonRuntimeEpoch
706
+ && cmd.daemonRuntimeEpoch !== this.daemonRuntimeEpoch) {
707
+ logger.warn(`[IPC] stale delegation rejected: session=${ipcLogToken(cmd.sessionId)} type=${ipcLogToken(cmd.type)} cause=daemon_runtime_epoch_mismatch`);
708
+ return {
709
+ ok: false,
710
+ code: 'DAEMON_RESTARTED',
711
+ error: 'The task delegation token belongs to a previous daemon runtime; start a new task authorization and do not retry this command in the current task',
712
+ };
713
+ }
414
714
  switch (cmd.type) {
415
715
  case 'status':
416
716
  return this.getStatus();
@@ -427,6 +727,7 @@ export class IpcServer {
427
727
  threadId: cmd.threadId,
428
728
  toolName: cmd.toolName,
429
729
  toolInput: cmd.toolInput,
730
+ callId: typeof cmd.callId === 'string' ? cmd.callId : undefined,
430
731
  signal: requestSignal,
431
732
  });
432
733
  }
@@ -910,16 +1211,25 @@ export class IpcServer {
910
1211
  }
911
1212
  }
912
1213
  case 'evolagent.list': {
1214
+ if (this.agentApplicationService) {
1215
+ return { ok: true, agents: this.agentApplicationService.list() };
1216
+ }
913
1217
  if (!this.agentRegistry)
914
1218
  return { ok: false, error: 'EvolAgentRegistry not available' };
915
1219
  return { ok: true, agents: this.agentRegistry.list() };
916
1220
  }
917
1221
  case 'evolagent.show': {
918
- if (!this.agentRegistry)
919
- return { ok: false, error: 'EvolAgentRegistry not available' };
920
1222
  const name = cmd.name;
921
1223
  if (!name || typeof name !== 'string')
922
1224
  return { ok: false, error: 'missing name' };
1225
+ if (this.agentApplicationService) {
1226
+ const info = this.agentApplicationService.info(name);
1227
+ if (!info)
1228
+ return { ok: false, error: `Agent "${name}" not found` };
1229
+ return { ok: true, agent: info };
1230
+ }
1231
+ if (!this.agentRegistry)
1232
+ return { ok: false, error: 'EvolAgentRegistry not available' };
923
1233
  const agent = this.agentRegistry.get(name);
924
1234
  if (!agent)
925
1235
  return { ok: false, error: `Agent "${name}" not found` };
@@ -948,11 +1258,23 @@ export class IpcServer {
948
1258
  }
949
1259
  }
950
1260
  case 'evolagent.reload': {
951
- if (!this.agentRegistry)
952
- return { ok: false, error: 'EvolAgentRegistry not available' };
953
1261
  const name = cmd.name;
954
1262
  if (!name || typeof name !== 'string')
955
1263
  return { ok: false, error: 'missing name' };
1264
+ if (this.agentApplicationService) {
1265
+ const result = await this.agentApplicationService.reload(name, { force: cmd.force === true });
1266
+ if (!result.ok) {
1267
+ return {
1268
+ ok: false,
1269
+ ...(result.code ? { code: result.code } : {}),
1270
+ ...(result.busyCount === undefined ? {} : { busyCount: result.busyCount }),
1271
+ error: result.error,
1272
+ };
1273
+ }
1274
+ return { ok: true, result: `Agent "${name}" reloaded` };
1275
+ }
1276
+ if (!this.agentRegistry)
1277
+ return { ok: false, error: 'EvolAgentRegistry not available' };
956
1278
  if (!this.agentReloadExecutor)
957
1279
  return { ok: false, error: 'Agent reload coordinator not initialized' };
958
1280
  try {
@@ -970,11 +1292,17 @@ export class IpcServer {
970
1292
  }
971
1293
  }
972
1294
  case 'evolagent.load': {
973
- if (!this.agentRegistry)
974
- return { ok: false, error: 'EvolAgentRegistry not available' };
975
1295
  const aid = cmd.aid;
976
1296
  if (!aid || typeof aid !== 'string')
977
1297
  return { ok: false, error: 'missing aid' };
1298
+ if (this.agentApplicationService) {
1299
+ const result = await this.agentApplicationService.load(aid);
1300
+ return result.ok
1301
+ ? { ok: true, result: `Agent "${aid}" loaded and online` }
1302
+ : { ok: false, ...(result.code ? { code: result.code } : {}), error: result.error };
1303
+ }
1304
+ if (!this.agentRegistry)
1305
+ return { ok: false, error: 'EvolAgentRegistry not available' };
978
1306
  const hotLoad = globalThis.__evolcore_hotLoadAgent;
979
1307
  if (!hotLoad)
980
1308
  return { ok: false, error: 'Hot-load handler not initialized' };
@@ -987,6 +1315,12 @@ export class IpcServer {
987
1315
  }
988
1316
  }
989
1317
  case 'evolagent.resync': {
1318
+ if (this.agentApplicationService) {
1319
+ const result = await this.agentApplicationService.resync();
1320
+ return result.ok
1321
+ ? { ok: true, results: result.results }
1322
+ : { ok: false, ...(result.code ? { code: result.code } : {}), error: result.error };
1323
+ }
990
1324
  if (!this.agentRegistry)
991
1325
  return { ok: false, error: 'EvolAgentRegistry not available' };
992
1326
  const resync = globalThis.__evolcore_resyncAgents;
@@ -1016,10 +1350,27 @@ export class IpcServer {
1016
1350
  return { ok: false, error: 'menu.exec not configured' };
1017
1351
  }
1018
1352
  try {
1353
+ const suppliedAuth = cmd.auth && typeof cmd.auth === 'object' && !Array.isArray(cmd.auth)
1354
+ ? cmd.auth
1355
+ : undefined;
1356
+ const actorAid = typeof suppliedAuth?.actorAid === 'string' && suppliedAuth.actorAid.trim()
1357
+ ? suppliedAuth.actorAid.trim()
1358
+ : null;
1019
1359
  const response = await this.menuExecutor(cmd.payload, {
1020
1360
  source: 'ecweb',
1021
- actorAid: null,
1022
- localDirect: true,
1361
+ // The ECWeb process resolved this actor from its bearer token. The
1362
+ // IPC control-token gate authenticates the local ECWeb process; the
1363
+ // menu handler still checks that actorAid remains a configured owner.
1364
+ actorAid,
1365
+ localDirect: suppliedAuth?.localDirect === true,
1366
+ // Reaching this handler means the control token gate succeeded.
1367
+ // Never trust an authenticated flag supplied by the caller.
1368
+ authenticated: true,
1369
+ // Reaching this handler means the ECWeb control token gate
1370
+ // succeeded. ECWeb management is daemon-owner-authorized; the
1371
+ // menu layer independently revalidates remote actorAid against
1372
+ // the current daemon owners before applying that authority.
1373
+ canApprovePersistentFullAccess: true,
1023
1374
  });
1024
1375
  const timedResponse = withMenuProcessingTime(response, { receivedAtMono });
1025
1376
  logMenuRequestCompleted(request, timedResponse, flowContext, { delivery: 'returned' });
@@ -1035,6 +1386,7 @@ export class IpcServer {
1035
1386
  }
1036
1387
  }
1037
1388
  case 'monitor-snapshot': {
1389
+ this.refreshProcessTreeMonitor(cmd.leaseId);
1038
1390
  // watch web Monitor 页用:进程级 + 系统级运行指标 + 全局 stats + per-agent 汇总。
1039
1391
  const mem = process.memoryUsage();
1040
1392
  const systemMemory = readSystemMemoryUsage();
@@ -1087,6 +1439,13 @@ export class IpcServer {
1087
1439
  memory: mem,
1088
1440
  },
1089
1441
  processTree: processTreeDetails,
1442
+ sampling: {
1443
+ cpuIntervalMs: CPU_SAMPLE_INTERVAL_MS,
1444
+ processTreeIntervalMs: PROCESS_TREE_SAMPLE_INTERVAL_MS,
1445
+ eventLoopLagMs: this.eventLoopLagMs,
1446
+ maxEventLoopLagMs: this.maxEventLoopLagMs,
1447
+ processTree: { ...this.processTreeSampling },
1448
+ },
1090
1449
  // 系统级:整机
1091
1450
  system: {
1092
1451
  ...systemMemory,
@@ -1107,6 +1466,20 @@ export class IpcServer {
1107
1466
  },
1108
1467
  };
1109
1468
  }
1469
+ case 'monitor-subscribe': {
1470
+ const leaseId = typeof cmd.leaseId === 'string' ? cmd.leaseId.trim() : '';
1471
+ if (!leaseId || leaseId.length > 128)
1472
+ return { ok: false, error: 'invalid monitor lease id' };
1473
+ this.acquireProcessTreeMonitor(leaseId);
1474
+ return { ok: true, leaseTtlMs: MONITOR_LEASE_TTL_MS };
1475
+ }
1476
+ case 'monitor-unsubscribe': {
1477
+ const leaseId = typeof cmd.leaseId === 'string' ? cmd.leaseId.trim() : '';
1478
+ if (!leaseId || leaseId.length > 128)
1479
+ return { ok: false, error: 'invalid monitor lease id' };
1480
+ this.releaseProcessTreeMonitor(leaseId);
1481
+ return { ok: true };
1482
+ }
1110
1483
  default:
1111
1484
  return { error: `unknown command: ${cmd.type}` };
1112
1485
  }
@@ -1125,6 +1498,31 @@ export class IpcServer {
1125
1498
  */
1126
1499
  /** Pass `null` as timeoutMs to wait without a client-side deadline. */
1127
1500
  export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
1501
+ const endpoint = normalizeIpcEndpoint(socketPath);
1502
+ let socketCandidates = [endpoint];
1503
+ if (isNamedPipe(endpoint)) {
1504
+ const paths = resolvePaths();
1505
+ if (endpoint === paths.socket)
1506
+ socketCandidates = getWindowsInstanceSocketPathCandidates(paths.root);
1507
+ }
1508
+ if (socketCandidates.length <= 1)
1509
+ return ipcQueryOnce(endpoint, cmd, timeoutMs);
1510
+ return (async () => {
1511
+ const startedAt = Date.now();
1512
+ for (const candidate of socketCandidates) {
1513
+ const remainingTimeout = timeoutMs === null
1514
+ ? null
1515
+ : Math.max(0, timeoutMs - (Date.now() - startedAt));
1516
+ if (remainingTimeout === 0)
1517
+ return null;
1518
+ const response = await ipcQueryOnce(candidate, cmd, remainingTimeout);
1519
+ if (response !== null)
1520
+ return response;
1521
+ }
1522
+ return null;
1523
+ })();
1524
+ }
1525
+ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1128
1526
  let authenticatedCmd = cmd;
1129
1527
  if (!process.env.EVOLCORE_SESSION_ID
1130
1528
  && requiresLocalControlToken(cmd)
@@ -1140,37 +1538,69 @@ export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
1140
1538
  && typeof authenticatedCmd.delegationCommandHash !== 'string'
1141
1539
  ? hashCurrentDelegatedCommand()
1142
1540
  : undefined;
1143
- const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
1541
+ const request = {
1542
+ ...(delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd),
1543
+ ...(process.env[DAEMON_RUNTIME_EPOCH_ENV]
1544
+ && typeof authenticatedCmd.delegationToken === 'string'
1545
+ ? { daemonRuntimeEpoch: process.env[DAEMON_RUNTIME_EPOCH_ENV] }
1546
+ : {}),
1547
+ };
1144
1548
  return new Promise((resolve) => {
1549
+ const startedAtMono = performance.now();
1550
+ const queryId = nextIpcQueryId(request);
1551
+ const commandType = ipcLogToken(request.type);
1552
+ const elapsedMs = () => Math.round(performance.now() - startedAtMono);
1553
+ const logPhase = (phase, details = '') => {
1554
+ logger.debug(`[IPC] query=${queryId} phase=${phase} type=${commandType} elapsed_ms=${elapsedMs()}${details}`);
1555
+ };
1145
1556
  const conn = net.connect(socketPath);
1146
1557
  let buf = '';
1558
+ let settled = false;
1147
1559
  const timer = timeoutMs === null
1148
1560
  ? undefined
1149
1561
  : setTimeout(() => {
1562
+ if (settled)
1563
+ return;
1564
+ settled = true;
1565
+ logPhase('timeout', ` timeout_ms=${timeoutMs}`);
1150
1566
  conn.destroy();
1151
1567
  resolve(null);
1152
1568
  }, timeoutMs);
1153
1569
  conn.on('connect', () => {
1570
+ if (settled)
1571
+ return;
1572
+ logPhase('connect');
1154
1573
  conn.write(JSON.stringify(request) + '\n');
1155
1574
  });
1156
1575
  conn.on('data', (data) => {
1576
+ if (settled)
1577
+ return;
1157
1578
  buf += data.toString();
1158
1579
  const idx = buf.indexOf('\n');
1159
1580
  if (idx !== -1) {
1581
+ settled = true;
1160
1582
  if (timer)
1161
1583
  clearTimeout(timer);
1584
+ let response = null;
1585
+ let parse = 'ok';
1162
1586
  try {
1163
- resolve(JSON.parse(buf.slice(0, idx)));
1587
+ response = JSON.parse(buf.slice(0, idx));
1164
1588
  }
1165
1589
  catch {
1166
- resolve(null);
1590
+ parse = 'invalid_json';
1167
1591
  }
1592
+ logPhase('response', ` bytes=${idx + 1} parse=${parse}`);
1593
+ resolve(response);
1168
1594
  conn.destroy();
1169
1595
  }
1170
1596
  });
1171
- conn.on('error', () => {
1597
+ conn.on('error', (error) => {
1598
+ if (settled)
1599
+ return;
1600
+ settled = true;
1172
1601
  if (timer)
1173
1602
  clearTimeout(timer);
1603
+ logPhase('error', ` code=${error.code ?? 'unknown'}`);
1174
1604
  resolve(null);
1175
1605
  });
1176
1606
  });