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.
- package/CHANGELOG.md +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- 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 +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- 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 +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- 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 +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- 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 +311 -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 +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- 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 +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- 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 +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- 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 +30 -6
- 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/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -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/message-fragments/item.md +1 -1
- 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/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- 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';
|
|
@@ -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
|
|
309
|
+
/** Start the lightweight background CPU sampling loop. Call after start(). */
|
|
253
310
|
startCpuTracking() {
|
|
254
311
|
if (this.cpuTimer)
|
|
255
312
|
return;
|
|
256
|
-
this.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
304
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1022
|
-
|
|
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 =
|
|
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
|
-
|
|
1587
|
+
response = JSON.parse(buf.slice(0, idx));
|
|
1164
1588
|
}
|
|
1165
1589
|
catch {
|
|
1166
|
-
|
|
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
|
});
|