evolcore 0.0.25 → 0.0.26

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 (231) hide show
  1. package/CHANGELOG.md +607 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +160 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent.js +96 -46
  10. package/dist/agents/claude-runner.js +50 -12
  11. package/dist/agents/codex/app-server-client.js +13 -5
  12. package/dist/agents/codex/catalog.js +1 -1
  13. package/dist/agents/codex/diagnostics.js +34 -1
  14. package/dist/agents/codex-runner.js +121 -38
  15. package/dist/agents/ecagent-runner.js +4 -9
  16. package/dist/agents/gemini-runner.js +2 -7
  17. package/dist/agents/runner-types.js +25 -17
  18. package/dist/aun/aid/agentmd.js +81 -8
  19. package/dist/aun/aid/client.js +1 -1
  20. package/dist/aun/aid/identity.js +1 -1
  21. package/dist/aun/aid/index.js +1 -1
  22. package/dist/aun/aid/store.js +17 -13
  23. package/dist/aun/menu-client.js +74 -0
  24. package/dist/aun/msg/group.js +1 -1
  25. package/dist/aun/msg/managed-operation.js +1 -1
  26. package/dist/aun/msg/p2p.js +6 -1
  27. package/dist/aun/msg/upload.js +30 -22
  28. package/dist/channels/aun.js +329 -94
  29. package/dist/cli/agent-command.js +105 -84
  30. package/dist/cli/agent-create.js +112 -10
  31. package/dist/cli/agent.js +16 -5
  32. package/dist/cli/aun-commands.js +297 -296
  33. package/dist/cli/bench.js +20 -20
  34. package/dist/cli/config.js +47 -28
  35. package/dist/cli/contact.js +70 -13
  36. package/dist/cli/ctl-command.js +22 -22
  37. package/dist/cli/daemon-commands.js +105 -40
  38. package/dist/cli/diag.js +31 -0
  39. package/dist/cli/fs-command.js +67 -67
  40. package/dist/cli/handoff-command.js +41 -9
  41. package/dist/cli/index.js +83 -77
  42. package/dist/cli/link-rules.js +21 -21
  43. package/dist/cli/managed-command-guard.js +3 -0
  44. package/dist/cli/menu-client.js +46 -31
  45. package/dist/cli/net-check.js +22 -22
  46. package/dist/cli/queue-command.js +29 -29
  47. package/dist/cli/response.js +30 -30
  48. package/dist/cli/restart-monitor.js +87 -40
  49. package/dist/cli/stats.js +51 -51
  50. package/dist/cli/trigger-command.js +56 -56
  51. package/dist/config/builtin-roles.js +1 -0
  52. package/dist/config/config-operation-service.js +145 -0
  53. package/dist/config/contact-operation-service.js +96 -7
  54. package/dist/config/resolved-config-op.js +27 -7
  55. package/dist/core/agent-application-service.js +2 -1
  56. package/dist/core/auth/authorization-audit.js +20 -12
  57. package/dist/core/auth/operation-authorizer.js +25 -4
  58. package/dist/core/auth/operation-catalog.js +35 -4
  59. package/dist/core/auth/remote-access-store.js +40 -1
  60. package/dist/core/bootstrap-service.js +1 -1
  61. package/dist/core/command/command-handler.js +83 -26
  62. package/dist/core/command/diag-service.js +118 -0
  63. package/dist/core/command/menu-catalog.js +12 -3
  64. package/dist/core/command/menu-handler.js +47 -26
  65. package/dist/core/command/menu-protocol.js +5 -0
  66. package/dist/core/command/menu-token-store.js +41 -8
  67. package/dist/core/command/slash-gate.js +34 -4
  68. package/dist/core/command/slash-handler.js +86 -21
  69. package/dist/core/handoff/runtime.js +205 -27
  70. package/dist/core/handoff/store.js +193 -4
  71. package/dist/core/message/im-renderer.js +72 -21
  72. package/dist/core/message/message-bridge.js +69 -9
  73. package/dist/core/message/message-queue.js +26 -0
  74. package/dist/core/message/message-utils.js +1 -0
  75. package/dist/core/message/response-engine.js +47 -28
  76. package/dist/core/model/catalog-service.js +79 -49
  77. package/dist/core/model/context-window-suffix.js +33 -0
  78. package/dist/core/model/model-catalog.js +1 -1
  79. package/dist/core/model/model-permission.js +12 -3
  80. package/dist/core/permission/ec-command-parser.js +51 -12
  81. package/dist/core/permission/sandbox-runtime.js +36 -14
  82. package/dist/core/permission/tool-policy.js +2 -1
  83. package/dist/index.js +280 -163
  84. package/dist/ipc.js +54 -2
  85. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  86. package/dist/stats/billing.js +41 -15
  87. package/dist/stats/db.js +159 -159
  88. package/dist/stats/eck-vars.js +9 -9
  89. package/dist/stats/price-resolver.js +28 -10
  90. package/dist/stats/query.js +139 -139
  91. package/dist/stats/role-budget.js +6 -6
  92. package/dist/stats/writer.js +51 -51
  93. package/dist/utils/cross-platform.js +30 -1
  94. package/dist/utils/error-dict.json +160 -160
  95. package/dist/utils/error-utils.js +21 -0
  96. package/dist/utils/evolcore-version.js +129 -0
  97. package/dist/utils/logger.js +3 -1
  98. package/dist/utils/macos-autostart.js +22 -22
  99. package/dist/utils/model-prices.jsonl +23 -23
  100. package/dist/utils/npm-ops.js +176 -35
  101. package/dist/utils/welcome.js +22 -22
  102. package/ecagent/LICENSE +21 -21
  103. package/ecagent/NOTICE +11 -11
  104. package/ecagent/README.md +61 -61
  105. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  106. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  107. package/ecagent/dist/harness/messages.js +7 -7
  108. package/kits/docs/GUIDE.md +20 -20
  109. package/kits/docs/INDEX.md +66 -66
  110. package/kits/docs/aun/CHEATSHEET.md +19 -19
  111. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  112. package/kits/docs/channels/aun.md +56 -56
  113. package/kits/docs/channels/feishu.md +56 -56
  114. package/kits/docs/context-assembly.md +364 -364
  115. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  116. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  117. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  118. package/kits/docs/eck_templates/runtime.template.md +19 -19
  119. package/kits/docs/evolcore/INDEX.md +71 -71
  120. package/kits/docs/evolcore/agent-create.md +170 -165
  121. package/kits/docs/evolcore/agent.md +66 -66
  122. package/kits/docs/evolcore/aid.md +60 -60
  123. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  124. package/kits/docs/evolcore/config.md +202 -195
  125. package/kits/docs/evolcore/contact.md +75 -68
  126. package/kits/docs/evolcore/ctl.md +46 -46
  127. package/kits/docs/evolcore/event.md +216 -216
  128. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  129. package/kits/docs/evolcore/fs.md +110 -110
  130. package/kits/docs/evolcore/group-collaboration.md +243 -243
  131. package/kits/docs/evolcore/group-fs.md +17 -17
  132. package/kits/docs/evolcore/group-rules.md +251 -251
  133. package/kits/docs/evolcore/group.md +152 -152
  134. package/kits/docs/evolcore/menu-client.md +42 -34
  135. package/kits/docs/evolcore/model.md +50 -50
  136. package/kits/docs/evolcore/msg.md +166 -165
  137. package/kits/docs/evolcore/response.md +75 -75
  138. package/kits/docs/evolcore/rpc.md +37 -37
  139. package/kits/docs/evolcore/self-summary.md +29 -29
  140. package/kits/docs/evolcore/stats.md +83 -83
  141. package/kits/docs/evolcore/storage.md +50 -50
  142. package/kits/docs/evolcore/trigger.md +575 -575
  143. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  144. package/kits/docs/identity/PATH_OPS.md +16 -16
  145. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  146. package/kits/docs/identity/identity-tools.md +26 -26
  147. package/kits/docs/path-registry.md +62 -62
  148. package/kits/docs/prompt-loading-architecture.md +269 -269
  149. package/kits/docs/venues/aun-group.md +45 -45
  150. package/kits/docs/venues/aun-private.md +10 -10
  151. package/kits/docs/venues/client-desktop.md +10 -10
  152. package/kits/docs/venues/client-mobile.md +10 -10
  153. package/kits/docs/venues/feishu-group.md +13 -13
  154. package/kits/docs/venues/feishu-private.md +9 -9
  155. package/kits/docs/venues/group.md +37 -25
  156. package/kits/docs/venues/private.md +10 -10
  157. package/kits/eck_manifest.auxiliary.json +43 -43
  158. package/kits/eck_manifest.json +190 -190
  159. package/kits/eck_message_manifest.json +74 -74
  160. package/kits/migrations/README-role-config-v4.md +32 -32
  161. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  162. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  163. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  164. package/kits/migrations/rename-config-file.mjs +99 -99
  165. package/kits/rules/01-overview.md +162 -162
  166. package/kits/rules/02-navigation.md +96 -96
  167. package/kits/rules/03-identity.md +36 -36
  168. package/kits/rules/04-relation.md +75 -75
  169. package/kits/rules/05-venue.md +60 -60
  170. package/kits/rules/06-channel.md +63 -63
  171. package/kits/schemas/_meta.json +53 -53
  172. package/kits/schemas/agent-config.schema.1.json +177 -177
  173. package/kits/schemas/agent-config.schema.10.json +395 -395
  174. package/kits/schemas/agent-config.schema.11.json +421 -421
  175. package/kits/schemas/agent-config.schema.12.json +427 -427
  176. package/kits/schemas/agent-config.schema.2.json +239 -239
  177. package/kits/schemas/agent-config.schema.3.json +119 -119
  178. package/kits/schemas/agent-config.schema.4.json +208 -208
  179. package/kits/schemas/agent-config.schema.5.json +326 -326
  180. package/kits/schemas/agent-config.schema.6.json +322 -322
  181. package/kits/schemas/agent-config.schema.7.json +303 -303
  182. package/kits/schemas/agent-config.schema.8.json +304 -304
  183. package/kits/schemas/agent-config.schema.9.json +364 -364
  184. package/kits/schemas/contact-book.schema.1.json +36 -36
  185. package/kits/schemas/contact-book.schema.2.json +43 -43
  186. package/kits/schemas/contact-book.schema.3.json +67 -67
  187. package/kits/schemas/daemon.schema.1.json +91 -91
  188. package/kits/schemas/daemon.schema.2.json +101 -101
  189. package/kits/schemas/daemon.schema.3.json +123 -123
  190. package/kits/schemas/daemon.schema.4.json +136 -136
  191. package/kits/schemas/daemon.schema.5.json +135 -135
  192. package/kits/schemas/daemon.schema.6.json +131 -131
  193. package/kits/schemas/defaults.schema.1.json +81 -81
  194. package/kits/schemas/defaults.schema.2.json +85 -85
  195. package/kits/schemas/defaults.schema.3.json +73 -73
  196. package/kits/schemas/defaults.schema.4.json +107 -107
  197. package/kits/schemas/defaults.schema.5.json +119 -119
  198. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  199. package/kits/schemas/migrations/README.md +30 -30
  200. package/kits/schemas/relation-config.schema.1.json +158 -158
  201. package/kits/schemas/relation-config.schema.2.json +73 -73
  202. package/kits/schemas/relation-config.schema.3.json +50 -50
  203. package/kits/schemas/relation-config.schema.4.json +47 -47
  204. package/kits/schemas/relation-config.schema.5.json +47 -47
  205. package/kits/schemas/relation-config.schema.6.json +46 -46
  206. package/kits/schemas/relation-config.schema.7.json +59 -59
  207. package/kits/schemas/relation-config.schema.8.json +103 -103
  208. package/kits/schemas/role-config.schema.1.json +201 -201
  209. package/kits/schemas/role-registry.schema.1.json +35 -35
  210. package/kits/schemas/single-session.schema.1.json +31 -31
  211. package/kits/schemas/single-session.schema.2.json +57 -57
  212. package/kits/schemas/single-session.schema.3.json +32 -32
  213. package/kits/templates/bootstrap-welcome.md +17 -17
  214. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  215. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  216. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  217. package/kits/templates/message-fragments/inject-default.md +2 -2
  218. package/kits/templates/message-fragments/item.md +2 -2
  219. package/kits/templates/roles/admin.json +92 -92
  220. package/kits/templates/roles/member.json +96 -96
  221. package/kits/templates/roles/owner.json +9 -9
  222. package/kits/templates/roles/visitor.json +85 -85
  223. package/kits/templates/system-fragments/baseagent.md +14 -14
  224. package/kits/templates/system-fragments/bootstrap.md +31 -29
  225. package/kits/templates/system-fragments/channel.md +64 -64
  226. package/kits/templates/system-fragments/commands.md +28 -28
  227. package/kits/templates/system-fragments/identity.md +13 -13
  228. package/kits/templates/system-fragments/relation.md +19 -19
  229. package/kits/templates/system-fragments/session.md +59 -59
  230. package/kits/templates/system-fragments/venue.md +31 -31
  231. package/package.json +100 -98
package/dist/cli/bench.js CHANGED
@@ -381,26 +381,26 @@ async function cliAuth(aid, slotId) {
381
381
  // ==================== Main Command ====================
382
382
  export async function cmdBench(args) {
383
383
  if (isHelpFlag(args[0]) || wantsHelp(args)) {
384
- console.log(`用法: ec bench [options]
385
-
386
- AUN 消息系统性能基准测试。使用多个本地 AID 并发互发消息,
387
- 测量吞吐量、延迟、丢失率,以及认证性能和账号切换性能。
388
-
389
- Options:
390
- --aids N 使用的 AID 数量 (默认 3, 范围 2-10)
391
- --rounds N 每个 AID 发送的消息轮数 (默认 20)
392
- --concurrency N 最大并发发送数 (默认 5)
393
- --wait N 发送后等待传播的秒数 (默认 3)
394
- --encrypt 发送加密消息(E2EE)
395
- --file 文件传输验证模式(压缩 evolcore 目录,拆片发送,接收后 MD5 校验+解压)
396
- --cli 使用 CLI 子进程调用(测试完整命令行链路,含进程启动开销)
397
- --aun-path <path> 自定义 AUN 目录
398
- --format json 以 JSON 输出结果
399
-
400
- 示例:
401
- ec bench --aids 5 --rounds 10
402
- ec bench --file --rounds 50
403
- ec bench --encrypt --aids 5 --rounds 20
384
+ console.log(`用法: ec bench [options]
385
+
386
+ AUN 消息系统性能基准测试。使用多个本地 AID 并发互发消息,
387
+ 测量吞吐量、延迟、丢失率,以及认证性能和账号切换性能。
388
+
389
+ Options:
390
+ --aids N 使用的 AID 数量 (默认 3, 范围 2-10)
391
+ --rounds N 每个 AID 发送的消息轮数 (默认 20)
392
+ --concurrency N 最大并发发送数 (默认 5)
393
+ --wait N 发送后等待传播的秒数 (默认 3)
394
+ --encrypt 发送加密消息(E2EE)
395
+ --file 文件传输验证模式(压缩 evolcore 目录,拆片发送,接收后 MD5 校验+解压)
396
+ --cli 使用 CLI 子进程调用(测试完整命令行链路,含进程启动开销)
397
+ --aun-path <path> 自定义 AUN 目录
398
+ --format json 以 JSON 输出结果
399
+
400
+ 示例:
401
+ ec bench --aids 5 --rounds 10
402
+ ec bench --file --rounds 50
403
+ ec bench --encrypt --aids 5 --rounds 20
404
404
  ec bench --cli --aids 3 --rounds 5`);
405
405
  return;
406
406
  }
@@ -1,7 +1,7 @@
1
1
  import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
2
2
  import { initConfigManager } from '../config/config-manager.js';
3
3
  import { resolveConfigCommand } from '../config/resolved-config-op.js';
4
- import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommand, } from '../config/config-operation-service.js';
4
+ import { AUTHENTICATED_CONFIG_ROLE_ENV, executeResolvedConfigCommandAsync, } from '../config/config-operation-service.js';
5
5
  import { ipcQuery } from '../ipc.js';
6
6
  import { resolvePaths } from '../paths.js';
7
7
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
@@ -31,6 +31,7 @@ function getResultPayload(result) {
31
31
  ...(result.source ? { source: result.source } : {}),
32
32
  ...(result.schemaDefault ? { schemaDefault: true } : {}),
33
33
  ...(result.note ? { note: result.note } : {}),
34
+ ...(result.roleDetails ? { role: result.roleDetails } : {}),
34
35
  };
35
36
  }
36
37
  function getResultLine(result) {
@@ -39,7 +40,10 @@ function getResultLine(result) {
39
40
  : !result.source
40
41
  ? `${result.field} = ${JSON.stringify(result.value)} (${result.scope})`
41
42
  : `${result.field} = ${JSON.stringify(result.value)} [source: ${result.source.target}]`;
42
- return result.note ? `${head}\n ↳ ${result.note}` : head;
43
+ const role = result.roleDetails
44
+ ? `\n role: explicit=${JSON.stringify(result.roleDetails.explicitRole)} effective=${JSON.stringify(result.roleDetails.effectiveRole)} [${result.roleDetails.effectiveRoleSource}]`
45
+ : '';
46
+ return `${head}${role}${result.note ? `\n ↳ ${result.note}` : ''}`;
43
47
  }
44
48
  function renderConfigExecution(result, formatJson) {
45
49
  if (!result.ok)
@@ -56,6 +60,7 @@ function renderConfigExecution(result, formatJson) {
56
60
  value: result.value,
57
61
  scope: result.scope,
58
62
  file: result.file,
63
+ ...(result.roleDetails ? { role: result.roleDetails } : {}),
59
64
  }, () => `Set ${result.field} = ${JSON.stringify(result.value)} [${result.scope}]`);
60
65
  case 'unset':
61
66
  return emit(formatJson, {
@@ -63,6 +68,7 @@ function renderConfigExecution(result, formatJson) {
63
68
  field: result.field,
64
69
  removed: result.removed,
65
70
  ...(result.removed ? { scope: result.scope } : {}),
71
+ ...(result.roleDetails ? { role: result.roleDetails } : {}),
66
72
  }, () => result.removed ? `Removed ${result.field} from ${result.scope}` : `${result.field} was not set`);
67
73
  case 'show':
68
74
  return emit(formatJson, { ok: true, scope: result.scope, configs: result.configs }, () => JSON.stringify(result.configs, null, 2));
@@ -172,18 +178,18 @@ function renderBatchGet(results, formatJson) {
172
178
  if (!allOk)
173
179
  process.exit(1);
174
180
  }
175
- function runDirectBatchGet(fields, rest, formatJson) {
176
- const results = fields.map(field => {
181
+ async function runDirectBatchGet(fields, rest, formatJson) {
182
+ const results = await Promise.all(fields.map(async (field) => {
177
183
  const resolved = resolveConfigCommand(['config', 'get', field, ...rest]);
178
184
  if (!resolved.ok)
179
185
  return { ok: false, field, code: resolved.code, error: resolved.reason };
180
- const result = executeResolvedConfigCommand(resolved.command);
186
+ const result = await executeResolvedConfigCommandAsync(resolved.command, { role: 'owner' });
181
187
  if (!result.ok)
182
188
  return { ok: false, field, code: result.code, error: result.error };
183
189
  if (result.subcommand === 'get')
184
190
  return result;
185
191
  return { ok: false, field, code: 'UNEXPECTED_RESULT', error: `config get ${field} produced a non-get result` };
186
- });
192
+ }));
187
193
  renderBatchGet(results, formatJson);
188
194
  }
189
195
  async function runManagedBatchGet(fields, rest, formatJson) {
@@ -196,26 +202,31 @@ async function runManagedBatchGet(fields, rest, formatJson) {
196
202
  }
197
203
  renderConfigExecution(response.result, formatJson);
198
204
  }
199
- const HELP = `Usage: ec config <command> [options]
200
-
201
- Fields:
202
- get <field> [<field> ...] one or more fields (batch)
203
- set <field> <value>
204
- unset <field>
205
-
206
- Scoped commands:
207
- show | effective | fields [field] | validate | init
208
-
209
- Global commands:
210
- list | snapshot | prune | history | diff | restore | current | boots
211
- schema overview of all schemas
212
- schema <name> [<version>] view a schema definition
213
- schema <name> --list list a schema's versions
214
-
215
- Selectors:
216
- --self <aid> [--peer <peerKey>] | --default | --process
217
-
218
- Output:
205
+ const HELP = `Usage: ec config <command> [options]
206
+
207
+ Fields:
208
+ get <field> [<field> ...] one or more fields (batch)
209
+ set <field> <value>
210
+ unset <field>
211
+
212
+ Scoped commands:
213
+ show | effective | fields [field] | validate | init
214
+
215
+ Global commands:
216
+ list | snapshot | prune | history | diff | restore | current | boots
217
+ schema overview of all schemas
218
+ schema <name> [<version>] view a schema definition
219
+ schema <name> --list list a schema's versions
220
+
221
+ Selectors:
222
+ --self <aid> [--peer <peerKey>] | --default | --process
223
+
224
+ Relation role (AUN peer AID):
225
+ get role --self <aid> --peer <peer-aid>
226
+ set role <role-id> --self <aid> --peer <peer-aid>
227
+ unset role --self <aid> --peer <peer-aid>
228
+
229
+ Output:
219
230
  --format json`;
220
231
  export async function cmdConfig(args) {
221
232
  const subcommand = args[0];
@@ -241,13 +252,21 @@ export async function cmdConfig(args) {
241
252
  fail(formatJson, 'SCHEMA_INIT_FAILED', error instanceof Error ? error.message : String(error));
242
253
  }
243
254
  if (isBatchGet) {
244
- runDirectBatchGet(batchGet.fields, batchGet.rest, formatJson);
255
+ await runDirectBatchGet(batchGet.fields, batchGet.rest, formatJson);
245
256
  return;
246
257
  }
247
258
  const resolved = resolveConfigCommand(['config', ...args]);
248
259
  if (!resolved.ok)
249
260
  fail(formatJson, resolved.code, resolved.reason);
250
- renderConfigExecution(executeResolvedConfigCommand(resolved.command, {
261
+ renderConfigExecution(await executeResolvedConfigCommandAsync(resolved.command, {
251
262
  role: process.env[AUTHENTICATED_CONFIG_ROLE_ENV],
263
+ roleContext: {
264
+ self: resolved.command.self || '',
265
+ actorRole: process.env[AUTHENTICATED_CONFIG_ROLE_ENV] || 'owner',
266
+ actorRoleSource: 'local-cli',
267
+ isDaemonOwner: false,
268
+ fromControlChannel: false,
269
+ aunAvailable: true,
270
+ },
252
271
  }), formatJson);
253
272
  }
@@ -1,4 +1,5 @@
1
1
  import { resolvePaths } from '../paths.js';
2
+ import { loadAllAgents } from '../config-store.js';
2
3
  import { executeResolvedContactCommand, resolveContactCommand, } from '../config/contact-operation-service.js';
3
4
  import { ipcQuery } from '../ipc.js';
4
5
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
@@ -14,9 +15,17 @@ export async function cmdContact(args) {
14
15
  output(result, json);
15
16
  return;
16
17
  }
17
- const resolved = resolveContactCommand(args);
18
- if (!resolved.ok)
19
- fail(resolved.reason, resolved.code);
18
+ if (!args.includes('--self') && isListCommand(args) && !process.env.EVOLCORE_SELF_AID?.trim()) {
19
+ output(await executeAggregateList(args), json);
20
+ return;
21
+ }
22
+ const resolved = resolveContactCommand(withDefaultSelf(args));
23
+ if (!resolved.ok) {
24
+ const message = resolved.code === 'INVALID_SELF' && !args.includes('--self')
25
+ ? `${resolved.reason}; multiple local Agents are configured, add --self <agent-aid>`
26
+ : resolved.reason;
27
+ fail(message, resolved.code);
28
+ }
20
29
  const result = await executeResolvedContactCommand(resolved.command);
21
30
  if (!result.ok)
22
31
  fail(result.error, result.code, result.data);
@@ -58,15 +67,63 @@ function fail(message, code = 'INVALID_ARGUMENT', data) {
58
67
  throw error;
59
68
  }
60
69
  function printHelp() {
61
- console.log(`Usage:
62
- ec contact add <primaryId> --self <agent-aid>
63
- ec contact block <primaryId> --self <agent-aid>
64
- ec contact unblock <primaryId> --self <agent-aid>
65
- ec contact list --blocked --self <agent-aid>
66
-
67
- Options:
68
- --self <agent-aid> 必填,本端 agent AID
69
- --blocked list 必填,仅列出已拉黑联系人
70
- --format json 输出 JSON
70
+ console.log(`Usage:
71
+ ec contact add <primaryId> --self <agent-aid>
72
+ ec contact block <primaryId> --self <agent-aid>
73
+ ec contact unblock <primaryId> --self <agent-aid>
74
+ ec contact list --blocked [--self <agent-aid>]
75
+ ec contact list --pending [--self <agent-aid>]
76
+ ec contact list --all [--self <agent-aid>]
77
+ ec contact approve <primaryId> --request-id <id> [--self <agent-aid>]
78
+ ec contact reject <primaryId> --request-id <id> [--self <agent-aid>]
79
+ ec contact block-request <primaryId> --request-id <id> [--self <agent-aid>]
80
+
81
+ Options:
82
+ --self <agent-aid> 本端 agent AID;省略时从当前运行环境推断
83
+ --blocked 仅列出已拉黑联系人
84
+ --pending 仅列出待审核联系人申请
85
+ --all 列出所有联系人及状态
86
+ --request-id <id> 联系人申请 ID(审批命令必填)
87
+ --contact-revision <revision> 申请审批时使用的联系人版本(可选)
88
+ --format json 输出 JSON
71
89
  --help, -h 显示帮助`);
72
90
  }
91
+ function withDefaultSelf(args) {
92
+ if (args.includes('--self'))
93
+ return args;
94
+ const selfAid = resolveStandaloneSelfAid();
95
+ return selfAid ? [...args, '--self', selfAid] : args;
96
+ }
97
+ function isListCommand(args) {
98
+ return args[0] === 'list';
99
+ }
100
+ async function executeAggregateList(args) {
101
+ const agents = loadAllAgents().agents.map(agent => agent.aid).filter(Boolean).sort();
102
+ const results = [];
103
+ for (const aid of agents) {
104
+ const resolved = resolveContactCommand([...args, '--self', aid]);
105
+ if (!resolved.ok)
106
+ fail(resolved.reason, resolved.code);
107
+ const result = await executeResolvedContactCommand(resolved.command);
108
+ if (!result.ok)
109
+ fail(result.error, result.code, result.data);
110
+ results.push(result);
111
+ }
112
+ return {
113
+ ok: true,
114
+ operation: args.includes('--blocked') ? 'list-blocked'
115
+ : args.includes('--pending') ? 'list-pending' : 'list-all',
116
+ scope: 'all-agents',
117
+ agents: results,
118
+ };
119
+ }
120
+ /** Resolve the local Agent identity for standalone CLI commands. */
121
+ export function resolveStandaloneSelfAid() {
122
+ const runtimeAid = process.env.EVOLCORE_SELF_AID?.trim();
123
+ if (runtimeAid)
124
+ return runtimeAid;
125
+ const agents = loadAllAgents().agents
126
+ .map(agent => agent.aid)
127
+ .filter(Boolean);
128
+ return agents.length === 1 ? agents[0] : undefined;
129
+ }
@@ -3,28 +3,28 @@ import { ipcQuery } from '../ipc.js';
3
3
  import { isHelpFlag, wantsHelp } from './help.js';
4
4
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
5
5
  // ==================== Ctl ====================
6
- const CTL_HELP = `用法: ec ctl <command> [args...]
7
-
8
- 查询:
9
- status 查看会话状态
10
- status --format json 查看当前任务的结构化运行上下文
11
- pwd 显示当前项目路径
12
- help 显示帮助
13
-
14
- 会话:
15
- name <名称> 重命名当前会话
16
-
17
- 消息:
18
- send <消息内容> 主动发送文本消息(proactive 模式)
19
- file [channel] <path> 发送项目内文件
20
- queue [--after <time>] [--before <time>] 查看/管理会话消息队列
21
-
22
- 示例:
23
- ec ctl status
24
- ec ctl status --format json
25
- ec ctl name "新会话名称"
26
- ec ctl send "任务已完成"
27
- ec ctl file report.md
6
+ const CTL_HELP = `用法: ec ctl <command> [args...]
7
+
8
+ 查询:
9
+ status 查看会话状态
10
+ status --format json 查看当前任务的结构化运行上下文
11
+ pwd 显示当前项目路径
12
+ help 显示帮助
13
+
14
+ 会话:
15
+ name <名称> 重命名当前会话
16
+
17
+ 消息:
18
+ send <消息内容> 主动发送文本消息(proactive 模式)
19
+ file [channel] <path> 发送项目内文件
20
+ queue [--after <time>] [--before <time>] 查看/管理会话消息队列
21
+
22
+ 示例:
23
+ ec ctl status
24
+ ec ctl status --format json
25
+ ec ctl name "新会话名称"
26
+ ec ctl send "任务已完成"
27
+ ec ctl file report.md
28
28
  ec ctl queue --full`;
29
29
  export async function cmdCtl(args) {
30
30
  if (args.length === 0) {
@@ -28,6 +28,7 @@ import { WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js
28
28
  import { rotateStdoutLog } from '../utils/log-writer.js';
29
29
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
30
30
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
31
+ import { computeBuildId } from '../utils/evolcore-version.js';
31
32
  import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '../agents/codex/app-server-registry.js';
32
33
  import { DEFAULT_ECWEB_PORT, ecwebService, setEcwebEnabled } from '../config/daemon-services.js';
33
34
  import { checkCodexProjectPath, inspectCodexManagedHook, ensureCodexManagedHook, runCodexHookLaunchDiagnostic, } from '../agents/codex/diagnostics.js';
@@ -312,7 +313,10 @@ export async function runUpgradeChecks(upgradeFn = signal => tryUpgrade({ signal
312
313
  clearTimeout(timer);
313
314
  }
314
315
  };
315
- const upgrade = await runWithTimeout('EvolCore', upgradeFn);
316
+ const [upgrade, ecwebUpgrade] = await Promise.all([
317
+ runWithTimeout('EvolCore', upgradeFn),
318
+ runWithTimeout(WEB_PACKAGE_NAME, ecwebUpgradeFn),
319
+ ]);
316
320
  switch (upgrade.status) {
317
321
  case 'upgraded':
318
322
  console.log(`✅ Upgraded: ${upgrade.from} → ${upgrade.to}`);
@@ -329,7 +333,6 @@ export async function runUpgradeChecks(upgradeFn = signal => tryUpgrade({ signal
329
333
  console.log(`⚠ Upgrade failed${upgrade.from && upgrade.to ? ` (${upgrade.from} → ${upgrade.to})` : ''}, continuing with current version${upgrade.error ? `: ${upgrade.error}` : ''}`);
330
334
  break;
331
335
  }
332
- const ecwebUpgrade = await runWithTimeout(WEB_PACKAGE_NAME, ecwebUpgradeFn);
333
336
  switch (ecwebUpgrade.status) {
334
337
  case 'upgraded':
335
338
  console.log(`✅ ${WEB_PACKAGE_NAME} upgraded: ${ecwebUpgrade.from} → ${ecwebUpgrade.to}`);
@@ -769,21 +772,50 @@ export async function cmdStart(opts = {}) {
769
772
  };
770
773
  setTimeout(checkReady, 1000);
771
774
  }
772
- async function stopPid(pid, gracefulRequested = false) {
775
+ function isPidReused(pid, expectedStartedAt) {
776
+ if (expectedStartedAt === undefined)
777
+ return false;
778
+ const actualStartedAt = getProcessStartTime(pid);
779
+ return actualStartedAt !== null && !startTimeMatches(expectedStartedAt, actualStartedAt);
780
+ }
781
+ function reportPidReused(pid) {
782
+ console.log(`ℹ EvolCore PID ${pid} no longer refers to the recorded process; treating it as stopped`);
783
+ }
784
+ async function stopPid(pid, gracefulRequested = false, expectedStartedAt) {
773
785
  console.log(`🛑 Stopping EvolCore (PID: ${pid})...`);
786
+ if (isPidReused(pid, expectedStartedAt)) {
787
+ reportPidReused(pid);
788
+ return true;
789
+ }
774
790
  if (!gracefulRequested)
775
791
  platform.killProcess(pid);
776
792
  if (await platform.waitForProcessExit(pid, gracefulRequested ? 30_000 : 10_000)) {
777
793
  console.log('✓ EvolCore stopped');
778
794
  return true;
779
795
  }
780
- console.log(`⚠ EvolCore PID ${pid} did not exit gracefully; forcing termination...`);
796
+ if (isPidReused(pid, expectedStartedAt)) {
797
+ reportPidReused(pid);
798
+ return true;
799
+ }
800
+ const state = platform.getProcessState(pid);
801
+ const stateSuffix = state ? ` (state=${state})` : '';
802
+ console.log(`⚠ EvolCore PID ${pid} did not exit gracefully${stateSuffix}; forcing termination...`);
803
+ if (isPidReused(pid, expectedStartedAt)) {
804
+ reportPidReused(pid);
805
+ return true;
806
+ }
781
807
  platform.killProcess(pid, true);
782
808
  if (await platform.waitForProcessExit(pid, 5_000)) {
783
809
  console.log('✓ EvolCore stopped (forced)');
784
810
  return true;
785
811
  }
786
- console.error(`❌ EvolCore PID ${pid} is still running; restart aborted`);
812
+ if (isPidReused(pid, expectedStartedAt)) {
813
+ reportPidReused(pid);
814
+ return true;
815
+ }
816
+ const finalState = platform.getProcessState(pid);
817
+ const finalStateSuffix = finalState ? ` (state=${finalState})` : '';
818
+ console.error(`❌ EvolCore PID ${pid} is still running${finalStateSuffix}; restart aborted`);
787
819
  return false;
788
820
  }
789
821
  function isRuntimeOrphan(orphan, currentHome = resolveRoot()) {
@@ -813,13 +845,14 @@ export async function cmdStop() {
813
845
  return;
814
846
  }
815
847
  const pids = new Set([...aliveMains.map(entry => entry.record.pid), ...runtimeOrphans.map(orphan => orphan.pid)]);
848
+ const expectedStarts = new Map(aliveMains.map(entry => [entry.record.pid, entry.record.startedAt]));
816
849
  if (runtimeOrphans.length > 0) {
817
850
  console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(orphan => orphan.pid).join(', ')}`);
818
851
  }
819
852
  const gracefulPid = ping && pids.has(ping.pid)
820
853
  ? await requestDaemonShutdown(p.socket, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec stop', pids)
821
854
  : null;
822
- const stopped = await Promise.all([...pids].map(pid => stopPid(pid, pid === gracefulPid)));
855
+ const stopped = await Promise.all([...pids].map(pid => stopPid(pid, pid === gracefulPid, expectedStarts.get(pid))));
823
856
  if (stopped.some(result => !result)) {
824
857
  process.exitCode = 1;
825
858
  return;
@@ -836,11 +869,13 @@ export async function cmdRestart(opts = {}) {
836
869
  if (!ensureCodexHookBeforeLifecycle())
837
870
  return;
838
871
  const initialStatus = scanInstances();
839
- const initialOrphans = findOrphanProcesses();
840
- const initialRuntimeOrphans = initialOrphans.filter(orphan => isRuntimeOrphan(orphan));
841
872
  const socketPath = resolvePaths().socket;
842
873
  const ping = await probeDaemon(socketPath);
843
874
  if (ping && !hasVisibleDaemonPid(initialStatus, ping)) {
875
+ // Only pay for a cross-host process-table scan when the IPC PID is not
876
+ // present in our registry. This is the exceptional PID-namespace case;
877
+ // healthy restarts no longer scan every Node process before stopping.
878
+ const initialRuntimeOrphans = findOrphanProcesses().filter(orphan => isRuntimeOrphan(orphan));
844
879
  if (await tryDelegatedDaemonRestart(socketPath))
845
880
  return;
846
881
  if (initialRuntimeOrphans.length === 0 && rejectPidIsolatedLifecycle('restart', initialStatus, ping))
@@ -848,13 +883,13 @@ export async function cmdRestart(opts = {}) {
848
883
  }
849
884
  // 构建交给 restart-monitor:它在 daemon 停止之后才编译,dist/ 不会被活进程
850
885
  // 占用,失败还能回滚到上一份产物。这里再编译一次会落在事务保护之外。
851
- // Keep restart behavior identical to startup: both paths use one upgrade
852
- // check implementation and continue when an upgrade is unavailable.
853
- await runUpgradeChecks();
886
+ // restart-monitor owns upgrade checks. Running them here as well caused a
887
+ // normal restart to perform every registry lookup twice, and in installed
888
+ // mode could race the monitor while it was preparing its rollback point.
854
889
  if (!requireCompletedDataMigration(resolveRoot(), 'restart'))
855
890
  return;
856
891
  // 停止所有活 main 进程(可能不止一个)
857
- const status = scanInstances();
892
+ const status = initialStatus;
858
893
  const aliveMains = status.mains.filter(m => m.alive);
859
894
  if (aliveMains.length > 0) {
860
895
  if (aliveMains.length > 1) {
@@ -865,7 +900,7 @@ export async function cmdRestart(opts = {}) {
865
900
  const gracefulPid = currentPing && mainPids.has(currentPing.pid)
866
901
  ? await requestDaemonShutdown(socketPath, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec restart', mainPids)
867
902
  : null;
868
- const stopped = await Promise.all(aliveMains.map(m => stopPid(m.record.pid, m.record.pid === gracefulPid)));
903
+ const stopped = await Promise.all(aliveMains.map(m => stopPid(m.record.pid, m.record.pid === gracefulPid, m.record.startedAt)));
869
904
  if (stopped.some(result => !result)) {
870
905
  console.error('❌ Restart aborted because an existing EvolCore process could not be stopped');
871
906
  process.exitCode = 1;
@@ -881,7 +916,8 @@ export async function cmdRestart(opts = {}) {
881
916
  return;
882
917
  }
883
918
  // 孤儿处理:同 HOME/同安装包的孤儿属于本运行时,先确认退出再替换;
884
- // 跨 HOME 的孤儿只在 --clear 时 kill,否则仅警告。
919
+ // 跨 HOME 的孤儿只在 --clear 时 kill,否则仅警告。findOrphanProcesses
920
+ // 在 Windows 上需要一次 CIM 查询;保留一个快照,避免同一阶段重复扫描。
885
921
  {
886
922
  const orphans = findOrphanProcesses();
887
923
  const confirmedTestDaemons = orphans.filter(o => o.confirmedTestDaemon);
@@ -890,7 +926,7 @@ export async function cmdRestart(opts = {}) {
890
926
  console.log(`☠ 已清理 ${killed.length} 个泄漏的测试 daemon: ${killed.join(', ')}`);
891
927
  await sleep(500);
892
928
  }
893
- const runtimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
929
+ const runtimeOrphans = orphans.filter(o => isRuntimeOrphan(o));
894
930
  if (runtimeOrphans.length > 0) {
895
931
  console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(o => o.pid).join(', ')}`);
896
932
  const orphanPids = new Set(runtimeOrphans.map(orphan => orphan.pid));
@@ -906,13 +942,9 @@ export async function cmdRestart(opts = {}) {
906
942
  }
907
943
  await sleep(500);
908
944
  }
909
- const remainingRuntimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
910
- if (remainingRuntimeOrphans.length > 0) {
911
- console.error(`❌ Restart aborted: EvolCore process still running (PID: ${remainingRuntimeOrphans.map(o => o.pid).join(', ')})`);
912
- process.exitCode = 1;
913
- return;
914
- }
915
- const otherHome = findOrphanProcesses().filter(o => !o.confirmedTestDaemon && !isRuntimeOrphan(o));
945
+ // stopPid() already waited for every runtime orphan above, so avoid a
946
+ // second scan before handling cross-HOME records.
947
+ const otherHome = orphans.filter(o => !o.confirmedTestDaemon && !isRuntimeOrphan(o));
916
948
  if (opts.clear && otherHome.length > 0) {
917
949
  const killed = killOrphans(otherHome);
918
950
  console.log(`☠ 已 SIGKILL ${killed.length} 个跨 HOME 孤儿进程: ${killed.join(', ')}`);
@@ -921,6 +953,14 @@ export async function cmdRestart(opts = {}) {
921
953
  else if (otherHome.length > 0) {
922
954
  reportOrphans(otherHome);
923
955
  }
956
+ // Keep the post-stop ownership guard: a process that appeared during the
957
+ // cleanup window must still block replacement.
958
+ const remainingRuntimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
959
+ if (remainingRuntimeOrphans.length > 0) {
960
+ console.error(`❌ Restart aborted: EvolCore process still running (PID: ${remainingRuntimeOrphans.map(o => o.pid).join(', ')})`);
961
+ process.exitCode = 1;
962
+ return;
963
+ }
924
964
  }
925
965
  // 只清理 EvolCore 自己登记的 codex app-server,不能影响桌面端实例。
926
966
  {
@@ -955,8 +995,7 @@ export async function cmdRestart(opts = {}) {
955
995
  process.exitCode = 1;
956
996
  return;
957
997
  }
958
- console.log('🔄 Restart monitor started, waiting for service to come back online...');
959
- await sleep(2000);
998
+ console.log('🔄 Restart monitor started; service is coming back online in the background...');
960
999
  // 代码统计(开发环境)
961
1000
  if (path.normalize(resolveRoot()) === path.normalize(getPackageRoot())) {
962
1001
  console.log('');
@@ -1125,6 +1164,7 @@ export async function cmdStatus() {
1125
1164
  const p = resolvePaths();
1126
1165
  const status = scanInstances();
1127
1166
  const aliveMains = status.mains.filter(m => m.alive);
1167
+ const aliveRestartMonitors = status.restartMonitors.filter(m => m.alive);
1128
1168
  // Start all read-only daemon queries together. On a busy Windows host each
1129
1169
  // query can otherwise wait behind the same IPC/event-loop delay and make the
1130
1170
  // total `ec status` time the sum of several individual timeouts.
@@ -1263,6 +1303,10 @@ export async function cmdStatus() {
1263
1303
  catch { }
1264
1304
  }
1265
1305
  }
1306
+ else if (aliveRestartMonitors.length > 0) {
1307
+ console.log(`🔄 EvolCore 正在重启,daemon 尚未就绪(monitor PID: ${aliveRestartMonitors.map(m => m.record.pid).join(', ')})`);
1308
+ console.log(' 请稍后再次执行 ec status。');
1309
+ }
1266
1310
  else {
1267
1311
  console.log(ipcFailure ? '⚠ 无法确认 EvolCore 运行状态:IPC 不可用,且没有可见的存活 PID。' : '⚠ EvolCore is not running');
1268
1312
  if (ipcFailure)
@@ -1312,6 +1356,24 @@ export async function cmdStatus() {
1312
1356
  ]);
1313
1357
  agentsResponse = agentResponse;
1314
1358
  if (statusResponse) {
1359
+ if (statusResponse.build) {
1360
+ if (statusResponse.build.runtimeMode === 'source') {
1361
+ console.log(` Build ID: runtime=source dist=${statusResponse.build.diskDistId || '?'}`);
1362
+ console.log(' ℹ daemon 当前由源码入口运行,dist build ID 不参与一致性比较。');
1363
+ }
1364
+ else {
1365
+ const runtimeBuildId = statusResponse.build.runtimeId || '?';
1366
+ const diskDistBuildId = statusResponse.build.diskDistId || '?';
1367
+ console.log(` Build ID: runtime=${runtimeBuildId} dist=${diskDistBuildId}`);
1368
+ if (runtimeBuildId !== '?' && diskDistBuildId !== '?' && runtimeBuildId !== diskDistBuildId) {
1369
+ console.log(' ⚠ 运行进程 build ID 与磁盘 dist 不一致;请重启 daemon 后再判断源码行为。');
1370
+ }
1371
+ }
1372
+ }
1373
+ else {
1374
+ console.log(` Build ID: runtime=unavailable (daemon 未提供 build identity) dist=${computeBuildId(path.join(getPackageRoot(), 'dist')) ?? '?'}`);
1375
+ console.log(' ⚠ 当前 daemon 未提供 build ID;请重启到包含 build identity 的版本后再判断源码行为。');
1376
+ }
1315
1377
  // 🔑 AUN AIDs 表格(详细 AUN 实例状态)
1316
1378
  try {
1317
1379
  if (aidsResponse?.ok && aidsResponse.aids?.length > 0) {
@@ -2771,9 +2833,12 @@ function removeEcwebInstanceFiles(p) {
2771
2833
  }
2772
2834
  /** 清理 EvolCore 登记的 codex app-server 孤儿进程,返回清理的进程数。 */
2773
2835
  export function stopCodexAppServerOrphans() {
2836
+ const entries = listCodexAppServerProcesses();
2837
+ if (entries.length === 0)
2838
+ return 0;
2774
2839
  const codexProcs = new Set(platform.findProcesses('codex app-server'));
2775
2840
  let killed = 0;
2776
- for (const entry of listCodexAppServerProcesses()) {
2841
+ for (const entry of entries) {
2777
2842
  const { pid, startedAt } = entry.record;
2778
2843
  if (!platform.isProcessRunning(pid)) {
2779
2844
  unregisterCodexAppServerProcess(entry.filePath);
@@ -3260,21 +3325,21 @@ export async function cmdWatchCommand(args) {
3260
3325
  }
3261
3326
  else if (args[0] === 'msg') {
3262
3327
  if (isHelpFlag(args[1])) {
3263
- console.log(`用法: ec watch msg
3264
-
3265
- 三面板交互式消息监控 TUI。
3266
-
3267
- 面板:
3268
- 左 (Scope) 本地 AID 列表,显示收发统计和对端数量
3269
- 中 (Stats) 选中 AID 的对端列表(默认 All),显示 per-peer 收发数
3270
- 右 (Messages) 消息流,带滚动条
3271
-
3272
- 操作:
3273
- ↑↓ 当前面板内导航
3274
- ←→ / Tab 切换面板
3275
- Enter 选中 AID / 选中对端
3276
- Backspace 返回上一级
3277
- Page Up/Down 消息滚动
3328
+ console.log(`用法: ec watch msg
3329
+
3330
+ 三面板交互式消息监控 TUI。
3331
+
3332
+ 面板:
3333
+ 左 (Scope) 本地 AID 列表,显示收发统计和对端数量
3334
+ 中 (Stats) 选中 AID 的对端列表(默认 All),显示 per-peer 收发数
3335
+ 右 (Messages) 消息流,带滚动条
3336
+
3337
+ 操作:
3338
+ ↑↓ 当前面板内导航
3339
+ ←→ / Tab 切换面板
3340
+ Enter 选中 AID / 选中对端
3341
+ Backspace 返回上一级
3342
+ Page Up/Down 消息滚动
3278
3343
  ESC 退出`);
3279
3344
  return;
3280
3345
  }