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,47 +1,77 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { agentConfig, agentContactConfig, agentRelationsDir } from '../paths.js';
|
|
5
|
+
import { formatPeerKey, parsePeerKey } from '../core/relation/peer-identity.js';
|
|
5
6
|
import { hasTrustedPrincipal } from '../core/auth/authenticated-actor.js';
|
|
6
7
|
import { ConfigTarget, read, write } from './config-manager.js';
|
|
7
8
|
import { isManagementRole } from './builtin-roles.js';
|
|
8
|
-
import { normalizeDefaultRole, roleExists
|
|
9
|
+
import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from './roles.js';
|
|
9
10
|
import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
|
|
10
11
|
import { resolvePrincipal } from './contact-book.js';
|
|
11
12
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
13
|
+
import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
12
14
|
export function isAuthenticated(userId) {
|
|
13
15
|
return isValidAid(String(userId || '').trim());
|
|
14
16
|
}
|
|
15
17
|
export function listStaticAgentOwners(aid, opts = {}) {
|
|
16
|
-
return
|
|
18
|
+
return listAgentOwners(aid, opts);
|
|
17
19
|
}
|
|
20
|
+
/** Direct AUN relations with role=admin are the addressable Agent admin set. */
|
|
18
21
|
export function listStaticAgentAdmins(aid, opts = {}) {
|
|
19
|
-
|
|
22
|
+
if (!aid)
|
|
23
|
+
return [];
|
|
24
|
+
const relations = agentRelationsDir(aid);
|
|
25
|
+
if (!fs.existsSync(relations))
|
|
26
|
+
return [];
|
|
27
|
+
const admins = new Set();
|
|
28
|
+
for (const entry of fs.readdirSync(relations, { withFileTypes: true })) {
|
|
29
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
30
|
+
continue;
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = parsePeerKey(entry.name);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (parsed.channelType !== 'aun'
|
|
39
|
+
|| !isValidAid(parsed.channelId)
|
|
40
|
+
|| isExplicitGroupId(parsed.channelId))
|
|
41
|
+
continue;
|
|
42
|
+
const config = read(ConfigTarget.Relation, { self: aid, peerKey: entry.name }, { cache: opts.fresh !== true });
|
|
43
|
+
if (config?.targetKind === 'group')
|
|
44
|
+
continue;
|
|
45
|
+
if (config?.role === 'admin')
|
|
46
|
+
admins.add(parsed.channelId);
|
|
47
|
+
}
|
|
48
|
+
return [...admins].sort();
|
|
20
49
|
}
|
|
21
50
|
export function getFirstStaticAgentOwner(aid) {
|
|
22
51
|
return listStaticAgentOwners(aid)[0];
|
|
23
52
|
}
|
|
24
|
-
/** Revision for security-sensitive
|
|
53
|
+
/** Revision for security-sensitive owner/contact/relation authorization inputs. */
|
|
25
54
|
export function authorizationConfigRevision(aid) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
const fingerprints = [fileRevision(agentConfig(aid)), fileRevision(agentContactConfig(aid))];
|
|
56
|
+
const relations = agentRelationsDir(aid);
|
|
57
|
+
try {
|
|
58
|
+
for (const entry of fs.readdirSync(relations, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
59
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
60
|
+
continue;
|
|
61
|
+
fingerprints.push(entry.name, fileRevision(path.join(relations, entry.name, 'config.json')));
|
|
33
62
|
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fileRevision(agentContactConfig(aid)),
|
|
38
|
-
])).digest('hex');
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
return crypto.createHash('sha256').update(JSON.stringify(fingerprints)).digest('hex');
|
|
39
66
|
}
|
|
40
67
|
export function isStaticAgentOwner(aid, actorId) {
|
|
41
68
|
return !!aid && !!actorId && listStaticAgentOwners(aid).includes(actorId);
|
|
42
69
|
}
|
|
43
70
|
export function isStaticAgentAdmin(aid, actorId) {
|
|
44
|
-
|
|
71
|
+
if (!aid || !isValidAid(actorId) || isExplicitGroupId(actorId))
|
|
72
|
+
return false;
|
|
73
|
+
const config = read(ConfigTarget.Relation, { self: aid, peerKey: formatPeerKey('aun', actorId) }, { cache: true });
|
|
74
|
+
return config?.targetKind !== 'group' && config?.role === 'admin';
|
|
45
75
|
}
|
|
46
76
|
export function addStaticAgentOwner(aid, ownerAid) {
|
|
47
77
|
if (!aid || !ownerAid)
|
|
@@ -56,32 +86,46 @@ export function hasStaticAgentOwner(aid) {
|
|
|
56
86
|
}
|
|
57
87
|
export function resolvePeerRoleDetail(ctx) {
|
|
58
88
|
const operatorId = String(ctx.actorId || '').trim();
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const principal = resolvePrincipal(ctx.selfAid,
|
|
89
|
+
const channelType = String(ctx.channelType || '').trim().toLowerCase();
|
|
90
|
+
const channelKey = String(ctx.channelKey || '').trim();
|
|
91
|
+
const principal = resolvePrincipal(ctx.selfAid, channelType, operatorId, channelKey || undefined);
|
|
62
92
|
const actor = {
|
|
63
93
|
operatorId,
|
|
64
|
-
operatorChannelKey:
|
|
65
|
-
operatorChannelType,
|
|
94
|
+
operatorChannelKey: channelKey || channelType,
|
|
95
|
+
operatorChannelType: channelType,
|
|
66
96
|
...principal,
|
|
67
97
|
};
|
|
68
|
-
const
|
|
69
|
-
if (isStaticAgentOwner(ctx.selfAid,
|
|
70
|
-
|
|
98
|
+
const candidates = [];
|
|
99
|
+
if (hasTrustedPrincipal(actor) && isStaticAgentOwner(ctx.selfAid, actor.principalId)) {
|
|
100
|
+
candidates.push({ role: 'owner', source: 'agent-owner', sourcePriority: 100 });
|
|
71
101
|
}
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
const actorRole = resolveActorDirectRole(ctx.selfAid, actor, channelType, operatorId);
|
|
103
|
+
if (ctx.chatType === 'private') {
|
|
104
|
+
if (actorRole)
|
|
105
|
+
candidates.push({ role: actorRole, source: 'relation-actor', sourcePriority: 80 });
|
|
106
|
+
const defaultRole = normalizeDefaultRole(readAgentDefaultRole(ctx.selfAid, 'private'), ctx.selfAid);
|
|
107
|
+
if (defaultRole)
|
|
108
|
+
candidates.push({ role: defaultRole, source: 'agent-default', sourcePriority: 40 });
|
|
74
109
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
else {
|
|
111
|
+
if (actorRole)
|
|
112
|
+
candidates.push({ role: actorRole, source: 'relation-actor', sourcePriority: 80 });
|
|
113
|
+
const groupRole = normalizeRelationRole(readDirectRelationRole(ctx.selfAid, channelType, String(ctx.conversationId || '').trim(), 'group'), ctx.selfAid);
|
|
114
|
+
if (groupRole) {
|
|
115
|
+
candidates.push({ role: groupRole, source: 'relation-peer', sourcePriority: 60 });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const defaultRole = normalizeDefaultRole(readAgentDefaultRole(ctx.selfAid, 'group'), ctx.selfAid);
|
|
119
|
+
if (defaultRole)
|
|
120
|
+
candidates.push({ role: defaultRole, source: 'agent-default', sourcePriority: 40 });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const winner = selectHighestRank(candidates, ctx.selfAid);
|
|
124
|
+
if (winner)
|
|
125
|
+
return resultFor(winner.role, winner.source, actor, ctx.selfAid);
|
|
126
|
+
return roleRegistryExists(ctx.selfAid) && roleExists('visitor', ctx.selfAid)
|
|
127
|
+
? resultFor('visitor', 'builtin-fallback', actor, ctx.selfAid)
|
|
128
|
+
: resultFor(null, 'none', actor, ctx.selfAid);
|
|
85
129
|
}
|
|
86
130
|
export function roleToSessionIdentity(role) {
|
|
87
131
|
return { role: role ?? 'none', mode: 'interactive' };
|
|
@@ -91,32 +135,52 @@ export function checkRoleAccess(role, selfAid) {
|
|
|
91
135
|
return false;
|
|
92
136
|
if (isManagementRole(role))
|
|
93
137
|
return true;
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
const definition = getRoleDefinition(role, selfAid);
|
|
139
|
+
return !!definition && (definition.allowAccess ?? true);
|
|
140
|
+
}
|
|
141
|
+
function selectHighestRank(candidates, selfAid) {
|
|
142
|
+
let winner = null;
|
|
143
|
+
for (const candidate of candidates) {
|
|
144
|
+
if (!normalizeRelationRole(candidate.role, selfAid) && candidate.role !== 'owner')
|
|
145
|
+
continue;
|
|
146
|
+
if (!winner) {
|
|
147
|
+
winner = candidate;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const rank = getRoleRank(candidate.role, selfAid);
|
|
151
|
+
const winnerRank = getRoleRank(winner.role, selfAid);
|
|
152
|
+
if (rank > winnerRank || (rank === winnerRank && candidate.sourcePriority > winner.sourcePriority)) {
|
|
153
|
+
winner = candidate;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return winner;
|
|
157
|
+
}
|
|
158
|
+
function resolveActorDirectRole(selfAid, actor, channelType, actorId) {
|
|
159
|
+
const role = hasTrustedPrincipal(actor)
|
|
160
|
+
? readDirectRelationRole(selfAid, 'aun', actor.principalId, 'private')
|
|
161
|
+
: actor.principalStatus === 'unmapped'
|
|
162
|
+
? readDirectRelationRole(selfAid, channelType, actorId, 'private')
|
|
163
|
+
: null;
|
|
164
|
+
return normalizeRelationRole(role, selfAid);
|
|
98
165
|
}
|
|
99
|
-
function resultFor(role, source, actor, selfAid
|
|
166
|
+
function resultFor(role, source, actor, selfAid) {
|
|
100
167
|
const exists = role ? (isManagementRole(role) || roleExists(role, selfAid)) : false;
|
|
101
168
|
const roleDef = role ? getRoleDefinition(role, selfAid) : null;
|
|
102
169
|
return {
|
|
103
170
|
effectiveRole: exists ? role : null,
|
|
104
171
|
source: exists ? source : 'none',
|
|
105
172
|
isAuthenticated: hasTrustedPrincipal(actor),
|
|
106
|
-
allowAccess:
|
|
173
|
+
allowAccess: !!(roleDef && (roleDef.allowAccess ?? true)),
|
|
107
174
|
roleExists: exists,
|
|
108
175
|
actor,
|
|
109
176
|
};
|
|
110
177
|
}
|
|
111
|
-
function
|
|
178
|
+
function listAgentOwners(aid, opts = {}) {
|
|
112
179
|
if (!aid)
|
|
113
180
|
return [];
|
|
114
181
|
try {
|
|
115
182
|
const config = read(ConfigTarget.Agent, { self: aid }, { cache: opts.fresh !== true });
|
|
116
|
-
|
|
117
|
-
if (!Array.isArray(values))
|
|
118
|
-
return [];
|
|
119
|
-
return Array.from(new Set(values.map(value => String(value || '').trim()).filter(Boolean)));
|
|
183
|
+
return Array.from(new Set((config?.owners ?? []).map(value => String(value || '').trim()).filter(Boolean)));
|
|
120
184
|
}
|
|
121
185
|
catch {
|
|
122
186
|
return [];
|
|
@@ -125,15 +189,30 @@ function listAgentAids(aid, field, opts = {}) {
|
|
|
125
189
|
function readAgentDefaultRole(aid, chatType) {
|
|
126
190
|
return getStoredRoleRegistry(aid).defaultRoles[chatType] ?? null;
|
|
127
191
|
}
|
|
128
|
-
function
|
|
192
|
+
function readDirectRelationRole(selfAid, channelType, channelId, expectedTargetKind) {
|
|
129
193
|
if (!selfAid || !channelType || !channelId)
|
|
130
194
|
return undefined;
|
|
195
|
+
if (expectedTargetKind === 'private' && channelType === 'aun' && isExplicitGroupId(channelId))
|
|
196
|
+
return undefined;
|
|
131
197
|
const peerKey = formatPeerKey(channelType, channelId);
|
|
132
198
|
const config = read(ConfigTarget.Relation, { self: selfAid, peerKey }, { cache: true });
|
|
199
|
+
if (expectedTargetKind && config?.targetKind && config.targetKind !== expectedTargetKind)
|
|
200
|
+
return undefined;
|
|
133
201
|
return config?.role;
|
|
134
202
|
}
|
|
135
|
-
function
|
|
136
|
-
if (typeof value !== 'string' || !
|
|
203
|
+
function normalizeRelationRole(value, selfAid) {
|
|
204
|
+
if (typeof value !== 'string' || !value || value === 'owner')
|
|
137
205
|
return null;
|
|
138
|
-
|
|
206
|
+
if (value === 'admin')
|
|
207
|
+
return getRoleDefinition('admin', selfAid) ? 'admin' : null;
|
|
208
|
+
return roleExists(value, selfAid) ? value : null;
|
|
209
|
+
}
|
|
210
|
+
function fileRevision(file) {
|
|
211
|
+
try {
|
|
212
|
+
const stat = fs.statSync(file);
|
|
213
|
+
return [stat.size, stat.mtimeMs].join(':');
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return 'missing';
|
|
217
|
+
}
|
|
139
218
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const ROLE_RANKS = Object.freeze({
|
|
2
|
+
owner: 900,
|
|
3
|
+
admin: 700,
|
|
4
|
+
custom: 500,
|
|
5
|
+
member: 300,
|
|
6
|
+
visitor: 100,
|
|
7
|
+
});
|
|
8
|
+
export function expectedRoleRank(role) {
|
|
9
|
+
if (role === 'owner')
|
|
10
|
+
return ROLE_RANKS.owner;
|
|
11
|
+
if (role === 'admin')
|
|
12
|
+
return ROLE_RANKS.admin;
|
|
13
|
+
if (role === 'member')
|
|
14
|
+
return ROLE_RANKS.member;
|
|
15
|
+
if (role === 'visitor')
|
|
16
|
+
return ROLE_RANKS.visitor;
|
|
17
|
+
return ROLE_RANKS.custom;
|
|
18
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
|
|
4
|
-
import {
|
|
4
|
+
import { agentRelationConfig, agentRelationsDir, agentRoleConfig, agentRolesIndex } from '../paths.js';
|
|
5
5
|
import { loadSchema } from './schema-registry.js';
|
|
6
6
|
import { clearRoleStoreCache, getStoredRoleRegistry, isRoleName, readRoleStore, roleRegistryExists, } from './role-store.js';
|
|
7
|
-
import { isManagementRole } from './builtin-roles.js';
|
|
7
|
+
import { expectedRoleRank, isManagementRole } from './builtin-roles.js';
|
|
8
|
+
import { readBuiltinRoleTemplate } from './builtin-role-templates.js';
|
|
8
9
|
import { validateRoleConfig } from './role-schema.js';
|
|
9
10
|
export class RoleServiceError extends Error {
|
|
10
11
|
code;
|
|
@@ -42,7 +43,7 @@ export function writeRoleRegistry(selfAid, value) {
|
|
|
42
43
|
export function writeRoleDefinition(selfAid, roleId, value) {
|
|
43
44
|
if (!isRoleName(roleId))
|
|
44
45
|
throw new RoleServiceError('INVALID_ROLE_ID', `Invalid role id: ${roleId}`);
|
|
45
|
-
const normalized = { ...value, $schema_version: 1 };
|
|
46
|
+
const normalized = { ...value, rank: expectedRoleRank(roleId), $schema_version: 1 };
|
|
46
47
|
validateRoleDefinition(normalized);
|
|
47
48
|
const registry = readRoleRegistry(selfAid);
|
|
48
49
|
if (!registry.roles.includes(roleId)) {
|
|
@@ -92,7 +93,7 @@ export function listRoleDiagnostics(selfAid) {
|
|
|
92
93
|
return readRoleStore(selfAid).diagnostics;
|
|
93
94
|
}
|
|
94
95
|
function writeRoleDefinitionFile(selfAid, roleId, value) {
|
|
95
|
-
const normalized = { ...value, $schema_version: 1 };
|
|
96
|
+
const normalized = { ...value, rank: expectedRoleRank(roleId), $schema_version: 1 };
|
|
96
97
|
validateRoleDefinition(normalized);
|
|
97
98
|
const file = agentRoleConfig(selfAid, roleId);
|
|
98
99
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -108,7 +109,7 @@ function validateRegistry(selfAid, value) {
|
|
|
108
109
|
throw new RoleServiceError('VISITOR_REQUIRED', 'visitor must remain registered');
|
|
109
110
|
for (const role of ['private', 'group']) {
|
|
110
111
|
const selected = registry.defaultRoles[role];
|
|
111
|
-
if (selected !== null && (
|
|
112
|
+
if (selected !== null && (selected === 'owner' || !registry.roles.includes(selected))) {
|
|
112
113
|
throw new RoleServiceError('INVALID_DEFAULT_ROLE', `defaultRoles.${role} must reference an assignable registered role`);
|
|
113
114
|
}
|
|
114
115
|
if (selected !== null && !hasValidRoleDefinition(selfAid, selected)) {
|
|
@@ -117,18 +118,14 @@ function validateRegistry(selfAid, value) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
function hasValidRoleDefinition(selfAid, roleId) {
|
|
120
|
-
|
|
121
|
-
agentRoleConfig(selfAid, roleId)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const value = atomicReadJson(file);
|
|
126
|
-
if (value !== null && validateRoleConfig(value).length === 0)
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
catch { }
|
|
121
|
+
try {
|
|
122
|
+
const value = atomicReadJson(agentRoleConfig(selfAid, roleId));
|
|
123
|
+
if (value !== null && validateRoleConfig(value).length === 0
|
|
124
|
+
&& (value.rank === undefined || value.rank === expectedRoleRank(roleId)))
|
|
125
|
+
return true;
|
|
130
126
|
}
|
|
131
|
-
|
|
127
|
+
catch { }
|
|
128
|
+
return !!readBuiltinRoleTemplate(roleId).definition;
|
|
132
129
|
}
|
|
133
130
|
function validateRoleDefinition(value) {
|
|
134
131
|
const issues = validateRoleConfig(value);
|
|
@@ -142,13 +139,13 @@ function findRoleReferences(selfAid, roleId, registry) {
|
|
|
142
139
|
references.push('defaultRoles.private');
|
|
143
140
|
if (registry.defaultRoles.group === roleId)
|
|
144
141
|
references.push('defaultRoles.group');
|
|
145
|
-
const relations =
|
|
142
|
+
const relations = agentRelationsDir(selfAid);
|
|
146
143
|
if (!fs.existsSync(relations))
|
|
147
144
|
return references;
|
|
148
145
|
for (const entry of fs.readdirSync(relations, { withFileTypes: true })) {
|
|
149
|
-
if (!entry.isDirectory())
|
|
146
|
+
if (!entry.isDirectory() || entry.name.startsWith('_'))
|
|
150
147
|
continue;
|
|
151
|
-
const config = atomicReadJson(
|
|
148
|
+
const config = atomicReadJson(agentRelationConfig(selfAid, entry.name));
|
|
152
149
|
if (config?.role === roleId)
|
|
153
150
|
references.push(`relations/${entry.name}/role`);
|
|
154
151
|
}
|
|
@@ -2,12 +2,11 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { atomicReadJson } from '../utils/atomic-write.js';
|
|
4
4
|
import { agentRoleConfig, agentRolesDir, agentRolesIndex, kitsRoleTemplatesDir, resolvePaths, } from '../paths.js';
|
|
5
|
-
import { applyManagementRoleCeiling, getManagementRoleDefinition } from './builtin-roles.js';
|
|
5
|
+
import { applyManagementRoleCeiling, expectedRoleRank, getManagementRoleDefinition } from './builtin-roles.js';
|
|
6
6
|
import { readBuiltinRoleTemplate } from './builtin-role-templates.js';
|
|
7
7
|
import { loadSchema } from './schema-registry.js';
|
|
8
8
|
import { validateRoleConfig } from './role-schema.js';
|
|
9
9
|
const ROLE_NAME_RE = /^[a-z0-9_-]+$/;
|
|
10
|
-
const RESERVED_ROLES = new Set(['owner', 'admin']);
|
|
11
10
|
const BUILTIN_ROLES = ['owner', 'admin', 'member', 'visitor'];
|
|
12
11
|
const cache = new Map();
|
|
13
12
|
export function clearRoleStoreCache(selfAid) {
|
|
@@ -79,9 +78,11 @@ export function getStoredRoleRegistry(selfAid) {
|
|
|
79
78
|
return readRoleStore(selfAid).registry;
|
|
80
79
|
}
|
|
81
80
|
export function roleIsAssignable(selfAid, roleId) {
|
|
82
|
-
if (!roleId || !isRoleName(roleId) ||
|
|
81
|
+
if (!roleId || !isRoleName(roleId) || roleId === 'owner')
|
|
83
82
|
return false;
|
|
84
83
|
const snapshot = readRoleStore(selfAid);
|
|
84
|
+
if (roleId === 'admin')
|
|
85
|
+
return snapshot.registry.roles.includes(roleId) && !!getStoredRoleDefinition(selfAid, roleId);
|
|
85
86
|
return snapshot.registry.roles.includes(roleId) && !!snapshot.definitions[roleId];
|
|
86
87
|
}
|
|
87
88
|
function readRegistry(selfAid, diagnostics) {
|
|
@@ -106,7 +107,7 @@ function readRegistry(selfAid, diagnostics) {
|
|
|
106
107
|
const defaultRoles = { ...value.defaultRoles };
|
|
107
108
|
for (const scene of ['private', 'group']) {
|
|
108
109
|
const selected = defaultRoles[scene];
|
|
109
|
-
if (selected === null || (roles.includes(selected) &&
|
|
110
|
+
if (selected === null || (roles.includes(selected) && selected !== 'owner'))
|
|
110
111
|
continue;
|
|
111
112
|
diagnostics.push({
|
|
112
113
|
code: 'INVALID_ROLE_ASSIGNMENT',
|
|
@@ -156,7 +157,17 @@ function readDefinition(selfAid, roleId, diagnostics) {
|
|
|
156
157
|
const issues = validateRoleConfig(raw);
|
|
157
158
|
if (issues.length === 0) {
|
|
158
159
|
const { $schema_version: _schemaVersion, ...definition } = raw;
|
|
159
|
-
|
|
160
|
+
const expectedRank = expectedRoleRank(roleId);
|
|
161
|
+
if (definition.rank !== undefined && definition.rank !== expectedRank) {
|
|
162
|
+
diagnostics.push({
|
|
163
|
+
code: 'INVALID_ROLE_FILE',
|
|
164
|
+
roleId,
|
|
165
|
+
file: path.relative(resolvePaths().root, file),
|
|
166
|
+
message: `Role definition ${roleId} rank must be ${expectedRank}`,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
return { ...definition, rank: expectedRank };
|
|
160
171
|
}
|
|
161
172
|
diagnostics.push({
|
|
162
173
|
code: file === local ? 'INVALID_ROLE_FILE' : 'INVALID_ROLE_FILE',
|
package/dist/config/roles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBuiltinRolesConfig, getManagementRoleDefinition, isManagementRole, isReservedRoleName, } from './builtin-roles.js';
|
|
1
|
+
import { getBuiltinRolesConfig, getManagementRoleDefinition, expectedRoleRank, isManagementRole, isReservedRoleName, } from './builtin-roles.js';
|
|
2
2
|
import { clearRoleStoreCache, getStoredRoleDefinition, readRoleStore, roleIsAssignable, } from './role-store.js';
|
|
3
3
|
const ROLE_NAME_RE = /^[a-z0-9_-]+$/;
|
|
4
4
|
export { getBuiltinRolesConfig };
|
|
@@ -51,5 +51,14 @@ export function normalizeDefaultRole(value, selfAid) {
|
|
|
51
51
|
return null;
|
|
52
52
|
if (typeof value !== 'string')
|
|
53
53
|
return null;
|
|
54
|
+
if (value === 'owner')
|
|
55
|
+
return null;
|
|
56
|
+
if (value === 'admin')
|
|
57
|
+
return getRoleDefinition(value, selfAid) ? value : null;
|
|
54
58
|
return roleExists(value, selfAid) ? value : null;
|
|
55
59
|
}
|
|
60
|
+
export function getRoleRank(role, selfAid) {
|
|
61
|
+
if (!role)
|
|
62
|
+
return 0;
|
|
63
|
+
return getRoleDefinition(role, selfAid)?.rank ?? expectedRoleRank(role);
|
|
64
|
+
}
|
package/dist/config-store.js
CHANGED
|
@@ -478,7 +478,6 @@ export async function migrateProject(oldPath, newPath) {
|
|
|
478
478
|
const active = readJsonFile(activePath);
|
|
479
479
|
if (active && active.projectPath === oldAbs) {
|
|
480
480
|
active.projectPath = newAbs;
|
|
481
|
-
active.updatedAt = Date.now();
|
|
482
481
|
atomicWriteJson(activePath, active);
|
|
483
482
|
updated++;
|
|
484
483
|
}
|
|
@@ -489,7 +488,6 @@ export async function migrateProject(oldPath, newPath) {
|
|
|
489
488
|
const meta = readLastJsonlLine(metaPath);
|
|
490
489
|
if (meta && meta.projectPath === oldAbs) {
|
|
491
490
|
meta.projectPath = newAbs;
|
|
492
|
-
meta.updatedAt = Date.now();
|
|
493
491
|
appendJsonl(metaPath, meta);
|
|
494
492
|
updated++;
|
|
495
493
|
}
|
|
@@ -47,7 +47,16 @@ function redactIdentifier(value) {
|
|
|
47
47
|
return crypto.createHash('sha256').update(value).digest('hex').slice(0, 12);
|
|
48
48
|
}
|
|
49
49
|
function logAuditEvent(record) {
|
|
50
|
-
|
|
50
|
+
// A role-management snapshot fans out into several successful read checks.
|
|
51
|
+
// Keep those available for verbose ECWeb diagnostics without flooding INFO.
|
|
52
|
+
const routineEcwebRoleRead = record.source === 'ecweb'
|
|
53
|
+
&& !record.dangerous
|
|
54
|
+
&& (record.operation === 'role.policy.read' || record.operation === 'role.relation.read');
|
|
55
|
+
const level = record.decision === 'deny'
|
|
56
|
+
? 'warn'
|
|
57
|
+
: routineEcwebRoleRead
|
|
58
|
+
? 'debug'
|
|
59
|
+
: 'info';
|
|
51
60
|
const marker = record.decision === 'deny' ? 'DENY' : record.dangerous ? 'DANGEROUS_ALLOW' : 'ALLOW';
|
|
52
61
|
const message = [
|
|
53
62
|
`[CommandAudit:${marker}]`,
|
|
@@ -304,6 +304,62 @@ const OPERATIONS = [
|
|
|
304
304
|
description: 'Modify role definitions, command permissions, default roles, or usage limits',
|
|
305
305
|
sources: ['menu', 'ecweb', 'control'],
|
|
306
306
|
},
|
|
307
|
+
{
|
|
308
|
+
id: 'contact.read',
|
|
309
|
+
category: 'read',
|
|
310
|
+
dangerous: false,
|
|
311
|
+
defaultScopes: ['agent'],
|
|
312
|
+
description: 'Read the Agent contact block list',
|
|
313
|
+
sources: ['agent-tool'],
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: 'contact.block',
|
|
317
|
+
category: 'write-agent',
|
|
318
|
+
dangerous: false,
|
|
319
|
+
defaultScopes: ['agent'],
|
|
320
|
+
description: 'Block or unblock an AUN contact',
|
|
321
|
+
sources: ['agent-tool'],
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: 'connect.read',
|
|
325
|
+
category: 'read',
|
|
326
|
+
dangerous: false,
|
|
327
|
+
defaultScopes: ['agent'],
|
|
328
|
+
description: 'Read contacts, groups, assignments, or contact details',
|
|
329
|
+
sources: ['menu', 'ecweb', 'control'],
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 'connect.assign',
|
|
333
|
+
category: 'write-agent',
|
|
334
|
+
dangerous: false,
|
|
335
|
+
defaultScopes: ['agent'],
|
|
336
|
+
description: 'Assign a role to a relation',
|
|
337
|
+
sources: ['menu', 'ecweb', 'control'],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: 'connect.revoke',
|
|
341
|
+
category: 'write-agent',
|
|
342
|
+
dangerous: false,
|
|
343
|
+
defaultScopes: ['agent'],
|
|
344
|
+
description: 'Revoke a role assignment from a relation',
|
|
345
|
+
sources: ['menu', 'ecweb', 'control'],
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: 'connect.admin',
|
|
349
|
+
category: 'write-agent',
|
|
350
|
+
dangerous: false,
|
|
351
|
+
defaultScopes: ['agent'],
|
|
352
|
+
description: 'Grant or revoke admin status',
|
|
353
|
+
sources: ['menu', 'ecweb', 'control'],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: 'connect.write',
|
|
357
|
+
category: 'write-agent',
|
|
358
|
+
dangerous: false,
|
|
359
|
+
defaultScopes: ['agent'],
|
|
360
|
+
description: 'Add contacts or modify contact status (block/unblock)',
|
|
361
|
+
sources: ['menu', 'ecweb', 'control'],
|
|
362
|
+
},
|
|
307
363
|
// Agent Operations
|
|
308
364
|
{
|
|
309
365
|
id: 'agent.list',
|