evolcore 0.0.24 → 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 (112) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +12 -1
  3. package/bin/install-codex-managed-hooks.mjs +28 -1
  4. package/dist/agents/baseagent.js +96 -46
  5. package/dist/agents/claude-runner.js +54 -13
  6. package/dist/agents/codex/app-server-client.js +13 -5
  7. package/dist/agents/codex/catalog.js +1 -1
  8. package/dist/agents/codex/diagnostics.js +34 -1
  9. package/dist/agents/codex-runner.js +128 -42
  10. package/dist/agents/ecagent-runner.js +14 -18
  11. package/dist/agents/gemini-runner.js +24 -9
  12. package/dist/agents/runner-types.js +25 -17
  13. package/dist/aun/aid/agentmd.js +135 -10
  14. package/dist/aun/aid/client.js +1 -1
  15. package/dist/aun/aid/identity.js +1 -1
  16. package/dist/aun/aid/index.js +2 -1
  17. package/dist/aun/aid/managed-operation.js +53 -1
  18. package/dist/aun/aid/store.js +17 -13
  19. package/dist/aun/menu-client.js +74 -0
  20. package/dist/aun/msg/group.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +1 -1
  22. package/dist/aun/msg/p2p.js +6 -1
  23. package/dist/aun/msg/upload.js +30 -22
  24. package/dist/aun/storage/client.js +4 -1
  25. package/dist/aun/storage/index.js +1 -1
  26. package/dist/channels/aun.js +329 -94
  27. package/dist/cli/agent-command.js +125 -5
  28. package/dist/cli/agent-create.js +151 -11
  29. package/dist/cli/agent.js +119 -6
  30. package/dist/cli/aun-commands.js +74 -7
  31. package/dist/cli/config.js +27 -8
  32. package/dist/cli/contact.js +63 -6
  33. package/dist/cli/daemon-commands.js +90 -25
  34. package/dist/cli/diag.js +31 -0
  35. package/dist/cli/handoff-command.js +35 -3
  36. package/dist/cli/index.js +7 -1
  37. package/dist/cli/managed-command-guard.js +3 -0
  38. package/dist/cli/menu-client.js +38 -23
  39. package/dist/cli/restart-monitor.js +57 -10
  40. package/dist/config/builtin-roles.js +2 -0
  41. package/dist/config/config-manager.js +15 -0
  42. package/dist/config/config-operation-service.js +165 -3
  43. package/dist/config/contact-operation-service.js +96 -7
  44. package/dist/config/resolved-config-op.js +66 -8
  45. package/dist/core/agent-application-service.js +2 -1
  46. package/dist/core/auth/authorization-audit.js +20 -12
  47. package/dist/core/auth/operation-authorizer.js +87 -8
  48. package/dist/core/auth/operation-catalog.js +51 -4
  49. package/dist/core/auth/remote-access-store.js +40 -1
  50. package/dist/core/bootstrap-service.js +1 -1
  51. package/dist/core/command/agent-control.js +54 -25
  52. package/dist/core/command/cli-intent-parser.js +18 -0
  53. package/dist/core/command/command-handler.js +152 -20
  54. package/dist/core/command/diag-service.js +118 -0
  55. package/dist/core/command/menu-catalog.js +13 -3
  56. package/dist/core/command/menu-handler.js +88 -31
  57. package/dist/core/command/menu-protocol.js +6 -0
  58. package/dist/core/command/menu-token-store.js +41 -8
  59. package/dist/core/command/slash-gate.js +34 -4
  60. package/dist/core/command/slash-handler.js +77 -12
  61. package/dist/core/event-catalog.js +5 -0
  62. package/dist/core/evolagent.js +3 -3
  63. package/dist/core/handoff/runtime.js +205 -27
  64. package/dist/core/handoff/store.js +193 -4
  65. package/dist/core/message/im-renderer.js +72 -21
  66. package/dist/core/message/message-bridge.js +69 -9
  67. package/dist/core/message/message-queue.js +26 -0
  68. package/dist/core/message/message-utils.js +1 -0
  69. package/dist/core/message/response-engine.js +48 -23
  70. package/dist/core/model/catalog-service.js +79 -49
  71. package/dist/core/model/context-window-suffix.js +33 -0
  72. package/dist/core/model/model-catalog.js +1 -1
  73. package/dist/core/model/model-permission.js +12 -3
  74. package/dist/core/permission/ec-command-parser.js +51 -12
  75. package/dist/core/permission/sandbox-runtime.js +36 -14
  76. package/dist/core/permission/tool-policy.js +47 -7
  77. package/dist/index.js +195 -31
  78. package/dist/ipc.js +54 -2
  79. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  80. package/dist/stats/billing.js +41 -15
  81. package/dist/stats/price-resolver.js +28 -10
  82. package/dist/utils/avatar-upload.js +282 -23
  83. package/dist/utils/cross-platform.js +30 -1
  84. package/dist/utils/error-utils.js +21 -0
  85. package/dist/utils/evolcore-version.js +129 -0
  86. package/dist/utils/logger.js +3 -1
  87. package/dist/utils/model-prices.jsonl +3 -3
  88. package/dist/utils/npm-ops.js +176 -35
  89. package/kits/docs/context-assembly.md +1 -0
  90. package/kits/docs/evolcore/INDEX.md +3 -1
  91. package/kits/docs/evolcore/agent-create.md +30 -11
  92. package/kits/docs/evolcore/agent.md +18 -8
  93. package/kits/docs/evolcore/aid.md +6 -4
  94. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  95. package/kits/docs/evolcore/config.md +7 -0
  96. package/kits/docs/evolcore/contact.md +12 -5
  97. package/kits/docs/evolcore/menu-client.md +11 -3
  98. package/kits/docs/evolcore/msg.md +1 -0
  99. package/kits/docs/prompt-loading-architecture.md +1 -0
  100. package/kits/docs/venues/group.md +12 -0
  101. package/kits/eck_manifest.json +12 -0
  102. package/kits/rules/03-identity.md +3 -1
  103. package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
  104. package/kits/templates/roles/admin.json +14 -2
  105. package/kits/templates/roles/member.json +3 -2
  106. package/kits/templates/roles/visitor.json +4 -3
  107. package/kits/templates/system-fragments/bootstrap.md +16 -5
  108. package/kits/templates/system-fragments/channel.md +1 -1
  109. package/kits/templates/system-fragments/session.md +1 -1
  110. package/package.json +6 -4
  111. package/skills/eclink/SKILL.md +0 -84
  112. package/skills/eclink/agents/openai.yaml +0 -4
@@ -116,7 +116,7 @@ async function runManagedAid(args, formatJson) {
116
116
  if (!parsed.ok)
117
117
  failManagedAid(formatJson, parsed.code, parsed.reason);
118
118
  const runtime = await readBestTaskRuntimeContext();
119
- const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put';
119
+ const requiresSelfAid = parsed.command.kind === 'show' || parsed.command.kind === 'agentmd.put' || parsed.command.kind === 'avatar';
120
120
  if (requiresSelfAid && runtime?.selfAid && parsed.command.aid !== runtime.selfAid.replace(/^@/, '')) {
121
121
  failManagedAid(formatJson, 'SCOPE_MISMATCH', `托管任务只能操作当前 self AID:${runtime.selfAid}`);
122
122
  }
@@ -155,6 +155,22 @@ async function runManagedAid(args, formatJson) {
155
155
  console.log(JSON.stringify({ ok: true, aid: response.result.aid, path: response.result.path }, null, 2));
156
156
  else
157
157
  console.log('✓ agent.md 已发布');
158
+ return;
159
+ case 'avatar':
160
+ if (formatJson)
161
+ console.log(JSON.stringify({
162
+ ok: true,
163
+ aid: response.result.aid,
164
+ avatar: response.result.avatar,
165
+ objectKey: response.result.objectKey,
166
+ sha256: response.result.sha256,
167
+ mime: response.result.mime,
168
+ width: response.result.width,
169
+ height: response.result.height,
170
+ aspectRatio: response.result.aspectRatio,
171
+ }, null, 2));
172
+ else
173
+ console.log(`✓ 头像已更新${response.result.avatar ? `: ${response.result.avatar}` : ''}${response.result.sha256 ? ` (sha256: ${response.result.sha256})` : ''}`);
158
174
  }
159
175
  }
160
176
  function failManagedGroup(formatJson, code, error) {
@@ -346,6 +362,7 @@ Commands:
346
362
  lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
347
363
  agentmd put <aid> 读本地 agent.md → 签名 → 上传
348
364
  agentmd get <aid> 下载 agent.md → 验签 → 本地持久化
365
+ avatar <aid> --file <path> | --url <https-url> 上传头像并更新 agent.md
349
366
 
350
367
  Options:
351
368
  --format json 输出 JSON 格式
@@ -795,7 +812,56 @@ Options:
795
812
  console.error(`未知子命令: aid agentmd ${verb ?? ''}\n用法: ec aid agentmd [put|get] <aid>`);
796
813
  process.exit(1);
797
814
  }
798
- console.error(`未知子命令: ${sub}\n用法: ec aid [list|show|new|delete|lookup|agentmd] <aid>`);
815
+ if (sub === 'avatar') {
816
+ const aid = args[1];
817
+ const fileIndex = args.indexOf('--file');
818
+ const urlIndex = args.indexOf('--url');
819
+ const filePath = fileIndex >= 0 ? args[fileIndex + 1] : undefined;
820
+ const url = urlIndex >= 0 ? args[urlIndex + 1] : undefined;
821
+ if (wantsHelp(args)) {
822
+ console.log('用法: ec aid avatar <aid> (--file <path> | --url <https-url>) [--format json]');
823
+ return;
824
+ }
825
+ const sourceCount = (fileIndex >= 0 ? 1 : 0) + (urlIndex >= 0 ? 1 : 0);
826
+ if (!aid || sourceCount !== 1 || (fileIndex >= 0 && (!filePath || filePath.startsWith('-')))
827
+ || (urlIndex >= 0 && (!url || url.startsWith('-'))) || (fileIndex >= 0 && fileIndex !== 2) || (urlIndex >= 0 && urlIndex !== 2)
828
+ || args.filter(value => value === '--file').length > 1 || args.filter(value => value === '--url').length > 1 || !isValidAid(aid)
829
+ || (args.includes('--format') && !formatJson) || (args.includes('--format') && args.filter(value => value === '--format').length !== 1)) {
830
+ const usage = '用法: ec aid avatar <aid> (--file <path> | --url <https-url>) [--format json]';
831
+ if (formatJson)
832
+ console.log(JSON.stringify({ ok: false, code: 'INVALID_ARGUMENT', error: usage }, null, 2));
833
+ else
834
+ console.error(usage);
835
+ process.exit(1);
836
+ }
837
+ const { uploadAvatarFile, uploadAvatarUrl } = await import('../utils/avatar-upload.js');
838
+ const result = filePath
839
+ ? await uploadAvatarFile(aid, filePath, { aunPath })
840
+ : await uploadAvatarUrl(aid, url, { aunPath });
841
+ if (!result.ok) {
842
+ if (formatJson)
843
+ console.log(JSON.stringify({ ok: false, code: result.code, error: result.error }, null, 2));
844
+ else
845
+ console.error(`❌ 头像上传失败: ${result.error}`);
846
+ process.exit(1);
847
+ }
848
+ if (formatJson)
849
+ console.log(JSON.stringify({
850
+ ok: true,
851
+ aid,
852
+ avatar: result.publicUrl,
853
+ objectKey: result.objectKey,
854
+ sha256: result.sha256,
855
+ mime: result.mime,
856
+ width: result.width,
857
+ height: result.height,
858
+ aspectRatio: result.aspectRatio,
859
+ }, null, 2));
860
+ else
861
+ console.log(`✓ 头像已更新${result.publicUrl ? `: ${result.publicUrl}` : ''}${result.sha256 ? ` (sha256: ${result.sha256})` : ''}`);
862
+ return;
863
+ }
864
+ console.error(`未知子命令: ${sub}\n用法: ec aid [list|show|new|delete|lookup|agentmd|avatar] <aid>`);
799
865
  process.exit(1);
800
866
  }
801
867
  // ==================== RPC ====================
@@ -1079,6 +1145,7 @@ Commands:
1079
1145
 
1080
1146
  Options:
1081
1147
  --text-from-file <path> 从文件读取文本(UTF-8),用于超长消息或避免 Shell 转义
1148
+ --handoff-context <text> 仅托管会话:为目标会话补充本地上下文,不发给对端
1082
1149
  --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
1083
1150
  --aun-path <path> 指定 AUN keystore 根
1084
1151
  --format json 输出 JSON 格式
@@ -1397,7 +1464,7 @@ Options:
1397
1464
  process.stderr.write(' ⏫ finalizing upload...\n');
1398
1465
  }
1399
1466
  };
1400
- const result = await msgSend({ from, to, body, encrypt, thread, returnPolicy: returnPolicyRaw, onUploadProgress, ...commonOpts });
1467
+ const result = await msgSend({ from, to, body, encrypt, thread, returnPolicy: returnPolicyRaw, handoffContext: getArgValue(args, '--handoff-context'), onUploadProgress, ...commonOpts });
1401
1468
  if (!result.ok) {
1402
1469
  if (formatJson) {
1403
1470
  console.log(JSON.stringify(result));
@@ -1622,7 +1689,7 @@ Options:
1622
1689
  --text <说明> 附件说明文字(仅 --file 模式)
1623
1690
  --transcript <text> 语音转写(仅 --as voice)
1624
1691
  --group-id <aid> 指定自定义群 AID(仅 create)
1625
- --member-type <type> 定向邀请成员类型:human|ai(仅 invite)
1692
+ --member-type <type> 定向邀请成员类型:human|agent(仅 invite)
1626
1693
  -- end-of-options:其后所有参数按正文处理
1627
1694
  (用于发送恰好等于某 flag 的文本,如 send a g -- --encrypt)
1628
1695
 
@@ -2226,8 +2293,8 @@ Options:
2226
2293
  }
2227
2294
  }
2228
2295
  const memberType = getArgValue(args, '--member-type');
2229
- if (memberType !== undefined && memberType !== 'human' && memberType !== 'ai') {
2230
- console.error('❌ --member-type 必须是 human 或 ai');
2296
+ if (memberType !== undefined && memberType !== 'human' && memberType !== 'agent') {
2297
+ console.error('❌ --member-type 必须是 human 或 agent');
2231
2298
  process.exit(1);
2232
2299
  }
2233
2300
  const result = await groupInvite({ from, groupId, members, memberType: memberType, ...commonOpts });
@@ -2406,7 +2473,7 @@ const VALUE_FLAGS = new Set([
2406
2473
  '--as', '--content-type', '--mention', '--visibility', '--join-mode',
2407
2474
  '--group-id', '--name', '--message', '--answer', '--page', '--size',
2408
2475
  '--member-type',
2409
- '--aun-path', '--thread', '--return',
2476
+ '--aun-path', '--thread', '--return', '--handoff-context',
2410
2477
  ]);
2411
2478
  const BOOLEAN_FLAGS = new Set([
2412
2479
  '--encrypt', '--no-encrypt', '--mention-all',
@@ -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) {
@@ -215,6 +221,11 @@ Global commands:
215
221
  Selectors:
216
222
  --self <aid> [--peer <peerKey>] | --default | --process
217
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
+
218
229
  Output:
219
230
  --format json`;
220
231
  export async function cmdConfig(args) {
@@ -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);
@@ -62,11 +71,59 @@ function printHelp() {
62
71
  ec contact add <primaryId> --self <agent-aid>
63
72
  ec contact block <primaryId> --self <agent-aid>
64
73
  ec contact unblock <primaryId> --self <agent-aid>
65
- ec contact list --blocked --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>]
66
80
 
67
81
  Options:
68
- --self <agent-aid> 必填,本端 agent AID
69
- --blocked list 必填,仅列出已拉黑联系人
82
+ --self <agent-aid> 本端 agent AID;省略时从当前运行环境推断
83
+ --blocked 仅列出已拉黑联系人
84
+ --pending 仅列出待审核联系人申请
85
+ --all 列出所有联系人及状态
86
+ --request-id <id> 联系人申请 ID(审批命令必填)
87
+ --contact-revision <revision> 申请审批时使用的联系人版本(可选)
70
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
+ }
@@ -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);
@@ -0,0 +1,31 @@
1
+ import { ipcQuery } from '../ipc.js';
2
+ import { resolvePaths } from '../paths.js';
3
+ import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
4
+ import { parseDiagArgs } from '../core/command/diag-service.js';
5
+ import { wantsHelp } from './help.js';
6
+ export async function cmdDiag(args) {
7
+ if (wantsHelp(args)) {
8
+ console.log('用法:ec diag <问题描述> [--owner <daemonOwner-aid>] [--format json]\n ec diag stop [--format json]\n未指定 --owner 时联系本机 daemon.json.owners[0]。');
9
+ return;
10
+ }
11
+ const json = args.at(-2) === '--format' && args.at(-1) === 'json';
12
+ const argv = json ? args.slice(0, -2) : args;
13
+ let result;
14
+ try {
15
+ parseDiagArgs(argv);
16
+ result = await ipcQuery(resolvePaths().socket, {
17
+ type: 'diag', argv,
18
+ ...(process.env.EVOLCORE_SESSION_ID ? {
19
+ sessionId: process.env.EVOLCORE_SESSION_ID,
20
+ delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
21
+ } : {}),
22
+ }, 30_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', message: 'daemon 未返回结果,请检查其运行状态;发送结果尚未确认' };
23
+ }
24
+ catch (error) {
25
+ result = { ok: false, code: 'INVALID_ARGUMENT', message: error.message };
26
+ }
27
+ result.message ??= result.error ?? '诊断请求未完成';
28
+ console.log(json ? JSON.stringify(result, null, 2) : result.message);
29
+ if (!result.ok)
30
+ process.exitCode = 1;
31
+ }