evolcore 0.0.24 → 0.0.26
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 +59 -0
- package/bin/ec.js +12 -1
- package/bin/install-codex-managed-hooks.mjs +28 -1
- package/dist/agents/baseagent.js +96 -46
- package/dist/agents/claude-runner.js +54 -13
- package/dist/agents/codex/app-server-client.js +13 -5
- package/dist/agents/codex/catalog.js +1 -1
- package/dist/agents/codex/diagnostics.js +34 -1
- package/dist/agents/codex-runner.js +128 -42
- package/dist/agents/ecagent-runner.js +14 -18
- package/dist/agents/gemini-runner.js +24 -9
- package/dist/agents/runner-types.js +25 -17
- package/dist/aun/aid/agentmd.js +135 -10
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/managed-operation.js +53 -1
- package/dist/aun/aid/store.js +17 -13
- package/dist/aun/menu-client.js +74 -0
- package/dist/aun/msg/group.js +1 -1
- package/dist/aun/msg/managed-operation.js +1 -1
- package/dist/aun/msg/p2p.js +6 -1
- package/dist/aun/msg/upload.js +30 -22
- package/dist/aun/storage/client.js +4 -1
- package/dist/aun/storage/index.js +1 -1
- package/dist/channels/aun.js +329 -94
- package/dist/cli/agent-command.js +125 -5
- package/dist/cli/agent-create.js +151 -11
- package/dist/cli/agent.js +119 -6
- package/dist/cli/aun-commands.js +74 -7
- package/dist/cli/config.js +27 -8
- package/dist/cli/contact.js +63 -6
- package/dist/cli/daemon-commands.js +90 -25
- package/dist/cli/diag.js +31 -0
- package/dist/cli/handoff-command.js +35 -3
- package/dist/cli/index.js +7 -1
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +38 -23
- package/dist/cli/restart-monitor.js +57 -10
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/config-manager.js +15 -0
- package/dist/config/config-operation-service.js +165 -3
- package/dist/config/contact-operation-service.js +96 -7
- package/dist/config/resolved-config-op.js +66 -8
- package/dist/core/agent-application-service.js +2 -1
- package/dist/core/auth/authorization-audit.js +20 -12
- package/dist/core/auth/operation-authorizer.js +87 -8
- package/dist/core/auth/operation-catalog.js +51 -4
- package/dist/core/auth/remote-access-store.js +40 -1
- package/dist/core/bootstrap-service.js +1 -1
- package/dist/core/command/agent-control.js +54 -25
- package/dist/core/command/cli-intent-parser.js +18 -0
- package/dist/core/command/command-handler.js +152 -20
- package/dist/core/command/diag-service.js +118 -0
- package/dist/core/command/menu-catalog.js +13 -3
- package/dist/core/command/menu-handler.js +88 -31
- package/dist/core/command/menu-protocol.js +6 -0
- package/dist/core/command/menu-token-store.js +41 -8
- package/dist/core/command/slash-gate.js +34 -4
- package/dist/core/command/slash-handler.js +77 -12
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +3 -3
- package/dist/core/handoff/runtime.js +205 -27
- package/dist/core/handoff/store.js +193 -4
- package/dist/core/message/im-renderer.js +72 -21
- package/dist/core/message/message-bridge.js +69 -9
- package/dist/core/message/message-queue.js +26 -0
- package/dist/core/message/message-utils.js +1 -0
- package/dist/core/message/response-engine.js +48 -23
- package/dist/core/model/catalog-service.js +79 -49
- package/dist/core/model/context-window-suffix.js +33 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-permission.js +12 -3
- package/dist/core/permission/ec-command-parser.js +51 -12
- package/dist/core/permission/sandbox-runtime.js +36 -14
- package/dist/core/permission/tool-policy.js +47 -7
- package/dist/index.js +195 -31
- package/dist/ipc.js +54 -2
- package/dist/response-system/engines/v1/proactive-flow.js +59 -2
- package/dist/stats/billing.js +41 -15
- package/dist/stats/price-resolver.js +28 -10
- package/dist/utils/avatar-upload.js +282 -23
- package/dist/utils/cross-platform.js +30 -1
- package/dist/utils/error-utils.js +21 -0
- package/dist/utils/evolcore-version.js +129 -0
- package/dist/utils/logger.js +3 -1
- package/dist/utils/model-prices.jsonl +3 -3
- package/dist/utils/npm-ops.js +176 -35
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolcore/INDEX.md +3 -1
- package/kits/docs/evolcore/agent-create.md +30 -11
- package/kits/docs/evolcore/agent.md +18 -8
- package/kits/docs/evolcore/aid.md +6 -4
- package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
- package/kits/docs/evolcore/config.md +7 -0
- package/kits/docs/evolcore/contact.md +12 -5
- package/kits/docs/evolcore/menu-client.md +11 -3
- package/kits/docs/evolcore/msg.md +1 -0
- package/kits/docs/prompt-loading-architecture.md +1 -0
- package/kits/docs/venues/group.md +12 -0
- package/kits/eck_manifest.json +12 -0
- package/kits/rules/03-identity.md +3 -1
- package/kits/templates/message-fragments/handoff-request-to-target.md +3 -3
- package/kits/templates/roles/admin.json +14 -2
- package/kits/templates/roles/member.json +3 -2
- package/kits/templates/roles/visitor.json +4 -3
- package/kits/templates/system-fragments/bootstrap.md +16 -5
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +6 -4
- package/skills/eclink/SKILL.md +0 -84
- package/skills/eclink/agents/openai.yaml +0 -4
|
@@ -5,6 +5,8 @@ import { agentConfig } from '../paths.js';
|
|
|
5
5
|
import { getContactSnapshot, resolveContactView } from './contact-book.js';
|
|
6
6
|
import { ContactMutationError } from './contact-book-store.js';
|
|
7
7
|
import { addContact, mutateContactWithOperation } from './contact-request-service.js';
|
|
8
|
+
import { expirePendingContactRequests, reviewContactRequest } from './contact-request-service.js';
|
|
9
|
+
import { listStaticAgentOwners } from './peer-role-resolver.js';
|
|
8
10
|
export function resolveContactCommand(argv) {
|
|
9
11
|
const args = argv[0] === 'contact' ? argv.slice(1) : [...argv];
|
|
10
12
|
const selfAid = String(argValue(args, '--self') || '').trim();
|
|
@@ -15,12 +17,41 @@ export function resolveContactCommand(argv) {
|
|
|
15
17
|
if (extra.length > 0)
|
|
16
18
|
return invalid('INVALID_ARGUMENT', `Unexpected contact arguments: ${extra.join(' ')}`);
|
|
17
19
|
if (command === 'list') {
|
|
18
|
-
if (subcommand !== undefined
|
|
19
|
-
return invalid('INVALID_ARGUMENT', '
|
|
20
|
+
if (subcommand !== undefined) {
|
|
21
|
+
return invalid('INVALID_ARGUMENT', 'contact list does not accept a positional argument');
|
|
20
22
|
}
|
|
23
|
+
const filter = args.includes('--blocked')
|
|
24
|
+
? 'blocked'
|
|
25
|
+
: args.includes('--pending')
|
|
26
|
+
? 'pending'
|
|
27
|
+
: args.includes('--all') ? 'all' : undefined;
|
|
28
|
+
if (!filter)
|
|
29
|
+
return invalid('INVALID_ARGUMENT', 'contact list requires --blocked, --pending, or --all');
|
|
21
30
|
return {
|
|
22
31
|
ok: true,
|
|
23
|
-
command: { kind: 'list-blocked', operationId: 'contact.read', selfAid, canonicalArgv: canonicalArgv(argv) },
|
|
32
|
+
command: { kind: 'list-blocked', operationId: 'contact.read', selfAid, filter, canonicalArgv: canonicalArgv(argv) },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (command === 'approve' || command === 'reject' || command === 'block-request') {
|
|
36
|
+
if (!isValidAid(subcommand || '') || isExplicitGroupId(subcommand || '')) {
|
|
37
|
+
return invalid('INVALID_AID', 'primaryId must be a valid individual AID');
|
|
38
|
+
}
|
|
39
|
+
const requestId = String(argValue(args, '--request-id') || '').trim();
|
|
40
|
+
if (!requestId)
|
|
41
|
+
return invalid('INVALID_ARGUMENT', `${command} requires --request-id <request-id>`);
|
|
42
|
+
const expectedContactRevision = argValue(args, '--contact-revision')?.trim() || undefined;
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
command: {
|
|
46
|
+
kind: 'review-request',
|
|
47
|
+
operationId: 'contact.review',
|
|
48
|
+
selfAid,
|
|
49
|
+
primaryId: subcommand,
|
|
50
|
+
requestId,
|
|
51
|
+
decision: command === 'approve' ? 'approve' : command === 'reject' ? 'reject' : 'block',
|
|
52
|
+
expectedContactRevision,
|
|
53
|
+
canonicalArgv: canonicalArgv(argv),
|
|
54
|
+
},
|
|
24
55
|
};
|
|
25
56
|
}
|
|
26
57
|
if (command === 'block' || command === 'unblock') {
|
|
@@ -62,13 +93,63 @@ export async function executeResolvedContactCommand(command, actor = 'local-cli'
|
|
|
62
93
|
}
|
|
63
94
|
try {
|
|
64
95
|
if (command.kind === 'list-blocked') {
|
|
96
|
+
await expirePendingContactRequests(command.selfAid);
|
|
65
97
|
const snapshot = getContactSnapshot(command.selfAid);
|
|
98
|
+
const ids = command.filter === 'blocked'
|
|
99
|
+
? [...snapshot.blockedIndex]
|
|
100
|
+
: [...snapshot.contacts.entries()]
|
|
101
|
+
.filter(([, entry]) => command.filter === 'all' || entry.status === 'pending')
|
|
102
|
+
.map(([id]) => id);
|
|
103
|
+
const items = ids.sort().map(id => contactItem(command.selfAid, id));
|
|
104
|
+
if (command.filter === 'blocked') {
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
operation: 'list-blocked',
|
|
108
|
+
selfAid: command.selfAid,
|
|
109
|
+
contactRevision: snapshot.contactRevision,
|
|
110
|
+
items,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
66
113
|
return {
|
|
67
114
|
ok: true,
|
|
68
|
-
operation: 'list-
|
|
115
|
+
operation: 'list-contacts',
|
|
69
116
|
selfAid: command.selfAid,
|
|
117
|
+
filter: command.filter,
|
|
70
118
|
contactRevision: snapshot.contactRevision,
|
|
71
|
-
items
|
|
119
|
+
items,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (command.kind === 'review-request') {
|
|
123
|
+
const expectedContactRevision = command.expectedContactRevision ?? getContactSnapshot(command.selfAid).contactRevision;
|
|
124
|
+
const reviewerAid = actor === 'local-cli'
|
|
125
|
+
? listStaticAgentOwners(command.selfAid)[0]
|
|
126
|
+
: actor;
|
|
127
|
+
if (!reviewerAid) {
|
|
128
|
+
return { ok: false, code: 'NO_OWNER', error: `Agent has no owner: ${command.selfAid}` };
|
|
129
|
+
}
|
|
130
|
+
const reviewed = await reviewContactRequest({
|
|
131
|
+
selfAid: command.selfAid,
|
|
132
|
+
primaryId: command.primaryId,
|
|
133
|
+
requestId: command.requestId,
|
|
134
|
+
expectedContactRevision,
|
|
135
|
+
approverId: reviewerAid,
|
|
136
|
+
actorId: reviewerAid,
|
|
137
|
+
decision: command.decision,
|
|
138
|
+
});
|
|
139
|
+
if (reviewed.code !== 'approved' && reviewed.code !== 'rejected' && reviewed.code !== 'blocked') {
|
|
140
|
+
const code = reviewed.code === 'request-expired' ? 'REQUEST_EXPIRED'
|
|
141
|
+
: reviewed.code === 'revision-conflict' ? 'CONFLICT'
|
|
142
|
+
: reviewed.code === 'forbidden' ? 'PERMISSION_DENIED' : 'REQUEST_STALE';
|
|
143
|
+
return { ok: false, code, error: reviewed.code, data: { contactRevision: reviewed.contactRevision } };
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
operation: 'review-request',
|
|
148
|
+
selfAid: command.selfAid,
|
|
149
|
+
primaryId: command.primaryId,
|
|
150
|
+
requestId: command.requestId,
|
|
151
|
+
result: reviewed.code,
|
|
152
|
+
contactRevision: reviewed.contactRevision,
|
|
72
153
|
};
|
|
73
154
|
}
|
|
74
155
|
if (command.kind === 'set-blocked') {
|
|
@@ -129,16 +210,24 @@ function stripOptions(args) {
|
|
|
129
210
|
const result = [];
|
|
130
211
|
for (let index = 0; index < args.length; index += 1) {
|
|
131
212
|
const arg = args[index];
|
|
132
|
-
if (arg === '--self' || arg === '--format') {
|
|
213
|
+
if (arg === '--self' || arg === '--format' || arg === '--request-id' || arg === '--contact-revision') {
|
|
133
214
|
index += 1;
|
|
134
215
|
continue;
|
|
135
216
|
}
|
|
136
|
-
if (arg === '--blocked')
|
|
217
|
+
if (arg === '--blocked' || arg === '--pending' || arg === '--all')
|
|
137
218
|
continue;
|
|
138
219
|
result.push(arg);
|
|
139
220
|
}
|
|
140
221
|
return result;
|
|
141
222
|
}
|
|
223
|
+
function contactItem(selfAid, primaryId) {
|
|
224
|
+
const view = resolveContactView(selfAid, primaryId);
|
|
225
|
+
const entry = getContactSnapshot(selfAid).contacts.get(primaryId);
|
|
226
|
+
return {
|
|
227
|
+
...view,
|
|
228
|
+
...(entry?.pendingRequest ? { pendingRequest: { ...entry.pendingRequest } } : {}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
142
231
|
function canonicalArgv(argv) {
|
|
143
232
|
return argv[0] === 'contact' ? [...argv] : ['contact', ...argv];
|
|
144
233
|
}
|
|
@@ -2,6 +2,7 @@ import { normalizeCliArgv } from '../cli/cli-argv.js';
|
|
|
2
2
|
import { parseConfigSelector } from '../cli/config-selector.js';
|
|
3
3
|
import { parseConfigFieldValue, resolveConfigFieldRule, } from './config-field-policy.js';
|
|
4
4
|
import { ConfigError, routeFieldPath } from './config-manager.js';
|
|
5
|
+
import { isValidAid } from '../aun/aid/validation.js';
|
|
5
6
|
const SELECTOR_OPTIONS = {
|
|
6
7
|
'--self': 'value',
|
|
7
8
|
'--peer': 'value',
|
|
@@ -96,11 +97,12 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
96
97
|
reason: 'permissionMode is defined by roles; use the role policy editor',
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
const isRelationRole = field === 'role';
|
|
101
|
+
if (isRelationRole && selector.scope !== 'relation') {
|
|
100
102
|
return {
|
|
101
103
|
ok: false,
|
|
102
|
-
code: '
|
|
103
|
-
reason:
|
|
104
|
+
code: 'RELATION_SELECTOR_REQUIRED',
|
|
105
|
+
reason: `config ${subcommand} role needs a relation target: --self <aid> --peer <peer-aid>`,
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
if (subcommand !== 'get' && (field === 'roles' || field.startsWith('roles.'))) {
|
|
@@ -128,12 +130,14 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
128
130
|
reason: `Config field ${field} does not support object writes`,
|
|
129
131
|
};
|
|
130
132
|
}
|
|
131
|
-
const commandScope = selector.scope === 'defaults' ? 'process' : selector.scope;
|
|
133
|
+
const commandScope = isRelationRole ? 'agent' : selector.scope === 'defaults' ? 'process' : selector.scope;
|
|
132
134
|
const isSafe = fieldRule.class === 'safe-scalar' || fieldRule.class === 'safe-readonly-object';
|
|
133
135
|
const isManagementOperation = commandScope === 'process' || !isSafe;
|
|
134
|
-
const operationId =
|
|
135
|
-
? subcommand === 'get' ? '
|
|
136
|
-
:
|
|
136
|
+
const operationId = isRelationRole
|
|
137
|
+
? subcommand === 'get' ? 'role.relation.read' : subcommand === 'set' ? 'role.assign' : 'role.revoke'
|
|
138
|
+
: isManagementOperation
|
|
139
|
+
? subcommand === 'get' ? 'config.read' : 'config.write'
|
|
140
|
+
: `config.${subcommand}`;
|
|
137
141
|
let route;
|
|
138
142
|
const routeRequired = subcommand !== 'get' || !isManagementOperation;
|
|
139
143
|
try {
|
|
@@ -166,7 +170,30 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
166
170
|
const rawValue = subcommand === 'set' ? business.values[1] : undefined;
|
|
167
171
|
let value;
|
|
168
172
|
if (subcommand === 'set') {
|
|
169
|
-
if (
|
|
173
|
+
if (field === 'owners') {
|
|
174
|
+
const parsedOwners = parseOwnersValue(rawValue);
|
|
175
|
+
if (!parsedOwners.ok)
|
|
176
|
+
return parsedOwners;
|
|
177
|
+
value = parsedOwners.value;
|
|
178
|
+
}
|
|
179
|
+
else if (isRelationRole) {
|
|
180
|
+
const raw = rawValue.trim();
|
|
181
|
+
if (!raw)
|
|
182
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'Config field role cannot be empty' };
|
|
183
|
+
try {
|
|
184
|
+
const parsed = JSON.parse(raw);
|
|
185
|
+
if (typeof parsed === 'string')
|
|
186
|
+
value = parsed;
|
|
187
|
+
else if (parsed === null)
|
|
188
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'config set role requires a role ID; use config unset role to revoke it' };
|
|
189
|
+
else
|
|
190
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'Config field role must be a role ID or null' };
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
value = raw;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (fieldRule.class === 'safe-scalar') {
|
|
170
197
|
const parsedValue = parseConfigFieldValue(field, rawValue);
|
|
171
198
|
if (!parsedValue.ok)
|
|
172
199
|
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: parsedValue.reason };
|
|
@@ -486,6 +513,37 @@ function coerceConfigValue(raw, route) {
|
|
|
486
513
|
return Number(raw);
|
|
487
514
|
return raw;
|
|
488
515
|
}
|
|
516
|
+
function parseOwnersValue(raw) {
|
|
517
|
+
let values;
|
|
518
|
+
const trimmed = raw.trim();
|
|
519
|
+
if (trimmed.startsWith('[')) {
|
|
520
|
+
try {
|
|
521
|
+
values = JSON.parse(trimmed);
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'owners must be a valid JSON array of AIDs' };
|
|
525
|
+
}
|
|
526
|
+
if (!Array.isArray(values)) {
|
|
527
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'owners must be a JSON array of AIDs' };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
values = [raw];
|
|
532
|
+
}
|
|
533
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
534
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: 'owners must contain at least one AID' };
|
|
535
|
+
}
|
|
536
|
+
const owners = [];
|
|
537
|
+
for (const value of values) {
|
|
538
|
+
if (typeof value !== 'string' || !value.trim() || !isValidAid(value.trim())) {
|
|
539
|
+
return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: `Invalid owner AID: ${JSON.stringify(value)}` };
|
|
540
|
+
}
|
|
541
|
+
const owner = value.trim();
|
|
542
|
+
if (!owners.includes(owner))
|
|
543
|
+
owners.push(owner);
|
|
544
|
+
}
|
|
545
|
+
return { ok: true, value: owners };
|
|
546
|
+
}
|
|
489
547
|
function configErrorResult(error) {
|
|
490
548
|
if (error instanceof ConfigError)
|
|
491
549
|
return { ok: false, code: error.code, reason: error.message };
|
|
@@ -85,13 +85,14 @@ export class AgentApplicationService {
|
|
|
85
85
|
unique_peer_count: stats?.uniquePeerCount ?? 0,
|
|
86
86
|
}
|
|
87
87
|
: null;
|
|
88
|
-
const config = agent.config;
|
|
88
|
+
const config = (loadAgent(aid) ?? agent.config);
|
|
89
89
|
const identity = readAgentMdIdentity(aid);
|
|
90
90
|
const paths = resolvePaths();
|
|
91
91
|
return {
|
|
92
92
|
ok: true,
|
|
93
93
|
aid,
|
|
94
94
|
status: info.status || 'stopped',
|
|
95
|
+
lifecycle: config?.lifecycle ?? null,
|
|
95
96
|
identity,
|
|
96
97
|
config: {
|
|
97
98
|
baseagent: info.baseagent || null,
|
|
@@ -333,23 +333,31 @@ function buildAuditRecord(event) {
|
|
|
333
333
|
: executed
|
|
334
334
|
? (event.exitCode === undefined || event.exitCode === 0 ? 'completed' : 'failed')
|
|
335
335
|
: 'authorized');
|
|
336
|
-
const
|
|
336
|
+
const ecwebRequestId = event.source === 'ecweb' ? event.ecwebRequestId : undefined;
|
|
337
|
+
const requestId = event.requestId ?? ecwebRequestId;
|
|
338
|
+
const correlationId = event.correlationId ?? requestId;
|
|
339
|
+
const lifecycleId = event.callId ?? correlationId ?? requestId;
|
|
337
340
|
return {
|
|
338
341
|
ts: event.ts,
|
|
339
|
-
eventKey: event.eventKey ?? (
|
|
340
|
-
?
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
342
|
+
eventKey: event.eventKey ?? (ecwebRequestId
|
|
343
|
+
? `ecweb:${crypto.createHash('sha256').update(JSON.stringify([
|
|
344
|
+
ecwebRequestId, event.targetAgentAid ?? event.selfAid, event.operation,
|
|
345
|
+
])).digest('hex').slice(0, 20)}`
|
|
346
|
+
: lifecycleId
|
|
347
|
+
? buildAuthorizationEventKey({
|
|
348
|
+
sessionId: event.sessionId,
|
|
349
|
+
callId: event.callId,
|
|
350
|
+
correlationId,
|
|
351
|
+
requestId,
|
|
352
|
+
operation: event.operation,
|
|
353
|
+
})
|
|
354
|
+
: undefined),
|
|
348
355
|
eventPhase: event.eventPhase,
|
|
349
356
|
callId: event.callId,
|
|
350
|
-
correlationId
|
|
351
|
-
requestId
|
|
357
|
+
correlationId,
|
|
358
|
+
requestId,
|
|
352
359
|
sessionId: event.sessionId,
|
|
360
|
+
...(event.source === 'ecweb' && !event.sessionId ? { sessionScope: 'not_applicable' } : {}),
|
|
353
361
|
agentAid: redactIdentifier(event.agentAid ?? event.selfAid),
|
|
354
362
|
agentName: resolveAgentName(event),
|
|
355
363
|
permissionMode: normalizeAuditPermissionMode(event.permissionMode),
|
|
@@ -13,6 +13,7 @@ const DAEMON_OWNER_PROCESS_OPERATIONS = new Set([
|
|
|
13
13
|
'agent.disable',
|
|
14
14
|
'system.restart',
|
|
15
15
|
'system.upgrade',
|
|
16
|
+
'system.paircode',
|
|
16
17
|
'gateway.read',
|
|
17
18
|
'gateway.write',
|
|
18
19
|
'stats.peers',
|
|
@@ -192,7 +193,8 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
192
193
|
};
|
|
193
194
|
}
|
|
194
195
|
// Any authenticated role may inspect the current Agent through the daemon's
|
|
195
|
-
// redacted view.
|
|
196
|
+
// redacted view. Management roles may inspect another Agent's redacted view;
|
|
197
|
+
// mutation and reload remain target-scoped below.
|
|
196
198
|
if (operation === 'agent.show' && isCurrentAgentTarget(ctx)) {
|
|
197
199
|
return {
|
|
198
200
|
allow: true,
|
|
@@ -203,6 +205,25 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
203
205
|
matchedRule: 'self-agent-read',
|
|
204
206
|
};
|
|
205
207
|
}
|
|
208
|
+
// Cross-Agent reads are a management/control-plane capability. A custom
|
|
209
|
+
// role must not widen this boundary merely by granting itself agent.show.
|
|
210
|
+
if (operation === 'agent.show'
|
|
211
|
+
&& !isCurrentAgentTarget(ctx)
|
|
212
|
+
&& !isManagementRole(role)
|
|
213
|
+
&& !hasDaemonOwnerRole(ctx)) {
|
|
214
|
+
return denyDecision(ctx, 'NOT_ALLOWED', `${operation} may only target the current Agent from a managed Agent task.`, operation, intent.scope, opMeta.dangerous, undefined, 'TARGET_SELF_ONLY');
|
|
215
|
+
}
|
|
216
|
+
if (operation === 'agent.persona' && isCurrentAgentTarget(ctx)
|
|
217
|
+
&& (role === 'owner' || hasDaemonOwnerRole(ctx))) {
|
|
218
|
+
return {
|
|
219
|
+
allow: true,
|
|
220
|
+
operation,
|
|
221
|
+
scope: intent.scope,
|
|
222
|
+
role,
|
|
223
|
+
dangerous: opMeta.dangerous,
|
|
224
|
+
matchedRule: role === 'owner' ? 'agent-owner-persona' : 'daemon-owner-persona',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
206
227
|
if (resolvedConfigCommand) {
|
|
207
228
|
const targetCheck = checkResolvedConfigTarget(ctx, resolvedConfigCommand);
|
|
208
229
|
if (!targetCheck.ok) {
|
|
@@ -215,7 +236,7 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
215
236
|
if (!isManagementRole(role) && intent.scope !== 'relation') {
|
|
216
237
|
return denyDecision(ctx, 'SCOPE_MISMATCH', `Role ${role} may only access relation-scoped config`, operation, intent.scope, opMeta.dangerous);
|
|
217
238
|
}
|
|
218
|
-
if (role === 'owner') {
|
|
239
|
+
if (role === 'owner' && isCurrentAgentTarget(ctx)) {
|
|
219
240
|
return {
|
|
220
241
|
allow: true,
|
|
221
242
|
operation,
|
|
@@ -225,6 +246,22 @@ function authorizeCommandInternal(ctx, resolvedConfigCommand) {
|
|
|
225
246
|
matchedRule: 'agent-owner',
|
|
226
247
|
};
|
|
227
248
|
}
|
|
249
|
+
// The daemon control identity is allowed to inspect any Agent config,
|
|
250
|
+
// including sensitive fields. This is intentionally handled before
|
|
251
|
+
// role-definition matching because daemon-owner subjects do not need an
|
|
252
|
+
// Agent relation role to read the daemon's local configuration store.
|
|
253
|
+
if (resolvedConfigCommand.kind === 'field'
|
|
254
|
+
&& resolvedConfigCommand.subcommand === 'get'
|
|
255
|
+
&& hasDaemonOwnerRole(ctx)) {
|
|
256
|
+
return {
|
|
257
|
+
allow: true,
|
|
258
|
+
operation,
|
|
259
|
+
scope: intent.scope,
|
|
260
|
+
role: 'daemon-owner',
|
|
261
|
+
dangerous: opMeta.dangerous,
|
|
262
|
+
matchedRule: 'daemon-owner-config-read',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
228
265
|
}
|
|
229
266
|
const roleDef = getRoleDefinition(role, ctx.selfAid);
|
|
230
267
|
if (!roleDef) {
|
|
@@ -357,10 +394,25 @@ function requiresDaemonOwner(ctx, resolvedConfigCommand) {
|
|
|
357
394
|
|| resolvedConfigCommand.configScope === 'process'
|
|
358
395
|
|| resolvedConfigCommand.configScope === 'defaults'))
|
|
359
396
|
return true;
|
|
397
|
+
// Sensitive reads of another Agent's config are daemon-scoped. Agent
|
|
398
|
+
// owner/admin roles may inspect safe fields cross-Agent, but only the
|
|
399
|
+
// daemon control identity may access config.read targets that are not the
|
|
400
|
+
// current Agent.
|
|
401
|
+
if (operation === 'config.read'
|
|
402
|
+
&& resolvedConfigCommand?.kind === 'field'
|
|
403
|
+
&& resolvedConfigCommand.configScope === 'agent'
|
|
404
|
+
&& !isCurrentAgentTarget(ctx)) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
360
407
|
if (operation === 'agent.reload' && hasDaemonOwnerRole(ctx)) {
|
|
361
408
|
return true;
|
|
362
409
|
}
|
|
363
|
-
|
|
410
|
+
// Avatar publication is an Agent-scoped mutation for Agent channels, but
|
|
411
|
+
// the trusted control plane may publish it for a selected Agent target.
|
|
412
|
+
if (operation === 'ec.aid.avatar' && hasDaemonOwnerRole(ctx)) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
if (operation === 'agent.reload') {
|
|
364
416
|
return !isCurrentAgentTarget(ctx);
|
|
365
417
|
}
|
|
366
418
|
if (operation.startsWith('trigger.')) {
|
|
@@ -529,6 +581,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
|
|
|
529
581
|
return { ok: false, reason: 'Resolved config operation is required' };
|
|
530
582
|
const field = resolvedConfigOp.field;
|
|
531
583
|
if (!resolvedConfigOp.route || (resolvedConfigOp.fieldRule.class !== 'safe-scalar' && resolvedConfigOp.fieldRule.class !== 'safe-readonly-object')) {
|
|
584
|
+
if (policy === 'behavior-read'
|
|
585
|
+
&& !isCurrentAgentTarget(ctx)
|
|
586
|
+
&& hasDaemonOwnerRole(ctx)
|
|
587
|
+
&& resolvedConfigOp.route) {
|
|
588
|
+
return { ok: true };
|
|
589
|
+
}
|
|
532
590
|
return { ok: false, reason: `Config field ${field} is not available to user roles` };
|
|
533
591
|
}
|
|
534
592
|
if (policy === 'role-overridable-write'
|
|
@@ -541,8 +599,12 @@ function checkConfigFieldPolicy(ctx, policy, resolvedConfigOp) {
|
|
|
541
599
|
const fieldPermission = roleDef
|
|
542
600
|
? resolveRoleFieldPermission(roleDef.permissions || {}, field)
|
|
543
601
|
: undefined;
|
|
544
|
-
if (policy === 'behavior-read')
|
|
602
|
+
if (policy === 'behavior-read') {
|
|
603
|
+
// Only the daemon control identity may read sensitive config fields from
|
|
604
|
+
// another Agent. Agent owners retain unrestricted reads for their own
|
|
605
|
+
// target via the self-target fast path above.
|
|
545
606
|
return { ok: true };
|
|
607
|
+
}
|
|
546
608
|
if (fieldPermission && !fieldPermission.allowOverride) {
|
|
547
609
|
return { ok: false, reason: `Role ${ctx.role} cannot override ${field}` };
|
|
548
610
|
}
|
|
@@ -579,12 +641,19 @@ function checkResolvedConfigTarget(ctx, command) {
|
|
|
579
641
|
return { ok: true };
|
|
580
642
|
}
|
|
581
643
|
if (command.configScope === 'agent') {
|
|
644
|
+
// Management roles may read another Agent's non-sensitive config fields;
|
|
645
|
+
// sensitive fields are rejected by the field policy. Writes stay self-only.
|
|
646
|
+
if (command.subcommand === 'get' && (isManagementRole(ctx.role) || hasDaemonOwnerRole(ctx))) {
|
|
647
|
+
return { ok: true };
|
|
648
|
+
}
|
|
582
649
|
if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
|
|
583
650
|
return { ok: false, reason: 'Only the current agent can be targeted' };
|
|
584
651
|
}
|
|
585
652
|
return { ok: true };
|
|
586
653
|
}
|
|
587
|
-
if (ctx.allowExplicitRelationTarget
|
|
654
|
+
if (ctx.allowExplicitRelationTarget
|
|
655
|
+
&& (ctx.role === 'owner' || ctx.role === 'admin')
|
|
656
|
+
&& (command.kind === 'field' && command.operationId.startsWith('role.'))) {
|
|
588
657
|
if (!ctx.selfAid || !command.self || command.self !== ctx.selfAid) {
|
|
589
658
|
return { ok: false, reason: 'Only the current agent relation can be targeted' };
|
|
590
659
|
}
|
|
@@ -748,10 +817,10 @@ function checkOwnPeer(ctx) {
|
|
|
748
817
|
}
|
|
749
818
|
if (ctx.actorId) {
|
|
750
819
|
if (requestedPeerId && requestedPeerId !== ctx.actorId) {
|
|
751
|
-
return { ok: false, reason:
|
|
820
|
+
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId) };
|
|
752
821
|
}
|
|
753
822
|
if (requestedPeerKeyId && requestedPeerKeyId !== ctx.actorId) {
|
|
754
|
-
return { ok: false, reason:
|
|
823
|
+
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerKeyId) };
|
|
755
824
|
}
|
|
756
825
|
}
|
|
757
826
|
if (!expectedPeerKey)
|
|
@@ -769,10 +838,20 @@ function checkOwnPeer(ctx) {
|
|
|
769
838
|
candidateKeys.add(`${legacyChannelType}::${argPeer}`);
|
|
770
839
|
}
|
|
771
840
|
if (!candidateKeys.has(expectedPeerKey)) {
|
|
772
|
-
return { ok: false, reason:
|
|
841
|
+
return { ok: false, reason: ownPeerOnlyDenialReason(ctx, requestedPeerId ?? requestedPeerKeyId) };
|
|
773
842
|
}
|
|
774
843
|
return { ok: true };
|
|
775
844
|
}
|
|
845
|
+
function ownPeerOnlyDenialReason(ctx, requestedPeer) {
|
|
846
|
+
if (ctx.intent.operation !== 'ec.msg.send' && ctx.intent.operation !== 'ec.msg.file') {
|
|
847
|
+
return 'Only the actor peer can be targeted';
|
|
848
|
+
}
|
|
849
|
+
const currentPeer = ctx.actorId || peerIdFromArg(ctx.peerKey || '') || '当前私聊对端';
|
|
850
|
+
const target = requestedPeer || '指定目标';
|
|
851
|
+
return `当前托管会话只能联系当前私聊对端(${currentPeer}),不能联系第三方目标(${target})。`
|
|
852
|
+
+ ' 如果本次请求来自 handoff,请执行 ec handoff return <handoff-id> "<失败原因>"(已有命令)将失败原因反馈给原会话;'
|
|
853
|
+
+ '否则请让目标先直接发起会话,或由 Agent owner 为当前角色配置允许 relay/第三方外联后再发送。';
|
|
854
|
+
}
|
|
776
855
|
function peerIdFromArg(value) {
|
|
777
856
|
try {
|
|
778
857
|
return parsePeerKey(value).channelId;
|
|
@@ -100,6 +100,13 @@ const HANDOFF_WRITE_OPERATIONS = [{
|
|
|
100
100
|
defaultScopes: ['relation', 'agent'],
|
|
101
101
|
description: 'Return the current handoff result',
|
|
102
102
|
sources: ['agent-tool'],
|
|
103
|
+
}, {
|
|
104
|
+
id: 'ec.handoff.recover',
|
|
105
|
+
category: 'write-agent',
|
|
106
|
+
dangerous: true,
|
|
107
|
+
defaultScopes: ['agent'],
|
|
108
|
+
description: 'Recover an open handoff after its target session changed',
|
|
109
|
+
sources: ['agent-tool'],
|
|
103
110
|
}];
|
|
104
111
|
const QUEUE_OPERATIONS = [['read', 'Read the current Agent message queue'], ['clear', 'Clear the current Agent message queue'], ['cancel', 'Cancel a current Agent queued message'], ['interrupt', 'Interrupt a current Agent session']].map(([kind, description]) => ({
|
|
105
112
|
id: `ec.queue.${kind}`,
|
|
@@ -446,7 +453,7 @@ const OPERATIONS = [
|
|
|
446
453
|
dangerous: false,
|
|
447
454
|
defaultScopes: ['agent'],
|
|
448
455
|
description: 'Assign a role to a private or group relation',
|
|
449
|
-
sources: ['menu', 'ecweb', 'control'],
|
|
456
|
+
sources: ['menu', 'menu.cli', 'ecweb', 'control', 'agent-tool'],
|
|
450
457
|
},
|
|
451
458
|
{
|
|
452
459
|
id: 'role.revoke',
|
|
@@ -454,7 +461,7 @@ const OPERATIONS = [
|
|
|
454
461
|
dangerous: false,
|
|
455
462
|
defaultScopes: ['agent'],
|
|
456
463
|
description: 'Revoke a private or group relation role assignment',
|
|
457
|
-
sources: ['menu', 'ecweb', 'control'],
|
|
464
|
+
sources: ['menu', 'menu.cli', 'ecweb', 'control', 'agent-tool'],
|
|
458
465
|
},
|
|
459
466
|
{
|
|
460
467
|
id: 'role.policy.read',
|
|
@@ -470,7 +477,7 @@ const OPERATIONS = [
|
|
|
470
477
|
dangerous: false,
|
|
471
478
|
defaultScopes: ['agent'],
|
|
472
479
|
description: 'Read private and group relation role assignments',
|
|
473
|
-
sources: ['menu', 'ecweb', 'control'],
|
|
480
|
+
sources: ['menu', 'menu.cli', 'ecweb', 'control', 'agent-tool'],
|
|
474
481
|
},
|
|
475
482
|
{
|
|
476
483
|
id: 'role.policy.write',
|
|
@@ -485,7 +492,7 @@ const OPERATIONS = [
|
|
|
485
492
|
category: 'read',
|
|
486
493
|
dangerous: false,
|
|
487
494
|
defaultScopes: ['agent'],
|
|
488
|
-
description: 'Read
|
|
495
|
+
description: 'Read Agent contacts, pending requests, and blocked contacts',
|
|
489
496
|
sources: ['agent-tool'],
|
|
490
497
|
},
|
|
491
498
|
{
|
|
@@ -504,6 +511,14 @@ const OPERATIONS = [
|
|
|
504
511
|
description: 'Block or unblock a canonical contact across bound private channels',
|
|
505
512
|
sources: ['agent-tool'],
|
|
506
513
|
},
|
|
514
|
+
{
|
|
515
|
+
id: 'contact.review',
|
|
516
|
+
category: 'write-agent',
|
|
517
|
+
dangerous: false,
|
|
518
|
+
defaultScopes: ['agent'],
|
|
519
|
+
description: 'Review pending Agent contact requests',
|
|
520
|
+
sources: ['agent-tool'],
|
|
521
|
+
},
|
|
507
522
|
{
|
|
508
523
|
id: 'connect.read',
|
|
509
524
|
category: 'read',
|
|
@@ -601,6 +616,14 @@ const OPERATIONS = [
|
|
|
601
616
|
description: '获取 agent 配置(含凭证)',
|
|
602
617
|
sources: ['menu', 'menu.cli', 'control'],
|
|
603
618
|
},
|
|
619
|
+
{
|
|
620
|
+
id: 'agent.persona',
|
|
621
|
+
category: 'write-agent',
|
|
622
|
+
dangerous: false,
|
|
623
|
+
defaultScopes: ['agent'],
|
|
624
|
+
description: '读取或更新当前 Agent 的 persona.md',
|
|
625
|
+
sources: ['agent-tool', 'control'],
|
|
626
|
+
},
|
|
604
627
|
{
|
|
605
628
|
id: 'agent.create',
|
|
606
629
|
category: 'process',
|
|
@@ -658,6 +681,14 @@ const OPERATIONS = [
|
|
|
658
681
|
description: '查看系统状态(敏感信息)',
|
|
659
682
|
sources: ['slash', 'menu', 'menu.cli', 'agent-tool'],
|
|
660
683
|
},
|
|
684
|
+
{
|
|
685
|
+
id: 'system.paircode',
|
|
686
|
+
category: 'process',
|
|
687
|
+
dangerous: true,
|
|
688
|
+
defaultScopes: ['process'],
|
|
689
|
+
description: '获取 daemon 远程控制配对码',
|
|
690
|
+
sources: ['menu', 'control'],
|
|
691
|
+
},
|
|
661
692
|
{
|
|
662
693
|
id: 'activity.current',
|
|
663
694
|
category: 'read',
|
|
@@ -944,6 +975,14 @@ const OPERATIONS = [
|
|
|
944
975
|
description: 'Sign and publish the current Agent local agent.md',
|
|
945
976
|
sources: ['agent-tool'],
|
|
946
977
|
},
|
|
978
|
+
{
|
|
979
|
+
id: 'ec.aid.avatar',
|
|
980
|
+
category: 'write-agent',
|
|
981
|
+
dangerous: true,
|
|
982
|
+
defaultScopes: ['agent'],
|
|
983
|
+
description: 'Upload and publish the current Agent avatar',
|
|
984
|
+
sources: ['agent-tool', 'menu', 'menu.cli'],
|
|
985
|
+
},
|
|
947
986
|
{
|
|
948
987
|
id: 'ec.msg.send',
|
|
949
988
|
category: 'write-own',
|
|
@@ -952,6 +991,14 @@ const OPERATIONS = [
|
|
|
952
991
|
description: '通过 ec msg send 发送私聊消息',
|
|
953
992
|
sources: ['agent-tool'],
|
|
954
993
|
},
|
|
994
|
+
{
|
|
995
|
+
id: 'ec.menu.request',
|
|
996
|
+
category: 'write-agent',
|
|
997
|
+
dangerous: false,
|
|
998
|
+
defaultScopes: ['relation'],
|
|
999
|
+
description: 'Send an authenticated remote Control Menu request through the current Agent daemon channel',
|
|
1000
|
+
sources: ['agent-tool'],
|
|
1001
|
+
},
|
|
955
1002
|
{
|
|
956
1003
|
id: 'ec.msg.file',
|
|
957
1004
|
category: 'write-own',
|