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.
Files changed (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /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-identity.js';
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 is intentionally Agent-only. Relation/defaults
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-identity.js';
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
- return isExplicitGroupId(parsed.channelId)
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-identity.js';
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-identity.js';
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-identity.js';
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-identity.js';
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 {
@@ -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
- input.callId ?? input.correlationId ?? input.requestId ?? 'unknown',
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 ?? input.correlationId ?? input.requestId;
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 = firstDefined(value, nested, ['eventPhase', 'event_phase']);
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 `${canonicalEventType(value)}:${String(correlation)}`;
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 = firstDefined(value, nested, ['eventKey', 'event_key']);
124
+ const eventKey = structuredRecordKey(value);
105
125
  const canonical = {
106
126
  eventKey,
107
- eventPhase: firstDefined(value, nested, ['eventPhase', 'event_phase']),
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
- input: firstDefined(value, nested, ['input']),
122
- result: firstDefined(value, nested, ['result', 'content']),
123
- error: firstDefined(value, nested, ['error', 'errorMessage']),
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.consumeApprovedCommand(tokenHash, sessionId, commandHash)
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
- if (!input.carrierToken || !isDelegationCommandHash(input.commandHash))
98
+ const sessionToken = input.sessionToken ?? input.carrierToken;
99
+ if (!sessionToken || !isDelegationCommandHash(input.commandHash))
70
100
  return false;
71
101
  this.deleteExpiredApprovedCommands();
72
- const carrierTokenHash = hashDelegationToken(input.carrierToken);
73
- const key = approvedCommandKey(carrierTokenHash, input.sessionId, input.commandHash);
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
- consumeApprovedCommand(carrierTokenHash, sessionId, commandHash) {
138
+ readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
109
139
  if (!isDelegationCommandHash(commandHash))
110
140
  return undefined;
111
- const key = approvedCommandKey(carrierTokenHash, sessionId, commandHash);
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 (approved.remainingUses <= 1)
128
- this.approvedCommands.delete(key);
129
- else
130
- approved.remainingUses--;
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(carrierTokenHash, sessionId, commandHash) {
232
- return JSON.stringify([carrierTokenHash, sessionId, commandHash]);
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,