evolcore 0.0.2 → 0.0.4
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 +93 -778
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +198 -48
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +203 -8
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/aun/outbox.js +8 -0
- package/dist/channels/aun.js +241 -116
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +1005 -260
- package/dist/channels/feishu.js +184 -56
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +1132 -164
- package/dist/cli/agent-command.js +2 -1
- package/dist/cli/aun-commands.js +98 -43
- 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 +4 -3
- package/dist/cli/help.js +9 -5
- package/dist/cli/index.js +132 -116
- package/dist/cli/init-channel.js +242 -129
- package/dist/cli/init.js +70 -31
- 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 +146 -25
- package/dist/cli/version.js +6 -1
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +42 -17
- package/dist/config/builtin-roles.js +31 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +409 -29
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +622 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +185 -300
- package/dist/config/contact-operation-service.js +116 -0
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +136 -55
- 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/schema-registry.js +49 -24
- package/dist/config-store.js +14 -4
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +16 -1
- package/dist/core/auth/operation-authorizer.js +10 -0
- package/dist/core/auth/operation-catalog.js +105 -1
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +113 -21
- package/dist/core/command/connect-menu.js +599 -0
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +382 -100
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/role-menu.js +128 -29
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +186 -49
- package/dist/core/daemon-file-cache.js +40 -6
- package/dist/core/event-catalog.js +99 -0
- package/dist/core/evolagent-registry.js +4 -40
- package/dist/core/evolagent.js +20 -9
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +20 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +670 -57
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +195 -10
- package/dist/core/message/message-utils.js +8 -2
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +642 -96
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +11 -2
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +115 -38
- package/dist/core/protected-paths.js +38 -11
- package/dist/core/session/session-fs-store.js +44 -3
- package/dist/core/session/session-manager.js +86 -8
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +285 -94
- package/dist/ipc.js +114 -8
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/manager.js +3 -0
- package/dist/trigger/parser.js +126 -11
- package/dist/trigger/patch.js +12 -2
- package/dist/trigger/scheduler.js +444 -40
- package/dist/trigger/validation.js +13 -0
- package/dist/utils/aid-bind.js +43 -29
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/instance-registry.js +14 -7
- package/dist/utils/log-writer.js +46 -0
- package/dist/utils/logger.js +2 -2
- 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/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/dist/utils/system-memory.js +62 -0
- package/kits/docs/INDEX.md +2 -1
- package/kits/docs/channels/aun.md +4 -13
- 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/config.md +47 -2
- package/kits/docs/evolcore/contact.md +61 -0
- package/kits/docs/evolcore/fs.md +9 -0
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +15 -6
- package/kits/docs/evolcore/model.md +4 -1
- package/kits/docs/evolcore/msg.md +12 -6
- 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 +42 -10
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +21 -7
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/schemas/role-registry.schema.1.json +2 -2
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +47 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/owner.json +1 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/commands.md +3 -1
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +7 -5
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- 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,6 +1,9 @@
|
|
|
1
1
|
import { agentContactConfig } from '../paths.js';
|
|
2
2
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
4
|
+
import { ConfigTarget, read, validateConfig } from './config-manager.js';
|
|
5
|
+
import { ContactMutationError, contactRevision, mutateContactBookSync } from './contact-book-store.js';
|
|
6
|
+
import { formatContactAlias, normalizeContactChannelKey, parseContactAlias, } from './contact-alias.js';
|
|
4
7
|
export class ContactBookError extends Error {
|
|
5
8
|
code;
|
|
6
9
|
constructor(code, message) {
|
|
@@ -10,13 +13,11 @@ export class ContactBookError extends Error {
|
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
15
|
let aliasMapCache = new WeakMap();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
conflictingLegacyAliases: new Set(),
|
|
16
|
+
let snapshotByBook = new WeakMap();
|
|
17
|
+
const EMPTY_ALIAS_MAP = {
|
|
18
|
+
aliasToPrimary: new Map(),
|
|
19
|
+
conflictingAliases: new Set(),
|
|
18
20
|
};
|
|
19
|
-
const SCOPED_ALIAS_PREFIX = 'channel-key:';
|
|
20
21
|
export function agentContactConfigPath(aid) {
|
|
21
22
|
return agentContactConfig(aid);
|
|
22
23
|
}
|
|
@@ -26,346 +27,230 @@ export function readContactBook(selfAid, opts = {}) {
|
|
|
26
27
|
return read(ConfigTarget.Contact, { self: selfAid }, { cache: opts.cache ?? true });
|
|
27
28
|
}
|
|
28
29
|
export function resolvePrincipal(selfAid, channelType, actorId, channelKey) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
return isValidAid(
|
|
33
|
-
? { principalId:
|
|
34
|
-
: { principalStatus:
|
|
30
|
+
const actor = String(actorId || '').trim();
|
|
31
|
+
const type = String(channelType || '').trim().toLowerCase();
|
|
32
|
+
if (type === 'aun') {
|
|
33
|
+
return isValidAid(actor)
|
|
34
|
+
? { principalId: actor, principalStatus: 'native_aid' }
|
|
35
|
+
: { principalStatus: actor ? 'unmapped' : 'error' };
|
|
35
36
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const legacyAlias = normalizeLegacyAlias(normalizedChannel, normalizedActor);
|
|
39
|
-
if (!isValidAid(normalizedSelf) || !normalizedChannelKey || !legacyAlias) {
|
|
37
|
+
const canonicalChannelKey = normalizeContactChannelKey(selfAid, type, channelKey);
|
|
38
|
+
if (!isValidAid(String(selfAid || '').trim()) || !actor || !canonicalChannelKey) {
|
|
40
39
|
return { principalStatus: 'error' };
|
|
41
40
|
}
|
|
42
|
-
const scopedAlias = scopedAliasLookupKey(normalizedChannelKey, normalizedActor);
|
|
43
41
|
try {
|
|
44
|
-
const aliases = loadAliasMap(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
const scopedPrincipalId = aliases.scopedAliasToPrimary.get(scopedAlias);
|
|
49
|
-
if (scopedPrincipalId) {
|
|
50
|
-
return { principalId: scopedPrincipalId, principalStatus: 'mapped' };
|
|
51
|
-
}
|
|
52
|
-
// Legacy aliases did not identify a channel instance. They remain trusted only
|
|
53
|
-
// while the configured channel type has exactly one matching instance.
|
|
54
|
-
if (!isUnambiguousLegacyChannel(normalizedSelf, normalizedChannel, normalizedChannelKey)) {
|
|
55
|
-
return { principalStatus: 'unmapped' };
|
|
56
|
-
}
|
|
57
|
-
if (aliases.conflictingLegacyAliases.has(legacyAlias)) {
|
|
42
|
+
const aliases = loadAliasMap(selfAid);
|
|
43
|
+
const exact = formatContactAlias(canonicalChannelKey, actor);
|
|
44
|
+
if (aliases.conflictingAliases.has(exact))
|
|
58
45
|
return { principalStatus: 'conflict' };
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
: { principalStatus: 'unmapped' };
|
|
46
|
+
const mapped = aliases.aliasToPrimary.get(exact);
|
|
47
|
+
if (mapped)
|
|
48
|
+
return { principalId: mapped, principalStatus: 'mapped' };
|
|
49
|
+
return { principalStatus: 'unmapped' };
|
|
64
50
|
}
|
|
65
51
|
catch {
|
|
66
52
|
return { principalStatus: 'error' };
|
|
67
53
|
}
|
|
68
54
|
}
|
|
69
55
|
export function resolvePrimaryId(selfAid, channelType, actorId, channelKey) {
|
|
70
|
-
const
|
|
71
|
-
return resolvePrincipal(selfAid, channelType,
|
|
56
|
+
const actor = String(actorId || '').trim();
|
|
57
|
+
return resolvePrincipal(selfAid, channelType, actor, channelKey).principalId ?? actor;
|
|
72
58
|
}
|
|
73
59
|
export function bindContactAlias(selfAid, primaryId, channelType, actorId, channelKey) {
|
|
74
60
|
const primary = normalizePrimaryId(primaryId);
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const contacts = next.contacts ?? {};
|
|
81
|
-
let removedLegacyAlias = false;
|
|
82
|
-
// An explicit scoped bind supersedes every unscoped copy of the same alias.
|
|
83
|
-
// Otherwise unbinding the scoped entry could silently reactivate a legacy
|
|
84
|
-
// mapping owned by another principal.
|
|
85
|
-
for (const [contactId, value] of Object.entries(contacts)) {
|
|
86
|
-
const entry = normalizeContactEntry(value);
|
|
87
|
-
const filtered = entry.aliases.filter(item => normalizeLegacyAliasString(item) !== legacyAlias);
|
|
88
|
-
if (filtered.length !== entry.aliases.length) {
|
|
89
|
-
contacts[contactId] = { ...entry, aliases: filtered };
|
|
90
|
-
removedLegacyAlias = true;
|
|
61
|
+
const type = String(channelType || '').trim().toLowerCase();
|
|
62
|
+
const actor = String(actorId || '').trim();
|
|
63
|
+
if (type === 'aun') {
|
|
64
|
+
if (!isValidAid(actor) || actor !== primary) {
|
|
65
|
+
throw new ContactBookError('NATIVE_AID_REMAP_FORBIDDEN', `Native AID "${actor}" cannot be remapped to "${primary}"`);
|
|
91
66
|
}
|
|
92
|
-
}
|
|
93
|
-
const current = normalizeContactEntry(contacts[primary]);
|
|
94
|
-
const alreadyScoped = current.aliases.some(value => (normalizeScopedAliasString(value, selfAid)?.lookupKey === alias.lookupKey));
|
|
95
|
-
if (alreadyScoped && !removedLegacyAlias) {
|
|
96
67
|
return false;
|
|
97
68
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
69
|
+
const canonicalChannelKey = normalizeContactChannelKey(selfAid, type, channelKey);
|
|
70
|
+
if (!canonicalChannelKey || !actor) {
|
|
71
|
+
throw new ContactBookError('INVALID_ALIAS', `Invalid contact alias: ${String(channelKey)}:${String(actorId)}`);
|
|
72
|
+
}
|
|
73
|
+
if (!getContactSnapshot(selfAid).contacts.has(primary)) {
|
|
74
|
+
throw new ContactBookError('NOT_FOUND', `Contact was not found: ${primary}`);
|
|
75
|
+
}
|
|
76
|
+
const alias = formatContactAlias(canonicalChannelKey, actor);
|
|
77
|
+
try {
|
|
78
|
+
return mutateContactBookSync({
|
|
79
|
+
selfAid,
|
|
80
|
+
mutation: { type: 'bind-alias', primaryId: primary, alias },
|
|
81
|
+
}).changed;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof ContactMutationError)
|
|
85
|
+
throw new ContactBookError(error.code, error.message);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
107
88
|
}
|
|
108
89
|
export function unbindContactAlias(selfAid, primaryId, channelType, actorId, channelKey) {
|
|
109
90
|
const primary = normalizePrimaryId(primaryId);
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Remove the requested scoped binding from the target principal and purge
|
|
116
|
-
// every unscoped copy of the same raw alias. Legacy aliases do not carry a
|
|
117
|
-
// channel-instance identity, so retaining one on another contact would
|
|
118
|
-
// silently reactivate that principal after this scoped binding is revoked.
|
|
119
|
-
for (const [contactId, value] of Object.entries(contacts)) {
|
|
120
|
-
const entry = normalizeContactEntry(value);
|
|
121
|
-
const filtered = entry.aliases.filter(item => {
|
|
122
|
-
const isTargetScoped = contactId.trim() === primary
|
|
123
|
-
&& normalizeScopedAliasString(item, selfAid)?.lookupKey === alias.lookupKey;
|
|
124
|
-
const isLegacyCopy = normalizeLegacyAliasString(item) === legacyAlias;
|
|
125
|
-
return !isTargetScoped && !isLegacyCopy;
|
|
126
|
-
});
|
|
127
|
-
if (filtered.length !== entry.aliases.length) {
|
|
128
|
-
contacts[contactId] = { ...entry, aliases: filtered };
|
|
129
|
-
changed = true;
|
|
130
|
-
}
|
|
91
|
+
const type = String(channelType || '').trim().toLowerCase();
|
|
92
|
+
const actor = String(actorId || '').trim();
|
|
93
|
+
const canonicalChannelKey = normalizeContactChannelKey(selfAid, type, channelKey);
|
|
94
|
+
if (!canonicalChannelKey || type === 'aun' || !actor) {
|
|
95
|
+
throw new ContactBookError('INVALID_ALIAS', `Invalid contact alias: ${String(channelKey)}:${String(actorId)}`);
|
|
131
96
|
}
|
|
132
|
-
|
|
133
|
-
|
|
97
|
+
const alias = formatContactAlias(canonicalChannelKey, actor);
|
|
98
|
+
try {
|
|
99
|
+
return mutateContactBookSync({
|
|
100
|
+
selfAid,
|
|
101
|
+
mutation: { type: 'unbind-alias', primaryId: primary, alias },
|
|
102
|
+
}).changed;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error instanceof ContactMutationError)
|
|
106
|
+
throw new ContactBookError(error.code, error.message);
|
|
107
|
+
throw error;
|
|
134
108
|
}
|
|
135
|
-
next.contacts = contacts;
|
|
136
|
-
write(ConfigTarget.Contact, next, { self: selfAid });
|
|
137
|
-
return true;
|
|
138
109
|
}
|
|
139
110
|
export function clearContactBookCache() {
|
|
140
111
|
aliasMapCache = new WeakMap();
|
|
112
|
+
snapshotByBook = new WeakMap();
|
|
141
113
|
}
|
|
142
|
-
function
|
|
143
|
-
const book = readContactBook(selfAid, { cache: true });
|
|
144
|
-
|
|
145
|
-
return emptyAliasMap;
|
|
146
|
-
const cached = aliasMapCache.get(book);
|
|
114
|
+
export function getContactSnapshot(selfAid) {
|
|
115
|
+
const book = readContactBook(selfAid, { cache: true }) ?? { $schema_version: 2, contacts: {} };
|
|
116
|
+
const cached = snapshotByBook.get(book);
|
|
147
117
|
if (cached)
|
|
148
118
|
return cached;
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
throw new ContactBookError('INVALID_CONTACT_BOOK', `Invalid contact book for ${selfAid}: ${
|
|
152
|
-
}
|
|
153
|
-
const built = buildAliasMap(book, selfAid);
|
|
154
|
-
aliasMapCache.set(book, built);
|
|
155
|
-
return built;
|
|
156
|
-
}
|
|
157
|
-
function buildAliasMap(config, selfAid) {
|
|
158
|
-
const scopedAliasToPrimary = new Map();
|
|
159
|
-
const conflictingScopedAliases = new Set();
|
|
160
|
-
const legacyAliasToPrimary = new Map();
|
|
161
|
-
const conflictingLegacyAliases = new Set();
|
|
162
|
-
const contacts = config?.contacts;
|
|
163
|
-
if (!contacts || typeof contacts !== 'object' || Array.isArray(contacts)) {
|
|
164
|
-
return {
|
|
165
|
-
scopedAliasToPrimary,
|
|
166
|
-
conflictingScopedAliases,
|
|
167
|
-
legacyAliasToPrimary,
|
|
168
|
-
conflictingLegacyAliases,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
for (const [rawPrimaryId, entry] of Object.entries(contacts)) {
|
|
172
|
-
const primaryId = rawPrimaryId.trim();
|
|
173
|
-
if (!isValidAid(primaryId) || !entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
174
|
-
continue;
|
|
175
|
-
const aliases = Array.isArray(entry.aliases) ? entry.aliases : [];
|
|
176
|
-
for (const rawAlias of aliases) {
|
|
177
|
-
const scoped = normalizeScopedAliasString(rawAlias, selfAid);
|
|
178
|
-
if (scoped) {
|
|
179
|
-
addAliasMapping(scopedAliasToPrimary, conflictingScopedAliases, scoped.lookupKey, primaryId);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
const legacy = normalizeLegacyAliasString(rawAlias);
|
|
183
|
-
if (legacy) {
|
|
184
|
-
addAliasMapping(legacyAliasToPrimary, conflictingLegacyAliases, legacy, primaryId);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
119
|
+
const errors = validateConfig(ConfigTarget.Contact, book);
|
|
120
|
+
if (errors.length) {
|
|
121
|
+
throw new ContactBookError('INVALID_CONTACT_BOOK', `Invalid contact book for ${selfAid}: ${errors.join('; ')}`);
|
|
188
122
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
legacyAliasToPrimary,
|
|
193
|
-
conflictingLegacyAliases,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
function addAliasMapping(aliases, conflicts, alias, primaryId) {
|
|
197
|
-
if (conflicts.has(alias))
|
|
198
|
-
return;
|
|
199
|
-
const existing = aliases.get(alias);
|
|
200
|
-
if (existing && existing !== primaryId) {
|
|
201
|
-
aliases.delete(alias);
|
|
202
|
-
conflicts.add(alias);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
aliases.set(alias, primaryId);
|
|
206
|
-
}
|
|
207
|
-
function cloneContactBook(config) {
|
|
208
|
-
const next = config ? { ...config } : {};
|
|
209
|
-
const contacts = {};
|
|
210
|
-
const rawContacts = config?.contacts;
|
|
211
|
-
if (rawContacts && typeof rawContacts === 'object' && !Array.isArray(rawContacts)) {
|
|
212
|
-
for (const [primaryId, entry] of Object.entries(rawContacts)) {
|
|
213
|
-
contacts[primaryId] = normalizeContactEntry(entry);
|
|
214
|
-
}
|
|
123
|
+
const aliases = buildAliasMap(book, selfAid);
|
|
124
|
+
if (aliases.conflictingAliases.size) {
|
|
125
|
+
throw new ContactBookError('ALIAS_CONFLICT', `Conflicting contact aliases: ${[...aliases.conflictingAliases].join(', ')}`);
|
|
215
126
|
}
|
|
216
|
-
|
|
217
|
-
|
|
127
|
+
const snapshot = buildSnapshot(book, selfAid, aliases);
|
|
128
|
+
snapshotByBook.set(book, snapshot);
|
|
129
|
+
return snapshot;
|
|
218
130
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
131
|
+
export function resolveContactView(selfAid, primaryId) {
|
|
132
|
+
const id = String(primaryId || '').trim();
|
|
133
|
+
const owner = listAgentOwners(selfAid).includes(id);
|
|
134
|
+
const entry = getContactSnapshot(selfAid).contacts.get(id);
|
|
135
|
+
const status = owner ? 'active' : entry?.status ?? 'active';
|
|
224
136
|
return {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
137
|
+
primaryId: id,
|
|
138
|
+
...(entry?.displayName ? { displayName: entry.displayName } : {}),
|
|
139
|
+
aliases: entry?.aliases ?? [],
|
|
140
|
+
status,
|
|
141
|
+
blocked: status === 'blocked',
|
|
142
|
+
isOwner: owner,
|
|
143
|
+
source: owner ? 'owner' : entry ? 'contact' : 'none',
|
|
229
144
|
};
|
|
230
145
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
if (owners.size === 0)
|
|
234
|
-
return;
|
|
235
|
-
if (owners.size === 1 && owners.has(primaryId))
|
|
236
|
-
return;
|
|
237
|
-
throw new ContactBookError('ALIAS_CONFLICT', `Alias "${alias}" is already bound to ${[...owners].join(', ')}`);
|
|
146
|
+
export function isBlockedContact(selfAid, primaryId) {
|
|
147
|
+
return resolveContactView(selfAid, primaryId).blocked;
|
|
238
148
|
}
|
|
239
|
-
function
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
for (const [rawPrimaryId, entry] of Object.entries(contacts)) {
|
|
245
|
-
const primaryId = rawPrimaryId.trim();
|
|
246
|
-
if (!primaryId || !entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
247
|
-
continue;
|
|
248
|
-
const aliases = Array.isArray(entry.aliases) ? entry.aliases : [];
|
|
249
|
-
if (aliases.some(value => normalizeScopedAliasString(value, selfAid)?.lookupKey === alias)) {
|
|
250
|
-
owners.add(primaryId);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return owners;
|
|
149
|
+
export function isAcceptedContact(selfAid, primaryId) {
|
|
150
|
+
const id = String(primaryId || '').trim();
|
|
151
|
+
const snapshot = getContactSnapshot(selfAid);
|
|
152
|
+
const entry = snapshot.contacts.get(id);
|
|
153
|
+
return !!entry && (entry.status === undefined || entry.status === 'active');
|
|
254
154
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return alias;
|
|
268
|
-
}
|
|
269
|
-
function normalizeLegacyAlias(channelType, actorId) {
|
|
270
|
-
const channel = String(channelType || '').trim().toLowerCase();
|
|
271
|
-
const id = String(actorId || '').trim();
|
|
272
|
-
if (!/^[a-z0-9_-]+$/.test(channel) || !id)
|
|
273
|
-
return null;
|
|
274
|
-
return `${channel}:${id}`;
|
|
275
|
-
}
|
|
276
|
-
function normalizeLegacyAliasString(value) {
|
|
277
|
-
if (typeof value !== 'string')
|
|
278
|
-
return null;
|
|
279
|
-
const trimmed = value.trim();
|
|
280
|
-
if (trimmed.startsWith(SCOPED_ALIAS_PREFIX))
|
|
281
|
-
return null;
|
|
282
|
-
const idx = trimmed.indexOf(':');
|
|
283
|
-
if (idx <= 0 || idx === trimmed.length - 1)
|
|
284
|
-
return null;
|
|
285
|
-
return normalizeLegacyAlias(trimmed.slice(0, idx), trimmed.slice(idx + 1));
|
|
286
|
-
}
|
|
287
|
-
function requireScopedAlias(selfAid, channelType, actorId, channelKey) {
|
|
288
|
-
const normalizedActor = String(actorId || '').trim();
|
|
289
|
-
const normalizedChannel = String(channelType || '').trim().toLowerCase();
|
|
290
|
-
const normalizedChannelKey = normalizeChannelKey(selfAid, normalizedChannel, channelKey);
|
|
291
|
-
if (!normalizedActor || !normalizedChannelKey) {
|
|
292
|
-
throw new ContactBookError('INVALID_CHANNEL_ALIAS', `Invalid scoped contact alias: ${String(channelKey)}:${String(actorId)}`);
|
|
293
|
-
}
|
|
294
|
-
return {
|
|
295
|
-
lookupKey: scopedAliasLookupKey(normalizedChannelKey, normalizedActor),
|
|
296
|
-
serialized: serializeScopedAlias(normalizedChannelKey, normalizedActor),
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
function normalizeScopedAliasString(value, selfAid) {
|
|
300
|
-
if (typeof value !== 'string')
|
|
301
|
-
return null;
|
|
302
|
-
const trimmed = value.trim();
|
|
303
|
-
if (!trimmed.startsWith(SCOPED_ALIAS_PREFIX))
|
|
304
|
-
return null;
|
|
305
|
-
const encoded = trimmed.slice(SCOPED_ALIAS_PREFIX.length);
|
|
306
|
-
const separator = encoded.indexOf(':');
|
|
307
|
-
if (separator <= 0 || separator === encoded.length - 1)
|
|
308
|
-
return null;
|
|
155
|
+
/**
|
|
156
|
+
* 对端展示名:联系人簿 displayName → principalId。仅用于展示(预览行/日志),
|
|
157
|
+
* 关系键始终用原生 peerId,不受此影响。
|
|
158
|
+
*
|
|
159
|
+
* 非 AUN 渠道的 actorId 是渠道原生 ID(飞书 open_id 等),直接展示不可读;
|
|
160
|
+
* 联系人簿已把它 alias 到主体 AID 上,取其 displayName 即可。
|
|
161
|
+
* 未绑定/联系人簿损坏时返回 undefined,由调用方按自己的兜底链继续。
|
|
162
|
+
*/
|
|
163
|
+
export function resolvePeerDisplayLabel(selfAid, channelType, actorId, channelKey) {
|
|
164
|
+
const actor = String(actorId || '').trim();
|
|
165
|
+
if (!String(selfAid || '').trim() || !actor)
|
|
166
|
+
return undefined;
|
|
309
167
|
try {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!normalizedChannelKey || !actorId)
|
|
315
|
-
return null;
|
|
316
|
-
return {
|
|
317
|
-
lookupKey: scopedAliasLookupKey(normalizedChannelKey, actorId),
|
|
318
|
-
serialized: serializeScopedAlias(normalizedChannelKey, actorId),
|
|
319
|
-
};
|
|
168
|
+
const { principalId } = resolvePrincipal(selfAid, channelType, actor, channelKey);
|
|
169
|
+
if (!principalId)
|
|
170
|
+
return undefined;
|
|
171
|
+
return resolveContactView(selfAid, principalId).displayName || principalId;
|
|
320
172
|
}
|
|
321
173
|
catch {
|
|
322
|
-
|
|
174
|
+
// 联系人簿损坏(校验失败/alias 冲突)不应影响消息处理
|
|
175
|
+
return undefined;
|
|
323
176
|
}
|
|
324
177
|
}
|
|
325
|
-
function
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
178
|
+
function buildSnapshot(book, selfAid, aliases) {
|
|
179
|
+
const contacts = new Map();
|
|
180
|
+
const blockedIndex = new Set();
|
|
181
|
+
const owners = new Set(listAgentOwners(selfAid));
|
|
182
|
+
for (const [primaryId, rawEntry] of Object.entries(book.contacts)) {
|
|
183
|
+
if (!isValidAid(primaryId) || isExplicitGroupId(primaryId)) {
|
|
184
|
+
throw new ContactBookError('INVALID_CONTACT_BOOK', `Invalid contact AID: ${primaryId}`);
|
|
185
|
+
}
|
|
186
|
+
if (rawEntry.status === 'blocked' && owners.has(primaryId)) {
|
|
187
|
+
throw new ContactBookError('INVALID_CONTACT_BOOK', `Owner contact ${primaryId} cannot be blocked`);
|
|
188
|
+
}
|
|
189
|
+
const entry = Object.freeze({
|
|
190
|
+
...rawEntry,
|
|
191
|
+
...(rawEntry.aliases ? { aliases: Object.freeze([...rawEntry.aliases]) } : {}),
|
|
192
|
+
});
|
|
193
|
+
contacts.set(primaryId, entry);
|
|
194
|
+
if (entry.status === 'blocked')
|
|
195
|
+
blockedIndex.add(primaryId);
|
|
196
|
+
}
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
contacts,
|
|
199
|
+
aliasIndex: aliases.aliasToPrimary,
|
|
200
|
+
conflictingAliases: aliases.conflictingAliases,
|
|
201
|
+
blockedIndex,
|
|
202
|
+
contactRevision: contactRevision(book),
|
|
203
|
+
});
|
|
330
204
|
}
|
|
331
|
-
function
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|| !name) {
|
|
342
|
-
return null;
|
|
205
|
+
function loadAliasMap(selfAid) {
|
|
206
|
+
const book = readContactBook(selfAid, { cache: true });
|
|
207
|
+
if (!book)
|
|
208
|
+
return EMPTY_ALIAS_MAP;
|
|
209
|
+
const cached = aliasMapCache.get(book);
|
|
210
|
+
if (cached)
|
|
211
|
+
return cached;
|
|
212
|
+
const errors = validateConfig(ConfigTarget.Contact, book);
|
|
213
|
+
if (errors.length) {
|
|
214
|
+
throw new ContactBookError('INVALID_CONTACT_BOOK', `Invalid contact book for ${selfAid}: ${errors.join('; ')}`);
|
|
343
215
|
}
|
|
344
|
-
|
|
216
|
+
const built = buildAliasMap(book, selfAid);
|
|
217
|
+
aliasMapCache.set(book, built);
|
|
218
|
+
return built;
|
|
345
219
|
}
|
|
346
|
-
function
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
220
|
+
function buildAliasMap(book, selfAid) {
|
|
221
|
+
const aliasToPrimary = new Map();
|
|
222
|
+
const conflictingAliases = new Set();
|
|
223
|
+
for (const [primaryId, entry] of Object.entries(book?.contacts ?? {})) {
|
|
224
|
+
if (!isValidAid(primaryId) || isExplicitGroupId(primaryId) || !entry || typeof entry !== 'object')
|
|
225
|
+
continue;
|
|
226
|
+
for (const rawAlias of entry.aliases ?? []) {
|
|
227
|
+
const parsed = parseContactAlias(rawAlias, selfAid);
|
|
228
|
+
if (!parsed) {
|
|
229
|
+
throw new ContactBookError('INVALID_CONTACT_BOOK', `Invalid contact alias for ${selfAid}: ${String(rawAlias)}`);
|
|
230
|
+
}
|
|
231
|
+
const alias = parsed.alias;
|
|
232
|
+
if (conflictingAliases.has(alias))
|
|
356
233
|
continue;
|
|
357
|
-
|
|
234
|
+
const existing = aliasToPrimary.get(alias);
|
|
235
|
+
if (existing && existing !== primaryId) {
|
|
236
|
+
aliasToPrimary.delete(alias);
|
|
237
|
+
conflictingAliases.add(alias);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
aliasToPrimary.set(alias, primaryId);
|
|
241
|
+
}
|
|
358
242
|
}
|
|
359
|
-
return matches.size === 1 && matches.has(channelKey);
|
|
360
|
-
}
|
|
361
|
-
catch {
|
|
362
|
-
return false;
|
|
363
243
|
}
|
|
244
|
+
return { aliasToPrimary, conflictingAliases };
|
|
364
245
|
}
|
|
365
|
-
function
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
throw new ContactBookError('NATIVE_AID_REMAP_FORBIDDEN', `Native AID "${actor}" cannot be remapped to "${primaryId}"`);
|
|
246
|
+
function normalizePrimaryId(value) {
|
|
247
|
+
const id = String(value || '').trim();
|
|
248
|
+
if (!isValidAid(id) || isExplicitGroupId(id)) {
|
|
249
|
+
throw new ContactBookError('INVALID_PRIMARY_ID', `Invalid individual contact AID: ${String(value)}`);
|
|
370
250
|
}
|
|
251
|
+
return id;
|
|
252
|
+
}
|
|
253
|
+
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))];
|
|
371
256
|
}
|