evolcore 0.0.26 → 0.0.28
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 +36 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +10 -5
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +70 -26
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex-runner.js +363 -421
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +13 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +544 -239
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +111 -162
- package/dist/cli/diag.js +3 -3
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +13 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +81 -19
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +105 -40
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +364 -96
- package/dist/core/command/menu-protocol.js +43 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +61 -37
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +94 -16
- package/dist/core/message/message-bridge.js +140 -13
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +729 -118
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +76 -36
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +8 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +38 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +322 -207
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +3 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/dist/utils/windows-shell-trust.js +37 -0
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +23 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
|
@@ -44,6 +44,16 @@ export function getManagementCommandPermissions(role) {
|
|
|
44
44
|
'agent.baseagent.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
45
45
|
'observable.current': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true } },
|
|
46
46
|
'observable.update': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
47
|
+
'file.root.agent': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
48
|
+
'file.root.storage': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
49
|
+
'file.storage.list': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
50
|
+
'file.storage.query': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
51
|
+
'file.storage.fetch': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
52
|
+
'file.storage.quota': { allow: true, scopes: ['agent'], constraints: { requireAgentOwner: true, targetCurrentAgentOnly: true } },
|
|
53
|
+
'file.root.relation': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
|
|
54
|
+
'file.data.list': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
|
|
55
|
+
'file.data.query': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
|
|
56
|
+
'file.data.fetch': { allow: true, scopes: ['relation', 'agent'], constraints: { targetCurrentAgentOnly: true } },
|
|
47
57
|
'config.get': { allow: true, scopes: ['agent', 'relation'] },
|
|
48
58
|
'config.set': { allow: true, scopes: ['agent', 'relation'] },
|
|
49
59
|
'config.unset': { allow: true, scopes: ['agent', 'relation'] },
|
|
@@ -29,14 +29,25 @@ const BASEAGENT_BEHAVIOR_FIELDS = new Set([
|
|
|
29
29
|
'useVertex',
|
|
30
30
|
]);
|
|
31
31
|
const SUPPORTED_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'ecagent']);
|
|
32
|
+
/** Public behavior fields for a runner this version does not implement. */
|
|
33
|
+
export function isUnsupportedBaseagentConfigField(fieldPath) {
|
|
34
|
+
const parts = fieldPath.split('.');
|
|
35
|
+
return parts.length === 3
|
|
36
|
+
&& parts[0] === 'baseagents'
|
|
37
|
+
&& /^[a-z][a-z0-9_-]*$/.test(parts[1])
|
|
38
|
+
&& !SUPPORTED_BASEAGENTS.has(parts[1])
|
|
39
|
+
&& BASEAGENT_BEHAVIOR_FIELDS.has(parts[2]);
|
|
40
|
+
}
|
|
32
41
|
const SENSITIVE_TOP_FIELDS = new Set([
|
|
33
42
|
'$schema_version',
|
|
34
43
|
'aid',
|
|
35
44
|
'enabled',
|
|
36
45
|
'lifecycle',
|
|
37
46
|
'initialized',
|
|
47
|
+
'initialization',
|
|
38
48
|
'owners',
|
|
39
49
|
'access',
|
|
50
|
+
'fullaccess',
|
|
40
51
|
'admins',
|
|
41
52
|
'channels',
|
|
42
53
|
'aun',
|
|
@@ -19,7 +19,8 @@ import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
|
|
|
19
19
|
import { fileCache } from '../core/daemon-file-cache.js';
|
|
20
20
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
21
21
|
import { normalizeAidDomain } from '../aun/aid/domain.js';
|
|
22
|
-
import { isExplicitGroupId } from '../aun/group
|
|
22
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
23
|
+
import { ensureRelationSharedDirectory } from '../core/relation/shared-data.js';
|
|
23
24
|
import { parseContactAlias } from './contact-alias.js';
|
|
24
25
|
import { loadSchema, listSchemaVersions, currentVersion, isSchemaName, } from './schema-registry.js';
|
|
25
26
|
import { mergeLayers, expandVars, buildEnvResolver } from './merge.js';
|
|
@@ -881,6 +882,9 @@ export function write(target, value, sel, opts = {}) {
|
|
|
881
882
|
}
|
|
882
883
|
// 2. 写入文件
|
|
883
884
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
885
|
+
if (target === ConfigTarget.Relation && sel?.self && sel.peerKey) {
|
|
886
|
+
ensureRelationSharedDirectory(sel.self, sel.peerKey);
|
|
887
|
+
}
|
|
884
888
|
atomicWriteJson(file, normalized);
|
|
885
889
|
finishConfigWrite(target, sel, file);
|
|
886
890
|
}
|
|
@@ -1291,6 +1295,27 @@ export function resolveAgentConfig(sel, opts = {}) {
|
|
|
1291
1295
|
stripRelationRoleData(stripStaticOwnerField(relationConfig)),
|
|
1292
1296
|
];
|
|
1293
1297
|
const merged = mergeLayers(layers, fields);
|
|
1298
|
+
// A baseagent block is a configuration record rather than an atomic value:
|
|
1299
|
+
// selecting a model at Agent or relation scope must retain the defaults
|
|
1300
|
+
// gateway credentials and endpoint. Keep the generic dictionary merge
|
|
1301
|
+
// contract unchanged for every other field.
|
|
1302
|
+
merged.baseagents = mergeBaseagentBlocks(layers);
|
|
1303
|
+
return merged;
|
|
1304
|
+
}
|
|
1305
|
+
function mergeBaseagentBlocks(layers) {
|
|
1306
|
+
const merged = {};
|
|
1307
|
+
for (const layer of layers) {
|
|
1308
|
+
const baseagents = layer?.baseagents;
|
|
1309
|
+
if (!baseagents || typeof baseagents !== 'object' || Array.isArray(baseagents))
|
|
1310
|
+
continue;
|
|
1311
|
+
for (const [name, rawBlock] of Object.entries(baseagents)) {
|
|
1312
|
+
if (!rawBlock || typeof rawBlock !== 'object' || Array.isArray(rawBlock)) {
|
|
1313
|
+
merged[name] = rawBlock;
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
merged[name] = { ...(merged[name] ?? {}), ...rawBlock };
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1294
1319
|
return merged;
|
|
1295
1320
|
}
|
|
1296
1321
|
/**
|
|
@@ -1326,12 +1351,13 @@ function stripStaticOwnerField(config) {
|
|
|
1326
1351
|
if (!Object.prototype.hasOwnProperty.call(config, 'owners')
|
|
1327
1352
|
&& !Object.prototype.hasOwnProperty.call(config, 'admins')
|
|
1328
1353
|
&& !Object.prototype.hasOwnProperty.call(config, 'access')
|
|
1329
|
-
&& !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions')
|
|
1354
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions')
|
|
1355
|
+
&& !Object.prototype.hasOwnProperty.call(config, 'fullaccess'))
|
|
1330
1356
|
return config;
|
|
1331
|
-
// groupSlashRequireMentions
|
|
1357
|
+
// fullaccess and groupSlashRequireMentions are Agent-only. Relation/defaults
|
|
1332
1358
|
// files allow forward-compatible unknown properties, so strip it before the
|
|
1333
1359
|
// generic merge chain can treat a manually written value as an override.
|
|
1334
|
-
const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, ...rest } = config;
|
|
1360
|
+
const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, fullaccess: _fullaccess, ...rest } = config;
|
|
1335
1361
|
return rest;
|
|
1336
1362
|
}
|
|
1337
1363
|
function stripRelationRoleData(config) {
|
|
@@ -1354,7 +1380,9 @@ export function resolveEffective(sel, opts = {}) {
|
|
|
1354
1380
|
enabled: config.enabled,
|
|
1355
1381
|
lifecycle: config.lifecycle,
|
|
1356
1382
|
initialized: config.initialized,
|
|
1383
|
+
initialization: config.initialization,
|
|
1357
1384
|
owners: config.owners,
|
|
1385
|
+
fullaccess: config.fullaccess,
|
|
1358
1386
|
aun: config.aun,
|
|
1359
1387
|
channels: config.channels ?? [],
|
|
1360
1388
|
models: config.models,
|
|
@@ -6,8 +6,9 @@ import fs from 'fs';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { resolvePaths, agentDir, agentRelationConfig } from '../paths.js';
|
|
8
8
|
import { roleMenuQuery, roleMenuUpdate } from '../core/command/role-menu.js';
|
|
9
|
-
import { parsePeerKey } from '../core/relation/peer-identity.js';
|
|
10
|
-
import { isExplicitGroupId } from '../aun/group
|
|
9
|
+
import { parsePeerKey, PeerIdentityCache } from '../core/relation/peer-identity.js';
|
|
10
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
11
|
+
import { isUnsupportedBaseagentConfigField } from './config-field-policy.js';
|
|
11
12
|
export const AUTHENTICATED_CONFIG_ROLE_ENV = 'EVOLCORE_AUTHENTICATED_CONFIG_ROLE';
|
|
12
13
|
export function executeResolvedConfigCommand(command, options = {}) {
|
|
13
14
|
try {
|
|
@@ -63,7 +64,7 @@ async function executeRelationRoleCommand(op, options) {
|
|
|
63
64
|
if (!op.self || !op.peerKey) {
|
|
64
65
|
return { ok: false, code: 'PEER_REQUIRED', error: 'role requires --self <aid> --peer <peer-aid>' };
|
|
65
66
|
}
|
|
66
|
-
const target = roleTargetFromPeerKey(op.peerKey);
|
|
67
|
+
const target = roleTargetFromPeerKey(op.self, op.peerKey);
|
|
67
68
|
const context = options.roleContext ?? {
|
|
68
69
|
self: op.self,
|
|
69
70
|
actorRole: options.role || 'owner',
|
|
@@ -139,7 +140,7 @@ async function executeRelationRoleCommand(op, options) {
|
|
|
139
140
|
roleDetails: updatedDetails,
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
|
-
function roleTargetFromPeerKey(peerKey) {
|
|
143
|
+
function roleTargetFromPeerKey(self, peerKey) {
|
|
143
144
|
let parsed;
|
|
144
145
|
try {
|
|
145
146
|
parsed = parsePeerKey(peerKey);
|
|
@@ -150,7 +151,13 @@ function roleTargetFromPeerKey(peerKey) {
|
|
|
150
151
|
if (parsed.channelType !== 'aun') {
|
|
151
152
|
throw new ConfigError('NOT_SUPPORTED', 'Relation role assignments currently support AUN peers only');
|
|
152
153
|
}
|
|
153
|
-
|
|
154
|
+
const relation = read(ConfigTarget.Relation, { self, peerKey });
|
|
155
|
+
const identity = PeerIdentityCache.get('aun', parsed.channelId, agentDir(self));
|
|
156
|
+
const verifiedType = identity?.aid === parsed.channelId && identity.source === 'agentmd' && identity.verifiedAt > 0
|
|
157
|
+
? identity.type : undefined;
|
|
158
|
+
const isGroup = relation?.targetKind ? relation.targetKind === 'group'
|
|
159
|
+
: verifiedType ? verifiedType === 'group' : isExplicitGroupId(parsed.channelId);
|
|
160
|
+
return isGroup
|
|
154
161
|
? { kind: 'group', groupId: parsed.channelId }
|
|
155
162
|
: { kind: 'private', peerAid: parsed.channelId };
|
|
156
163
|
}
|
|
@@ -159,6 +166,11 @@ export function executeResolvedConfigOperation(op, options = {}) {
|
|
|
159
166
|
const missingAgent = selectedAgentError(op);
|
|
160
167
|
if (missingAgent)
|
|
161
168
|
return missingAgent;
|
|
169
|
+
if (op.configScope === 'agent'
|
|
170
|
+
&& (op.field === 'fullaccess' || op.field.startsWith('fullaccess.'))
|
|
171
|
+
&& op.subcommand !== 'get') {
|
|
172
|
+
return { ok: false, code: 'FORBIDDEN', error: 'Agent fullaccess can only be configured in ECWeb Agent settings' };
|
|
173
|
+
}
|
|
162
174
|
if (op.subcommand === 'get')
|
|
163
175
|
return executeGet(op, options);
|
|
164
176
|
if (op.subcommand === 'set')
|
|
@@ -353,6 +365,9 @@ function executeSchema(command) {
|
|
|
353
365
|
};
|
|
354
366
|
}
|
|
355
367
|
function executeGet(op, options) {
|
|
368
|
+
if (isUnsupportedBaseagentConfigField(op.field)) {
|
|
369
|
+
return { ok: false, code: 'UNSUPPORTED_FIELD', error: `Config field ${op.field} is not supported by this version` };
|
|
370
|
+
}
|
|
356
371
|
const selector = selectorFor(op, options);
|
|
357
372
|
if (op.configScope === 'process') {
|
|
358
373
|
const config = read(ConfigTarget.Process, selector) || {};
|
|
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
5
|
-
import { isExplicitGroupId } from '../aun/group
|
|
5
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
6
6
|
import { agentConfig, agentContactConfig, agentDir, resolvePaths } from '../paths.js';
|
|
7
7
|
import { atomicReadJson, atomicWrite, atomicWriteJson } from '../utils/atomic-write.js';
|
|
8
8
|
import { fileCache } from '../core/daemon-file-cache.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentContactConfig } from '../paths.js';
|
|
2
2
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
3
|
-
import { isExplicitGroupId } from '../aun/group
|
|
3
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
4
4
|
import { ConfigTarget, read, validateConfig } from './config-manager.js';
|
|
5
5
|
import { ContactMutationError, contactRevision, mutateContactBookSync } from './contact-book-store.js';
|
|
6
6
|
import { formatContactAlias, normalizeContactChannelKey, parseContactAlias, } from './contact-alias.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
3
|
-
import { isExplicitGroupId } from '../aun/group
|
|
3
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
4
4
|
import { agentConfig } from '../paths.js';
|
|
5
5
|
import { getContactSnapshot, resolveContactView } from './contact-book.js';
|
|
6
6
|
import { ContactMutationError } from './contact-book-store.js';
|
|
@@ -10,7 +10,7 @@ import { getRoleDefinition, getRoleRank, normalizeDefaultRole, roleExists } from
|
|
|
10
10
|
import { getStoredRoleRegistry, roleRegistryExists } from './role-store.js';
|
|
11
11
|
import { resolvePrincipal } from './contact-book.js';
|
|
12
12
|
import { isValidAid } from '../aun/aid/validation.js';
|
|
13
|
-
import { isExplicitGroupId } from '../aun/group
|
|
13
|
+
import { isExplicitGroupId } from '../aun/group/index.js';
|
|
14
14
|
export function isAuthenticated(userId) {
|
|
15
15
|
return isValidAid(String(userId || '').trim());
|
|
16
16
|
}
|
|
@@ -97,6 +97,9 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
97
97
|
reason: 'permissionMode is defined by roles; use the role policy editor',
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
+
if (subcommand !== 'get' && (field === 'initialization' || field.startsWith('initialization.'))) {
|
|
101
|
+
return { ok: false, code: 'INITIALIZATION_MANAGED', reason: 'initialization is owned by the creation service' };
|
|
102
|
+
}
|
|
100
103
|
const isRelationRole = field === 'role';
|
|
101
104
|
if (isRelationRole && selector.scope !== 'relation') {
|
|
102
105
|
return {
|
package/dist/config-store.js
CHANGED
|
@@ -58,7 +58,7 @@ export function loadDaemonConfig() {
|
|
|
58
58
|
return validateDaemonConfig(normalizeProcessConfigCompat(normalizeDaemonServices(sanitized)), false);
|
|
59
59
|
}
|
|
60
60
|
export function isFullAccessEnabled(config = loadDaemonConfig()) {
|
|
61
|
-
return config.fullaccess?.enabled === true;
|
|
61
|
+
return config.fullaccess?.enabled === undefined || config.fullaccess.enabled === true;
|
|
62
62
|
}
|
|
63
63
|
let eckSnapshotsConfigCache;
|
|
64
64
|
/** Parse the process-level snapshot gate once for the current daemon lifecycle. */
|
|
@@ -48,6 +48,8 @@ function errorFrom(error, fallback = 'agent operation failed') {
|
|
|
48
48
|
*/
|
|
49
49
|
export class AgentApplicationService {
|
|
50
50
|
deps;
|
|
51
|
+
initialization;
|
|
52
|
+
setInitializationService(service) { this.initialization = service; }
|
|
51
53
|
constructor(deps) {
|
|
52
54
|
this.deps = deps;
|
|
53
55
|
}
|
|
@@ -212,6 +214,8 @@ export class AgentApplicationService {
|
|
|
212
214
|
}
|
|
213
215
|
if (!config)
|
|
214
216
|
return { ok: false, error: `Agent "${aid}" not found`, code: 'NOT_FOUND' };
|
|
217
|
+
if (!enabled)
|
|
218
|
+
await this.initialization?.cancel(aid);
|
|
215
219
|
const previousEnabled = config.enabled;
|
|
216
220
|
try {
|
|
217
221
|
config.enabled = enabled;
|
|
@@ -240,6 +244,7 @@ export class AgentApplicationService {
|
|
|
240
244
|
return { ok: true, aid, enabled, reloaded: result.ok };
|
|
241
245
|
}
|
|
242
246
|
async delete(aid, purge = false) {
|
|
247
|
+
await this.initialization?.cancel(aid);
|
|
243
248
|
const paths = resolvePaths();
|
|
244
249
|
const agentDir = path.join(paths.agentsDir, aid);
|
|
245
250
|
const configPath = path.join(agentDir, 'config.json');
|
|
@@ -7,16 +7,31 @@ function stableDigest(parts) {
|
|
|
7
7
|
const material = parts.map(part => compactPart(part) ?? '-').join('\u0000');
|
|
8
8
|
return crypto.createHash('sha256').update(material).digest('hex').slice(0, 20);
|
|
9
9
|
}
|
|
10
|
+
/** Preserve distinct source IDs; fill aliases only when the producer omitted them. */
|
|
11
|
+
export function normalizeToolCorrelation(input) {
|
|
12
|
+
const callId = compactPart(input.callId);
|
|
13
|
+
const correlationId = compactPart(input.correlationId);
|
|
14
|
+
const requestId = compactPart(input.requestId);
|
|
15
|
+
return {
|
|
16
|
+
callId: callId ?? correlationId ?? requestId,
|
|
17
|
+
correlationId: correlationId ?? callId ?? requestId,
|
|
18
|
+
requestId: requestId ?? callId ?? correlationId,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
10
21
|
/** Stable across source/mirror records for one tool-call lifecycle. */
|
|
11
22
|
export function buildToolLifecycleEventKey(input) {
|
|
23
|
+
const { callId } = normalizeToolCorrelation(input);
|
|
24
|
+
// No source ID means no safe join. Never merge unrelated anonymous calls.
|
|
25
|
+
if (!callId)
|
|
26
|
+
return `tool:unlinked:${crypto.randomUUID()}`;
|
|
12
27
|
return `tool:${stableDigest([
|
|
13
28
|
input.sessionId ?? 'unknown',
|
|
14
|
-
|
|
29
|
+
callId,
|
|
15
30
|
])}`;
|
|
16
31
|
}
|
|
17
32
|
/** Stable across repeated projections of one authorization decision. */
|
|
18
33
|
export function buildAuthorizationEventKey(input) {
|
|
19
|
-
const lifecycleId = input.callId
|
|
34
|
+
const lifecycleId = normalizeToolCorrelation(input).callId;
|
|
20
35
|
if (lifecycleId) {
|
|
21
36
|
return `${buildToolLifecycleEventKey({
|
|
22
37
|
sessionId: input.sessionId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { buildToolLifecycleEventKey, normalizeToolCorrelation } from './event-key.js';
|
|
4
5
|
// Logger rotation is hourly/daily, but the suffix is intentionally generic so
|
|
5
6
|
// this check also covers future rotation formats without changing the audit.
|
|
6
7
|
const STRUCTURED_LOG_RE = /^(?:daemon|events|channel-in|channel-out|messages|command-audit)(?:-[^.]+)?\.log$/;
|
|
@@ -33,6 +34,7 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
33
34
|
// daemon logs mix human-readable entries with structured audit lines.
|
|
34
35
|
// Ignore the former, but still validate malformed structured payloads.
|
|
35
36
|
if (MIXED_TEXT_LOG_RE.test(file)) {
|
|
37
|
+
raw = raw.replace(/^\[[^\]]+\] \[(?:DEBUG|INFO|WARN|ERROR)\] /, '');
|
|
36
38
|
if (raw.startsWith('[CommandAuditRecord] '))
|
|
37
39
|
raw = raw.slice('[CommandAuditRecord] '.length);
|
|
38
40
|
else if (!raw.startsWith('{'))
|
|
@@ -57,8 +59,9 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
57
59
|
issues.push({ file, line: index + 1, reason: 'missing_agent', key });
|
|
58
60
|
if (isLifecycleRecord(value) && !hasContextValue(lifecycleField(value, 'permissionMode')))
|
|
59
61
|
issues.push({ file, line: index + 1, reason: 'missing_permission_mode', key });
|
|
60
|
-
if (isCorrelatableRecord(value) && !key)
|
|
62
|
+
if (isCorrelatableRecord(value) && (!key || value.correlationMissing === true || key.startsWith('tool:unlinked:'))) {
|
|
61
63
|
issues.push({ file, line: index + 1, reason: 'missing_correlation' });
|
|
64
|
+
}
|
|
62
65
|
});
|
|
63
66
|
}
|
|
64
67
|
const seen = new Set();
|
|
@@ -79,16 +82,33 @@ export function inspectStructuredLogs(logDir, options = {}) {
|
|
|
79
82
|
function structuredRecordKey(value) {
|
|
80
83
|
const nested = nestedRecord(value);
|
|
81
84
|
const eventKey = firstDefined(value, nested, ['eventKey', 'event_key']);
|
|
82
|
-
const eventPhase =
|
|
85
|
+
const eventPhase = recordPhase(value);
|
|
83
86
|
if (eventKey)
|
|
84
87
|
return `${String(eventKey)}${eventPhase ? `:${String(eventPhase)}` : ''}`;
|
|
88
|
+
if (isLifecycleRecord(value)) {
|
|
89
|
+
const correlation = normalizeToolCorrelation({
|
|
90
|
+
callId: firstDefined(value, nested, ['callId', 'call_id', 'toolUseId', 'tool_use_id']),
|
|
91
|
+
correlationId: firstDefined(value, nested, ['correlationId', 'correlation_id']),
|
|
92
|
+
requestId: firstDefined(value, nested, ['requestId', 'request_id']),
|
|
93
|
+
});
|
|
94
|
+
if (!correlation.callId)
|
|
95
|
+
return undefined;
|
|
96
|
+
return `${buildToolLifecycleEventKey({
|
|
97
|
+
sessionId: lifecycleField(value, 'sessionId'),
|
|
98
|
+
...correlation,
|
|
99
|
+
})}:${eventPhase}`;
|
|
100
|
+
}
|
|
85
101
|
const correlation = firstDefined(value, nested, [
|
|
86
102
|
'correlationId', 'correlation_id', 'callId', 'call_id', 'toolUseId', 'tool_use_id',
|
|
87
103
|
'requestId', 'request_id', 'msgId', 'operationId',
|
|
88
104
|
]);
|
|
89
105
|
if (!correlation)
|
|
90
106
|
return undefined;
|
|
91
|
-
return
|
|
107
|
+
return JSON.stringify([lifecycleField(value, 'sessionId'), canonicalEventType(value), correlation, eventPhase]);
|
|
108
|
+
}
|
|
109
|
+
function recordPhase(value) {
|
|
110
|
+
return firstDefined(value, nestedRecord(value), ['eventPhase', 'event_phase'])
|
|
111
|
+
?? (canonicalEventType(value) === 'tool_use' ? 'use' : canonicalEventType(value) === 'tool_result' ? 'result' : undefined);
|
|
92
112
|
}
|
|
93
113
|
function isLifecycleRecord(value) {
|
|
94
114
|
const type = canonicalEventType(value);
|
|
@@ -101,11 +121,13 @@ function isCorrelatableRecord(value) {
|
|
|
101
121
|
}
|
|
102
122
|
function stableHash(value) {
|
|
103
123
|
const nested = nestedRecord(value);
|
|
104
|
-
const eventKey =
|
|
124
|
+
const eventKey = structuredRecordKey(value);
|
|
105
125
|
const canonical = {
|
|
106
126
|
eventKey,
|
|
107
|
-
eventPhase:
|
|
127
|
+
eventPhase: recordPhase(value),
|
|
108
128
|
type: canonicalEventType(value),
|
|
129
|
+
operation: firstDefined(value, nested, ['operation']),
|
|
130
|
+
targetAgentAid: firstDefined(value, nested, ['targetAgentAid']),
|
|
109
131
|
toolName: firstDefined(value, nested, ['toolName', 'tool', 'name']),
|
|
110
132
|
callId: eventKey ? undefined : firstDefined(value, nested, ['callId', 'call_id', 'toolUseId', 'tool_use_id']),
|
|
111
133
|
correlationId: eventKey ? undefined : firstDefined(value, nested, ['correlationId', 'correlation_id']),
|
|
@@ -116,11 +138,15 @@ function stableHash(value) {
|
|
|
116
138
|
isError: firstDefined(value, nested, ['isError', 'is_error']) ?? (value.ok === false || nested?.ok === false ? true : undefined),
|
|
117
139
|
errorCode: firstDefined(value, nested, ['errorCode', 'error_code']),
|
|
118
140
|
decision: firstDefined(value, nested, ['decision']),
|
|
141
|
+
decisionSource: firstDefined(value, nested, ['decisionSource', 'decision_source']),
|
|
142
|
+
policyCode: firstDefined(value, nested, ['policyCode', 'policy_code']),
|
|
119
143
|
executed: firstDefined(value, nested, ['executed']),
|
|
120
144
|
executionState: firstDefined(value, nested, ['executionState', 'execution_state']),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
// A lifecycle stage may be mirrored without its large input/output body.
|
|
146
|
+
// Keep decision/classification differences, not projection payload size.
|
|
147
|
+
input: eventKey && isLifecycleRecord(value) ? undefined : firstDefined(value, nested, ['input']),
|
|
148
|
+
result: eventKey && isLifecycleRecord(value) ? undefined : firstDefined(value, nested, ['result', 'content']),
|
|
149
|
+
error: eventKey && isLifecycleRecord(value) ? undefined : firstDefined(value, nested, ['error', 'errorMessage']),
|
|
124
150
|
};
|
|
125
151
|
return crypto.createHash('sha256').update(JSON.stringify(canonical)).digest('hex').slice(0, 16);
|
|
126
152
|
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import { assertAgentPolicyAuthorization } from '../role/runtime-policy.js';
|
|
1
2
|
import crypto from 'crypto';
|
|
2
3
|
import { authorizeOperation, buildAuthSubject } from './auth-gateway.js';
|
|
3
4
|
import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
6
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
7
|
+
export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
|
|
6
8
|
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the credential carried by an EC CLI process. A live task token has
|
|
11
|
+
* priority; persistent Codex threads fall back to their session credential.
|
|
12
|
+
*/
|
|
13
|
+
export function readDelegationCredential(env = process.env) {
|
|
14
|
+
const taskToken = env[AGENT_DELEGATION_TOKEN_ENV]?.trim();
|
|
15
|
+
if (taskToken)
|
|
16
|
+
return taskToken;
|
|
17
|
+
const sessionToken = env[CODEX_SESSION_TOKEN_ENV]?.trim();
|
|
18
|
+
return sessionToken || undefined;
|
|
19
|
+
}
|
|
7
20
|
export class AgentDelegationRegistry {
|
|
8
21
|
runtimeEpoch = crypto.randomBytes(16).toString('hex');
|
|
9
22
|
grantsByHash = new Map();
|
|
@@ -24,6 +37,13 @@ export class AgentDelegationRegistry {
|
|
|
24
37
|
return token;
|
|
25
38
|
}
|
|
26
39
|
validate(token, sessionId, commandHash) {
|
|
40
|
+
return this.validateToken(token, sessionId, commandHash, true);
|
|
41
|
+
}
|
|
42
|
+
/** Authenticate a CLI context lookup without spending its approved operation. */
|
|
43
|
+
validateForContext(token, sessionId, commandHash) {
|
|
44
|
+
return this.validateToken(token, sessionId, commandHash, false);
|
|
45
|
+
}
|
|
46
|
+
validateToken(token, sessionId, commandHash, consumeApproval) {
|
|
27
47
|
if (!token) {
|
|
28
48
|
return {
|
|
29
49
|
ok: false,
|
|
@@ -37,10 +57,19 @@ export class AgentDelegationRegistry {
|
|
|
37
57
|
if (grant
|
|
38
58
|
&& grant.sessionId === sessionId
|
|
39
59
|
&& this.activeHashBySession.get(sessionId) === tokenHash) {
|
|
60
|
+
if (grant.executionIdentity?.source === 'agent-policy') {
|
|
61
|
+
try {
|
|
62
|
+
assertAgentPolicyAuthorization(grant.executionIdentity);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
this.revokeSession(sessionId);
|
|
66
|
+
return { ok: false, code: 'INVALID_DELEGATION', reason: 'Agent fullaccess policy was revoked', diagnosticCause: 'task_replaced' };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
40
69
|
return { ok: true, grant };
|
|
41
70
|
}
|
|
42
71
|
const approved = commandHash
|
|
43
|
-
? this.
|
|
72
|
+
? this.readApprovedCommand(tokenHash, sessionId, commandHash, consumeApproval)
|
|
44
73
|
: undefined;
|
|
45
74
|
if (approved)
|
|
46
75
|
return { ok: true, grant: approved };
|
|
@@ -66,11 +95,12 @@ export class AgentDelegationRegistry {
|
|
|
66
95
|
const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
|
|
67
96
|
if (!grant || grant.taskId !== input.taskId)
|
|
68
97
|
return false;
|
|
69
|
-
|
|
98
|
+
const sessionToken = input.sessionToken ?? input.carrierToken;
|
|
99
|
+
if (!sessionToken || !isDelegationCommandHash(input.commandHash))
|
|
70
100
|
return false;
|
|
71
101
|
this.deleteExpiredApprovedCommands();
|
|
72
|
-
const
|
|
73
|
-
const key = approvedCommandKey(
|
|
102
|
+
const sessionTokenHash = hashDelegationToken(sessionToken);
|
|
103
|
+
const key = approvedCommandKey(sessionTokenHash, input.sessionId, input.commandHash);
|
|
74
104
|
const existing = this.approvedCommands.get(key);
|
|
75
105
|
const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
|
|
76
106
|
this.approvedCommands.set(key, {
|
|
@@ -105,10 +135,10 @@ export class AgentDelegationRegistry {
|
|
|
105
135
|
this.activeHashBySession.delete(sessionId);
|
|
106
136
|
this.deleteApprovedCommandsForSession(sessionId);
|
|
107
137
|
}
|
|
108
|
-
|
|
138
|
+
readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
|
|
109
139
|
if (!isDelegationCommandHash(commandHash))
|
|
110
140
|
return undefined;
|
|
111
|
-
const key = approvedCommandKey(
|
|
141
|
+
const key = approvedCommandKey(sessionTokenHash, sessionId, commandHash);
|
|
112
142
|
const approved = this.approvedCommands.get(key);
|
|
113
143
|
if (!approved)
|
|
114
144
|
return undefined;
|
|
@@ -124,10 +154,12 @@ export class AgentDelegationRegistry {
|
|
|
124
154
|
this.approvedCommands.delete(key);
|
|
125
155
|
return undefined;
|
|
126
156
|
}
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
if (consume) {
|
|
158
|
+
if (approved.remainingUses <= 1)
|
|
159
|
+
this.approvedCommands.delete(key);
|
|
160
|
+
else
|
|
161
|
+
approved.remainingUses--;
|
|
162
|
+
}
|
|
131
163
|
return grant;
|
|
132
164
|
}
|
|
133
165
|
deleteExpiredApprovedCommands() {
|
|
@@ -216,8 +248,10 @@ function isValidFullAccessExecutionIdentity(identity) {
|
|
|
216
248
|
|| identity.processRole !== 'fullaccess-run'
|
|
217
249
|
|| identity.dataScope !== 'daemon')
|
|
218
250
|
return false;
|
|
219
|
-
if (identity.source === 'fullaccess-command') {
|
|
220
|
-
return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
251
|
+
if (identity.source === 'fullaccess-command' || identity.source === 'agent-policy') {
|
|
252
|
+
return typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
253
|
+
&& (identity.source !== 'agent-policy'
|
|
254
|
+
|| (typeof identity.agentAid === 'string' && identity.agentAid.length > 0));
|
|
221
255
|
}
|
|
222
256
|
return identity.source === 'trigger'
|
|
223
257
|
&& typeof identity.authorizedBy === 'string' && identity.authorizedBy.length > 0
|
|
@@ -228,8 +262,8 @@ function isValidFullAccessExecutionIdentity(identity) {
|
|
|
228
262
|
function hashDelegationToken(token) {
|
|
229
263
|
return crypto.createHash('sha256').update(token).digest('hex');
|
|
230
264
|
}
|
|
231
|
-
function approvedCommandKey(
|
|
232
|
-
return JSON.stringify([
|
|
265
|
+
function approvedCommandKey(sessionTokenHash, sessionId, commandHash) {
|
|
266
|
+
return JSON.stringify([sessionTokenHash, sessionId, commandHash]);
|
|
233
267
|
}
|
|
234
268
|
function isDelegationCommandHash(value) {
|
|
235
269
|
return /^[a-f0-9]{64}$/.test(value);
|
|
@@ -70,6 +70,7 @@ export function buildAuthSubject(input) {
|
|
|
70
70
|
identity: input.identity ?? roleToSessionIdentity(role === 'none' ? null : role),
|
|
71
71
|
isDaemonOwner,
|
|
72
72
|
fromControlChannel: !!input.fromControlChannel,
|
|
73
|
+
ecwebManagement: input.ecwebManagement === true && isTrustedDaemonOwner && input.fromControlChannel === true,
|
|
73
74
|
allowAccess: processRole !== 'none' || (suppliedRole
|
|
74
75
|
? checkRoleAccess(suppliedRole, input.selfAid)
|
|
75
76
|
: roleDetail.allowAccess && checkRoleAccess(role, input.selfAid)),
|
|
@@ -114,6 +115,7 @@ export function authorizeOperation(params) {
|
|
|
114
115
|
return access;
|
|
115
116
|
}
|
|
116
117
|
const ctx = {
|
|
118
|
+
principalId: params.subject.principalId,
|
|
117
119
|
intent: params.intent,
|
|
118
120
|
actorId: params.subject.actorId,
|
|
119
121
|
channel: params.subject.channel,
|