evolcore 0.0.33 → 0.0.34

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 (129) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +12 -1
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +82 -13
  6. package/dist/agents/codex/diagnostics.js +37 -11
  7. package/dist/agents/codex-runner.js +218 -44
  8. package/dist/agents/ecagent-runner.js +73 -20
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/identity.js +1 -1
  11. package/dist/channels/aun.js +16 -6
  12. package/dist/channels/daemon.js +6 -3
  13. package/dist/cli/aun-commands.js +3 -2
  14. package/dist/cli/ctl-command.js +3 -1
  15. package/dist/cli/daemon-commands.js +120 -19
  16. package/dist/cli/fs-command.js +2 -1
  17. package/dist/cli/index.js +20 -6
  18. package/dist/cli/integrity-command.js +196 -0
  19. package/dist/cli/memory-command.js +158 -145
  20. package/dist/cli/thread-command.js +5 -3
  21. package/dist/cli/trigger-command.js +6 -1
  22. package/dist/config/config-field-policy.js +4 -0
  23. package/dist/config/config-manager.js +19 -0
  24. package/dist/config/contact-book-store.js +5 -0
  25. package/dist/config/role-service.js +13 -0
  26. package/dist/config/runtime-env.js +16 -0
  27. package/dist/config-store.js +30 -0
  28. package/dist/core/audit/log-integrity.js +3 -1
  29. package/dist/core/auth/agent-delegation.js +108 -35
  30. package/dist/core/auth/delegation-diagnostics.js +414 -0
  31. package/dist/core/auth/operation-authorizer.js +1 -1
  32. package/dist/core/auth/operation-catalog.js +16 -0
  33. package/dist/core/command/agent-control.js +18 -26
  34. package/dist/core/command/command-handler.js +138 -4
  35. package/dist/core/command/daemon-diagnostics.js +1 -0
  36. package/dist/core/command/menu-catalog.js +3 -0
  37. package/dist/core/command/menu-handler.js +22 -7
  38. package/dist/core/command/menu-protocol.js +1 -0
  39. package/dist/core/command/slash-handler.js +2 -0
  40. package/dist/core/daemon-bootstrap.js +4 -7
  41. package/dist/core/file-integrity/read.js +61 -0
  42. package/dist/core/file-integrity/store.js +262 -0
  43. package/dist/core/ipc-registration.js +36 -1
  44. package/dist/core/memory/batch-meta.js +132 -0
  45. package/dist/core/memory/consolidation-context.js +35 -14
  46. package/dist/core/memory/consolidation-ops-records.js +14 -16
  47. package/dist/core/memory/consolidation.js +91 -26
  48. package/dist/core/memory/index-manager.js +19 -28
  49. package/dist/core/memory/memory-admin.js +19 -14
  50. package/dist/core/memory/memory-document.js +53 -9
  51. package/dist/core/memory/memory-writer.js +30 -15
  52. package/dist/core/message/im-renderer.js +20 -4
  53. package/dist/core/message/message-bridge.js +2 -1
  54. package/dist/core/message/response-engine.js +584 -47
  55. package/dist/core/permission/ec-command-parser.js +93 -4
  56. package/dist/core/permission/protected-paths.js +5 -1
  57. package/dist/core/permission/readonly-shell-query.js +43 -5
  58. package/dist/core/permission/session-path-grants.js +334 -0
  59. package/dist/core/permission/tool-canonical.js +226 -0
  60. package/dist/core/permission/tool-policy.js +30 -62
  61. package/dist/core/role/runtime-policy.js +21 -10
  62. package/dist/core/session/session-fs-store.js +123 -8
  63. package/dist/core/session/session-manager.js +75 -5
  64. package/dist/core/skill/index-generator.js +17 -3
  65. package/dist/core/system-session/grants.js +40 -0
  66. package/dist/core/system-session/memory.js +222 -0
  67. package/dist/core/system-session/registry.js +74 -0
  68. package/dist/core/system-session/types.js +14 -0
  69. package/dist/core/thread/thread-service.js +13 -20
  70. package/dist/eck/kit-renderer.js +7 -2
  71. package/dist/eck/manifest-engine.js +24 -4
  72. package/dist/index.js +11 -2
  73. package/dist/ipc.js +51 -40
  74. package/dist/paths.js +70 -0
  75. package/dist/stats/billing.js +20 -8
  76. package/dist/trigger/feedback.js +6 -1
  77. package/dist/trigger/parser.js +8 -1
  78. package/dist/trigger/system-definitions.js +3 -3
  79. package/dist/utils/cross-platform.js +11 -2
  80. package/dist/utils/error-dict.json +18 -4
  81. package/dist/utils/error-utils.js +121 -0
  82. package/dist/utils/instance-registry.js +13 -8
  83. package/dist/utils/log-writer.js +12 -0
  84. package/dist/utils/process-introspect.js +11 -0
  85. package/kits/docs/context-assembly.md +0 -1
  86. package/kits/docs/evolcore/agent-create.md +5 -4
  87. package/kits/docs/evolcore/agent.md +2 -2
  88. package/kits/docs/evolcore/config.md +2 -2
  89. package/kits/docs/evolcore/ctl.md +2 -2
  90. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  91. package/kits/docs/evolcore/fs-architecture.md +4 -5
  92. package/kits/docs/evolcore/fs.md +1 -1
  93. package/kits/docs/evolcore/group-collaboration.md +2 -2
  94. package/kits/docs/evolcore/group.md +10 -10
  95. package/kits/docs/evolcore/memory-architecture.md +4 -5
  96. package/kits/docs/evolcore/memory-consolidation.md +18 -14
  97. package/kits/docs/evolcore/memory-tags.md +3 -1
  98. package/kits/docs/evolcore/memory.md +24 -12
  99. package/kits/docs/evolcore/model.md +16 -0
  100. package/kits/docs/evolcore/msg.md +7 -7
  101. package/kits/docs/evolcore/thread-chain.md +5 -5
  102. package/kits/docs/prompt-loading-architecture.md +0 -1
  103. package/kits/eck_manifest.json +0 -20
  104. package/kits/eck_manifest.memory.json +0 -12
  105. package/kits/rules/01-overview.md +11 -0
  106. package/kits/rules/03-identity.md +6 -3
  107. package/kits/rules/06-channel.md +1 -1
  108. package/kits/rules/07-memory-and-thread.md +12 -15
  109. package/kits/schemas/_meta.json +7 -1
  110. package/kits/schemas/agent-config.schema.13.json +1 -1
  111. package/kits/schemas/daemon.schema.7.json +140 -0
  112. package/kits/skills/ec-group/SKILL.md +4 -4
  113. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  114. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  115. package/kits/templates/memory-consolidation/forget.md +2 -2
  116. package/kits/templates/memory-consolidation/generalize.md +4 -4
  117. package/kits/templates/memory-consolidation/notes.md +11 -11
  118. package/kits/templates/memory-consolidation/profile.md +3 -3
  119. package/kits/templates/memory-consolidation/promote.md +3 -3
  120. package/kits/templates/memory-consolidation/workflow.md +6 -6
  121. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  122. package/kits/templates/system-fragments/bootstrap.md +4 -3
  123. package/kits/templates/system-fragments/channel.md +2 -2
  124. package/kits/templates/system-fragments/memory.md +16 -9
  125. package/kits/templates/system-fragments/reply.md +6 -6
  126. package/kits/templates/system-fragments/session.md +1 -1
  127. package/package.json +4 -3
  128. package/dist/core/memory/memory-session-policy.js +0 -41
  129. package/dist/core/memory/memory-signature.js +0 -257
@@ -17,6 +17,7 @@ import crypto from 'crypto';
17
17
  import { resolvePaths, agentConfig as agentConfigPath, agentContactConfig, agentRelationConfig, agentDir, agentRelationsDir, agentRolesIndex, agentRoleConfig, } from '../paths.js';
18
18
  import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
19
19
  import { fileCache } from '../core/daemon-file-cache.js';
20
+ import { registerFile } from '../core/file-integrity/store.js';
20
21
  import { isValidAid } from '../aun/aid/validation.js';
21
22
  import { normalizeAidDomain } from '../aun/aid/domain.js';
22
23
  import { isExplicitGroupId } from '../aun/group/index.js';
@@ -764,6 +765,15 @@ export function normalizeProcessConfigCompat(value) {
764
765
  if (changed)
765
766
  normalized = { ...normalized, idleMonitor: nextIdleMonitor };
766
767
  }
768
+ const delegationDiagnostics = normalized.delegationDiagnostics;
769
+ if (delegationDiagnostics && typeof delegationDiagnostics === 'object' && !Array.isArray(delegationDiagnostics)
770
+ && (Object.prototype.hasOwnProperty.call(delegationDiagnostics, 'eventRetentionDays')
771
+ || Object.prototype.hasOwnProperty.call(delegationDiagnostics, 'stateRetentionDays'))) {
772
+ const nextDiagnostics = { ...delegationDiagnostics };
773
+ delete nextDiagnostics.eventRetentionDays;
774
+ delete nextDiagnostics.stateRetentionDays;
775
+ normalized = { ...normalized, delegationDiagnostics: nextDiagnostics };
776
+ }
767
777
  if (debug && typeof debug === 'object' && !Array.isArray(debug)
768
778
  && Object.prototype.hasOwnProperty.call(debug, 'flusherDiag')) {
769
779
  const nextDebug = { ...debug };
@@ -1049,6 +1059,15 @@ function finishConfigWrite(target, sel, file) {
1049
1059
  if (sel?.self && (target === ConfigTarget.Agent || target === ConfigTarget.RoleRegistry || target === ConfigTarget.Role)) {
1050
1060
  clearRoleStoreCache(sel.self);
1051
1061
  }
1062
+ // 配置落盘后登记到完整性台账;台账不可用时只告警不阻断
1063
+ if (fs.existsSync(file)) {
1064
+ try {
1065
+ registerFile(file);
1066
+ }
1067
+ catch (err) {
1068
+ configWarn(`[file-integrity] register failed for ${file}: ${err instanceof Error ? err.message : String(err)}`);
1069
+ }
1070
+ }
1052
1071
  const event = { target, selector: { ...(sel ?? {}) } };
1053
1072
  for (const listener of configWriteListeners) {
1054
1073
  try {
@@ -6,6 +6,7 @@ import { isExplicitGroupId } from '../aun/group/index.js';
6
6
  import { agentConfig, agentContactAuditPath, 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';
9
+ import { unregisterFile } from '../core/file-integrity/store.js';
9
10
  import { ensureManagedLockParent, resolveManagedLockPath } from '../core/runtime-lock.js';
10
11
  import { ConfigTarget, notifyConfigWrite, validateConfig } from './config-manager.js';
11
12
  import { parseContactAlias } from './contact-alias.js';
@@ -584,6 +585,10 @@ function applyTransactionChange(change) {
584
585
  for (const candidate of [change.file, `${change.file}_`, `${change.file}__`]) {
585
586
  fs.rmSync(candidate, { force: true });
586
587
  }
588
+ try {
589
+ unregisterFile(change.file);
590
+ }
591
+ catch { /* best-effort */ }
587
592
  }
588
593
  else {
589
594
  atomicWrite(change.file, change.after);
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
4
4
  import { agentRelationConfig, agentRelationsDir, agentRoleConfig, agentRolesIndex } from '../paths.js';
5
+ import { registerFile, unregisterFile } from '../core/file-integrity/store.js';
5
6
  import { loadSchema } from './schema-registry.js';
6
7
  import { clearRoleStoreCache, getStoredRoleRegistry, isRoleName, readRoleStore, roleRegistryExists, } from './role-store.js';
7
8
  import { expectedRoleRank, isManagementRole } from './builtin-roles.js';
@@ -38,6 +39,10 @@ export function writeRoleRegistry(selfAid, value) {
38
39
  const file = agentRolesIndex(selfAid);
39
40
  fs.mkdirSync(path.dirname(file), { recursive: true });
40
41
  atomicWriteJson(file, { ...value, $schema_version: 1 });
42
+ try {
43
+ registerFile(file);
44
+ }
45
+ catch { /* best-effort */ }
41
46
  clearRoleStoreCache(selfAid);
42
47
  }
43
48
  export function writeRoleDefinition(selfAid, roleId, value) {
@@ -52,6 +57,10 @@ export function writeRoleDefinition(selfAid, roleId, value) {
52
57
  const file = agentRoleConfig(selfAid, roleId);
53
58
  fs.mkdirSync(path.dirname(file), { recursive: true });
54
59
  atomicWriteJson(file, normalized);
60
+ try {
61
+ registerFile(file);
62
+ }
63
+ catch { /* best-effort */ }
55
64
  clearRoleStoreCache(selfAid);
56
65
  }
57
66
  /** Remove an agent-local override so the builtin role template is used again. */
@@ -62,6 +71,10 @@ export function resetRoleDefinition(selfAid, roleId) {
62
71
  const changed = fs.existsSync(file);
63
72
  if (changed) {
64
73
  fs.rmSync(file, { force: true });
74
+ try {
75
+ unregisterFile(file);
76
+ }
77
+ catch { /* best-effort */ }
65
78
  clearRoleStoreCache(selfAid);
66
79
  }
67
80
  return changed;
@@ -43,9 +43,23 @@ function envFileValue(file, name) {
43
43
  catch { }
44
44
  return undefined;
45
45
  }
46
+ // Registry values do not depend on the agent/project scope. Without this, every
47
+ // agent pays its own reg.exe launches (2 hives x 3 names, ~50ms each), which
48
+ // serializes daemon startup at roughly 150ms per Claude runner.
49
+ const windowsValueCache = new Map();
46
50
  function windowsValue(hive, name) {
47
51
  if (process.platform !== 'win32')
48
52
  return undefined;
53
+ const key = `${hive}:${name}`;
54
+ const now = Date.now();
55
+ const hit = windowsValueCache.get(key);
56
+ if (hit && hit.expires > now)
57
+ return hit.value;
58
+ const value = readWindowsValue(hive, name);
59
+ windowsValueCache.set(key, { value, expires: now + 3000 });
60
+ return value;
61
+ }
62
+ function readWindowsValue(hive, name) {
49
63
  const registryPath = hive === 'user' ? 'HKCU\\Environment'
50
64
  : 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment';
51
65
  try {
@@ -129,9 +143,11 @@ export function setRuntimeEnvironmentOverride(aid, values) {
129
143
  else
130
144
  overrides.delete(scope);
131
145
  cache.clear();
146
+ windowsValueCache.clear();
132
147
  }
133
148
  export function resetRuntimeEnvironmentForTests() {
134
149
  overrides.clear();
135
150
  cache.clear();
151
+ windowsValueCache.clear();
136
152
  captureStartupValues();
137
153
  }
@@ -19,6 +19,7 @@ import fs from 'fs';
19
19
  import path from 'path';
20
20
  import { resolvePaths, agentDir, } from './paths.js';
21
21
  import { atomicReadJson, atomicWriteJson } from './utils/atomic-write.js';
22
+ import { registerFile as registerFileIntegrity } from './core/file-integrity/store.js';
22
23
  import { checkAgentDir, isValidAid } from './aun/aid/validation.js';
23
24
  import { normalizeAidDomain } from './aun/aid/domain.js';
24
25
  import { isValidChannelName } from './core/channel-loader.js';
@@ -60,6 +61,18 @@ export function loadDaemonConfig() {
60
61
  export function isRestartRecoveryEnabled(config = loadDaemonConfig()) {
61
62
  return config.restartRecovery?.enabled === true;
62
63
  }
64
+ export function isDelegationDiagnosticsEnabled() {
65
+ const config = cfgRead(ConfigTarget.Process, undefined, { cache: true });
66
+ const diagnostics = config?.delegationDiagnostics;
67
+ if (diagnostics !== undefined && (!diagnostics || typeof diagnostics !== 'object' || Array.isArray(diagnostics))) {
68
+ throw new Error('daemon.json.delegationDiagnostics must be an object');
69
+ }
70
+ const enabled = diagnostics?.enabled;
71
+ if (enabled !== undefined && typeof enabled !== 'boolean') {
72
+ throw new Error('daemon.json.delegationDiagnostics.enabled must be a boolean');
73
+ }
74
+ return enabled !== false;
75
+ }
63
76
  export function isSkillWatchEnabled(config = loadDaemonConfig()) {
64
77
  return config.skills?.watch === true;
65
78
  }
@@ -92,6 +105,10 @@ export function saveDaemonConfig(value) {
92
105
  || current.aun?.defaultAidDomain !== value.aun?.defaultAidDomain;
93
106
  const normalized = normalizeProcessConfigCompat(normalizeDaemonServices(stripLegacyAunGatewayConfig(value, configPath, message => logger.warn(message))));
94
107
  atomicWriteJson(configPath, validateDaemonConfig({ ...normalized, $schema_version: currentVersion('daemon') }, validateAidDomain));
108
+ try {
109
+ registerFileIntegrity(configPath);
110
+ }
111
+ catch { /* best-effort */ }
95
112
  }
96
113
  function validateDaemonConfig(value, validateAidDomain) {
97
114
  if (value.restartRecovery !== undefined) {
@@ -118,6 +135,19 @@ function validateDaemonConfig(value, validateAidDomain) {
118
135
  throw new Error('daemon.json.skills.watch must be a boolean');
119
136
  }
120
137
  }
138
+ if (value.delegationDiagnostics !== undefined) {
139
+ if (!value.delegationDiagnostics || typeof value.delegationDiagnostics !== 'object' || Array.isArray(value.delegationDiagnostics)) {
140
+ throw new Error('daemon.json.delegationDiagnostics must be an object');
141
+ }
142
+ const unknownFields = Object.keys(value.delegationDiagnostics)
143
+ .filter(key => key !== 'enabled');
144
+ if (unknownFields.length > 0) {
145
+ throw new Error('daemon.json.delegationDiagnostics contains unsupported fields: ' + unknownFields.join(', '));
146
+ }
147
+ if (value.delegationDiagnostics.enabled !== undefined && typeof value.delegationDiagnostics.enabled !== 'boolean') {
148
+ throw new Error('daemon.json.delegationDiagnostics.enabled must be a boolean');
149
+ }
150
+ }
121
151
  const minEvolVersion = value.aun?.minEvolVersion;
122
152
  if (minEvolVersion !== undefined && !parseStableSemver(minEvolVersion)) {
123
153
  throw new Error('daemon.json.aun.minEvolVersion must use stable X.Y.Z format');
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { buildToolLifecycleEventKey, normalizeToolCorrelation } from './event-key.js';
5
5
  // Logger rotation is hourly/daily, but the suffix is intentionally generic so
6
6
  // this check also covers future rotation formats without changing the audit.
7
- const STRUCTURED_LOG_RE = /^(?:daemon|events|channel-in|channel-out|messages|command-audit)(?:-[^.]+)?\.log$/;
7
+ const STRUCTURED_LOG_RE = /^(?:daemon|events|channel-in|channel-out|messages|command-audit|delegation-audit)(?:-[^.]+)?\.log$/;
8
8
  const MIXED_TEXT_LOG_RE = /^daemon(?:-[^.]+)?\.log$/;
9
9
  const LEGACY_JSONL_FILE = /\.jsonl$/i;
10
10
  /** Validate and de-duplicate structured event streams by lifecycle identity. */
@@ -80,6 +80,8 @@ export function inspectStructuredLogs(logDir, options = {}) {
80
80
  return { files: files.length, records: records.length, uniqueRecords: records.length - duplicates, duplicates, issues };
81
81
  }
82
82
  function structuredRecordKey(value) {
83
+ if (typeof value.eventId === 'string' && value.eventId.startsWith('del-audit-'))
84
+ return value.eventId;
83
85
  const nested = nestedRecord(value);
84
86
  const eventKey = firstDefined(value, nested, ['eventKey', 'event_key']);
85
87
  const eventPhase = recordPhase(value);
@@ -6,6 +6,7 @@ import { logger } from '../../utils/logger.js';
6
6
  import { canonicalizeEvolcoreCommandArgv } from '../permission/ec-command-parser.js';
7
7
  import { MAIN_CLI_BIN } from '../../product.js';
8
8
  import { DebugDelegationRegistry, DEBUG_TOKEN_PREFIX } from './debug-context.js';
9
+ import { DelegationDiagnostics, } from './delegation-diagnostics.js';
9
10
  export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
10
11
  export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
11
12
  export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
@@ -22,10 +23,15 @@ export function readDelegationCredential(env = process.env) {
22
23
  }
23
24
  export class AgentDelegationRegistry {
24
25
  runtimeEpoch = crypto.randomBytes(16).toString('hex');
26
+ diagnostics;
25
27
  grantsByHash = new Map();
26
28
  activeHashBySession = new Map();
29
+ sessionTokenHashesBySession = new Map();
27
30
  approvedCommands = new Map();
28
31
  debugRegistry = new DebugDelegationRegistry();
32
+ constructor() {
33
+ this.diagnostics = new DelegationDiagnostics(this.runtimeEpoch);
34
+ }
29
35
  getRuntimeEpoch() {
30
36
  return this.runtimeEpoch;
31
37
  }
@@ -34,13 +40,45 @@ export class AgentDelegationRegistry {
34
40
  if (input.executionIdentity && !isValidFullAccessExecutionIdentity(input.executionIdentity)) {
35
41
  throw new Error('invalid fullaccess task execution identity');
36
42
  }
37
- this.revokeSession(input.sessionId);
43
+ this.revokeSession(input.sessionId, 'replaced');
38
44
  const token = crypto.randomBytes(32).toString('base64url');
39
45
  const tokenHash = hashDelegationToken(token);
40
46
  this.grantsByHash.set(tokenHash, { ...input, tokenHash });
41
47
  this.activeHashBySession.set(input.sessionId, tokenHash);
48
+ this.diagnostics.put('task.issued', {
49
+ kind: 'task', tokenHash, sessionId: input.sessionId, taskId: input.taskId,
50
+ runtimeEpoch: this.runtimeEpoch, status: 'active', credentialType: 'task',
51
+ ...(input.selfAid ? { selfAid: input.selfAid } : {}),
52
+ ...(input.peerKey ? { peerKey: input.peerKey } : {}),
53
+ ...(input.actorId ? { actorId: input.actorId } : {}),
54
+ ...(input.issuedRole ? { issuedRole: input.issuedRole } : {}),
55
+ ...(input.permissionMode ? { permissionMode: input.permissionMode } : {}),
56
+ ...(input.messageId ? { messageId: input.messageId } : {}),
57
+ });
42
58
  return token;
43
59
  }
60
+ registerSessionCredential(input) {
61
+ const activeTokenHash = this.activeHashBySession.get(input.sessionId);
62
+ const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
63
+ if (!grant || grant.taskId !== input.taskId || !input.sessionToken)
64
+ return false;
65
+ const tokenHash = hashDelegationToken(input.sessionToken);
66
+ let sessionTokens = this.sessionTokenHashesBySession.get(input.sessionId);
67
+ if (!sessionTokens) {
68
+ sessionTokens = new Set();
69
+ this.sessionTokenHashesBySession.set(input.sessionId, sessionTokens);
70
+ }
71
+ sessionTokens.add(tokenHash);
72
+ this.diagnostics.put('session.bound', {
73
+ kind: 'session', tokenHash,
74
+ sessionId: input.sessionId, taskId: input.taskId, runtimeEpoch: this.runtimeEpoch,
75
+ status: 'active', credentialType: 'codex-session', activeTokenHash,
76
+ threadId: input.threadId, selfAid: grant.selfAid, peerKey: grant.peerKey,
77
+ actorId: grant.actorId, issuedRole: grant.issuedRole, permissionMode: grant.permissionMode,
78
+ ...(grant.messageId ? { messageId: grant.messageId } : {}),
79
+ });
80
+ return true;
81
+ }
44
82
  /** 发行 debug token(dbg: 前缀),仅 debug 模式下调用 */
45
83
  issueDebug(input) {
46
84
  return this.debugRegistry.issue(input);
@@ -60,12 +98,7 @@ export class AgentDelegationRegistry {
60
98
  }
61
99
  validateToken(token, sessionId, commandHash, consumeApproval) {
62
100
  if (!token) {
63
- return {
64
- ok: false,
65
- code: 'DELEGATION_REQUIRED',
66
- reason: 'An active task delegation token is required',
67
- diagnosticCause: 'missing_token',
68
- };
101
+ return this.diagnostics.failure('missing_token', sessionId);
69
102
  }
70
103
  const tokenHash = hashDelegationToken(token);
71
104
  const grant = this.grantsByHash.get(tokenHash);
@@ -77,10 +110,11 @@ export class AgentDelegationRegistry {
77
110
  assertAgentPolicyAuthorization(grant.executionIdentity);
78
111
  }
79
112
  catch {
80
- this.revokeSession(sessionId);
81
- return { ok: false, code: 'INVALID_DELEGATION', reason: 'Agent fullaccess policy was revoked', diagnosticCause: 'task_replaced' };
113
+ this.revokeSession(sessionId, 'policy_revoked');
114
+ return this.diagnostics.failure('task_revoked', sessionId, tokenHash);
82
115
  }
83
116
  }
117
+ this.diagnostics.accepted(sessionId, tokenHash, commandHash);
84
118
  return { ok: true, grant };
85
119
  }
86
120
  const approved = commandHash
@@ -88,20 +122,9 @@ export class AgentDelegationRegistry {
88
122
  : undefined;
89
123
  if (approved)
90
124
  return { ok: true, grant: approved };
91
- const diagnosticCause = grant
92
- ? grant.sessionId !== sessionId
93
- ? 'session_mismatch'
94
- : 'task_replaced'
95
- : commandHash
96
- ? 'approved_command_unavailable'
97
- : 'unknown_token';
125
+ const diagnosticCause = this.diagnostics.classify(tokenHash, sessionId, commandHash);
98
126
  logger.warn(`[AgentDelegation] validate failed: cause=${diagnosticCause} session=${sessionId} grantSession=${grant?.sessionId ?? 'none'} taskId=${grant?.taskId ?? 'none'} hasCommandHash=${!!commandHash} tokenKnown=${!!grant} activeForSession=${this.activeHashBySession.get(sessionId) ?? 'none'} grantTokenHash=${grant?.tokenHash ?? 'none'}`);
99
- return {
100
- ok: false,
101
- code: 'INVALID_DELEGATION',
102
- reason: 'The task delegation token is invalid, revoked, or belongs to another session',
103
- diagnosticCause,
104
- };
127
+ return this.diagnostics.failure(diagnosticCause, sessionId, tokenHash, commandHash);
105
128
  }
106
129
  armApprovedCommand(input) {
107
130
  const activeTokenHash = this.activeHashBySession.get(input.sessionId);
@@ -115,10 +138,15 @@ export class AgentDelegationRegistry {
115
138
  return false;
116
139
  this.deleteExpiredApprovedCommands();
117
140
  const sessionTokenHash = hashDelegationToken(sessionToken);
141
+ const credential = this.diagnostics.find('session', sessionTokenHash, input.sessionId);
142
+ if (credential?.taskId !== input.taskId || credential.runtimeEpoch !== this.runtimeEpoch) {
143
+ this.registerSessionCredential({ sessionToken, sessionId: input.sessionId, taskId: input.taskId });
144
+ }
118
145
  const key = approvedCommandKey(sessionTokenHash, input.sessionId, input.commandHash);
119
146
  const existing = this.approvedCommands.get(key);
120
147
  const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
121
148
  this.approvedCommands.set(key, {
149
+ sessionTokenHash,
122
150
  sessionId: input.sessionId,
123
151
  taskId: input.taskId,
124
152
  activeTokenHash,
@@ -130,25 +158,42 @@ export class AgentDelegationRegistry {
130
158
  ? existing.remainingUses + 1
131
159
  : 1,
132
160
  });
161
+ this.recordApproval('approval.issued', this.approvedCommands.get(key), 'active');
133
162
  return true;
134
163
  }
135
- revokeTask(sessionId, taskId) {
164
+ revokeTask(sessionId, taskId, reason = 'revoked') {
136
165
  const tokenHash = this.activeHashBySession.get(sessionId);
137
166
  if (!tokenHash)
138
167
  return;
139
168
  const grant = this.grantsByHash.get(tokenHash);
140
169
  if (!grant || grant.taskId !== taskId)
141
170
  return;
142
- this.grantsByHash.delete(tokenHash);
143
- this.activeHashBySession.delete(sessionId);
144
- this.deleteApprovedCommandsForSession(sessionId);
171
+ this.revokeSession(sessionId, reason);
145
172
  }
146
- revokeSession(sessionId) {
173
+ revokeSession(sessionId, reason = 'revoked') {
147
174
  const tokenHash = this.activeHashBySession.get(sessionId);
148
- if (tokenHash)
175
+ for (const sessionTokenHash of this.sessionTokenHashesBySession.get(sessionId) ?? []) {
176
+ const credential = this.diagnostics.find('session', sessionTokenHash, sessionId);
177
+ if (credential)
178
+ this.diagnostics.put('session.revoked', {
179
+ ...credential, status: reason === 'replaced' ? 'replaced' : 'revoked', revocationReason: reason,
180
+ });
181
+ }
182
+ this.sessionTokenHashesBySession.delete(sessionId);
183
+ if (tokenHash) {
184
+ const grant = this.grantsByHash.get(tokenHash);
185
+ if (grant)
186
+ this.diagnostics.put('task.revoked', {
187
+ kind: 'task', tokenHash, sessionId, taskId: grant.taskId, runtimeEpoch: this.runtimeEpoch,
188
+ status: reason === 'replaced' ? 'replaced' : 'revoked', revocationReason: reason,
189
+ credentialType: 'task', selfAid: grant.selfAid, peerKey: grant.peerKey,
190
+ actorId: grant.actorId, issuedRole: grant.issuedRole, permissionMode: grant.permissionMode,
191
+ ...(grant.messageId ? { messageId: grant.messageId } : {}),
192
+ });
149
193
  this.grantsByHash.delete(tokenHash);
194
+ }
150
195
  this.activeHashBySession.delete(sessionId);
151
- this.deleteApprovedCommandsForSession(sessionId);
196
+ this.deleteApprovedCommandsForSession(sessionId, reason);
152
197
  }
153
198
  readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
154
199
  if (!isDelegationCommandHash(commandHash))
@@ -158,6 +203,7 @@ export class AgentDelegationRegistry {
158
203
  if (!approved)
159
204
  return undefined;
160
205
  if (approved.expiresAt <= Date.now()) {
206
+ this.recordApproval('approval.expired', approved, 'expired');
161
207
  this.approvedCommands.delete(key);
162
208
  return undefined;
163
209
  }
@@ -166,30 +212,57 @@ export class AgentDelegationRegistry {
166
212
  if (!grant
167
213
  || activeTokenHash !== approved.activeTokenHash
168
214
  || grant.taskId !== approved.taskId) {
215
+ this.recordApproval('approval.revoked', approved, 'replaced', 'replaced');
169
216
  this.approvedCommands.delete(key);
170
217
  return undefined;
171
218
  }
219
+ this.diagnostics.accepted(sessionId, sessionTokenHash, commandHash);
172
220
  if (consume) {
173
- if (approved.remainingUses <= 1)
221
+ approved.remainingUses--;
222
+ this.recordApproval('approval.consumed', approved, approved.remainingUses <= 0 ? 'consumed' : 'active');
223
+ if (approved.remainingUses <= 0)
174
224
  this.approvedCommands.delete(key);
175
- else
176
- approved.remainingUses--;
177
225
  }
178
226
  return grant;
179
227
  }
180
228
  deleteExpiredApprovedCommands() {
181
229
  const now = Date.now();
182
230
  for (const [key, approved] of this.approvedCommands) {
183
- if (approved.expiresAt <= now)
231
+ if (approved.expiresAt <= now) {
232
+ this.recordApproval('approval.expired', approved, 'expired');
184
233
  this.approvedCommands.delete(key);
234
+ }
185
235
  }
186
236
  }
187
- deleteApprovedCommandsForSession(sessionId) {
237
+ deleteApprovedCommandsForSession(sessionId, reason) {
188
238
  for (const [key, approved] of this.approvedCommands) {
189
- if (approved.sessionId === sessionId)
239
+ if (approved.sessionId === sessionId) {
240
+ this.recordApproval('approval.revoked', approved, reason === 'replaced' ? 'replaced' : 'revoked', reason);
190
241
  this.approvedCommands.delete(key);
242
+ }
191
243
  }
192
244
  }
245
+ recordApproval(event, approved, status, revocationReason) {
246
+ this.diagnostics.put(event, {
247
+ kind: 'approval', tokenHash: approved.sessionTokenHash, sessionId: approved.sessionId,
248
+ taskId: approved.taskId, runtimeEpoch: this.runtimeEpoch, status, credentialType: 'codex-session',
249
+ activeTokenHash: approved.activeTokenHash, commandHash: approved.commandHash,
250
+ expiresAt: approved.expiresAt, remainingUses: approved.remainingUses,
251
+ ...(revocationReason ? { revocationReason } : {}),
252
+ });
253
+ }
254
+ recordDiagnosticFailure(cause, sessionId, token, commandHash) {
255
+ const tokenHash = token ? hashDelegationToken(token) : undefined;
256
+ let diagnosticCause = cause;
257
+ if (cause === 'command_hash_mismatch') {
258
+ diagnosticCause = !tokenHash ? 'missing_token'
259
+ : sessionId ? this.diagnostics.classifyCommandHashMismatch(tokenHash, sessionId) : 'unknown_token';
260
+ }
261
+ return this.diagnostics.failure(diagnosticCause, sessionId, tokenHash, commandHash);
262
+ }
263
+ close() {
264
+ this.diagnostics.close();
265
+ }
193
266
  }
194
267
  export function authorizeDelegatedAunMsgSend(registry, input) {
195
268
  if (!input.sessionId) {