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.
- package/CHANGELOG.md +37 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +12 -1
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +82 -13
- package/dist/agents/codex/diagnostics.js +37 -11
- package/dist/agents/codex-runner.js +218 -44
- package/dist/agents/ecagent-runner.js +73 -20
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/channels/aun.js +16 -6
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +3 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +120 -19
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/index.js +20 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/memory-command.js +158 -145
- package/dist/cli/thread-command.js +5 -3
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +1 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +138 -4
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +22 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +36 -1
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +35 -14
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation.js +91 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +2 -1
- package/dist/core/message/response-engine.js +584 -47
- package/dist/core/permission/ec-command-parser.js +93 -4
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +75 -5
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/thread-service.js +13 -20
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +2 -2
- package/kits/docs/evolcore/group.md +10 -10
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +18 -14
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/thread-chain.md +5 -5
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.json +0 -20
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +12 -15
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +4 -4
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- 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
|
}
|
package/dist/config-store.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|