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.
- package/CHANGELOG.md +42 -0
- package/README.md +6 -0
- package/bin/install-codex-managed-hooks.mjs +20 -3
- package/dist/agents/baseagent-availability.js +1 -1
- package/dist/agents/baseagent.js +88 -0
- package/dist/agents/claude-runner.js +59 -12
- package/dist/agents/codex/app-server-client.js +18 -1
- package/dist/agents/codex-runner.js +262 -21
- package/dist/agents/ecagent-runner.js +47 -11
- package/dist/agents/gemini-runner.js +36 -12
- package/dist/aun/aid/client.js +4 -3
- package/dist/aun/aid/control-aid.js +270 -8
- package/dist/aun/aid/identity.js +6 -3
- package/dist/aun/aid/ownership-operation.js +58 -0
- package/dist/aun/aid/store.js +1 -0
- package/dist/aun/group/fs-download.js +67 -2
- package/dist/aun/group/history.js +20 -5
- package/dist/aun/group/operations.js +8 -2
- package/dist/aun/menu/client.js +39 -2
- package/dist/aun/menu/transport.js +1 -1
- package/dist/aun/msg/history.js +43 -4
- package/dist/aun/msg/managed-operation.js +30 -53
- package/dist/aun/msg/p2p.js +51 -15
- package/dist/aun/outbox.js +4 -0
- package/dist/channels/aun.js +260 -73
- package/dist/channels/daemon.js +6 -4
- package/dist/channels/feishu.js +3 -0
- package/dist/cli/agent-command.js +161 -223
- package/dist/cli/agent-create.js +190 -223
- package/dist/cli/agent.js +3 -1
- package/dist/cli/aid-bind-command.js +53 -0
- package/dist/cli/aun-commands.js +384 -91
- package/dist/cli/bench.js +1 -1
- package/dist/cli/cli-argv.js +74 -0
- package/dist/cli/daemon-commands.js +105 -74
- package/dist/cli/diag.js +1 -1
- package/dist/cli/fs-command.js +18 -19
- package/dist/cli/getmeta-command.js +159 -6
- package/dist/cli/index.js +6 -12
- package/dist/cli/init-channel.js +8 -55
- package/dist/cli/init.js +125 -218
- package/dist/cli/queue-command.js +11 -8
- package/dist/cli/restart-monitor.js +177 -85
- package/dist/cli/session-command.js +1 -1
- package/dist/cli/stats.js +7 -5
- package/dist/cli/thread-command.js +103 -30
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-msg-diag.js +1 -0
- package/dist/config/access-policy.js +2 -2
- package/dist/config/boot-log.js +14 -11
- package/dist/config/config-field-policy.js +9 -1
- package/dist/config/config-manager.js +176 -90
- package/dist/config/config-operation-service.js +7 -14
- package/dist/config/contact-book-store.js +142 -26
- package/dist/config/contact-book.js +4 -2
- package/dist/config/lifecycle.js +1 -1
- package/dist/config/owner-config.js +96 -0
- package/dist/config/peer-role-resolver.js +12 -18
- package/dist/config/providers-config.js +382 -0
- package/dist/config/providers-import.js +74 -0
- package/dist/config/providers-management.js +328 -0
- package/dist/config/providers.js +22 -0
- package/dist/config/resolved-config-op.js +17 -14
- package/dist/config/snapshot.js +28 -16
- package/dist/config-store.js +18 -14
- package/dist/core/agent-ipc-registration.js +21 -63
- package/dist/core/agent-runtime.js +16 -5
- package/dist/core/auth/agent-delegation.js +199 -25
- package/dist/core/auth/auth-gateway.js +44 -2
- package/dist/core/auth/authorization-denial.js +1 -1
- package/dist/core/auth/debug-context.js +2 -1
- package/dist/core/auth/delegation-authorization.js +2 -2
- package/dist/core/auth/delegation-diagnostics.js +4 -1
- package/dist/core/auth/operation-authorizer.js +10 -1
- package/dist/core/auth/operation-catalog.js +19 -1
- package/dist/core/bootstrap-service.js +43 -20
- package/dist/core/channel-loader.js +0 -1
- package/dist/core/channel-runtime.js +30 -4
- package/dist/core/command/agent-control.js +196 -37
- package/dist/core/command/cli-intent-parser.js +60 -4
- package/dist/core/command/command-handler.js +212 -60
- package/dist/core/command/connect-menu.js +1 -0
- package/dist/core/command/diag-service.js +5 -4
- package/dist/core/command/file-search.js +368 -0
- package/dist/core/command/menu-catalog.js +2 -1
- package/dist/core/command/menu-handler.js +292 -107
- package/dist/core/command/menu-protocol.js +23 -0
- package/dist/core/command/role-menu.js +7 -19
- package/dist/core/command/skill-slash.js +130 -25
- package/dist/core/command/slash-handler.js +101 -53
- package/dist/core/command/topic-slash.js +16 -5
- package/dist/core/daemon-bootstrap.js +7 -1
- package/dist/core/daemon-control.js +19 -1
- package/dist/core/daemon-lifecycle.js +2 -1
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/evolagent-registry.js +13 -2
- package/dist/core/evolagent.js +3 -6
- package/dist/core/handoff/ipc-registration.js +2 -2
- package/dist/core/handoff/runtime.js +40 -6
- package/dist/core/handoff/store.js +1 -0
- package/dist/core/inference/text-inference.js +95 -6
- package/dist/core/ipc-registration.js +43 -24
- package/dist/core/memory/consolidation-context.js +4 -1
- package/dist/core/memory/consolidation.js +25 -14
- package/dist/core/message/aun-ipc-sender.js +296 -88
- package/dist/core/message/aun-request-registry.js +226 -0
- package/dist/core/message/create-status.js +33 -4
- package/dist/core/message/message-bridge.js +62 -13
- package/dist/core/message/message-log.js +0 -1
- package/dist/core/message/message-queue.js +4 -1
- package/dist/core/message/message-utils.js +8 -1
- package/dist/core/message/peer-mode.js +17 -9
- package/dist/core/message/response-engine.js +266 -124
- package/dist/core/message/retry-scheduler.js +11 -8
- package/dist/core/model/catalog-service.js +60 -18
- package/dist/core/model/model-catalog.js +112 -9
- package/dist/core/model/model-fallback.js +94 -26
- package/dist/core/ownership/owner-resolver.js +687 -0
- package/dist/core/permission/approval-gateway.js +196 -62
- package/dist/core/permission/ec-command-parser.js +138 -25
- package/dist/core/permission/filesystem-approval-scope.js +141 -0
- package/dist/core/permission/protected-paths.js +14 -1
- package/dist/core/permission/sandbox-runtime.js +171 -18
- package/dist/core/permission/tool-policy.js +13 -2
- package/dist/core/role/runtime-policy.js +3 -4
- package/dist/core/session/session-idle-monitor.js +10 -8
- package/dist/core/session/session-manager.js +72 -45
- package/dist/core/session/session-mapper.js +18 -7
- package/dist/core/session/session-renew.js +5 -4
- package/dist/core/session/session-title.js +2 -1
- package/dist/core/session/session-turn-coordinator.js +4 -2
- package/dist/core/skill/index-generator.js +2 -16
- package/dist/core/task/task-recovery-service.js +15 -2
- package/dist/core/thread/command-parser.js +28 -13
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +11 -8
- package/dist/core/thread/thread-operation.js +79 -44
- package/dist/core/thread/thread-route.js +54 -6
- package/dist/core/thread/thread-service.js +236 -45
- package/dist/core/thread/topic-id.js +248 -0
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/eck/message-renderer.js +12 -2
- package/dist/index.js +50 -27
- package/dist/ipc.js +59 -6
- package/dist/paths.js +1 -0
- package/dist/product.js +1 -0
- package/dist/response-system/engines/v1/proactive-flow.js +10 -3
- package/dist/stats/db.js +8 -0
- package/dist/stats/writer.js +26 -4
- package/dist/trigger/authorization.js +48 -3
- package/dist/trigger/feedback.js +28 -10
- package/dist/trigger/ipc-service.js +86 -10
- package/dist/trigger/local-files.js +55 -23
- package/dist/trigger/scheduler.js +57 -11
- package/dist/trigger/system-definitions.js +1 -0
- package/dist/trigger/validation.js +44 -15
- package/dist/utils/aid-bind.js +17 -297
- package/dist/utils/avatar-upload.js +16 -4
- package/dist/utils/cross-platform.js +115 -36
- package/dist/utils/ecweb-supervisor.js +1 -1
- package/dist/utils/ecweb-utils.js +8 -1
- package/dist/utils/error-utils.js +44 -6
- package/dist/utils/instance-registry.js +12 -2
- package/dist/utils/npm-ops.js +102 -27
- package/dist/utils/package-version-info.js +72 -27
- package/dist/utils/restart-safety.js +50 -0
- package/dist/utils/upgrade-jobs.js +304 -0
- package/dist/utils/windows-autostart.js +17 -10
- package/kits/docs/INDEX.md +5 -0
- package/kits/docs/aun/ABILITY_CALLING.md +239 -0
- package/kits/docs/evolcore/INDEX.md +14 -3
- package/kits/docs/evolcore/agent-create.md +55 -141
- package/kits/docs/evolcore/agent.md +38 -16
- package/kits/docs/evolcore/aid.md +34 -0
- package/kits/docs/evolcore/config.md +20 -1
- package/kits/docs/evolcore/event.md +2 -2
- package/kits/docs/evolcore/fs.md +18 -2
- package/kits/docs/evolcore/group-collaboration.md +13 -246
- package/kits/docs/evolcore/group.md +2 -1
- package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +65 -1
- package/kits/docs/evolcore/msg.md +64 -2
- package/kits/docs/evolcore/skill.md +86 -0
- package/kits/docs/evolcore/stats.md +6 -0
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/thread-chain.md +145 -51
- package/kits/docs/evolcore/trigger.md +48 -3
- package/kits/eck_manifest.json +21 -0
- package/kits/eck_message_manifest.json +4 -4
- package/kits/migrations/migrate-role-config-v5.mjs +20 -7
- package/kits/rules/01-overview.md +2 -1
- package/kits/rules/03-identity.md +3 -1
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/07-memory-and-thread.md +1 -1
- package/kits/schemas/_meta.json +16 -1
- package/kits/schemas/agent-config.schema.13.json +44 -2
- package/kits/schemas/contact-book.schema.3.json +5 -0
- package/kits/schemas/daemon.schema.6.json +3 -1
- package/kits/schemas/daemon.schema.8.json +329 -0
- package/kits/schemas/defaults.schema.5.json +39 -3
- package/kits/schemas/providers.schema.1.json +76 -0
- package/kits/schemas/relation-config.schema.9.json +5 -1
- package/kits/skills/.switches.json +28 -0
- package/kits/skills/aid-ability/SKILL.md +214 -0
- package/kits/skills/ec-agent-create/SKILL.md +162 -0
- package/kits/skills/ec-group/SKILL.md +2 -1
- package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
- package/kits/skills/ec-identity/SKILL.md +143 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
- package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
- package/kits/skills/ec-trigger/SKILL.md +167 -0
- package/kits/skills/ec-trigger/references/examples.md +153 -0
- package/kits/skills/thread-ops/SKILL.md +108 -420
- package/kits/templates/message-fragments/thread-context.md +1 -2
- package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
- package/kits/templates/roles/member.json +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +1 -0
- package/kits/templates/system-fragments/channel.md +2 -1
- package/kits/templates/system-fragments/commands.md +5 -4
- package/kits/templates/system-fragments/identity.md +2 -0
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/templates/system-fragments/venue.md +1 -1
- 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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
253
|
-
throw new ContactMutationError('OWNER_PROTECTED', 'An Agent owner
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
||
|
|
580
|
+
|| windowsRenameDenied;
|
|
462
581
|
if (!lockMayAlreadyExist)
|
|
463
582
|
throw error;
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
return [...new Set((agent?.owners ?? []).map(value => String(value || '').trim()).filter(Boolean))];
|
|
257
|
+
return getProtocolOwners(selfAid);
|
|
256
258
|
}
|
package/dist/config/lifecycle.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|