evolcore 0.0.22 → 0.0.24

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 (117) hide show
  1. package/CHANGELOG.md +74 -14
  2. package/README.md +4 -0
  3. package/bin/codex-managed-acl.ps1 +160 -0
  4. package/bin/codex-managed-hook.mjs +127 -10
  5. package/bin/ec-safe-output.js +85 -3
  6. package/bin/install-codex-managed-hooks.mjs +384 -95
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +30 -2
  9. package/dist/agents/claude-runner.js +179 -240
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
  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 +369 -0
  14. package/dist/agents/codex-runner.js +839 -192
  15. package/dist/agents/ecagent-runner.js +27 -34
  16. package/dist/agents/gemini-runner.js +62 -8
  17. package/dist/agents/runner-types.js +1 -1
  18. package/dist/aun/aid/managed-operation.js +28 -6
  19. package/dist/aun/aid/store.js +43 -0
  20. package/dist/aun/menu-client.js +272 -0
  21. package/dist/aun/msg/group.js +65 -4
  22. package/dist/aun/msg/index.js +1 -1
  23. package/dist/aun/msg/managed-operation.js +57 -5
  24. package/dist/aun/outbox.js +56 -18
  25. package/dist/aun/rpc/client.js +7 -1
  26. package/dist/channels/aun.js +178 -77
  27. package/dist/channels/daemon.js +7 -1
  28. package/dist/channels/dingtalk.js +1 -0
  29. package/dist/channels/feishu.js +10 -4
  30. package/dist/channels/wechat.js +81 -10
  31. package/dist/channels/wecom.js +2 -1
  32. package/dist/cli/agent-command.js +29 -3
  33. package/dist/cli/agent-create.js +538 -0
  34. package/dist/cli/agent.js +26 -675
  35. package/dist/cli/aun-commands.js +108 -24
  36. package/dist/cli/command-log.js +21 -2
  37. package/dist/cli/ctl-command.js +2 -0
  38. package/dist/cli/daemon-commands.js +289 -61
  39. package/dist/cli/index.js +22 -2
  40. package/dist/cli/init.js +2 -10
  41. package/dist/cli/managed-command-guard.js +3 -0
  42. package/dist/cli/menu-client.js +95 -0
  43. package/dist/cli/model.js +23 -8
  44. package/dist/cli/restart-monitor.js +432 -8
  45. package/dist/cli/stats.js +110 -0
  46. package/dist/cli/task-context.js +12 -2
  47. package/dist/cli/trigger-command.js +9 -1
  48. package/dist/config/builtin-roles.js +2 -0
  49. package/dist/config/schema-migration.js +14 -2
  50. package/dist/core/auth/agent-delegation.js +10 -7
  51. package/dist/core/auth/auth-gateway.js +3 -2
  52. package/dist/core/auth/authorization-audit.js +75 -18
  53. package/dist/core/auth/operation-authorizer.js +63 -3
  54. package/dist/core/auth/operation-catalog.js +14 -2
  55. package/dist/core/auth/remote-access-store.js +136 -0
  56. package/dist/core/baseagent-loader.js +4 -4
  57. package/dist/core/bootstrap-messages.js +39 -18
  58. package/dist/core/bootstrap-service.js +32 -26
  59. package/dist/core/capability/capability-manager.js +8 -4
  60. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  61. package/dist/core/command/agent-control.js +7 -2
  62. package/dist/core/command/command-handler.js +128 -30
  63. package/dist/core/command/daemon-diagnostics.js +448 -0
  64. package/dist/core/command/menu-catalog.js +6 -1
  65. package/dist/core/command/menu-handler.js +410 -98
  66. package/dist/core/command/menu-protocol.js +123 -0
  67. package/dist/core/command/slash-gate.js +4 -6
  68. package/dist/core/command/slash-handler.js +199 -70
  69. package/dist/core/data-migration.js +5 -15
  70. package/dist/core/event-catalog.js +3 -0
  71. package/dist/core/handoff/runtime.js +3 -1
  72. package/dist/core/handoff/store.js +7 -1
  73. package/dist/core/message/im-renderer.js +13 -6
  74. package/dist/core/message/message-bridge.js +5 -36
  75. package/dist/core/message/message-queue.js +20 -15
  76. package/dist/core/message/message-utils.js +6 -3
  77. package/dist/core/message/response-engine.js +147 -94
  78. package/dist/core/model/catalog-service.js +310 -0
  79. package/dist/core/model/model-catalog.js +134 -334
  80. package/dist/core/model/model-diagnostics.js +4 -5
  81. package/dist/core/model/model-fallback.js +9 -16
  82. package/dist/core/model/model-permission.js +5 -2
  83. package/dist/core/permission/approval-gateway.js +183 -32
  84. package/dist/core/permission/protected-paths.js +63 -4
  85. package/dist/core/permission/sandbox-runtime.js +4 -2
  86. package/dist/core/permission/tool-policy.js +257 -31
  87. package/dist/core/runtime-lock.js +13 -8
  88. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  89. package/dist/core/session/session-manager.js +49 -13
  90. package/dist/core/session/session-renew.js +1 -0
  91. package/dist/index.js +214 -57
  92. package/dist/ipc.js +191 -102
  93. package/dist/paths.js +23 -1
  94. package/dist/stats/billing.js +18 -4
  95. package/dist/trigger/manager.js +3 -1
  96. package/dist/trigger/parser.js +77 -13
  97. package/dist/trigger/patch.js +104 -1
  98. package/dist/utils/atomic-write.js +26 -2
  99. package/dist/utils/cross-platform.js +436 -1
  100. package/dist/utils/ecweb-supervisor.js +11 -2
  101. package/dist/utils/ecweb-utils.js +8 -26
  102. package/dist/utils/error-utils.js +16 -2
  103. package/dist/utils/model-prices.jsonl +23 -20
  104. package/dist/utils/npm-ops.js +70 -15
  105. package/dist/utils/windows-autostart.js +1 -1
  106. package/kits/docs/evolcore/INDEX.md +1 -0
  107. package/kits/docs/evolcore/agent-create.md +13 -0
  108. package/kits/docs/evolcore/group-collaboration.md +7 -2
  109. package/kits/docs/evolcore/menu-client.md +34 -0
  110. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  111. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  112. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  113. package/kits/templates/roles/admin.json +2 -0
  114. package/kits/templates/roles/visitor.json +1 -0
  115. package/kits/templates/system-fragments/bootstrap.md +8 -1
  116. package/package.json +16 -4
  117. package/dist/utils/codex-cli.js +0 -43
package/dist/ipc.js CHANGED
@@ -17,6 +17,8 @@ import { classifyProcessTree, ProcessTreeSampler, readProcessMetrics } from './u
17
17
  import { readSystemMemoryUsage } from './utils/system-memory.js';
18
18
  import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
19
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';
20
22
  const isWindows = process.platform === 'win32';
21
23
  const isNamedPipe = (p) => isWindows && p.toLowerCase().startsWith('\\\\.\\pipe\\');
22
24
  const CPU_SAMPLE_INTERVAL_MS = 1000;
@@ -44,10 +46,15 @@ const LOCAL_CONTROL_COMMANDS = new Set([
44
46
  'evolagent.load',
45
47
  'evolagent.resync',
46
48
  'menu.exec',
49
+ 'model.catalog',
47
50
  'monitor-snapshot',
48
51
  'monitor-subscribe',
49
52
  'monitor-unsubscribe',
50
53
  'shutdown',
54
+ 'remote-auth.pair-code',
55
+ 'remote-auth.issue-local',
56
+ 'remote-auth.redeem',
57
+ 'remote-auth.validate',
51
58
  ]);
52
59
  let ipcQuerySequence = 0;
53
60
  function ipcLogToken(value, fallback = 'unknown') {
@@ -127,6 +134,7 @@ export class IpcServer {
127
134
  wechatContactBindExecutor;
128
135
  controlToken;
129
136
  daemonRuntimeEpoch;
137
+ remoteAccessService = remoteAccessService;
130
138
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
131
139
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
132
140
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -177,6 +185,10 @@ export class IpcServer {
177
185
  setControlToken(token) {
178
186
  this.controlToken = Buffer.from(token);
179
187
  }
188
+ /** Use the daemon-owned remote access service for local consumers such as ECWeb. */
189
+ setRemoteAccessService(service) {
190
+ this.remoteAccessService = service;
191
+ }
180
192
  /** Set the process-lifetime marker used to reject stale managed children. */
181
193
  setDaemonRuntimeEpoch(epoch) {
182
194
  this.daemonRuntimeEpoch = epoch;
@@ -233,6 +245,87 @@ export class IpcServer {
233
245
  setAgentStatsProvider(provider) {
234
246
  this.agentStatsProvider = provider;
235
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
+ }
236
329
  /** Inject queue snapshot provider for queue-snapshot IPC handler (query mode) */
237
330
  setQueueSnapshotProvider(provider) {
238
331
  this.queueSnapshotProvider = provider;
@@ -623,6 +716,7 @@ export class IpcServer {
623
716
  return roots;
624
717
  }
625
718
  start() {
719
+ setModelCatalogDaemonOwner(true);
626
720
  if (this.server)
627
721
  return Promise.resolve();
628
722
  // Remove stale socket file (Unix only — named pipes auto-cleanup on process exit)
@@ -685,6 +779,7 @@ export class IpcServer {
685
779
  });
686
780
  }
687
781
  stop() {
782
+ setModelCatalogDaemonOwner(false);
688
783
  if (this.server) {
689
784
  this.server.close();
690
785
  this.server = null;
@@ -712,6 +807,19 @@ export class IpcServer {
712
807
  };
713
808
  }
714
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
+ }
715
823
  case 'status':
716
824
  return this.getStatus();
717
825
  case 'codex-pre-tool-use': {
@@ -722,12 +830,21 @@ export class IpcServer {
722
830
  || typeof cmd.toolName !== 'string' || !cmd.toolName) {
723
831
  return { ok: false, applicable: true, decision: 'deny', reason: 'invalid Codex hook request' };
724
832
  }
833
+ // Unmanaged Codex processes carry no credential and are allowed
834
+ // without consulting any runner; see bin/codex-managed-hook.mjs.
835
+ const sessionToken = typeof cmd.sessionToken === 'string' && cmd.sessionToken
836
+ ? cmd.sessionToken
837
+ : undefined;
838
+ if (!sessionToken) {
839
+ return { ok: true, applicable: true, decision: 'allow' };
840
+ }
725
841
  try {
726
842
  return await this.codexPreToolUseExecutor({
727
843
  threadId: cmd.threadId,
728
844
  toolName: cmd.toolName,
729
845
  toolInput: cmd.toolInput,
730
846
  callId: typeof cmd.callId === 'string' ? cmd.callId : undefined,
847
+ sessionToken,
731
848
  signal: requestSignal,
732
849
  });
733
850
  }
@@ -769,6 +886,19 @@ export class IpcServer {
769
886
  ttlMs: cmd.ttlMs,
770
887
  });
771
888
  }
889
+ case 'remote-auth.pair-code': {
890
+ const pair = this.remoteAccessService.getPairingCode(typeof cmd.authorizedBy === 'string' ? cmd.authorizedBy : undefined);
891
+ return { ok: true, ...pair };
892
+ }
893
+ case 'remote-auth.issue-local': {
894
+ return this.remoteAccessService.issueLocalToken(cmd.label);
895
+ }
896
+ case 'remote-auth.redeem': {
897
+ return this.remoteAccessService.redeemPairingCode(cmd.code, cmd.label, cmd.authorizedBy);
898
+ }
899
+ case 'remote-auth.validate': {
900
+ return { ok: true, ...this.remoteAccessService.validateToken(cmd.token) };
901
+ }
772
902
  case 'bind.status': {
773
903
  if (!this.bindExecutor)
774
904
  return { ok: false, error: 'bind executor not configured' };
@@ -1385,87 +1515,8 @@ export class IpcServer {
1385
1515
  return { ok: false, error: e?.message ?? String(e) };
1386
1516
  }
1387
1517
  }
1388
- case 'monitor-snapshot': {
1389
- this.refreshProcessTreeMonitor(cmd.leaseId);
1390
- // watch web Monitor 页用:进程级 + 系统级运行指标 + 全局 stats + per-agent 汇总。
1391
- const mem = process.memoryUsage();
1392
- const systemMemory = readSystemMemoryUsage();
1393
- const aids = this.aunAidProvider ? this.aunAidProvider() : [];
1394
- const aidStats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
1395
- const statsMap = new Map(aidStats.map((s) => [s.aid, s]));
1396
- const agentInfos = this.agentRegistry ? this.agentRegistry.list() : [];
1397
- const agentRuntimeStats = this.agentStatsProvider ? this.agentStatsProvider() : [];
1398
- const agentRuntimeStatsMap = new Map(agentRuntimeStats.map((s) => [s.aid, s]));
1399
- const aidStateMap = new Map(aids.map((state) => [state.agentName || state.aid, state]));
1400
- const agentSummary = summarizeMonitorAgents(agentInfos, aids);
1401
- const processTree = this.processTreeSnapshot;
1402
- const processTreeCpu = processTree?.processCount ? processTree.cpuPercent : this.cpuPercent;
1403
- const processTreeRss = processTree?.processCount ? processTree.rss : mem.rss;
1404
- const processTreeDetails = processTree ? {
1405
- ...processTree,
1406
- processes: classifyProcessTree(processTree.processes, process.pid, this.associatedProcessKinds),
1407
- } : {
1408
- rootPid: process.pid,
1409
- rootPids: [process.pid],
1410
- processCount: 1,
1411
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1412
- rss: mem.rss,
1413
- processes: [{
1414
- pid: process.pid,
1415
- ppid: process.ppid,
1416
- name: 'evolcore',
1417
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1418
- rss: mem.rss,
1419
- kind: 'daemon',
1420
- }],
1421
- };
1422
- return {
1423
- ok: true,
1424
- snapshot: {
1425
- ts: Date.now(),
1426
- uptimeMs: Math.round(process.uptime() * 1000),
1427
- cpuCount: os.cpus().length,
1428
- // 进程级:daemon + 子进程 + 登记在册的关联进程树
1429
- memory: {
1430
- rss: processTreeRss,
1431
- heapUsed: mem.heapUsed,
1432
- heapTotal: mem.heapTotal,
1433
- external: mem.external,
1434
- },
1435
- cpuPercent: processTreeCpu,
1436
- mainProcess: {
1437
- pid: process.pid,
1438
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1439
- memory: mem,
1440
- },
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
- },
1449
- // 系统级:整机
1450
- system: {
1451
- ...systemMemory,
1452
- cpuPercent: Math.round(this.sysCpuPercent * 10) / 10,
1453
- loadAvg: os.loadavg(), // [1m, 5m, 15m](Windows 恒 0)
1454
- },
1455
- stats: this.statsProvider ? this.statsProvider() : null,
1456
- agentSummary,
1457
- agents: agentInfos.map((a) => ({
1458
- aid: a.aid,
1459
- agentName: a.name,
1460
- status: a.status,
1461
- connectionStatus: aidStateMap.get(a.aid)?.status ?? (a.status === 'disabled' ? 'disabled' : 'unavailable'),
1462
- online: a.status !== 'disabled' && aidStateMap.get(a.aid)?.status === 'connected',
1463
- stats: statsMap.get(a.aid) ?? null,
1464
- runtimeStats: agentRuntimeStatsMap.get(a.aid) ?? null,
1465
- })),
1466
- },
1467
- };
1468
- }
1518
+ case 'monitor-snapshot':
1519
+ return this.getMonitorSnapshot(cmd.leaseId);
1469
1520
  case 'monitor-subscribe': {
1470
1521
  const leaseId = typeof cmd.leaseId === 'string' ? cmd.leaseId.trim() : '';
1471
1522
  if (!leaseId || leaseId.length > 128)
@@ -1492,12 +1543,26 @@ export class IpcServer {
1492
1543
  && crypto.timingSafeEqual(provided, this.controlToken);
1493
1544
  }
1494
1545
  }
1495
- /**
1496
- * Query the running EvolCore daemon via Unix socket.
1497
- * Returns null if the service is not running or the socket is unreachable.
1498
- */
1546
+ export class IpcTransportError extends Error {
1547
+ code;
1548
+ endpoint;
1549
+ constructor(code, endpoint) {
1550
+ const hint = code === 'EPERM' || code === 'EACCES'
1551
+ ? '权限被拒绝;Windows 下请检查 daemon 与 CLI 是否使用相同用户和提升权限级别。'
1552
+ : code === 'ETIMEDOUT'
1553
+ ? '等待 daemon 响应超时;请求可能已执行,请先检查状态再重试。'
1554
+ : code === 'ENOENT' || code === 'ECONNREFUSED'
1555
+ ? 'daemon 未运行或 IPC 端点不存在,请先执行 ec start,再重试。'
1556
+ : '请检查 daemon 进程及 IPC 端点状态。';
1557
+ super(`daemon IPC 失败 (${code}): ${endpoint}。${hint}`);
1558
+ this.code = code;
1559
+ this.endpoint = endpoint;
1560
+ this.name = 'IpcTransportError';
1561
+ }
1562
+ }
1563
+ /** Query the daemon; opt in to transport errors instead of the legacy null result. */
1499
1564
  /** Pass `null` as timeoutMs to wait without a client-side deadline. */
1500
- export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
1565
+ export async function ipcQuery(socketPath, cmd, timeoutMs = 3000, options = {}) {
1501
1566
  const endpoint = normalizeIpcEndpoint(socketPath);
1502
1567
  let socketCandidates = [endpoint];
1503
1568
  if (isNamedPipe(endpoint)) {
@@ -1505,22 +1570,35 @@ export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
1505
1570
  if (endpoint === paths.socket)
1506
1571
  socketCandidates = getWindowsInstanceSocketPathCandidates(paths.root);
1507
1572
  }
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;
1573
+ const startedAt = Date.now();
1574
+ let failure;
1575
+ for (const candidate of socketCandidates) {
1576
+ const remainingTimeout = timeoutMs === null
1577
+ ? null
1578
+ : Math.max(0, timeoutMs - (Date.now() - startedAt));
1579
+ if (remainingTimeout === 0) {
1580
+ failure ??= new IpcTransportError('ETIMEDOUT', candidate);
1581
+ break;
1582
+ }
1583
+ try {
1518
1584
  const response = await ipcQueryOnce(candidate, cmd, remainingTimeout);
1519
1585
  if (response !== null)
1520
1586
  return response;
1521
1587
  }
1522
- return null;
1523
- })();
1588
+ catch (error) {
1589
+ if (!(error instanceof IpcTransportError))
1590
+ throw error;
1591
+ // A missing legacy pipe must not hide a permission failure on the canonical pipe.
1592
+ if (!failure || ['ENOENT', 'ECONNREFUSED'].includes(failure.code))
1593
+ failure = error;
1594
+ // Only connection failures are safe to retry against a legacy endpoint.
1595
+ if (!['ENOENT', 'ECONNREFUSED', 'EPERM', 'EACCES'].includes(error.code))
1596
+ break;
1597
+ }
1598
+ }
1599
+ if (options.throwOnError && failure)
1600
+ throw failure;
1601
+ return null;
1524
1602
  }
1525
1603
  function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1526
1604
  let authenticatedCmd = cmd;
@@ -1545,7 +1623,7 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1545
1623
  ? { daemonRuntimeEpoch: process.env[DAEMON_RUNTIME_EPOCH_ENV] }
1546
1624
  : {}),
1547
1625
  };
1548
- return new Promise((resolve) => {
1626
+ return new Promise((resolve, reject) => {
1549
1627
  const startedAtMono = performance.now();
1550
1628
  const queryId = nextIpcQueryId(request);
1551
1629
  const commandType = ipcLogToken(request.type);
@@ -1564,7 +1642,7 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1564
1642
  settled = true;
1565
1643
  logPhase('timeout', ` timeout_ms=${timeoutMs}`);
1566
1644
  conn.destroy();
1567
- resolve(null);
1645
+ reject(new IpcTransportError('ETIMEDOUT', socketPath));
1568
1646
  }, timeoutMs);
1569
1647
  conn.on('connect', () => {
1570
1648
  if (settled)
@@ -1590,7 +1668,10 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1590
1668
  parse = 'invalid_json';
1591
1669
  }
1592
1670
  logPhase('response', ` bytes=${idx + 1} parse=${parse}`);
1593
- resolve(response);
1671
+ if (parse === 'invalid_json')
1672
+ reject(new IpcTransportError('EPROTO', socketPath));
1673
+ else
1674
+ resolve(response);
1594
1675
  conn.destroy();
1595
1676
  }
1596
1677
  });
@@ -1601,7 +1682,15 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
1601
1682
  if (timer)
1602
1683
  clearTimeout(timer);
1603
1684
  logPhase('error', ` code=${error.code ?? 'unknown'}`);
1604
- resolve(null);
1685
+ reject(new IpcTransportError(error.code ?? 'UNKNOWN', socketPath));
1686
+ });
1687
+ conn.on('close', () => {
1688
+ if (settled)
1689
+ return;
1690
+ settled = true;
1691
+ if (timer)
1692
+ clearTimeout(timer);
1693
+ reject(new IpcTransportError('ECONNRESET', socketPath));
1605
1694
  });
1606
1695
  });
1607
1696
  }
package/dist/paths.js CHANGED
@@ -129,11 +129,22 @@ export function agentHandoffDir(aid) {
129
129
  return path.join(agentDataDir(aid), 'handoff');
130
130
  }
131
131
  export function agentOutboxPath(aid) {
132
- return path.join(agentDataDir(aid), 'outbox.jsonl');
132
+ return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'outbox.jsonl');
133
133
  }
134
134
  export function agentActivityOutboxPath(aid) {
135
+ return path.join(resolvePaths().outboxDir, encodeURIComponent(aid), 'activity-outbox.jsonl');
136
+ }
137
+ /** Pre-v4 per-Agent outbox paths retained only for compatibility import. */
138
+ export function legacyAgentOutboxPath(aid) {
139
+ return path.join(agentDataDir(aid), 'outbox.jsonl');
140
+ }
141
+ export function legacyAgentActivityOutboxPath(aid) {
135
142
  return path.join(agentDataDir(aid), 'activity-outbox.jsonl');
136
143
  }
144
+ /** Earliest global outbox layout: data/outbox/<aid>.jsonl. */
145
+ export function legacyFlatOutboxPath(aid) {
146
+ return path.join(resolvePaths().outboxDir, `${aid}.jsonl`);
147
+ }
137
148
  /** Directory-safe channel instance key used by connector runtime state. */
138
149
  function encodePathKey(value) {
139
150
  return encodeURIComponent(value);
@@ -283,6 +294,17 @@ function migrateFromAun() {
283
294
  const markerPath = path.join(newRoot, MIGRATION_MARKER);
284
295
  if (fs.existsSync(markerPath))
285
296
  return;
297
+ // Test workers use isolated temporary homes. Copying the developer's
298
+ // machine-wide ~/.aun tree into every fresh fixture is both unnecessary and
299
+ // can make process-boundary tests exceed their timeout. Production roots
300
+ // retain the normal one-time migration behavior.
301
+ if (process.env.EVOLCORE_TEST === '1') {
302
+ try {
303
+ fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
304
+ }
305
+ catch { }
306
+ return;
307
+ }
286
308
  const oldRoot = path.join(os.homedir(), '.aun');
287
309
  if (!fs.existsSync(oldRoot) || oldRoot === newRoot) {
288
310
  try {
@@ -1,11 +1,13 @@
1
1
  /**
2
- * billing.ts — 读 model-prices.jsonl,按 billing_fn 调对应算法函数计算费用。
2
+ * billing.ts — 读 model-prices.jsonl,按模型参数中的 billing_fn 计算费用。
3
3
  * 费用查询时实时计算,不存入 DB(价格可能变动)。
4
4
  */
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { getPackageRoot } from '../paths.js';
8
- // 价格表缓存(进程内,5 分钟 TTL)
8
+ // 模型参数缓存(进程内,5 分钟 TTL)
9
+ let _parameterCache = null;
10
+ let _parameterCacheTs = 0;
9
11
  let _priceCache = null;
10
12
  let _priceCacheTs = 0;
11
13
  const PRICE_CACHE_TTL = 5 * 60 * 1000;
@@ -44,6 +46,14 @@ function _loadJsonlMerged(evolcoreHome, filename) {
44
46
  }
45
47
  return results;
46
48
  }
49
+ function loadModelParameters(evolcoreHome) {
50
+ if (_parameterCache && Date.now() - _parameterCacheTs < PRICE_CACHE_TTL)
51
+ return _parameterCache;
52
+ const params = _loadJsonlMerged(evolcoreHome, 'model-prices.jsonl');
53
+ _parameterCache = params;
54
+ _parameterCacheTs = Date.now();
55
+ return params;
56
+ }
47
57
  let _aliasCache = null;
48
58
  let _aliasCacheTs = 0;
49
59
  function loadAliases(evolcoreHome) {
@@ -64,7 +74,8 @@ function loadPrices(evolcoreHome) {
64
74
  const now = Date.now();
65
75
  if (_priceCache && now - _priceCacheTs < PRICE_CACHE_TTL)
66
76
  return _priceCache;
67
- _priceCache = _loadJsonlMerged(evolcoreHome, 'model-prices.jsonl');
77
+ _priceCache = loadModelParameters(evolcoreHome).filter((record) => (typeof record.billing_fn === 'string'
78
+ && (record.currency === 'USD' || record.currency === 'CNY')));
68
79
  _priceCacheTs = now;
69
80
  return _priceCache;
70
81
  }
@@ -140,13 +151,16 @@ export function calcCost(evolcoreHome, event) {
140
151
  return {};
141
152
  return fn(event, priceRow);
142
153
  }
154
+ // ── Model Specs ──────────────────────────────────────────────────────────────
143
155
  let _specCache = null;
144
156
  let _specCacheTs = 0;
145
157
  function loadSpecs(evolcoreHome) {
146
158
  const now = Date.now();
147
159
  if (_specCache && now - _specCacheTs < PRICE_CACHE_TTL)
148
160
  return _specCache;
149
- _specCache = _loadJsonlMerged(evolcoreHome, 'model-specs.jsonl');
161
+ _specCache = loadModelParameters(evolcoreHome).filter((record) => (typeof record.context_window === 'number'
162
+ && typeof record.max_input_tokens === 'number'
163
+ && typeof record.max_output_tokens === 'number'));
150
164
  _specCacheTs = now;
151
165
  return _specCache;
152
166
  }
@@ -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();
@@ -1,8 +1,11 @@
1
1
  import { CronExpressionParser } from 'cron-parser';
2
2
  const TRIGGER_UPDATE_FLAGS = new Set([
3
3
  'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
4
- 'prompt', 'name',
5
- 'script-file',
4
+ 'prompt', 'name', 'description',
5
+ 'script-file', 'script-runtime', 'script-args', 'script-timeout',
6
+ 'feedback', 'target-channel', 'target-channel-id', 'target-chat-type', 'target-session', 'target-thread-id',
7
+ 'trigger-thread', 'baseagent',
8
+ 'on-error', 'noop-sentinel', 'retry-attempts', 'retry-backoff',
6
9
  'model', 'effort', 'permission',
7
10
  'max-runs', 'max-duration', 'concurrency', 'missed-policy',
8
11
  ]);
@@ -396,18 +399,22 @@ function commonParsed(flags, opts = {}) {
396
399
  if (!opts.update && !execution.value) {
397
400
  return { ok: false, error: '创建 trigger 必须显式指定 --exec script|trigger-session|target-session' };
398
401
  }
402
+ // update 场景下执行类型未知(不重命名 --exec),execution-type 相关校验
403
+ // 全部交给 patch/validation 按现有定义执行。
399
404
  const inferredExecution = execution.value;
400
- if (baseagent.value && inferredExecution !== 'trigger_session') {
401
- return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
402
- }
403
- if (inferredExecution === 'script') {
404
- if (!scriptPath && !opts.update)
405
- return { ok: false, error: '--exec script 需要 --script-path' };
406
- if (scriptPath && !scriptRuntime && !opts.update)
407
- return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
408
- }
409
- else if (!prompt && !opts.update) {
410
- return { ok: false, error: '--prompt 为必填项' };
405
+ if (!opts.update) {
406
+ if (baseagent.value && inferredExecution !== 'trigger_session') {
407
+ return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
408
+ }
409
+ if (inferredExecution === 'script') {
410
+ if (!scriptPath)
411
+ return { ok: false, error: '--exec script 需要 --script-path' };
412
+ if (scriptPath && !scriptRuntime)
413
+ return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
414
+ }
415
+ else if (!prompt) {
416
+ return { ok: false, error: '--prompt 为必填项' };
417
+ }
411
418
  }
412
419
  const targetSessionValue = targetSession.value ?? (targetThreadId ? 'thread' : 'main');
413
420
  const feedbackStrategy = feedback.value ?? (hasTargetField ? 'target' : 'origin');
@@ -645,6 +652,63 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
645
652
  }
646
653
  update.missedPolicy = normalized;
647
654
  }
655
+ const description = optionalFlagString(flags, 'description');
656
+ if ('error' in description)
657
+ return { ok: false, error: description.error };
658
+ if (description.value !== undefined)
659
+ update.description = description.value || null;
660
+ if (flags.has('feedback') && value.feedbackStrategy !== undefined)
661
+ update.feedback = value.feedbackStrategy;
662
+ if (value.targetChannel !== undefined)
663
+ update.targetChannel = value.targetChannel;
664
+ if (value.targetChannelId !== undefined)
665
+ update.targetChannelId = value.targetChannelId;
666
+ if (value.targetChatType !== undefined)
667
+ update.targetChatType = value.targetChatType;
668
+ if (value.targetSession !== undefined && flags.has('target-session'))
669
+ update.targetSession = value.targetSession;
670
+ if (value.targetThreadId !== undefined)
671
+ update.targetThreadId = value.targetThreadId;
672
+ if (value.triggerThread !== undefined)
673
+ update.triggerThread = value.triggerThread;
674
+ if (value.baseagent !== undefined)
675
+ update.baseagent = value.baseagent;
676
+ const onError = optionalFlagString(flags, 'on-error');
677
+ if ('error' in onError)
678
+ return { ok: false, error: onError.error };
679
+ if (onError.value !== undefined) {
680
+ if (onError.value !== 'fail' && onError.value !== 'retry') {
681
+ return { ok: false, error: '--on-error 只接受 fail 或 retry' };
682
+ }
683
+ update.onError = onError.value;
684
+ }
685
+ const noopSentinel = optionalFlagString(flags, 'noop-sentinel');
686
+ if ('error' in noopSentinel)
687
+ return { ok: false, error: noopSentinel.error };
688
+ if (noopSentinel.value !== undefined)
689
+ update.noopSentinel = noopSentinel.value || null;
690
+ const retryAttempts = optionalFlagString(flags, 'retry-attempts');
691
+ if ('error' in retryAttempts)
692
+ return { ok: false, error: retryAttempts.error };
693
+ if (retryAttempts.value !== undefined) {
694
+ if (!/^\d+$/.test(retryAttempts.value))
695
+ return { ok: false, error: '--retry-attempts 必须是非负整数' };
696
+ update.retryAttempts = Number(retryAttempts.value);
697
+ }
698
+ const retryBackoff = optionalFlagString(flags, 'retry-backoff');
699
+ if ('error' in retryBackoff)
700
+ return { ok: false, error: retryBackoff.error };
701
+ if (retryBackoff.value !== undefined) {
702
+ if (!/^\d+$/.test(retryBackoff.value))
703
+ return { ok: false, error: '--retry-backoff 必须是毫秒正整数' };
704
+ update.retryBackoff = Number(retryBackoff.value);
705
+ }
706
+ if (value.scriptRuntime !== undefined)
707
+ update.scriptRuntime = value.scriptRuntime;
708
+ if (value.scriptArgs !== undefined)
709
+ update.scriptArgs = value.scriptArgs;
710
+ if (value.scriptTimeoutMs !== undefined)
711
+ update.scriptTimeout = value.scriptTimeoutMs;
648
712
  if (Object.keys(update).length === 0)
649
713
  return { ok: false, error: '没有可更新的 trigger 参数' };
650
714
  return { ok: true, nameOrId, value: update };