evolcore 0.0.2 → 0.0.3
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 +44 -793
- package/dist/agents/claude-runner.js +197 -17
- package/dist/agents/codex-runner.js +46 -3
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +21 -4
- package/dist/channels/contact-bind-code.js +134 -0
- package/dist/channels/dingtalk.js +979 -149
- package/dist/channels/feishu.js +130 -54
- package/dist/channels/wecom-card.js +101 -0
- package/dist/channels/wecom-onboarding.js +82 -0
- package/dist/channels/wecom-state.js +191 -0
- package/dist/channels/wecom.js +755 -163
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +88 -33
- package/dist/cli/bench.js +2 -2
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +77 -214
- package/dist/cli/handoff-command.js +2 -2
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +92 -97
- package/dist/cli/init.js +63 -26
- package/dist/cli/model.js +2 -1
- package/dist/cli/net-check.js +2 -2
- package/dist/cli/queue-command.js +30 -6
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +5 -6
- package/dist/cli/restart-monitor.js +25 -1
- package/dist/cli/stats.js +6 -4
- package/dist/cli/trigger-command.js +55 -15
- package/dist/cli/version.js +6 -1
- package/dist/config/builtin-role-templates.js +22 -10
- package/dist/config/builtin-roles.js +7 -1
- package/dist/config/config-manager.js +221 -17
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-book-store.js +454 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +156 -303
- package/dist/config/contact-operation-service.js +110 -0
- package/dist/config/peer-role-resolver.js +133 -54
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-service.js +16 -19
- package/dist/config/role-store.js +16 -5
- package/dist/config/roles.js +10 -1
- package/dist/config-store.js +0 -2
- package/dist/core/auth/authorization-audit.js +10 -1
- package/dist/core/auth/operation-authorizer.js +2 -0
- package/dist/core/auth/operation-catalog.js +56 -0
- package/dist/core/command/command-handler.js +105 -10
- package/dist/core/command/connect-menu.js +374 -0
- package/dist/core/command/menu-handler.js +114 -16
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-handler.js +28 -18
- package/dist/core/daemon-file-cache.js +12 -6
- package/dist/core/event-catalog.js +70 -0
- package/dist/core/evolagent-registry.js +0 -1
- package/dist/core/evolagent.js +20 -9
- package/dist/core/message/im-renderer.js +2 -0
- package/dist/core/message/message-bridge.js +79 -8
- package/dist/core/message/message-queue.js +10 -0
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/response-engine.js +269 -25
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/tool-policy.js +47 -15
- package/dist/core/protected-paths.js +2 -0
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +61 -5
- package/dist/index.js +62 -6
- package/dist/ipc.js +34 -5
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +77 -2
- package/dist/trigger/patch.js +8 -1
- package/dist/trigger/scheduler.js +3 -1
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/media-cache.js +4 -1
- package/dist/utils/model-prices.jsonl +6 -3
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/evolcore/INDEX.md +5 -3
- package/kits/docs/evolcore/agent.md +9 -1
- package/kits/docs/evolcore/aid.md +5 -2
- package/kits/docs/evolcore/contact.md +57 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group.md +12 -3
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +9 -3
- package/kits/docs/evolcore/response.md +16 -21
- package/kits/docs/evolcore/rpc.md +2 -0
- package/kits/docs/evolcore/stats.md +15 -2
- package/kits/docs/evolcore/storage.md +1 -0
- package/kits/docs/evolcore/trigger.md +17 -2
- package/kits/eck_manifest.json +12 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/commands.md +3 -1
- package/package.json +4 -4
- package/assets/brand/evolcore/README.md +0 -19
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +0 -14
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +0 -10
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readBuiltinRoleTemplate } from './builtin-role-templates.js';
|
|
2
|
+
import { expectedRoleRank } from './role-ranks.js';
|
|
3
|
+
export { expectedRoleRank, ROLE_RANKS } from './role-ranks.js';
|
|
2
4
|
export const BUILTIN_USER_ROLES = ['member', 'visitor'];
|
|
3
5
|
export const MANAGEMENT_ROLES = ['owner', 'admin'];
|
|
4
6
|
export function isManagementRole(role) {
|
|
@@ -22,8 +24,10 @@ export function getBuiltinRolesConfig() {
|
|
|
22
24
|
* Management grants are a code-level ceiling, not configurable role policy.
|
|
23
25
|
* A role file may narrow these rules but can never add a management grant.
|
|
24
26
|
*/
|
|
25
|
-
export function getManagementCommandPermissions(
|
|
27
|
+
export function getManagementCommandPermissions(role) {
|
|
26
28
|
return {
|
|
29
|
+
'contact.read': { allow: true, scopes: ['agent'] },
|
|
30
|
+
'contact.block': { allow: true, scopes: ['agent'] },
|
|
27
31
|
'role.assign': { allow: true, scopes: ['agent'] },
|
|
28
32
|
'role.revoke': { allow: true, scopes: ['agent'] },
|
|
29
33
|
'role.policy.read': { allow: true, scopes: ['agent'] },
|
|
@@ -37,6 +41,7 @@ export function getManagementCommandPermissions(_role) {
|
|
|
37
41
|
export function applyManagementRoleCeiling(role, configured) {
|
|
38
42
|
const fallback = {
|
|
39
43
|
description: role === 'owner' ? 'Agent owner' : 'Agent administrator',
|
|
44
|
+
rank: expectedRoleRank(role),
|
|
40
45
|
allowAccess: true,
|
|
41
46
|
permissions: {
|
|
42
47
|
permissionMode: { default: 'request', allowOverride: false },
|
|
@@ -46,6 +51,7 @@ export function applyManagementRoleCeiling(role, configured) {
|
|
|
46
51
|
const policy = configured ?? readBuiltinRoleTemplate(role).definition ?? fallback;
|
|
47
52
|
return {
|
|
48
53
|
...policy,
|
|
54
|
+
rank: expectedRoleRank(role),
|
|
49
55
|
allowAccess: true,
|
|
50
56
|
commandPermissions: narrowManagementCommandPermissions(getManagementCommandPermissions(role), policy.commandPermissions || {}),
|
|
51
57
|
};
|
|
@@ -16,6 +16,8 @@ import { resolvePaths, agentConfig as agentConfigPath, agentContactConfig, agent
|
|
|
16
16
|
import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
|
|
17
17
|
import { fileCache } from '../core/daemon-file-cache.js';
|
|
18
18
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
19
|
+
import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
20
|
+
import { parseContactAlias } from './contact-alias.js';
|
|
19
21
|
import { loadSchema, currentVersion, isSchemaName, } from './schema-registry.js';
|
|
20
22
|
import { mergeLayers, expandVars, buildEnvResolver } from './merge.js';
|
|
21
23
|
import { normalizeAgentLifecycle } from './lifecycle.js';
|
|
@@ -76,10 +78,7 @@ export function assertRoleConfigV4Ready() {
|
|
|
76
78
|
if (!fs.existsSync(agentsRoot))
|
|
77
79
|
return;
|
|
78
80
|
const issues = [];
|
|
79
|
-
const
|
|
80
|
-
const agentSchemaV4 = loadSchema('agent-config', 4);
|
|
81
|
-
const relationSchema = loadSchema('relation-config');
|
|
82
|
-
const inspect = (file, schema, legacyAgent = false) => {
|
|
81
|
+
const inspect = (file, logicalName, minimumVersion) => {
|
|
83
82
|
let value;
|
|
84
83
|
try {
|
|
85
84
|
value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
@@ -88,18 +87,25 @@ export function assertRoleConfigV4Ready() {
|
|
|
88
87
|
issues.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
|
|
89
88
|
return;
|
|
90
89
|
}
|
|
91
|
-
const expectedVersion = schema.version;
|
|
92
90
|
const actualVersion = value && typeof value === 'object'
|
|
93
91
|
? value.$schema_version
|
|
94
92
|
: undefined;
|
|
95
|
-
|
|
96
|
-
if (actualVersion !== expectedVersion && !acceptedLegacyAgent) {
|
|
93
|
+
if (typeof actualVersion !== 'number' || actualVersion < minimumVersion) {
|
|
97
94
|
issues.push({
|
|
98
95
|
file,
|
|
99
|
-
detail: `$schema_version must be ${
|
|
96
|
+
detail: `$schema_version must be at least ${minimumVersion}, got ${String(actualVersion)}`,
|
|
100
97
|
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let schema;
|
|
101
|
+
try {
|
|
102
|
+
schema = loadSchema(logicalName, actualVersion);
|
|
101
103
|
}
|
|
102
|
-
|
|
104
|
+
catch {
|
|
105
|
+
issues.push({ file, detail: `unsupported ${logicalName} schema version ${actualVersion}` });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!schema.validate(value)) {
|
|
103
109
|
const detail = (schema.validate.errors ?? []).map(error => {
|
|
104
110
|
const extra = typeof error.params?.additionalProperty === 'string'
|
|
105
111
|
? ` (additionalProperty=${error.params.additionalProperty})`
|
|
@@ -128,12 +134,10 @@ export function assertRoleConfigV4Ready() {
|
|
|
128
134
|
const agentDirPath = path.join(agentsRoot, entry.name);
|
|
129
135
|
const agentFile = path.join(agentDirPath, 'config.json');
|
|
130
136
|
if (fs.existsSync(agentFile)) {
|
|
131
|
-
|
|
132
|
-
const legacyVersion = typeof raw?.$schema_version === 'number' && raw.$schema_version <= 4;
|
|
133
|
-
inspect(agentFile, legacyVersion ? agentSchemaV4 : agentSchema, raw?.$schema_version === 4);
|
|
137
|
+
inspect(agentFile, 'agent-config', 4);
|
|
134
138
|
}
|
|
135
139
|
for (const relationFile of walkRelationConfigs(path.join(agentDirPath, 'relations'))) {
|
|
136
|
-
inspect(relationFile,
|
|
140
|
+
inspect(relationFile, 'relation-config', 4);
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
if (!issues.length)
|
|
@@ -156,7 +160,6 @@ export function assertRoleConfigV5Ready() {
|
|
|
156
160
|
if (!fs.existsSync(agentsRoot))
|
|
157
161
|
return;
|
|
158
162
|
const issues = [];
|
|
159
|
-
const agentSchema = loadSchema('agent-config', 5);
|
|
160
163
|
const registrySchema = loadSchema('role-registry');
|
|
161
164
|
const readJson = (file) => {
|
|
162
165
|
try {
|
|
@@ -177,9 +180,22 @@ export function assertRoleConfigV5Ready() {
|
|
|
177
180
|
if (!fs.existsSync(agentFile))
|
|
178
181
|
continue;
|
|
179
182
|
const agent = readJson(agentFile);
|
|
180
|
-
|
|
183
|
+
const agentVersion = agent?.$schema_version;
|
|
184
|
+
let agentSchema = null;
|
|
185
|
+
if (typeof agentVersion === 'number' && agentVersion >= 5) {
|
|
186
|
+
try {
|
|
187
|
+
agentSchema = loadSchema('agent-config', agentVersion);
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
}
|
|
191
|
+
if (!agent || !agentSchema || !agentSchema.validate(agent)) {
|
|
181
192
|
if (agent)
|
|
182
|
-
issues.push({
|
|
193
|
+
issues.push({
|
|
194
|
+
file: agentFile,
|
|
195
|
+
detail: agentSchema
|
|
196
|
+
? schemaErrors(agentSchema)
|
|
197
|
+
: `agent schema version must be supported and >= 5, got ${String(agentVersion)}`,
|
|
198
|
+
});
|
|
183
199
|
continue;
|
|
184
200
|
}
|
|
185
201
|
const registryFile = agentRolesIndex(aid);
|
|
@@ -201,6 +217,191 @@ export function assertRoleConfigV5Ready() {
|
|
|
201
217
|
`node kits/migrations/migrate-role-config-v5.mjs --home ${JSON.stringify(root)}\n` +
|
|
202
218
|
`node kits/migrations/migrate-role-config-v5.mjs --apply --home ${JSON.stringify(root)}`, { issues });
|
|
203
219
|
}
|
|
220
|
+
/** Final startup gate for the contact-book authorization migration. */
|
|
221
|
+
export function assertContactBookV2Ready() {
|
|
222
|
+
const root = resolvePaths().root;
|
|
223
|
+
const agentsRoot = resolvePaths().agentsDir;
|
|
224
|
+
if (!fs.existsSync(agentsRoot))
|
|
225
|
+
return;
|
|
226
|
+
const issues = [];
|
|
227
|
+
const agentSchema = loadSchema('agent-config', 6);
|
|
228
|
+
const contactSchema = loadSchema('contact-book', 2);
|
|
229
|
+
const relationSchema = loadSchema('relation-config', 5);
|
|
230
|
+
const inspect = (file, schema) => {
|
|
231
|
+
let value;
|
|
232
|
+
try {
|
|
233
|
+
value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
issues.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!schema.validate(value)) {
|
|
240
|
+
const detail = (schema.validate.errors ?? [])
|
|
241
|
+
.map(error => `${error.instancePath || '/'} ${error.message}`)
|
|
242
|
+
.join('; ');
|
|
243
|
+
issues.push({ file, detail });
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const walkRelationConfigs = (dir) => {
|
|
247
|
+
if (!fs.existsSync(dir))
|
|
248
|
+
return [];
|
|
249
|
+
const files = [];
|
|
250
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
251
|
+
const full = path.join(dir, entry.name);
|
|
252
|
+
if (entry.isDirectory())
|
|
253
|
+
files.push(...walkRelationConfigs(full));
|
|
254
|
+
else if (entry.isFile() && entry.name === 'config.json')
|
|
255
|
+
files.push(full);
|
|
256
|
+
}
|
|
257
|
+
return files;
|
|
258
|
+
};
|
|
259
|
+
for (const entry of fs.readdirSync(agentsRoot, { withFileTypes: true })) {
|
|
260
|
+
if (!entry.isDirectory() || !isValidAid(entry.name))
|
|
261
|
+
continue;
|
|
262
|
+
const dir = path.join(agentsRoot, entry.name);
|
|
263
|
+
const agentFile = path.join(dir, 'config.json');
|
|
264
|
+
if (!fs.existsSync(agentFile))
|
|
265
|
+
continue;
|
|
266
|
+
inspect(agentFile, agentSchema);
|
|
267
|
+
const contactFile = path.join(dir, 'contact.json');
|
|
268
|
+
if (fs.existsSync(contactFile)) {
|
|
269
|
+
inspect(contactFile, contactSchema);
|
|
270
|
+
inspectContactBookDomain(contactFile, agentFile, issues);
|
|
271
|
+
}
|
|
272
|
+
for (const relationFile of walkRelationConfigs(path.join(dir, 'relations'))) {
|
|
273
|
+
inspect(relationFile, relationSchema);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const correctionReceipt = path.join(root, 'data', 'contact-book-v2-relation-role-authority.json');
|
|
277
|
+
if (!hasContactRoleAuthorityReceipt(correctionReceipt)) {
|
|
278
|
+
const recoverable = findRelationRoleRemovedByExperimentalMigration(root);
|
|
279
|
+
if (recoverable) {
|
|
280
|
+
issues.push({
|
|
281
|
+
file: recoverable,
|
|
282
|
+
detail: 'experimental contact-book migration backup must be reviewed and relation role authority correction recorded',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (!issues.length)
|
|
287
|
+
return;
|
|
288
|
+
const shown = issues.slice(0, 20)
|
|
289
|
+
.map(issue => `- ${path.relative(root, issue.file)}: ${issue.detail}`)
|
|
290
|
+
.join('\n');
|
|
291
|
+
const omitted = issues.length > 20 ? `\n- ... ${issues.length - 20} more issue(s)` : '';
|
|
292
|
+
throw new ConfigError('CONTACT_BOOK_V2_MIGRATION_REQUIRED', `Contact book authorization migration is required before daemon startup:\n${shown}${omitted}\n` +
|
|
293
|
+
`node kits/migrations/migrate-contact-book-v2.mjs --home ${JSON.stringify(root)}\n` +
|
|
294
|
+
`node kits/migrations/migrate-contact-book-v2.mjs --apply --home ${JSON.stringify(root)}`, { issues });
|
|
295
|
+
}
|
|
296
|
+
function inspectContactBookDomain(contactFile, agentFile, issues) {
|
|
297
|
+
let contact;
|
|
298
|
+
let agent;
|
|
299
|
+
try {
|
|
300
|
+
contact = JSON.parse(fs.readFileSync(contactFile, 'utf8'));
|
|
301
|
+
agent = JSON.parse(fs.readFileSync(agentFile, 'utf8'));
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const owners = new Set(Array.isArray(agent?.owners) ? agent.owners.map(String) : []);
|
|
307
|
+
const configuredChannelKeys = new Set((Array.isArray(agent?.channels) ? agent.channels : [])
|
|
308
|
+
.filter((channel) => channel && typeof channel === 'object' && channel.type !== 'aun')
|
|
309
|
+
.map((channel) => {
|
|
310
|
+
const type = String(channel.type || '').trim().toLowerCase();
|
|
311
|
+
const name = String(channel.name || '').trim();
|
|
312
|
+
return type && name ? `${type}#${String(agent?.aid || '').trim()}#${name}` : '';
|
|
313
|
+
})
|
|
314
|
+
.filter(Boolean));
|
|
315
|
+
const aliases = new Map();
|
|
316
|
+
for (const [primaryId, entry] of Object.entries(contact?.contacts ?? {})) {
|
|
317
|
+
if (isExplicitGroupId(primaryId)) {
|
|
318
|
+
issues.push({ file: contactFile, detail: `group contact ${primaryId} is not supported` });
|
|
319
|
+
}
|
|
320
|
+
if (entry?.status === 'blocked' && owners.has(primaryId)) {
|
|
321
|
+
issues.push({ file: contactFile, detail: `owner contact ${primaryId} cannot be blocked` });
|
|
322
|
+
}
|
|
323
|
+
for (const alias of Array.isArray(entry?.aliases) ? entry.aliases : []) {
|
|
324
|
+
const parsed = parseContactAlias(alias, agent?.aid);
|
|
325
|
+
if (!parsed) {
|
|
326
|
+
issues.push({ file: contactFile, detail: `invalid Contact alias ${String(alias)}; expected <channelKey>:<urlEncode(peerId)>` });
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (!configuredChannelKeys.has(parsed.channelKey)) {
|
|
330
|
+
issues.push({ file: contactFile, detail: `alias ${alias} references an unconfigured channel instance` });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const previous = aliases.get(parsed.alias);
|
|
334
|
+
if (previous && previous !== primaryId) {
|
|
335
|
+
issues.push({ file: contactFile, detail: `alias ${parsed.alias} is assigned to both ${previous} and ${primaryId}` });
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
aliases.set(parsed.alias, primaryId);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function hasContactRoleAuthorityReceipt(file) {
|
|
344
|
+
try {
|
|
345
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
346
|
+
return value?.migration === 'contact-book-v2-relation-role-authority' && value?.designVersion === 2;
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function findRelationRoleRemovedByExperimentalMigration(root) {
|
|
353
|
+
const backupsRoot = path.join(root, 'backups', 'contact-book-v2-migration');
|
|
354
|
+
if (!fs.existsSync(backupsRoot))
|
|
355
|
+
return null;
|
|
356
|
+
for (const stamp of fs.readdirSync(backupsRoot, { withFileTypes: true })) {
|
|
357
|
+
if (!stamp.isDirectory())
|
|
358
|
+
continue;
|
|
359
|
+
try {
|
|
360
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(backupsRoot, stamp.name, 'manifest.json'), 'utf8'));
|
|
361
|
+
if (manifest?.designVersion >= 2)
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
catch { }
|
|
365
|
+
const agentsRoot = path.join(backupsRoot, stamp.name, 'files', 'agents');
|
|
366
|
+
if (!fs.existsSync(agentsRoot))
|
|
367
|
+
continue;
|
|
368
|
+
for (const agent of fs.readdirSync(agentsRoot, { withFileTypes: true })) {
|
|
369
|
+
if (!agent.isDirectory() || !isValidAid(agent.name))
|
|
370
|
+
continue;
|
|
371
|
+
const relationsRoot = path.join(agentsRoot, agent.name, 'relations');
|
|
372
|
+
if (!fs.existsSync(relationsRoot))
|
|
373
|
+
continue;
|
|
374
|
+
for (const relation of fs.readdirSync(relationsRoot, { withFileTypes: true })) {
|
|
375
|
+
if (!relation.isDirectory() || relation.name.startsWith('_'))
|
|
376
|
+
continue;
|
|
377
|
+
const backupFile = path.join(relationsRoot, relation.name, 'config.json');
|
|
378
|
+
if (!fs.existsSync(backupFile))
|
|
379
|
+
continue;
|
|
380
|
+
let backupValue;
|
|
381
|
+
try {
|
|
382
|
+
backupValue = JSON.parse(fs.readFileSync(backupFile, 'utf8'));
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (typeof backupValue?.role !== 'string' || backupValue.role === 'owner')
|
|
388
|
+
continue;
|
|
389
|
+
const currentFile = path.join(root, 'agents', agent.name, 'relations', relation.name, 'config.json');
|
|
390
|
+
let currentValue;
|
|
391
|
+
try {
|
|
392
|
+
currentValue = JSON.parse(fs.readFileSync(currentFile, 'utf8'));
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (currentValue?.$schema_version === 5) {
|
|
398
|
+
return currentFile;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
204
405
|
/**
|
|
205
406
|
* 启动时检查 schema 版本(daemon 启动时调用)。
|
|
206
407
|
* 扫描所有配置文件,如有版本不匹配则警告一次。
|
|
@@ -459,6 +660,10 @@ function finishConfigWrite(target, sel, file) {
|
|
|
459
660
|
}
|
|
460
661
|
}
|
|
461
662
|
}
|
|
663
|
+
/** Notify cache and security listeners after a caller-owned atomic transaction commits. */
|
|
664
|
+
export function notifyConfigWrite(target, sel) {
|
|
665
|
+
finishConfigWrite(target, sel, targetPath(target, sel));
|
|
666
|
+
}
|
|
462
667
|
function ensureSchemaVersion(value, version) {
|
|
463
668
|
return value && typeof value === 'object'
|
|
464
669
|
? { ...value, $schema_version: version }
|
|
@@ -643,7 +848,6 @@ export function resolveEffective(sel, opts = {}) {
|
|
|
643
848
|
lifecycle: config.lifecycle,
|
|
644
849
|
initialized: config.initialized,
|
|
645
850
|
owners: config.owners,
|
|
646
|
-
admins: config.admins,
|
|
647
851
|
aun: config.aun,
|
|
648
852
|
channels: config.channels ?? [],
|
|
649
853
|
models: config.models,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isValidAid } from '../aun/aid/validation.js';
|
|
2
|
+
const CHANNEL_SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
3
|
+
/** Parse the canonical channel instance key used by Contact aliases. */
|
|
4
|
+
export function parseContactChannelKey(value) {
|
|
5
|
+
if (typeof value !== 'string')
|
|
6
|
+
return null;
|
|
7
|
+
const text = value.trim();
|
|
8
|
+
const parts = text.split('#');
|
|
9
|
+
if (parts.length !== 3)
|
|
10
|
+
return null;
|
|
11
|
+
const [rawType, selfAid, channelName] = parts;
|
|
12
|
+
const channelType = rawType.toLowerCase();
|
|
13
|
+
if (!CHANNEL_SEGMENT.test(channelType)
|
|
14
|
+
|| rawType !== channelType
|
|
15
|
+
|| !isValidAid(selfAid)
|
|
16
|
+
|| !CHANNEL_SEGMENT.test(channelName))
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
channelType,
|
|
20
|
+
selfAid,
|
|
21
|
+
channelName,
|
|
22
|
+
channelKey: `${channelType}#${selfAid}#${channelName}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Validate that a runtime channelKey belongs to the expected Agent and channel type. */
|
|
26
|
+
export function normalizeContactChannelKey(selfAid, channelType, channelKey) {
|
|
27
|
+
const parsed = parseContactChannelKey(channelKey);
|
|
28
|
+
const expectedType = String(channelType || '').trim().toLowerCase();
|
|
29
|
+
const expectedSelf = String(selfAid || '').trim();
|
|
30
|
+
if (!parsed || parsed.channelType !== expectedType || parsed.selfAid !== expectedSelf)
|
|
31
|
+
return null;
|
|
32
|
+
return parsed.channelKey;
|
|
33
|
+
}
|
|
34
|
+
/** Format `<channelKey>:<urlEncode(peerId)>`. */
|
|
35
|
+
export function formatContactAlias(channelKey, peerId) {
|
|
36
|
+
const parsed = parseContactChannelKey(channelKey);
|
|
37
|
+
const peer = String(peerId || '').trim();
|
|
38
|
+
if (!parsed || parsed.channelType === 'aun' || !peer) {
|
|
39
|
+
throw new Error(`Invalid Contact alias input: ${String(channelKey)}:${String(peerId)}`);
|
|
40
|
+
}
|
|
41
|
+
return `${parsed.channelKey}:${encodeURIComponent(peer)}`;
|
|
42
|
+
}
|
|
43
|
+
/** Parse and canonicalize a stored Contact alias. Non-canonical URL encoding is rejected. */
|
|
44
|
+
export function parseContactAlias(value, expectedSelfAid) {
|
|
45
|
+
if (typeof value !== 'string')
|
|
46
|
+
return null;
|
|
47
|
+
const text = value.trim();
|
|
48
|
+
const separator = text.indexOf(':');
|
|
49
|
+
if (separator <= 0 || separator === text.length - 1)
|
|
50
|
+
return null;
|
|
51
|
+
const parsedChannel = parseContactChannelKey(text.slice(0, separator));
|
|
52
|
+
if (!parsedChannel || parsedChannel.channelType === 'aun')
|
|
53
|
+
return null;
|
|
54
|
+
if (expectedSelfAid !== undefined && parsedChannel.selfAid !== String(expectedSelfAid || '').trim())
|
|
55
|
+
return null;
|
|
56
|
+
const encodedPeerId = text.slice(separator + 1);
|
|
57
|
+
let peerId;
|
|
58
|
+
try {
|
|
59
|
+
peerId = decodeURIComponent(encodedPeerId);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (!peerId || encodeURIComponent(peerId) !== encodedPeerId)
|
|
65
|
+
return null;
|
|
66
|
+
const alias = `${parsedChannel.channelKey}:${encodedPeerId}`;
|
|
67
|
+
return { ...parsedChannel, peerId, alias };
|
|
68
|
+
}
|