evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -5,10 +5,10 @@ import { getAidStore, loadAid, SLOT } from './store.js';
5
5
  import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
6
6
  export const DEFAULT_AGENT_DESCRIPTION = 'EvolCore AI agent.';
7
7
  /**
8
- * AUN 主体类型白名单。没有 `ai` / `codeagent` 这类取值:使用大模型的服务程序
9
- * 一律归为 `agent`。新建 AID 未指定类型时使用 DEFAULT_AGENT_TYPE。
8
+ * AUN 主体类型白名单。没有 `ai` / `codeagent` 这类取值:使用大模型且没有
9
+ * 专用主体类型的服务程序默认归为 `agent`。新建 AID 未指定类型时使用 DEFAULT_AGENT_TYPE。
10
10
  */
11
- export const AGENT_TYPES = ['human', 'agent', 'group', 'service', 'device', 'org'];
11
+ export const AGENT_TYPES = ['human', 'agent', 'group', 'service', 'device', 'org', 'host'];
12
12
  export const DEFAULT_AGENT_TYPE = 'agent';
13
13
  /**
14
14
  * 归一化主体类型:大小写不敏感,统一收敛为白名单里的小写形式。
@@ -86,6 +86,12 @@ export function validateAgentTags(tags, options = {}) {
86
86
  }
87
87
  return errors;
88
88
  }
89
+ function resolveAgentMdAunPath(opts) {
90
+ return opts?.aunPath ?? opts?.store?.aunPath ?? resolveRoot();
91
+ }
92
+ // A process can host agents that deliberately use different AUN keystore
93
+ // roots. Keep their display-name state separate so one root cannot shadow the
94
+ // other with a same-named AID.
89
95
  const displayNameCache = new Map();
90
96
  const displayNamePending = new Map();
91
97
  const displayNameRevision = new Map();
@@ -101,9 +107,13 @@ export function parseAgentDisplayName(content) {
101
107
  const name = frontmatter.match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim();
102
108
  return name || undefined;
103
109
  }
104
- function readLocalAgentDisplayName(aid) {
110
+ function displayNameKey(aid, aunPath) {
111
+ const root = path.resolve(aunPath ?? resolveRoot());
112
+ return `${root}\u0000${aid.toLowerCase()}`;
113
+ }
114
+ function readLocalAgentDisplayName(aid, aunPath) {
105
115
  try {
106
- return parseAgentDisplayName(fs.readFileSync(agentMdPath(aid), 'utf-8'));
116
+ return parseAgentDisplayName(fs.readFileSync(agentMdPath(aid, aunPath), 'utf-8'));
107
117
  }
108
118
  catch {
109
119
  return undefined;
@@ -114,56 +124,77 @@ function readLocalAgentDisplayName(aid) {
114
124
  * Local content is returned immediately; a missing value starts one shared
115
125
  * background refresh and remains undefined until a later lookup.
116
126
  */
117
- export function resolveAgentDisplayName(aid) {
127
+ export function resolveAgentDisplayName(aid, opts) {
118
128
  const normalizedAid = normalizeAid(aid);
119
129
  if (!normalizedAid)
120
130
  return undefined;
121
- const cached = displayNameCache.get(normalizedAid);
131
+ const aunPath = resolveAgentMdAunPath(opts);
132
+ const key = displayNameKey(normalizedAid, aunPath);
133
+ const cached = displayNameCache.get(key);
122
134
  if (cached)
123
135
  return cached;
124
- const localName = readLocalAgentDisplayName(normalizedAid);
136
+ const localName = readLocalAgentDisplayName(normalizedAid, aunPath);
125
137
  if (localName) {
126
- displayNameCache.set(normalizedAid, localName);
138
+ displayNameCache.set(key, localName);
127
139
  return localName;
128
140
  }
129
- void refreshAgentDisplayName(normalizedAid);
141
+ void refreshAgentDisplayName(normalizedAid, opts);
130
142
  return undefined;
131
143
  }
132
144
  /** Fetch agent.md once and update the shared display-name cache. */
133
- export function refreshAgentDisplayName(aid) {
145
+ export function refreshAgentDisplayName(aid, opts) {
134
146
  const normalizedAid = normalizeAid(aid);
135
147
  if (!normalizedAid)
136
148
  return Promise.resolve(undefined);
137
- const existing = displayNamePending.get(normalizedAid);
149
+ const aunPath = resolveAgentMdAunPath(opts);
150
+ const key = displayNameKey(normalizedAid, aunPath);
151
+ const existing = displayNamePending.get(key);
138
152
  if (existing)
139
153
  return existing;
140
- const revision = displayNameRevision.get(normalizedAid) ?? 0;
154
+ const revision = displayNameRevision.get(key) ?? 0;
141
155
  let pending;
142
- pending = agentmdGet(normalizedAid)
156
+ pending = agentmdGet(normalizedAid, { aunPath, store: opts?.store })
143
157
  .then(content => {
144
158
  const name = typeof content === 'string' ? parseAgentDisplayName(content) : undefined;
145
- if ((displayNameRevision.get(normalizedAid) ?? 0) !== revision)
159
+ if ((displayNameRevision.get(key) ?? 0) !== revision)
146
160
  return undefined;
147
161
  if (name)
148
- displayNameCache.set(normalizedAid, name);
162
+ displayNameCache.set(key, name);
149
163
  return name;
150
164
  })
151
165
  .catch(() => undefined)
152
166
  .finally(() => {
153
- if (displayNamePending.get(normalizedAid) === pending) {
154
- displayNamePending.delete(normalizedAid);
167
+ if (displayNamePending.get(key) === pending) {
168
+ displayNamePending.delete(key);
155
169
  }
156
170
  });
157
- displayNamePending.set(normalizedAid, pending);
171
+ displayNamePending.set(key, pending);
158
172
  return pending;
159
173
  }
160
- export function invalidateAgentDisplayName(aid) {
174
+ export function invalidateAgentDisplayName(aid, aunPath) {
161
175
  const normalizedAid = normalizeAid(aid);
162
176
  if (!normalizedAid)
163
177
  return;
164
- displayNameCache.delete(normalizedAid);
165
- displayNamePending.delete(normalizedAid);
166
- displayNameRevision.set(normalizedAid, (displayNameRevision.get(normalizedAid) ?? 0) + 1);
178
+ if (aunPath !== undefined) {
179
+ const key = displayNameKey(normalizedAid, aunPath);
180
+ displayNameCache.delete(key);
181
+ displayNamePending.delete(key);
182
+ displayNameRevision.set(key, (displayNameRevision.get(key) ?? 0) + 1);
183
+ return;
184
+ }
185
+ const suffix = `\u0000${normalizedAid.toLowerCase()}`;
186
+ const keys = new Set([
187
+ ...displayNameCache.keys(),
188
+ ...displayNamePending.keys(),
189
+ ...displayNameRevision.keys(),
190
+ ]);
191
+ for (const key of keys) {
192
+ if (!key.endsWith(suffix))
193
+ continue;
194
+ displayNameCache.delete(key);
195
+ displayNamePending.delete(key);
196
+ displayNameRevision.set(key, (displayNameRevision.get(key) ?? 0) + 1);
197
+ }
167
198
  }
168
199
  export function buildInitialAgentMd(opts) {
169
200
  const agentName = opts.name?.trim() || opts.aid.split('.')[0];
@@ -354,10 +385,10 @@ function verifyLocal(store, aid, content) {
354
385
  }
355
386
  }
356
387
  export async function agentmdGet(aid, opts) {
357
- const aunPath = opts?.aunPath ?? resolveRoot();
388
+ const aunPath = resolveAgentMdAunPath(opts);
358
389
  const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
359
390
  const ownStore = !opts?.store;
360
- const localPath = agentMdPath(aid);
391
+ const localPath = agentMdPath(aid, aunPath);
361
392
  try {
362
393
  let content;
363
394
  let verification;
@@ -367,6 +398,14 @@ export async function agentmdGet(aid, opts) {
367
398
  verification = normalizeVerification(r.data.verification);
368
399
  }
369
400
  else {
401
+ if (opts?.remoteOnly) {
402
+ const code = String(r.error?.code ?? '');
403
+ const message = String(r.error?.message ?? r.error);
404
+ if (code === 'AGENTMD_NOT_FOUND' || /not found|404|missing/i.test(message)) {
405
+ throw Object.assign(new Error(`agent.md not found for ${aid}`), { code: 'AGENTMD_NOT_FOUND' });
406
+ }
407
+ throw new Error(`Remote agent.md verification unavailable for ${aid}`);
408
+ }
370
409
  // Network/fetch failed — fall back to local file.
371
410
  if (!fs.existsSync(localPath)) {
372
411
  throw new Error(`fetch agent.md failed for ${aid}: ${r.error.message}`);
@@ -399,10 +438,10 @@ export async function agentmdPut(content, opts) {
399
438
  if (!validation.valid) {
400
439
  throw new Error(`invalid agent.md frontmatter: ${validation.errors.join('; ')}`);
401
440
  }
402
- const aunPath = opts.aunPath ?? resolveRoot();
441
+ const aunPath = resolveAgentMdAunPath(opts);
403
442
  const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
404
443
  const ownStore = !opts.store;
405
- const dir = aidLocalDir(opts.aid);
444
+ const dir = aidLocalDir(opts.aid, aunPath);
406
445
  const filePath = path.join(dir, 'agent.md');
407
446
  const existed = fs.existsSync(filePath);
408
447
  // 先写本地文件(与旧行为一致:本地内容更新应在上传失败时仍保留)
@@ -443,10 +482,10 @@ export async function agentmdPut(content, opts) {
443
482
  * so a freshly-built store reports local_found=false and will fetch.
444
483
  */
445
484
  export async function agentmdSync(aid, opts) {
446
- const aunPath = opts?.aunPath ?? resolveRoot();
485
+ const aunPath = resolveAgentMdAunPath(opts);
447
486
  const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
448
487
  const ownStore = !opts?.store;
449
- const localPath = agentMdPath(aid);
488
+ const localPath = agentMdPath(aid, aunPath);
450
489
  try {
451
490
  // AIDStore owns the agent.md consistency API in fastaun 0.5.x. Keep this
452
491
  // runtime guard because callers and test doubles can still accidentally
@@ -455,9 +494,11 @@ export async function agentmdSync(aid, opts) {
455
494
  if (typeof candidate.checkAgentMd !== 'function' || typeof candidate.downloadAgentMd !== 'function') {
456
495
  throw new TypeError('invalid AIDStore: checkAgentMd/downloadAgentMd unavailable');
457
496
  }
458
- const check = await store.checkAgentMd(aid, 30);
497
+ // A change event can describe Last-Modified alone. Bypass the SDK's
498
+ // independent freshness cache when Core knows its snapshot is stale.
499
+ const check = opts?.forceRefresh ? undefined : await store.checkAgentMd(aid, opts?.ttlDays ?? 30);
459
500
  // In sync (cache fresh) — return local file content with cached verification status.
460
- if (check.ok && !check.data.needs_update && check.data.local_found) {
501
+ if (check?.ok && !check.data.needs_update && check.data.local_found) {
461
502
  const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
462
503
  const verification = check.data.verify_status
463
504
  ? normalizeVerification({ status: check.data.verify_status, reason: check.data.verify_error || undefined })
@@ -468,6 +509,7 @@ export async function agentmdSync(aid, opts) {
468
509
  // SDK's downloadAgentMd persists to disk internally (AgentMdManager.saveRecord → writeContent).
469
510
  const fetched = await store.downloadAgentMd(aid);
470
511
  if (fetched.ok) {
512
+ invalidateAgentDisplayName(aid);
471
513
  return { changed: true, content: fetched.data.content, verification: normalizeVerification(fetched.data.verification) };
472
514
  }
473
515
  // Fetch failed (network) — fall back to local file if present.
@@ -39,7 +39,7 @@ export function suppressSdkLogs() {
39
39
  };
40
40
  }
41
41
  // ==================== SDK package ====================
42
- export const MIN_AUN_CORE_SDK = [0, 5, 16];
42
+ export const MIN_AUN_CORE_SDK = [0, 5, 17];
43
43
  export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
44
44
  function compareVersion(a, min) {
45
45
  const parts = a.split('.').map(n => parseInt(n, 10));
@@ -1,10 +1,183 @@
1
1
  import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { parseDocument } from 'yaml';
2
5
  import { aidCreate } from './index.js';
3
6
  import { DEFAULT_AID_DOMAIN, normalizeAidDomain } from './domain.js';
4
7
  import { getAidStore, SLOT } from './store.js';
8
+ import { validateAgentMdDocument, verifyAgentMdUpload } from '@agentunion/fastaun';
9
+ import { agentMdPath, resolvePaths } from '../../paths.js';
10
+ import { PeerIdentityCache } from '../../core/relation/peer-identity.js';
11
+ import { agentmdPut, buildInitialAgentMd, invalidateAgentDisplayName, stripAgentMdSignature } from './agentmd.js';
5
12
  import { logger } from '../../utils/logger.js';
6
13
  const MAX_ATTEMPTS = 5;
7
14
  export { DEFAULT_AID_DOMAIN } from './domain.js';
15
+ /** Human-facing platform label used in the control AID's agent.md name. */
16
+ export function controlHostPlatformLabel(platform = process.platform) {
17
+ switch (String(platform).trim().toLowerCase()) {
18
+ case 'win32':
19
+ case 'windows':
20
+ return 'Win';
21
+ case 'linux':
22
+ return 'Linux';
23
+ case 'darwin':
24
+ case 'macos':
25
+ case 'mac':
26
+ return 'MacOS';
27
+ default: {
28
+ const value = String(platform).trim();
29
+ return value ? `${value[0].toUpperCase()}${value.slice(1)}` : 'Host';
30
+ }
31
+ }
32
+ }
33
+ /** Build the stable display name for a daemon/control AID. */
34
+ export function buildControlAgentName(aid, platform = process.platform) {
35
+ const normalized = String(aid ?? '').trim().replace(/^@/, '');
36
+ const prefix = normalized.split('.', 1)[0] || normalized || 'unknown';
37
+ return `${controlHostPlatformLabel(platform)}主机-${prefix}`;
38
+ }
39
+ const CONTROL_AGENT_DESCRIPTION = 'EvolCore daemon host.';
40
+ const CONTROL_AGENT_TAGS = ['evolcore', 'control', 'host'];
41
+ const controlAgentMdPending = new Map();
42
+ function readJsonFile(filePath) {
43
+ try {
44
+ const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
45
+ return value && typeof value === 'object' && !Array.isArray(value)
46
+ ? value
47
+ : undefined;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ function hasCanonicalControlFrontmatter(content, aid, name, aunRoot) {
54
+ try {
55
+ // A frontmatter-only match is insufficient here: an unsigned or malformed
56
+ // local card must be republished so the SDK can produce a valid signature.
57
+ validateAgentMdDocument(content, { expectedAid: aid, requireSignature: true });
58
+ const payload = stripAgentMdSignature(content);
59
+ const frontmatter = payload.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
60
+ if (!frontmatter)
61
+ return false;
62
+ const document = parseDocument(frontmatter);
63
+ if (document.errors.length > 0)
64
+ return false;
65
+ const fields = document.toJS();
66
+ if (!fields || typeof fields !== 'object' || Array.isArray(fields))
67
+ return false;
68
+ const values = fields;
69
+ const canonical = typeof values.aid === 'string'
70
+ && values.aid.trim().toLowerCase() === aid.toLowerCase()
71
+ && typeof values.name === 'string'
72
+ && values.name.trim() === name
73
+ && typeof values.type === 'string'
74
+ && values.type.trim().toLowerCase() === 'host';
75
+ if (!canonical)
76
+ return false;
77
+ // The SDK validator checks that a signature block is well formed, but it
78
+ // deliberately does not verify the signature against the local cert.
79
+ // A damaged or stale control card must be republished, otherwise peers
80
+ // continue to reject it even though the frontmatter looks canonical.
81
+ const certPath = path.join(aunRoot, 'AIDs', aid, 'public', 'cert.pem');
82
+ const certPem = fs.readFileSync(certPath, 'utf8');
83
+ verifyAgentMdUpload(content, certPem, aid);
84
+ return true;
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ }
90
+ function clearControlAgentPeerCaches(aid, root = resolvePaths().root) {
91
+ const agentsDir = path.join(root, 'agents');
92
+ // A first-run installation may not have created any EvolCore agent yet.
93
+ // There is no cache to clear in that state, and ENOENT is not a warning.
94
+ if (!fs.existsSync(agentsDir))
95
+ return;
96
+ try {
97
+ for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
98
+ if (!entry.isDirectory())
99
+ continue;
100
+ PeerIdentityCache.clear('aun', aid, path.join(agentsDir, entry.name));
101
+ }
102
+ }
103
+ catch (error) {
104
+ logger.warn(`[control-aid] 清理旧身份缓存失败(不影响名片发布): ${error instanceof Error ? error.message : String(error)}`);
105
+ }
106
+ }
107
+ /**
108
+ * Ensure a daemon/control AID has a signed host agent.md.
109
+ *
110
+ * Registration in fastaun creates a default `type: agent` card. This helper
111
+ * upgrades that card, supplements cards missing on older installations, and
112
+ * is idempotent once the local card is canonical. Upload errors are allowed to
113
+ * propagate to the caller so startup can log them without becoming blocked.
114
+ */
115
+ export async function ensureControlAgentMd(aid, opts = {}) {
116
+ const normalizedAid = String(aid ?? '').trim().replace(/^@/, '');
117
+ if (!normalizedAid)
118
+ throw new Error('control AID is required');
119
+ const aunRoot = opts.aunPath ?? opts.store?.aunPath ?? resolvePaths().root;
120
+ const cacheRoot = opts.cacheRoot ?? resolvePaths().root;
121
+ const pendingKey = path.resolve(aunRoot) + '\u0000' + normalizedAid.toLowerCase();
122
+ const pending = controlAgentMdPending.get(pendingKey);
123
+ if (pending)
124
+ return pending;
125
+ const run = (async () => {
126
+ const name = buildControlAgentName(normalizedAid, opts.platform);
127
+ const localPath = agentMdPath(normalizedAid, aunRoot);
128
+ const metadataPath = path.join(path.dirname(localPath), 'agentmd.json');
129
+ let existing;
130
+ try {
131
+ existing = fs.readFileSync(localPath, 'utf8');
132
+ }
133
+ catch { /* missing */ }
134
+ const metadata = readJsonFile(metadataPath);
135
+ const remoteMissing = String(metadata?.remote_status ?? '').trim().toLowerCase() === 'missing';
136
+ const needsPublish = !existing || !hasCanonicalControlFrontmatter(existing, normalizedAid, name, aunRoot) || remoteMissing;
137
+ if (!needsPublish)
138
+ return { aid: normalizedAid, name, published: false };
139
+ const content = buildInitialAgentMd({
140
+ aid: normalizedAid,
141
+ name,
142
+ type: 'host',
143
+ description: CONTROL_AGENT_DESCRIPTION,
144
+ tags: CONTROL_AGENT_TAGS,
145
+ });
146
+ try {
147
+ await agentmdPut(content, { aid: normalizedAid, store: opts.store, aunPath: aunRoot });
148
+ }
149
+ catch (error) {
150
+ // agentmdPut intentionally keeps edits to an existing card on failure;
151
+ // a control-card migration must restore the old card so a transient
152
+ // gateway failure cannot make the local state look successfully migrated.
153
+ try {
154
+ if (existing !== undefined) {
155
+ fs.mkdirSync(path.dirname(localPath), { recursive: true });
156
+ fs.writeFileSync(localPath, existing, 'utf8');
157
+ }
158
+ else {
159
+ fs.unlinkSync(localPath);
160
+ }
161
+ }
162
+ catch { /* preserve the original upload error */ }
163
+ throw error;
164
+ }
165
+ // The upload changes the control AID's display name. Drop the process-wide
166
+ // name cache so subsequent context/status rendering cannot keep the legacy
167
+ // `agent` name until the normal TTL refresh.
168
+ invalidateAgentDisplayName(normalizedAid);
169
+ clearControlAgentPeerCaches(normalizedAid, cacheRoot);
170
+ return { aid: normalizedAid, name, published: true };
171
+ })();
172
+ controlAgentMdPending.set(pendingKey, run);
173
+ try {
174
+ return await run;
175
+ }
176
+ finally {
177
+ if (controlAgentMdPending.get(pendingKey) === run)
178
+ controlAgentMdPending.delete(pendingKey);
179
+ }
180
+ }
8
181
  function environmentValue(name) {
9
182
  if (!Object.prototype.hasOwnProperty.call(process.env, name))
10
183
  return undefined;
@@ -66,7 +239,7 @@ async function candidateExists(store, candidate) {
66
239
  * 查重通过但注册失败时也换候选重试,覆盖查重与注册之间的竞态。
67
240
  * - 查重走 GET 证书(见 candidateExists;不拉 agent.md,控制 AID 本就不传 agent.md)
68
241
  * - fail-fast:查重探测失败(网关不可达)立即抛错,不掩盖成"均冲突"
69
- * - agent.md 不上传:aidCreate 仅注册身份 + 写私钥,不调 agentmdPut
242
+ * - 注册后补发控制主机 agent.md;失败只告警,不丢弃已经注册的 AID
70
243
  */
71
244
  export async function generateControlAid(aidDomain) {
72
245
  const finalAidDomain = aidDomain === undefined
@@ -83,6 +256,12 @@ export async function generateControlAid(aidDomain) {
83
256
  }
84
257
  try {
85
258
  const created = await aidCreate(candidate);
259
+ try {
260
+ await ensureControlAgentMd(created.aid, { store: created.store });
261
+ }
262
+ catch (e) {
263
+ logger.warn(`[control-aid] ${created.aid} agent.md 发布失败(控制面可继续,后续启动会重试): ${e instanceof Error ? e.message : String(e)}`);
264
+ }
86
265
  // 清理 aidCreate 内部另开的 client/store——关闭失败不可丢弃已注册的 AID(否则下次 init
87
266
  // 会把它当冲突,白白消耗一次重试)。close 异常降级为 warn。
88
267
  try {
@@ -76,7 +76,7 @@ export function aidList(aunPath) {
76
76
  canSign: hasPrivateKey && hasCert && !certExpired && keyMatchesCert === true,
77
77
  }),
78
78
  hasPrivateKey,
79
- hasAgentMd: fs.existsSync(agentMdPath(e.name)),
79
+ hasAgentMd: fs.existsSync(agentMdPath(e.name, root)),
80
80
  hasCert,
81
81
  certExpired,
82
82
  keyMatchesCert,
@@ -265,7 +265,7 @@ export async function aidShow(aid, opts) {
265
265
  const aunPath = opts?.aunPath ?? defaultAunPath();
266
266
  const aidDir = path.join(aunPath, 'AIDs', aid);
267
267
  const hasPrivateKey = fs.existsSync(path.join(aidDir, 'private'));
268
- const hasAgentMd = fs.existsSync(agentMdPath(aid));
268
+ const hasAgentMd = fs.existsSync(agentMdPath(aid, aunPath));
269
269
  let certExpiresAt = null;
270
270
  let certSubject = null;
271
271
  let certExpired = false;
@@ -323,7 +323,7 @@ export async function aidShow(aid, opts) {
323
323
  }
324
324
  if (hasAgentMd) {
325
325
  try {
326
- const content = fs.readFileSync(agentMdPath(aid), 'utf-8');
326
+ const content = fs.readFileSync(agentMdPath(aid, aunPath), 'utf-8');
327
327
  if (!content.includes('AUN-SIGNATURE')) {
328
328
  agentMdSignature = 'unsigned';
329
329
  }