evolcore 0.0.35 → 0.0.37

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 (224) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +177 -85
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +115 -36
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -3,14 +3,17 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { isValidAid } from '../aun/aid/validation.js';
5
5
  import { isExplicitGroupId } from '../aun/group/index.js';
6
- import { agentConfig, agentContactAuditPath, agentContactConfig, agentDir, resolvePaths } from '../paths.js';
6
+ import { agentConfig, agentContactAuditPath, agentContactConfig, agentDir, agentRelationsDir, resolvePaths } from '../paths.js';
7
7
  import { atomicReadJson, atomicWrite, atomicWriteJson } from '../utils/atomic-write.js';
8
8
  import { fileCache } from '../core/daemon-file-cache.js';
9
9
  import { unregisterFile } from '../core/file-integrity/store.js';
10
10
  import { ensureManagedLockParent, resolveManagedLockPath } from '../core/runtime-lock.js';
11
+ import { parsePeerKey } from '../core/relation/peer-identity.js';
11
12
  import { ConfigTarget, notifyConfigWrite, validateConfig } from './config-manager.js';
13
+ import { ownerResolver } from '../core/ownership/owner-resolver.js';
12
14
  import { parseContactAlias } from './contact-alias.js';
13
15
  import { clearRoleStoreCache } from './role-store.js';
16
+ import { currentVersion } from './schema-registry.js';
14
17
  const LOCK_NAME = '.contact-book-mutation.lock';
15
18
  const JOURNAL_NAME = '.contact-book-mutation.json';
16
19
  const LOCK_WAIT_MS = 5_000;
@@ -84,6 +87,86 @@ function enqueueContactMutation(request, changes) {
84
87
  export function mutateContactBookSync(request) {
85
88
  return mutateContactBookSyncInternal(request, []);
86
89
  }
90
+ export function synchronizeManagementContactsSync(selfAid) {
91
+ return mutateContactBookSync({
92
+ selfAid,
93
+ actor: 'management-role-sync',
94
+ mutation: { type: 'sync-management-roles' },
95
+ });
96
+ }
97
+ /** Synchronize contacts using current verified ownership, never a requested binding target. */
98
+ export function synchronizeOwnershipContactsSync(subjectAid) {
99
+ synchronizeManagementContactsSync(subjectAid);
100
+ const ownerAid = ownerResolver.owner(subjectAid);
101
+ if (!ownerAid || !fs.existsSync(agentConfig(ownerAid)))
102
+ return;
103
+ // Add the subject to its local Owner's contacts, preserving existing admission decisions.
104
+ mutateContactBookSync({
105
+ selfAid: ownerAid,
106
+ actor: 'ownership-contact-sync',
107
+ mutation: { type: 'ensure-contacts', primaryIds: [subjectAid] },
108
+ });
109
+ }
110
+ function managementRoleMapForAgent(selfAid, changes) {
111
+ const roles = {};
112
+ for (const owner of ownerResolver.protocolOwners(selfAid)) {
113
+ if (isValidAid(owner) && !isExplicitGroupId(owner))
114
+ roles[owner] = 'owner';
115
+ }
116
+ const relationsRoot = agentRelationsDir(selfAid);
117
+ let entries = [];
118
+ try {
119
+ entries = fs.readdirSync(relationsRoot, { withFileTypes: true });
120
+ }
121
+ catch (error) {
122
+ if (error.code !== 'ENOENT')
123
+ throw error;
124
+ }
125
+ const peerKeys = new Set(entries.filter(entry => entry.isDirectory()).map(entry => entry.name));
126
+ for (const change of changes) {
127
+ const parts = path.relative(relationsRoot, change.file).split(path.sep);
128
+ if (parts.length === 2 && parts[1] === 'config.json')
129
+ peerKeys.add(parts[0]);
130
+ }
131
+ for (const peerKey of peerKeys) {
132
+ if (peerKey.startsWith('_'))
133
+ continue;
134
+ let parsed;
135
+ try {
136
+ parsed = parsePeerKey(peerKey);
137
+ }
138
+ catch {
139
+ continue;
140
+ }
141
+ if (parsed.channelType !== 'aun' || !isValidAid(parsed.channelId) || isExplicitGroupId(parsed.channelId))
142
+ continue;
143
+ const config = readTransactionJson(path.join(relationsRoot, peerKey, 'config.json'), changes);
144
+ if (config?.targetKind !== 'group' && config?.role === 'admin' && roles[parsed.channelId] !== 'owner') {
145
+ roles[parsed.channelId] = 'admin';
146
+ }
147
+ }
148
+ return roles;
149
+ }
150
+ export function synchronizeAllManagementContactsSync() {
151
+ const agentsRoot = resolvePaths().agentsDir;
152
+ let entries = [];
153
+ try {
154
+ entries = fs.readdirSync(agentsRoot, { withFileTypes: true });
155
+ }
156
+ catch (error) {
157
+ if (error.code === 'ENOENT')
158
+ return 0;
159
+ throw error;
160
+ }
161
+ let changed = 0;
162
+ for (const entry of entries) {
163
+ if (!entry.isDirectory() || !isValidAid(entry.name) || !fs.existsSync(agentConfig(entry.name)))
164
+ continue;
165
+ if (synchronizeManagementContactsSync(entry.name).changed)
166
+ changed += 1;
167
+ }
168
+ return changed;
169
+ }
87
170
  /** Recover every interrupted Contact transaction before startup reads config. */
88
171
  export function recoverAllContactBookMutationsSync() {
89
172
  const agentsRoot = resolvePaths().agentsDir;
@@ -121,11 +204,22 @@ function mutateContactBookSyncInternal(request, transactionChanges) {
121
204
  contactRevision: previousRevision,
122
205
  });
123
206
  }
207
+ validateTransactionChanges(selfAid, transactionChanges);
124
208
  const next = cloneBook(current);
125
209
  applyMutation(next, request.mutation, selfAid);
126
- validateContactBook(next, selfAid);
127
- const nextRevision = contactRevision(next);
128
- validateTransactionChanges(selfAid, transactionChanges);
210
+ synchronizeManagementRoles(next, selfAid, transactionChanges);
211
+ if (request.mutation.type === 'replace') {
212
+ const errors = validateConfig(ConfigTarget.Contact, next);
213
+ if (errors.length)
214
+ throw new ContactMutationError('INVALID_CONTACT_BOOK', errors.join('; '));
215
+ }
216
+ else {
217
+ validateContactBook(next, selfAid, transactionChanges);
218
+ }
219
+ const omitEmptySync = request.mutation.type === 'sync-management-roles'
220
+ && current === null
221
+ && Object.keys(next.contacts).length === 0;
222
+ const nextRevision = omitEmptySync ? previousRevision : contactRevision(next);
129
223
  if (nextRevision === previousRevision && transactionChanges.length === 0) {
130
224
  return { changed: false, contactRevision: previousRevision, previousContactRevision: previousRevision, contact: next };
131
225
  }
@@ -176,6 +270,7 @@ function mutateContactBookPreparedSyncInternal(selfAid, prepare) {
176
270
  const prepared = prepare({ contact: next, contactRevision: previousRevision });
177
271
  if (prepared.mutation)
178
272
  applyMutation(next, prepared.mutation, selfAid);
273
+ synchronizeManagementRoles(next, selfAid, []);
179
274
  validateContactBook(next, selfAid);
180
275
  const nextRevision = contactRevision(next);
181
276
  if (nextRevision === previousRevision) {
@@ -232,6 +327,10 @@ function mutateContactBookPreparedSyncInternal(selfAid, prepare) {
232
327
  }
233
328
  }
234
329
  function applyMutation(book, mutation, selfAid) {
330
+ if (mutation.type === 'replace') {
331
+ book.contacts = cloneBook(mutation.contact).contacts;
332
+ return;
333
+ }
235
334
  const contacts = book.contacts;
236
335
  if (mutation.type === 'batch') {
237
336
  for (const item of mutation.mutations)
@@ -246,11 +345,14 @@ function applyMutation(book, mutation, selfAid) {
246
345
  }
247
346
  return;
248
347
  }
348
+ if (mutation.type === 'sync-management-roles') {
349
+ return;
350
+ }
249
351
  const primaryId = normalizeContactAid(mutation.primaryId);
250
352
  const existing = contacts[primaryId];
251
353
  if (mutation.type === 'set-status') {
252
- if (isOwner(selfAid, primaryId) && mutation.status === 'blocked') {
253
- throw new ContactMutationError('OWNER_PROTECTED', 'An Agent owner cannot be blocked');
354
+ if (isOwner(selfAid, primaryId) && mutation.status !== 'active') {
355
+ throw new ContactMutationError('OWNER_PROTECTED', 'An Agent owner must remain active');
254
356
  }
255
357
  contacts[primaryId] = compactEntry({
256
358
  ...existing,
@@ -334,13 +436,27 @@ function applyMutation(book, mutation, selfAid) {
334
436
  }
335
437
  throw new ContactMutationError('INVALID_MUTATION', `Unsupported Contact Book mutation: ${String(mutation?.type)}`);
336
438
  }
337
- function validateContactBook(book, selfAid) {
439
+ function synchronizeManagementRoles(book, selfAid, changes) {
440
+ const roles = managementRoleMapForAgent(selfAid, changes);
441
+ for (const [primaryId, entry] of Object.entries(book.contacts)) {
442
+ book.contacts[primaryId] = compactEntry({
443
+ ...entry,
444
+ ...(roles[primaryId] === 'owner' ? { status: undefined, pendingRequest: undefined, declinedAt: undefined } : {}),
445
+ managementRole: roles[primaryId],
446
+ });
447
+ }
448
+ for (const [primaryId, managementRole] of Object.entries(roles)) {
449
+ book.contacts[primaryId] = compactEntry({ ...book.contacts[primaryId], managementRole });
450
+ }
451
+ }
452
+ function validateContactBook(book, selfAid, changes = []) {
338
453
  const schemaErrors = validateConfig(ConfigTarget.Contact, book);
339
454
  if (schemaErrors.length) {
340
455
  throw new ContactMutationError('INVALID_CONTACT_BOOK', schemaErrors.join('; '));
341
456
  }
342
457
  const aliases = new Map();
343
- const configuredChannelKeys = readConfiguredContactChannelKeys(selfAid);
458
+ const agent = readTransactionJson(agentConfig(selfAid), changes);
459
+ const configuredChannelKeys = readConfiguredContactChannelKeys(selfAid, agent);
344
460
  for (const [primaryId, entry] of Object.entries(book.contacts)) {
345
461
  if (!isValidAid(primaryId) || isExplicitGroupId(primaryId)) {
346
462
  throw new ContactMutationError('INVALID_CONTACT_BOOK', `Invalid contact AID: ${primaryId}`);
@@ -380,14 +496,7 @@ function validateContactBook(book, selfAid) {
380
496
  }
381
497
  }
382
498
  }
383
- function readConfiguredContactChannelKeys(selfAid) {
384
- let agent;
385
- try {
386
- agent = atomicReadJson(agentConfig(selfAid));
387
- }
388
- catch (error) {
389
- throw new ContactMutationError('INVALID_CONTACT_BOOK', `Cannot read Agent channel inventory: ${error instanceof Error ? error.message : String(error)}`);
390
- }
499
+ function readConfiguredContactChannelKeys(selfAid, agent) {
391
500
  return new Set((Array.isArray(agent?.channels) ? agent.channels : [])
392
501
  .filter((channel) => channel && typeof channel === 'object' && String(channel.type || '').toLowerCase() !== 'aun')
393
502
  .map((channel) => {
@@ -406,7 +515,7 @@ function cloneBook(book) {
406
515
  ...(entry.pendingRequest ? { pendingRequest: { ...entry.pendingRequest } } : {}),
407
516
  };
408
517
  }
409
- return { $schema_version: 3, contacts };
518
+ return { $schema_version: currentVersion('contact-book'), contacts };
410
519
  }
411
520
  function compactEntry(entry) {
412
521
  return Object.fromEntries(Object.entries(entry).filter(([, value]) => value !== undefined));
@@ -428,6 +537,15 @@ function readContactBookFromDisk(selfAid) {
428
537
  throw new ContactMutationError('INVALID_CONTACT_BOOK', `Cannot read contact book: ${error instanceof Error ? error.message : String(error)}`);
429
538
  }
430
539
  }
540
+ function readTransactionJson(file, changes) {
541
+ const change = changes.find(candidate => path.resolve(candidate.file) === path.resolve(file));
542
+ try {
543
+ return change ? change.after === null ? null : JSON.parse(change.after) : atomicReadJson(file);
544
+ }
545
+ catch (error) {
546
+ throw new ContactMutationError('INVALID_AUTHORITY', `Cannot read management role authority ${file}: ${error instanceof Error ? error.message : String(error)}`);
547
+ }
548
+ }
431
549
  function acquireAgentLock(selfAid) {
432
550
  const legacyLockDir = path.join(agentDir(selfAid), LOCK_NAME);
433
551
  const lockDir = resolveManagedLockPath('contact', selfAid, legacyLockDir);
@@ -456,14 +574,18 @@ function acquireAgentLock(selfAid) {
456
574
  return () => releaseAgentLock(lockDir, ownerFile, lockToken);
457
575
  }
458
576
  catch (error) {
577
+ const windowsRenameDenied = process.platform === 'win32' && error?.code === 'EPERM';
459
578
  const lockMayAlreadyExist = error?.code === 'EEXIST'
460
579
  || error?.code === 'ENOTEMPTY'
461
- || (process.platform === 'win32' && error?.code === 'EPERM' && fs.existsSync(lockDir));
580
+ || windowsRenameDenied;
462
581
  if (!lockMayAlreadyExist)
463
582
  throw error;
464
- if (removeStaleLock(lockDir, ownerFile))
583
+ const lockExists = fs.existsSync(lockDir);
584
+ if (lockExists && removeStaleLock(lockDir, ownerFile))
465
585
  continue;
466
586
  if (Date.now() >= deadline) {
587
+ if (windowsRenameDenied && !lockExists)
588
+ throw error;
467
589
  throw new ContactMutationError('LOCK_TIMEOUT', `Timed out waiting for contact mutation lock: ${lockDir}`);
468
590
  }
469
591
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, LOCK_RETRY_MS);
@@ -618,13 +740,7 @@ function appendAudit(selfAid, request, previousContactRevision, contactRevisionV
618
740
  })}\n`, { mode: 0o600 });
619
741
  }
620
742
  function isOwner(selfAid, primaryId) {
621
- try {
622
- const config = atomicReadJson(path.join(agentDir(selfAid), 'config.json'));
623
- return config?.owners?.some(value => String(value || '').trim() === primaryId) ?? false;
624
- }
625
- catch {
626
- return false;
627
- }
743
+ return ownerResolver.protocolOwners(selfAid).includes(primaryId);
628
744
  }
629
745
  function normalizeAid(value, label) {
630
746
  const aid = String(value || '').trim();
@@ -4,6 +4,7 @@ import { isExplicitGroupId } from '../aun/group/index.js';
4
4
  import { ConfigTarget, read, validateConfig } from './config-manager.js';
5
5
  import { ContactMutationError, contactRevision, mutateContactBookSync } from './contact-book-store.js';
6
6
  import { formatContactAlias, normalizeContactChannelKey, parseContactAlias, } from './contact-alias.js';
7
+ import { getProtocolOwners } from '../core/ownership/owner-resolver.js';
7
8
  export class ContactBookError extends Error {
8
9
  code;
9
10
  constructor(code, message) {
@@ -133,10 +134,12 @@ export function resolveContactView(selfAid, primaryId) {
133
134
  const owner = listAgentOwners(selfAid).includes(id);
134
135
  const entry = getContactSnapshot(selfAid).contacts.get(id);
135
136
  const status = owner ? 'active' : entry?.status ?? 'active';
137
+ const managementRole = owner ? 'owner' : entry?.managementRole === 'admin' ? 'admin' : undefined;
136
138
  return {
137
139
  primaryId: id,
138
140
  ...(entry?.displayName ? { displayName: entry.displayName } : {}),
139
141
  aliases: entry?.aliases ?? [],
142
+ ...(managementRole ? { managementRole } : {}),
140
143
  status,
141
144
  blocked: status === 'blocked',
142
145
  isOwner: owner,
@@ -251,6 +254,5 @@ function normalizePrimaryId(value) {
251
254
  return id;
252
255
  }
253
256
  function listAgentOwners(selfAid) {
254
- const agent = read(ConfigTarget.Agent, { self: selfAid }, { cache: true });
255
- return [...new Set((agent?.owners ?? []).map(value => String(value || '').trim()).filter(Boolean))];
257
+ return getProtocolOwners(selfAid);
256
258
  }
@@ -1,5 +1,5 @@
1
1
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
2
- const VALID_LIFECYCLES = new Set(['created', 'bootstrapping', 'active']);
2
+ const VALID_LIFECYCLES = new Set(['created', 'ownerpending', 'bootstrapping', 'active']);
3
3
  export function isAgentLifecycle(value) {
4
4
  return typeof value === 'string' && VALID_LIFECYCLES.has(value);
5
5
  }
@@ -0,0 +1,96 @@
1
+ function normalizeOwnerValue(value) {
2
+ if (typeof value !== 'string')
3
+ return undefined;
4
+ const owner = value.trim();
5
+ return owner || undefined;
6
+ }
7
+ /** Resolve the canonical owner, preferring the new scalar field. */
8
+ export function firstConfiguredOwner(value) {
9
+ if (!value || typeof value !== 'object' || Array.isArray(value))
10
+ return undefined;
11
+ const record = value;
12
+ const explicit = normalizeOwnerValue(record.owner);
13
+ if (explicit)
14
+ return explicit;
15
+ if (Array.isArray(record.owners)) {
16
+ for (const candidate of record.owners) {
17
+ const owner = normalizeOwnerValue(candidate);
18
+ if (owner)
19
+ return owner;
20
+ }
21
+ }
22
+ return undefined;
23
+ }
24
+ /** Runtime compatibility projection for code that still consumes an owner list. */
25
+ export function configuredOwnerList(value) {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value))
27
+ return [];
28
+ const record = value;
29
+ const values = [record.owner, ...(Array.isArray(record.owners) ? record.owners : [])];
30
+ return [...new Set(values
31
+ .filter((candidate) => typeof candidate === 'string')
32
+ .map(candidate => candidate.trim())
33
+ .filter(Boolean))];
34
+ }
35
+ export function assertOwnerConfigRecord(value, label, validateId) {
36
+ if (!value || typeof value !== 'object' || Array.isArray(value))
37
+ return;
38
+ const record = value;
39
+ if (record.owner !== undefined && (typeof record.owner !== 'string' || !record.owner.trim())) {
40
+ throw new Error(`${label}.owner must be a non-empty string`);
41
+ }
42
+ if (typeof record.owner === 'string' && validateId && !validateId(record.owner.trim())) {
43
+ throw new Error(`${label}.owner is invalid: ${JSON.stringify(record.owner)}`);
44
+ }
45
+ if (record.owners !== undefined && !Array.isArray(record.owners)) {
46
+ throw new Error(`${label}.owners must be an array`);
47
+ }
48
+ if (Array.isArray(record.owners)) {
49
+ record.owners.forEach((owner, index) => {
50
+ if (typeof owner !== 'string' || !owner.trim()) {
51
+ throw new Error(`${label}.owners[${index}] must be a non-empty string`);
52
+ }
53
+ if (validateId && !validateId(owner.trim())) {
54
+ throw new Error(`${label}.owners[${index}] is invalid: ${JSON.stringify(owner)}`);
55
+ }
56
+ });
57
+ }
58
+ for (const owner of configuredOwnerList(record)) {
59
+ if (validateId && !validateId(owner)) {
60
+ throw new Error(`${label}.owner is invalid: ${JSON.stringify(owner)}`);
61
+ }
62
+ }
63
+ }
64
+ /** Normalize one config record for persistence: write only the scalar owner. */
65
+ export function normalizeSingleOwnerRecord(value) {
66
+ if (!value || typeof value !== 'object' || Array.isArray(value))
67
+ return value;
68
+ const record = value;
69
+ const normalized = { ...record };
70
+ const owner = firstConfiguredOwner(record);
71
+ delete normalized.owners;
72
+ if (owner)
73
+ normalized.owner = owner;
74
+ else
75
+ delete normalized.owner;
76
+ return normalized;
77
+ }
78
+ /** Normalize root owner plus channel instance owner compatibility fields. */
79
+ export function normalizeOwnerConfigTree(value, options = {}) {
80
+ if (!value || typeof value !== 'object' || Array.isArray(value))
81
+ return value;
82
+ const normalized = { ...value };
83
+ // Keep root-level local owner values intact. Ownership consensus is a
84
+ // separate runtime source and must not overwrite development configuration.
85
+ if (options.projectChannels !== false && Array.isArray(normalized.channels)) {
86
+ normalized.channels = normalized.channels.map((channel) => normalizeSingleOwnerRecord(channel));
87
+ }
88
+ return normalized;
89
+ }
90
+ /** Add a legacy list projection for old runtime callers without persisting it. */
91
+ export function withOwnerListCompatibility(value) {
92
+ return normalizeOwnerConfigTree(value);
93
+ }
94
+ export function assertNoRootOwner(value) {
95
+ // Compatibility no-op: local root owners remain supported during rollout.
96
+ }
@@ -4,19 +4,26 @@ import path from 'path';
4
4
  import { agentConfig, agentContactConfig, agentRelationsDir } from '../paths.js';
5
5
  import { formatPeerKey, parsePeerKey } from '../core/relation/peer-identity.js';
6
6
  import { hasTrustedPrincipal } from '../core/auth/authenticated-actor.js';
7
- import { ConfigTarget, read, write } from './config-manager.js';
7
+ import { ConfigTarget, read } from './config-manager.js';
8
8
  import { isManagementRole } from './builtin-roles.js';
9
9
  import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from './roles.js';
10
10
  import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
11
11
  import { resolvePrincipal } from './contact-book.js';
12
+ import { configuredOwnerList } from './owner-config.js';
12
13
  import { tryParseChannelKey } from '../core/channel-loader.js';
13
14
  import { isValidAid } from '../aun/aid/validation.js';
14
15
  import { isExplicitGroupId } from '../aun/group/index.js';
16
+ import { getOwners, ownerResolver } from '../core/ownership/owner-resolver.js';
15
17
  export function isAuthenticated(userId) {
16
18
  return isValidAid(String(userId || '').trim());
17
19
  }
18
20
  export function listStaticAgentOwners(aid, opts = {}) {
19
- return listAgentOwners(aid, opts);
21
+ try {
22
+ const config = read(ConfigTarget.Agent, { self: aid }, { cache: opts.fresh !== true });
23
+ ownerResolver.setAgentConfiguredOwners(aid, configuredOwnerList(config));
24
+ }
25
+ catch { }
26
+ return getOwners(aid);
20
27
  }
21
28
  /** Direct AUN relations with role=admin are the addressable Agent admin set. */
22
29
  export function listStaticAgentAdmins(aid, opts = {}) {
@@ -53,7 +60,7 @@ export function getFirstStaticAgentOwner(aid) {
53
60
  }
54
61
  /** Revision for security-sensitive owner/contact/relation authorization inputs. */
55
62
  export function authorizationConfigRevision(aid) {
56
- const fingerprints = [fileRevision(agentConfig(aid)), fileRevision(agentContactConfig(aid))];
63
+ const fingerprints = [ownerResolver.revision(aid), fileRevision(agentConfig(aid)), fileRevision(agentContactConfig(aid))];
57
64
  const relations = agentRelationsDir(aid);
58
65
  try {
59
66
  for (const entry of fs.readdirSync(relations, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
@@ -75,12 +82,7 @@ export function isStaticAgentAdmin(aid, actorId) {
75
82
  return config?.targetKind !== 'group' && config?.role === 'admin';
76
83
  }
77
84
  export function addStaticAgentOwner(aid, ownerAid) {
78
- if (!aid || !ownerAid)
79
- return;
80
- const config = read(ConfigTarget.Agent, { self: aid }) ?? { $schema_version: 3, aid, channels: [] };
81
- const owners = new Set((config.owners ?? []).filter(Boolean));
82
- owners.add(ownerAid);
83
- write(ConfigTarget.Agent, { ...config, aid, owners: [...owners] }, { self: aid });
85
+ throw new Error('Owner is protocol-managed; use ec aid bind');
84
86
  }
85
87
  export function hasStaticAgentOwner(aid) {
86
88
  return listStaticAgentOwners(aid).length > 0;
@@ -203,15 +205,7 @@ function resultFor(role, source, actor, selfAid) {
203
205
  };
204
206
  }
205
207
  function listAgentOwners(aid, opts = {}) {
206
- if (!aid)
207
- return [];
208
- try {
209
- const config = read(ConfigTarget.Agent, { self: aid }, { cache: opts.fresh !== true });
210
- return Array.from(new Set((config?.owners ?? []).map(value => String(value || '').trim()).filter(Boolean)));
211
- }
212
- catch {
213
- return [];
214
- }
208
+ return getOwners(aid);
215
209
  }
216
210
  function readAgentDefaultRole(aid, chatType) {
217
211
  return getStoredRoleRegistry(aid).defaultRoles[chatType] ?? null;