evolcore 0.0.21 → 0.0.23

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 (112) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/codex-managed-hook.mjs +3 -0
  6. package/bin/install-codex-managed-hooks.mjs +376 -96
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +1 -1
  9. package/dist/agents/claude-runner.js +44 -226
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
  11. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  12. package/dist/agents/codex/catalog.js +70 -0
  13. package/dist/agents/codex/diagnostics.js +154 -0
  14. package/dist/agents/codex-runner.js +1292 -219
  15. package/dist/agents/ecagent-runner.js +10 -31
  16. package/dist/agents/gemini-runner.js +2 -6
  17. package/dist/aun/aid/store.js +2 -0
  18. package/dist/aun/menu-client.js +272 -0
  19. package/dist/aun/msg/group.js +65 -4
  20. package/dist/aun/msg/index.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +51 -4
  22. package/dist/aun/outbox.js +7 -0
  23. package/dist/aun/rpc/client.js +8 -1
  24. package/dist/channels/aun.js +240 -36
  25. package/dist/channels/wechat.js +81 -10
  26. package/dist/cli/aun-commands.js +48 -4
  27. package/dist/cli/command-log.js +21 -2
  28. package/dist/cli/daemon-commands.js +169 -51
  29. package/dist/cli/index.js +20 -0
  30. package/dist/cli/init.js +2 -10
  31. package/dist/cli/menu-client.js +95 -0
  32. package/dist/cli/model.js +23 -8
  33. package/dist/cli/task-context.js +16 -2
  34. package/dist/cli/trigger-command.js +22 -5
  35. package/dist/config/config-field-policy.js +3 -0
  36. package/dist/config/config-manager.js +32 -5
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/schema-migration.js +14 -2
  39. package/dist/core/auth/agent-delegation.js +15 -0
  40. package/dist/core/auth/auth-gateway.js +8 -0
  41. package/dist/core/auth/authorization-audit.js +70 -6
  42. package/dist/core/auth/operation-catalog.js +4 -0
  43. package/dist/core/auth/remote-access-store.js +135 -0
  44. package/dist/core/bootstrap-messages.js +8 -0
  45. package/dist/core/bootstrap-service.js +93 -25
  46. package/dist/core/command/command-handler.js +62 -5
  47. package/dist/core/command/daemon-diagnostics.js +448 -0
  48. package/dist/core/command/menu-catalog.js +3 -0
  49. package/dist/core/command/menu-handler.js +150 -33
  50. package/dist/core/command/menu-protocol.js +120 -1
  51. package/dist/core/command/slash-gate.js +3 -3
  52. package/dist/core/command/slash-handler.js +83 -40
  53. package/dist/core/data-migration.js +11 -1
  54. package/dist/core/event-catalog.js +32 -0
  55. package/dist/core/handoff/runtime.js +23 -3
  56. package/dist/core/message/im-renderer.js +7 -3
  57. package/dist/core/message/message-bridge.js +60 -2
  58. package/dist/core/message/message-log.js +33 -0
  59. package/dist/core/message/message-queue.js +41 -15
  60. package/dist/core/message/response-engine.js +246 -114
  61. package/dist/core/model/catalog-service.js +308 -0
  62. package/dist/core/model/model-catalog.js +124 -334
  63. package/dist/core/model/model-diagnostics.js +4 -5
  64. package/dist/core/model/model-fallback.js +9 -16
  65. package/dist/core/model/model-permission.js +1 -1
  66. package/dist/core/permission/approval-gateway.js +16 -1
  67. package/dist/core/permission/ec-command-parser.js +272 -70
  68. package/dist/core/permission/protected-paths.js +74 -14
  69. package/dist/core/permission/sandbox-runtime.js +4 -2
  70. package/dist/core/permission/tool-error-code.js +12 -0
  71. package/dist/core/permission/tool-policy.js +290 -30
  72. package/dist/core/runtime-lock.js +13 -8
  73. package/dist/core/session/session-manager.js +30 -0
  74. package/dist/core/session/session-renew.js +18 -1
  75. package/dist/core/session/session-turn-coordinator.js +5 -1
  76. package/dist/index.js +104 -34
  77. package/dist/ipc.js +218 -96
  78. package/dist/paths.js +18 -0
  79. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  80. package/dist/stats/price-resolver.js +4 -0
  81. package/dist/trigger/feedback.js +14 -2
  82. package/dist/trigger/manager.js +3 -1
  83. package/dist/trigger/parser.js +86 -13
  84. package/dist/trigger/patch.js +104 -1
  85. package/dist/trigger/scheduler.js +20 -3
  86. package/dist/utils/ecweb-supervisor.js +11 -2
  87. package/dist/utils/ecweb-utils.js +8 -26
  88. package/dist/utils/error-utils.js +7 -0
  89. package/dist/utils/logger.js +9 -4
  90. package/dist/utils/npm-ops.js +47 -15
  91. package/dist/utils/tool-summary.js +59 -0
  92. package/dist/utils/windows-shell-trust.js +201 -0
  93. package/kits/docs/evolcore/INDEX.md +3 -2
  94. package/kits/docs/evolcore/agent-create.md +159 -0
  95. package/kits/docs/evolcore/agent.md +6 -0
  96. package/kits/docs/evolcore/group-collaboration.md +256 -0
  97. package/kits/docs/evolcore/group-rules.md +1 -19
  98. package/kits/docs/evolcore/group.md +3 -1
  99. package/kits/docs/evolcore/menu-client.md +34 -0
  100. package/kits/docs/evolcore/trigger.md +6 -3
  101. package/kits/docs/prompt-loading-architecture.md +6 -0
  102. package/kits/eck_message_manifest.json +6 -6
  103. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  104. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  105. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  106. package/kits/schemas/_meta.json +3 -2
  107. package/kits/schemas/agent-config.schema.12.json +427 -0
  108. package/kits/templates/message-fragments/item.md +1 -1
  109. package/kits/templates/roles/visitor.json +1 -0
  110. package/kits/templates/system-fragments/bootstrap.md +10 -2
  111. package/kits/templates/system-fragments/commands.md +2 -2
  112. package/package.json +11 -3
package/dist/ipc.js CHANGED
@@ -8,18 +8,22 @@ import path from 'path';
8
8
  import { logger } from './utils/logger.js';
9
9
  import { withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
10
10
  import { fileCache } from './core/daemon-file-cache.js';
11
+ import { DAEMON_RUNTIME_EPOCH_ENV } from './cli/task-context.js';
11
12
  import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
12
13
  import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
13
- import { getWindowsInstanceSocketPathCandidates, resolvePaths } from './paths.js';
14
+ import { getWindowsInstanceSocketPathCandidates, normalizeIpcEndpoint, resolvePaths } from './paths.js';
14
15
  import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
15
16
  import { classifyProcessTree, ProcessTreeSampler, readProcessMetrics } from './utils/process-tree-stats.js';
16
17
  import { readSystemMemoryUsage } from './utils/system-memory.js';
17
18
  import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
18
19
  import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
20
+ import { remoteAccessService } from './core/auth/remote-access-store.js';
21
+ import { setModelCatalogDaemonOwner } from './core/model/model-catalog.js';
19
22
  const isWindows = process.platform === 'win32';
20
- const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
23
+ const isNamedPipe = (p) => isWindows && p.toLowerCase().startsWith('\\\\.\\pipe\\');
21
24
  const CPU_SAMPLE_INTERVAL_MS = 1000;
22
25
  const PROCESS_TREE_SAMPLE_INTERVAL_MS = 5000;
26
+ const MONITOR_LEASE_TTL_MS = 15_000;
23
27
  const PROCESS_TREE_SAMPLE_WARN_MS = 250;
24
28
  const EVENT_LOOP_LAG_WARN_MS = 100;
25
29
  const SAMPLE_WARN_LOG_INTERVAL_MS = 60_000;
@@ -42,8 +46,15 @@ const LOCAL_CONTROL_COMMANDS = new Set([
42
46
  'evolagent.load',
43
47
  'evolagent.resync',
44
48
  'menu.exec',
49
+ 'model.catalog',
45
50
  'monitor-snapshot',
51
+ 'monitor-subscribe',
52
+ 'monitor-unsubscribe',
46
53
  'shutdown',
54
+ 'remote-auth.pair-code',
55
+ 'remote-auth.issue-local',
56
+ 'remote-auth.redeem',
57
+ 'remote-auth.validate',
47
58
  ]);
48
59
  let ipcQuerySequence = 0;
49
60
  function ipcLogToken(value, fallback = 'unknown') {
@@ -80,9 +91,9 @@ export function summarizeMonitorAgents(agents, aids) {
80
91
  };
81
92
  }
82
93
  export class IpcServer {
83
- socketPath;
84
94
  getStatus;
85
95
  commandExecutor;
96
+ socketPath;
86
97
  server = null;
87
98
  agentRegistry;
88
99
  agentApplicationService;
@@ -122,6 +133,8 @@ export class IpcServer {
122
133
  wecomContactBindExecutor;
123
134
  wechatContactBindExecutor;
124
135
  controlToken;
136
+ daemonRuntimeEpoch;
137
+ remoteAccessService = remoteAccessService;
125
138
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
126
139
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
127
140
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -138,6 +151,7 @@ export class IpcServer {
138
151
  processTreeWorkerRequestStartedAt = 0;
139
152
  processTreeWorkerRequestTimer = null;
140
153
  processTreeSamplingInFlight = false;
154
+ processTreeMonitorLeases = new Map();
141
155
  lastCpuTickMono = null;
142
156
  eventLoopLagMs = 0;
143
157
  maxEventLoopLagMs = 0;
@@ -155,9 +169,9 @@ export class IpcServer {
155
169
  lastError: null,
156
170
  };
157
171
  constructor(socketPath, getStatus, commandExecutor) {
158
- this.socketPath = socketPath;
159
172
  this.getStatus = getStatus;
160
173
  this.commandExecutor = commandExecutor;
174
+ this.socketPath = normalizeIpcEndpoint(socketPath);
161
175
  }
162
176
  /** Inject EvolAgentRegistry for evolagent.* IPC handlers */
163
177
  setAgentRegistry(registry) {
@@ -171,6 +185,14 @@ export class IpcServer {
171
185
  setControlToken(token) {
172
186
  this.controlToken = Buffer.from(token);
173
187
  }
188
+ /** Use the daemon-owned remote access service for local consumers such as ECWeb. */
189
+ setRemoteAccessService(service) {
190
+ this.remoteAccessService = service;
191
+ }
192
+ /** Set the process-lifetime marker used to reject stale managed children. */
193
+ setDaemonRuntimeEpoch(epoch) {
194
+ this.daemonRuntimeEpoch = epoch;
195
+ }
174
196
  /** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
175
197
  setMenuExecutor(executor) {
176
198
  this.menuExecutor = executor;
@@ -223,6 +245,87 @@ export class IpcServer {
223
245
  setAgentStatsProvider(provider) {
224
246
  this.agentStatsProvider = provider;
225
247
  }
248
+ /** Single current snapshot shared by ECWeb IPC and the daemon Menu view. */
249
+ getMonitorSnapshot(leaseId) {
250
+ if (typeof leaseId === 'string' && leaseId.trim())
251
+ this.refreshProcessTreeMonitor(leaseId);
252
+ else
253
+ this.sampleProcessTree();
254
+ const mem = process.memoryUsage();
255
+ const systemMemory = readSystemMemoryUsage();
256
+ const aids = this.aunAidProvider ? this.aunAidProvider() : [];
257
+ const aidStats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
258
+ const statsMap = new Map(aidStats.map(stat => [stat.aid, stat]));
259
+ const agentInfos = this.agentRegistry ? this.agentRegistry.list() : [];
260
+ const agentRuntimeStats = this.agentStatsProvider ? this.agentStatsProvider() : [];
261
+ const agentRuntimeStatsMap = new Map(agentRuntimeStats.map(stat => [stat.aid, stat]));
262
+ const aidStateMap = new Map(aids.map(state => [state.agentName || state.aid, state]));
263
+ const processTree = this.processTreeSnapshot;
264
+ const processTreeCpu = processTree?.processCount ? processTree.cpuPercent : this.cpuPercent;
265
+ const processTreeRss = processTree?.processCount ? processTree.rss : mem.rss;
266
+ const processTreeDetails = processTree ? {
267
+ ...processTree,
268
+ processes: classifyProcessTree(processTree.processes, process.pid, this.associatedProcessKinds),
269
+ } : {
270
+ rootPid: process.pid,
271
+ rootPids: [process.pid],
272
+ processCount: 1,
273
+ cpuPercent: Math.round(this.cpuPercent * 10) / 10,
274
+ rss: mem.rss,
275
+ processes: [{
276
+ pid: process.pid,
277
+ ppid: process.ppid,
278
+ name: 'evolcore',
279
+ cpuPercent: Math.round(this.cpuPercent * 10) / 10,
280
+ rss: mem.rss,
281
+ kind: 'daemon',
282
+ }],
283
+ };
284
+ return {
285
+ ok: true,
286
+ snapshot: {
287
+ ts: Date.now(),
288
+ uptimeMs: Math.round(process.uptime() * 1000),
289
+ cpuCount: os.cpus().length,
290
+ memory: {
291
+ rss: processTreeRss,
292
+ heapUsed: mem.heapUsed,
293
+ heapTotal: mem.heapTotal,
294
+ external: mem.external,
295
+ },
296
+ cpuPercent: processTreeCpu,
297
+ mainProcess: {
298
+ pid: process.pid,
299
+ cpuPercent: Math.round(this.cpuPercent * 10) / 10,
300
+ memory: mem,
301
+ },
302
+ processTree: processTreeDetails,
303
+ sampling: {
304
+ cpuIntervalMs: CPU_SAMPLE_INTERVAL_MS,
305
+ processTreeIntervalMs: PROCESS_TREE_SAMPLE_INTERVAL_MS,
306
+ eventLoopLagMs: this.eventLoopLagMs,
307
+ maxEventLoopLagMs: this.maxEventLoopLagMs,
308
+ processTree: { ...this.processTreeSampling },
309
+ },
310
+ system: {
311
+ ...systemMemory,
312
+ cpuPercent: Math.round(this.sysCpuPercent * 10) / 10,
313
+ loadAvg: os.loadavg(),
314
+ },
315
+ stats: this.statsProvider ? this.statsProvider() : null,
316
+ agentSummary: summarizeMonitorAgents(agentInfos, aids),
317
+ agents: agentInfos.map(agent => ({
318
+ aid: agent.aid,
319
+ agentName: agent.name,
320
+ status: agent.status,
321
+ connectionStatus: aidStateMap.get(agent.aid)?.status ?? (agent.status === 'disabled' ? 'disabled' : 'unavailable'),
322
+ online: agent.status !== 'disabled' && aidStateMap.get(agent.aid)?.status === 'connected',
323
+ stats: statsMap.get(agent.aid) ?? null,
324
+ runtimeStats: agentRuntimeStatsMap.get(agent.aid) ?? null,
325
+ })),
326
+ },
327
+ };
328
+ }
226
329
  /** Inject queue snapshot provider for queue-snapshot IPC handler (query mode) */
227
330
  setQueueSnapshotProvider(provider) {
228
331
  this.queueSnapshotProvider = provider;
@@ -296,11 +399,10 @@ export class IpcServer {
296
399
  setWechatContactBindExecutor(executor) {
297
400
  this.wechatContactBindExecutor = executor;
298
401
  }
299
- /** Start the background CPU and process-tree sampling loops. Call after start(). */
402
+ /** Start the lightweight background CPU sampling loop. Call after start(). */
300
403
  startCpuTracking() {
301
404
  if (this.cpuTimer)
302
405
  return;
303
- this.sampleProcessTree();
304
406
  this.lastCpuTickMono = performance.now();
305
407
  this.cpuTimer = setInterval(() => {
306
408
  const tickMono = performance.now();
@@ -342,17 +444,30 @@ export class IpcServer {
342
444
  }, CPU_SAMPLE_INTERVAL_MS);
343
445
  // Don't keep the event loop alive for sampling alone.
344
446
  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?.();
349
447
  }
350
- /** Stop the CPU and process-tree sampling loops. */
448
+ /** Stop all CPU and process-tree sampling loops. */
351
449
  stopCpuTracking() {
352
450
  if (this.cpuTimer) {
353
451
  clearInterval(this.cpuTimer);
354
452
  this.cpuTimer = null;
355
453
  }
454
+ this.processTreeMonitorLeases.clear();
455
+ this.stopProcessTreeTracking();
456
+ this.lastCpuTickMono = null;
457
+ }
458
+ startProcessTreeTracking() {
459
+ if (this.processTreeTimer)
460
+ return;
461
+ this.sampleProcessTree();
462
+ this.processTreeTimer = setInterval(() => {
463
+ this.pruneExpiredMonitorLeases();
464
+ if (this.processTreeMonitorLeases.size === 0)
465
+ return;
466
+ this.sampleProcessTree();
467
+ }, PROCESS_TREE_SAMPLE_INTERVAL_MS);
468
+ this.processTreeTimer.unref?.();
469
+ }
470
+ stopProcessTreeTracking() {
356
471
  if (this.processTreeTimer) {
357
472
  clearInterval(this.processTreeTimer);
358
473
  this.processTreeTimer = null;
@@ -372,7 +487,31 @@ export class IpcServer {
372
487
  this.processTreeWorker = null;
373
488
  if (worker)
374
489
  void worker.terminate();
375
- this.lastCpuTickMono = null;
490
+ this.processTreeSnapshot = null;
491
+ this.associatedProcessKinds.clear();
492
+ }
493
+ acquireProcessTreeMonitor(leaseId) {
494
+ this.processTreeMonitorLeases.set(leaseId, Date.now() + MONITOR_LEASE_TTL_MS);
495
+ this.startProcessTreeTracking();
496
+ }
497
+ refreshProcessTreeMonitor(leaseId) {
498
+ if (typeof leaseId !== 'string' || !this.processTreeMonitorLeases.has(leaseId))
499
+ return;
500
+ this.processTreeMonitorLeases.set(leaseId, Date.now() + MONITOR_LEASE_TTL_MS);
501
+ }
502
+ releaseProcessTreeMonitor(leaseId) {
503
+ this.processTreeMonitorLeases.delete(leaseId);
504
+ if (this.processTreeMonitorLeases.size === 0)
505
+ this.stopProcessTreeTracking();
506
+ }
507
+ pruneExpiredMonitorLeases() {
508
+ const now = Date.now();
509
+ for (const [leaseId, expiresAt] of this.processTreeMonitorLeases) {
510
+ if (expiresAt <= now)
511
+ this.processTreeMonitorLeases.delete(leaseId);
512
+ }
513
+ if (this.processTreeMonitorLeases.size === 0)
514
+ this.stopProcessTreeTracking();
376
515
  }
377
516
  sampleProcessTree() {
378
517
  if (isWindows) {
@@ -577,6 +716,7 @@ export class IpcServer {
577
716
  return roots;
578
717
  }
579
718
  start() {
719
+ setModelCatalogDaemonOwner(true);
580
720
  if (this.server)
581
721
  return Promise.resolve();
582
722
  // Remove stale socket file (Unix only — named pipes auto-cleanup on process exit)
@@ -639,6 +779,7 @@ export class IpcServer {
639
779
  });
640
780
  }
641
781
  stop() {
782
+ setModelCatalogDaemonOwner(false);
642
783
  if (this.server) {
643
784
  this.server.close();
644
785
  this.server = null;
@@ -654,7 +795,31 @@ export class IpcServer {
654
795
  if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
655
796
  return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
656
797
  }
798
+ if (typeof cmd.sessionId === 'string' && cmd.sessionId
799
+ && typeof cmd.delegationToken === 'string' && cmd.delegationToken
800
+ && this.daemonRuntimeEpoch
801
+ && cmd.daemonRuntimeEpoch !== this.daemonRuntimeEpoch) {
802
+ logger.warn(`[IPC] stale delegation rejected: session=${ipcLogToken(cmd.sessionId)} type=${ipcLogToken(cmd.type)} cause=daemon_runtime_epoch_mismatch`);
803
+ return {
804
+ ok: false,
805
+ code: 'DAEMON_RESTARTED',
806
+ 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',
807
+ };
808
+ }
657
809
  switch (cmd.type) {
810
+ case 'model.catalog': {
811
+ if ((cmd.self !== undefined && typeof cmd.self !== 'string')
812
+ || (cmd.baseagent !== undefined && !['claude', 'codex', 'ecagent', 'gemini'].includes(cmd.baseagent))) {
813
+ return { ok: false, error: 'Invalid model catalog scope' };
814
+ }
815
+ try {
816
+ const { getLocalCatalog } = await import('./core/model/model-catalog.js');
817
+ return { ok: true, snapshot: await getLocalCatalog(cmd.self, cmd.baseagent, cmd.refresh === true) };
818
+ }
819
+ catch {
820
+ return { ok: false, error: 'Unable to resolve model catalog configuration' };
821
+ }
822
+ }
658
823
  case 'status':
659
824
  return this.getStatus();
660
825
  case 'codex-pre-tool-use': {
@@ -670,6 +835,7 @@ export class IpcServer {
670
835
  threadId: cmd.threadId,
671
836
  toolName: cmd.toolName,
672
837
  toolInput: cmd.toolInput,
838
+ callId: typeof cmd.callId === 'string' ? cmd.callId : undefined,
673
839
  signal: requestSignal,
674
840
  });
675
841
  }
@@ -711,6 +877,19 @@ export class IpcServer {
711
877
  ttlMs: cmd.ttlMs,
712
878
  });
713
879
  }
880
+ case 'remote-auth.pair-code': {
881
+ const pair = this.remoteAccessService.getPairingCode(typeof cmd.authorizedBy === 'string' ? cmd.authorizedBy : undefined);
882
+ return { ok: true, ...pair };
883
+ }
884
+ case 'remote-auth.issue-local': {
885
+ return this.remoteAccessService.issueLocalToken(cmd.label);
886
+ }
887
+ case 'remote-auth.redeem': {
888
+ return this.remoteAccessService.redeemPairingCode(cmd.code, cmd.label, cmd.authorizedBy);
889
+ }
890
+ case 'remote-auth.validate': {
891
+ return { ok: true, ...this.remoteAccessService.validateToken(cmd.token) };
892
+ }
714
893
  case 'bind.status': {
715
894
  if (!this.bindExecutor)
716
895
  return { ok: false, error: 'bind executor not configured' };
@@ -1327,85 +1506,21 @@ export class IpcServer {
1327
1506
  return { ok: false, error: e?.message ?? String(e) };
1328
1507
  }
1329
1508
  }
1330
- case 'monitor-snapshot': {
1331
- // watch web Monitor 页用:进程级 + 系统级运行指标 + 全局 stats + per-agent 汇总。
1332
- const mem = process.memoryUsage();
1333
- const systemMemory = readSystemMemoryUsage();
1334
- const aids = this.aunAidProvider ? this.aunAidProvider() : [];
1335
- const aidStats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
1336
- const statsMap = new Map(aidStats.map((s) => [s.aid, s]));
1337
- const agentInfos = this.agentRegistry ? this.agentRegistry.list() : [];
1338
- const agentRuntimeStats = this.agentStatsProvider ? this.agentStatsProvider() : [];
1339
- const agentRuntimeStatsMap = new Map(agentRuntimeStats.map((s) => [s.aid, s]));
1340
- const aidStateMap = new Map(aids.map((state) => [state.agentName || state.aid, state]));
1341
- const agentSummary = summarizeMonitorAgents(agentInfos, aids);
1342
- const processTree = this.processTreeSnapshot;
1343
- const processTreeCpu = processTree?.processCount ? processTree.cpuPercent : this.cpuPercent;
1344
- const processTreeRss = processTree?.processCount ? processTree.rss : mem.rss;
1345
- const processTreeDetails = processTree ? {
1346
- ...processTree,
1347
- processes: classifyProcessTree(processTree.processes, process.pid, this.associatedProcessKinds),
1348
- } : {
1349
- rootPid: process.pid,
1350
- rootPids: [process.pid],
1351
- processCount: 1,
1352
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1353
- rss: mem.rss,
1354
- processes: [{
1355
- pid: process.pid,
1356
- ppid: process.ppid,
1357
- name: 'evolcore',
1358
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1359
- rss: mem.rss,
1360
- kind: 'daemon',
1361
- }],
1362
- };
1363
- return {
1364
- ok: true,
1365
- snapshot: {
1366
- ts: Date.now(),
1367
- uptimeMs: Math.round(process.uptime() * 1000),
1368
- cpuCount: os.cpus().length,
1369
- // 进程级:daemon + 子进程 + 登记在册的关联进程树
1370
- memory: {
1371
- rss: processTreeRss,
1372
- heapUsed: mem.heapUsed,
1373
- heapTotal: mem.heapTotal,
1374
- external: mem.external,
1375
- },
1376
- cpuPercent: processTreeCpu,
1377
- mainProcess: {
1378
- pid: process.pid,
1379
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1380
- memory: mem,
1381
- },
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
- },
1390
- // 系统级:整机
1391
- system: {
1392
- ...systemMemory,
1393
- cpuPercent: Math.round(this.sysCpuPercent * 10) / 10,
1394
- loadAvg: os.loadavg(), // [1m, 5m, 15m](Windows 恒 0)
1395
- },
1396
- stats: this.statsProvider ? this.statsProvider() : null,
1397
- agentSummary,
1398
- agents: agentInfos.map((a) => ({
1399
- aid: a.aid,
1400
- agentName: a.name,
1401
- status: a.status,
1402
- connectionStatus: aidStateMap.get(a.aid)?.status ?? (a.status === 'disabled' ? 'disabled' : 'unavailable'),
1403
- online: a.status !== 'disabled' && aidStateMap.get(a.aid)?.status === 'connected',
1404
- stats: statsMap.get(a.aid) ?? null,
1405
- runtimeStats: agentRuntimeStatsMap.get(a.aid) ?? null,
1406
- })),
1407
- },
1408
- };
1509
+ case 'monitor-snapshot':
1510
+ return this.getMonitorSnapshot(cmd.leaseId);
1511
+ case 'monitor-subscribe': {
1512
+ const leaseId = typeof cmd.leaseId === 'string' ? cmd.leaseId.trim() : '';
1513
+ if (!leaseId || leaseId.length > 128)
1514
+ return { ok: false, error: 'invalid monitor lease id' };
1515
+ this.acquireProcessTreeMonitor(leaseId);
1516
+ return { ok: true, leaseTtlMs: MONITOR_LEASE_TTL_MS };
1517
+ }
1518
+ case 'monitor-unsubscribe': {
1519
+ const leaseId = typeof cmd.leaseId === 'string' ? cmd.leaseId.trim() : '';
1520
+ if (!leaseId || leaseId.length > 128)
1521
+ return { ok: false, error: 'invalid monitor lease id' };
1522
+ this.releaseProcessTreeMonitor(leaseId);
1523
+ return { ok: true };
1409
1524
  }
1410
1525
  default:
1411
1526
  return { error: `unknown command: ${cmd.type}` };
@@ -1425,14 +1540,15 @@ export class IpcServer {
1425
1540
  */
1426
1541
  /** Pass `null` as timeoutMs to wait without a client-side deadline. */
1427
1542
  export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
1428
- let socketCandidates = [socketPath];
1429
- if (isNamedPipe(socketPath)) {
1543
+ const endpoint = normalizeIpcEndpoint(socketPath);
1544
+ let socketCandidates = [endpoint];
1545
+ if (isNamedPipe(endpoint)) {
1430
1546
  const paths = resolvePaths();
1431
- if (socketPath === paths.socket)
1547
+ if (endpoint === paths.socket)
1432
1548
  socketCandidates = getWindowsInstanceSocketPathCandidates(paths.root);
1433
1549
  }
1434
1550
  if (socketCandidates.length <= 1)
1435
- return ipcQueryOnce(socketPath, cmd, timeoutMs);
1551
+ return ipcQueryOnce(endpoint, cmd, timeoutMs);
1436
1552
  return (async () => {
1437
1553
  const startedAt = Date.now();
1438
1554
  for (const candidate of socketCandidates) {
@@ -1464,7 +1580,13 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1464
1580
  && typeof authenticatedCmd.delegationCommandHash !== 'string'
1465
1581
  ? hashCurrentDelegatedCommand()
1466
1582
  : undefined;
1467
- const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
1583
+ const request = {
1584
+ ...(delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd),
1585
+ ...(process.env[DAEMON_RUNTIME_EPOCH_ENV]
1586
+ && typeof authenticatedCmd.delegationToken === 'string'
1587
+ ? { daemonRuntimeEpoch: process.env[DAEMON_RUNTIME_EPOCH_ENV] }
1588
+ : {}),
1589
+ };
1468
1590
  return new Promise((resolve) => {
1469
1591
  const startedAtMono = performance.now();
1470
1592
  const queryId = nextIpcQueryId(request);
package/dist/paths.js CHANGED
@@ -179,6 +179,24 @@ function windowsSocketPathForRoot(root) {
179
179
  const hash = crypto.createHash('sha1').update(root).digest('hex').slice(0, 12);
180
180
  return `\\\\.\\pipe\\${WINDOWS_PIPE_PREFIX}-${hash}`;
181
181
  }
182
+ /**
183
+ * Normalize an IPC endpoint before it reaches node:net.
184
+ *
185
+ * Windows does not support Unix-domain socket files at ordinary filesystem
186
+ * paths. Callers that construct an ad-hoc `.sock` path (tests and embedded
187
+ * runtimes in particular) must therefore use the same deterministic named
188
+ * pipe on both the server and client sides. Existing named pipes and all
189
+ * non-Windows endpoints are preserved verbatim.
190
+ */
191
+ export function normalizeIpcEndpoint(endpoint) {
192
+ // Windows object-manager paths are case-insensitive. Preserve an explicit
193
+ // named pipe verbatim even when a caller spells the namespace as `PIPE`.
194
+ if (!isWindows || endpoint.toLowerCase().startsWith('\\\\.\\pipe\\'))
195
+ return endpoint;
196
+ const normalized = path.win32.resolve(endpoint).toLowerCase();
197
+ const hash = crypto.createHash('sha1').update(normalized).digest('hex').slice(0, 20);
198
+ return `\\\\.\\pipe\\${WINDOWS_PIPE_PREFIX}-endpoint-${hash}`;
199
+ }
182
200
  /**
183
201
  * Return the canonical and pre-normalization Windows daemon pipe names.
184
202
  *
@@ -197,6 +197,7 @@ export const proactiveFlow = {
197
197
  const queueMsg = queueLen >= 5
198
198
  ? `⚠️ 有 ${queueLen} 条消息未读,请尽快完成当前任务,或使用 ec ctl queue 读取后同步处理。`
199
199
  : `⚠️ 有 ${queueLen} 条消息未读,可使用 ec ctl queue 读取。`;
200
+ ctx.recordReminder?.({ kind: 'queue_unread', text: queueMsg, queueLength: queueLen });
200
201
  ctx.injectToModel(queueMsg);
201
202
  }
202
203
  }
@@ -208,13 +209,17 @@ export const proactiveFlow = {
208
209
  state.toolReportPending = true;
209
210
  const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
210
211
  const target = state.chatType === 'group' ? '群里' : '对方';
211
- ctx.injectToModel(`⚠️ 工具调用已达到 ${state.toolCount} 次,请立即用 ${cmdHint} 向${target}汇报当前情况和下一步意图。`);
212
+ const reportMsg = `⚠️ 工具调用已达到 ${state.toolCount} 次,请立即用 ${cmdHint} 向${target}汇报当前情况和下一步意图。`;
213
+ ctx.recordReminder?.({ kind: 'tool_report', text: reportMsg, toolCount: state.toolCount });
214
+ ctx.injectToModel(reportMsg);
212
215
  }
213
216
  },
214
217
  // ─── onToolResult:成功发送回执后解除门禁 ───
215
218
  onToolResult(ctx) {
216
219
  const state = ctx.state.get(STATE_KEY);
217
- if (!state || !ctx.isSendCommand(ctx.toolName, ctx.toolInput))
220
+ if (!state)
221
+ return;
222
+ if (!ctx.isSendCommand(ctx.toolName, ctx.toolInput))
218
223
  return;
219
224
  if (!hasSuccessfulSendReceipt(ctx.result, ctx.isError || !!ctx.error))
220
225
  return;
@@ -5,6 +5,10 @@
5
5
  import { resolvePriceRow, BILLING_FNS } from './billing.js';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
+ /** Keep wire/log metadata readable while preserving full precision in storage. */
9
+ export function roundCostForOutput(value) {
10
+ return Number.isFinite(value) ? Number(value.toFixed(4)) : value;
11
+ }
8
12
  // 网关价格表缓存(从用户覆盖层 model-prices-gateway.jsonl 读取)
9
13
  let _gatewayPriceCache = null;
10
14
  let _gatewayPriceCacheTs = 0;
@@ -251,12 +251,24 @@ export class TriggerFeedbackDispatcher {
251
251
  };
252
252
  }
253
253
  if (event?.type === 'trigger:skipped') {
254
+ const interruption = event.executionState === 'interrupted' && event.decisionSource === 'infrastructure'
255
+ ? {
256
+ reasonCode: event.reasonCode ?? event.reason,
257
+ decisionSource: event.decisionSource,
258
+ executionState: event.executionState,
259
+ ...(event.generation !== undefined ? { generation: event.generation } : {}),
260
+ ...(event.currentGeneration !== undefined ? { currentGeneration: event.currentGeneration } : {}),
261
+ }
262
+ : undefined;
254
263
  return {
255
- status: 'failed',
264
+ status: interruption ? 'skipped' : 'failed',
256
265
  reason: event.reason,
266
+ ...(interruption ? { interruption } : {}),
257
267
  feedback,
258
268
  effects: [this.inboundEffect('success', target, session.id, startedAt)],
259
- error: { code: event.reason || 'target_session_skipped', message: event.reason || 'target session skipped' },
269
+ error: interruption
270
+ ? null
271
+ : { code: event.reason || 'target_session_skipped', message: event.reason || 'target session skipped' },
260
272
  };
261
273
  }
262
274
  return {
@@ -92,7 +92,9 @@ export class TriggerDefinitionManager {
92
92
  assertExplicitAunDeliveryRoutes(updated);
93
93
  this.assertAgent(updated);
94
94
  if (existing.execution.baseagent !== updated.execution.baseagent) {
95
- throw new Error('execution.baseagent is immutable; create a new Trigger to use another baseagent');
95
+ if (existing.execution.type !== 'trigger_session' || updated.execution.type !== 'trigger_session') {
96
+ throw new Error('execution.baseagent is only mutable between trigger_session Triggers');
97
+ }
96
98
  }
97
99
  updated.createdAt = existing.createdAt;
98
100
  updated.updatedAt = Date.now();