evolcore 0.0.32 → 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 +61 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +135 -37
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +111 -15
- package/dist/agents/codex/app-server-client.js +3 -0
- package/dist/agents/codex/diagnostics.js +112 -54
- package/dist/agents/codex-runner.js +241 -63
- package/dist/agents/ecagent-runner.js +78 -33
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/aun/group/operations.js +0 -1
- package/dist/aun/msg/history.js +1 -0
- package/dist/aun/msg/p2p.js +0 -1
- package/dist/aun/outbox.js +50 -1
- package/dist/channels/aun.js +44 -8
- package/dist/channels/daemon.js +261 -3
- package/dist/cli/agent-command.js +58 -24
- package/dist/cli/agent.js +1 -1
- package/dist/cli/aun-commands.js +368 -31
- package/dist/cli/contact.js +8 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +197 -27
- package/dist/cli/debug-mode.js +154 -0
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +98 -67
- package/dist/cli/init.js +2 -2
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/managed-command-guard.js +5 -0
- package/dist/cli/memory-command.js +166 -146
- package/dist/cli/queue-command.js +9 -4
- package/dist/cli/session-command.js +194 -0
- package/dist/cli/skill-command.js +78 -2
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +622 -0
- package/dist/cli/trigger-command.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +6 -3
- package/dist/config/config-manager.js +26 -2
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +45 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +133 -36
- package/dist/core/auth/debug-context.js +139 -0
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +25 -2
- package/dist/core/auth/operation-catalog.js +49 -0
- package/dist/core/background-session/session-cleanup.js +212 -0
- package/dist/core/background-session/types.js +10 -0
- package/dist/core/bootstrap-messages.js +1 -1
- package/dist/core/bootstrap-service.js +6 -3
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +197 -27
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/memory-slash.js +6 -2
- package/dist/core/command/menu-catalog.js +51 -1
- package/dist/core/command/menu-handler.js +114 -23
- package/dist/core/command/menu-protocol.js +25 -1
- package/dist/core/command/skill-slash.js +322 -0
- package/dist/core/command/slash-gate.js +6 -4
- package/dist/core/command/slash-handler.js +195 -13
- package/dist/core/command/topic-slash.js +528 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/evolagent-registry.js +20 -5
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/handoff/runtime.js +5 -11
- package/dist/core/handoff/store.js +5 -0
- package/dist/core/ipc-registration.js +175 -3
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +83 -29
- package/dist/core/memory/consolidation-ops-records.js +49 -16
- package/dist/core/memory/consolidation-queue-format.js +21 -0
- package/dist/core/memory/consolidation.js +264 -205
- 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/aun-ipc-sender.js +161 -6
- package/dist/core/message/im-renderer.js +169 -10
- package/dist/core/message/message-bridge.js +15 -4
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/peer-mode.js +6 -1
- package/dist/core/message/response-engine.js +929 -110
- package/dist/core/message/subtask-tool-projection.js +140 -0
- package/dist/core/model/catalog-service.js +17 -12
- package/dist/core/permission/ec-command-parser.js +217 -9
- 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-idle-monitor.js +249 -22
- package/dist/core/session/session-manager.js +129 -14
- package/dist/core/skill/command-parser.js +13 -0
- package/dist/core/skill/index-generator.js +21 -4
- package/dist/core/skill/startup.js +41 -0
- package/dist/core/skill/switch-resolver.js +55 -29
- 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/command-parser.js +125 -0
- package/dist/core/thread/index.js +1 -0
- package/dist/core/thread/ipc-operation.js +95 -0
- package/dist/core/thread/thread-context-storage.js +131 -0
- package/dist/core/thread/thread-operation.js +258 -0
- package/dist/core/thread/thread-route.js +33 -0
- package/dist/core/thread/thread-service.js +770 -0
- package/dist/core/thread/types.js +1 -0
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/eck/message-renderer.js +35 -0
- package/dist/index.js +19 -7
- package/dist/ipc.js +140 -50
- package/dist/paths.js +93 -0
- package/dist/response-system/engines/v1/proactive-flow.js +169 -80
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/authorization.js +57 -28
- package/dist/trigger/feedback.js +13 -2
- package/dist/trigger/parser.js +70 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/trigger/validation.js +14 -0
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +143 -1
- 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 +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +40 -28
- package/kits/docs/evolcore/agent.md +21 -6
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +5 -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 +4 -6
- package/kits/docs/evolcore/group.md +11 -12
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +19 -15
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
- package/kits/docs/evolcore/menu-protocol.md +7 -0
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +8 -11
- package/kits/docs/evolcore/thread-chain.md +267 -0
- package/kits/docs/prompt-loading-architecture.md +1 -1
- package/kits/eck_manifest.json +55 -40
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/eck_message_manifest.json +169 -10
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +199 -0
- 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/schemas/relation-config.schema.9.json +18 -3
- package/kits/schemas/role-config.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.1.json +6 -6
- package/kits/schemas/single-session.schema.2.json +6 -6
- package/kits/schemas/single-session.schema.3.json +6 -6
- package/kits/skills/.switches.json +12 -1
- package/kits/skills/ec-group/SKILL.md +234 -0
- package/kits/skills/ec-skill/SKILL.md +10 -1
- package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
- package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
- package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
- package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
- package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
- package/kits/skills/thread-ops/SKILL.md +492 -0
- package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
- package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
- package/kits/skills/wechat-article-reader/SKILL.md +97 -0
- package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
- package/kits/templates/bootstrap-welcome.md +7 -7
- package/kits/templates/memory-consolidation/forget.md +7 -2
- package/kits/templates/memory-consolidation/generalize.md +9 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +8 -3
- package/kits/templates/memory-consolidation/promote.md +8 -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/message-fragments/item.md +2 -1
- package/kits/templates/message-fragments/thread-context.md +6 -0
- package/kits/templates/message-fragments/trigger-session.md +2 -0
- package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
- package/kits/templates/roles/member.json +2 -0
- package/kits/templates/roles/visitor.json +2 -0
- package/kits/templates/system-fragments/bootstrap.md +67 -72
- package/kits/templates/system-fragments/channel.md +3 -6
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/managed-runtime.md +15 -0
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +7 -6
- package/kits/templates/system-fragments/session.md +5 -14
- package/package.json +6 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
- package/kits/docs/evolcore/group-fs.md +0 -17
- package/kits/docs/venues/aun-group.md +0 -45
- package/kits/docs/venues/group.md +0 -43
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { SYSTEM_SESSION_TYPES } from '../core/system-session/types.js';
|
|
1
2
|
const BEHAVIOR_TOP_FIELDS = new Set([
|
|
2
3
|
'active_baseagent',
|
|
3
4
|
'chatmode',
|
|
4
5
|
'mentionMode',
|
|
5
6
|
'autoMentionQuietWindowMs',
|
|
6
|
-
'sessionIdleSec',
|
|
7
7
|
'groupSlashRequireMentions',
|
|
8
8
|
'responseMode',
|
|
9
9
|
'responseModeParams',
|
|
@@ -128,8 +128,6 @@ export function resolveConfigFieldRule(fieldPath, scope) {
|
|
|
128
128
|
return scalar(field, 'mention-mode');
|
|
129
129
|
if (field === 'autoMentionQuietWindowMs')
|
|
130
130
|
return scalar(field, 'nonnegative-number');
|
|
131
|
-
if (field === 'sessionIdleSec')
|
|
132
|
-
return scalar(field, 'finite-number');
|
|
133
131
|
if (field === 'groupSlashRequireMentions')
|
|
134
132
|
return scalar(field, 'boolean');
|
|
135
133
|
if (field === 'show_activities')
|
|
@@ -142,6 +140,8 @@ export function resolveConfigFieldRule(fieldPath, scope) {
|
|
|
142
140
|
return scalar('configSnapshots', 'boolean');
|
|
143
141
|
if (field === 'restartRecovery.enabled')
|
|
144
142
|
return scalar('restartRecovery.enabled', 'boolean');
|
|
143
|
+
if (field === 'skills.watch')
|
|
144
|
+
return scalar('skills.watch', 'boolean');
|
|
145
145
|
if (top === 'chatmode') {
|
|
146
146
|
if (parts.length === 1)
|
|
147
147
|
return readonlyObject('chatmode');
|
|
@@ -184,6 +184,9 @@ export function resolveConfigFieldRule(fieldPath, scope) {
|
|
|
184
184
|
if (top === 'sessionManifests') {
|
|
185
185
|
if (parts.length === 1)
|
|
186
186
|
return readonlyObject('sessionManifests');
|
|
187
|
+
// 系统会话类型的 manifest 由代码决定,不可配置
|
|
188
|
+
if (parts.length === 2 && SYSTEM_SESSION_TYPES.includes(parts[1]))
|
|
189
|
+
return { class: 'unknown' };
|
|
187
190
|
if (parts.length === 2 && /^[A-Za-z0-9_-]+$/.test(parts[1]))
|
|
188
191
|
return scalar('sessionManifests', 'manifest-file');
|
|
189
192
|
return { class: 'unknown' };
|
|
@@ -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 {
|
|
@@ -1464,6 +1483,9 @@ export function routeField(topField, scope) {
|
|
|
1464
1483
|
*/
|
|
1465
1484
|
export function routeFieldPath(fieldPath, scope) {
|
|
1466
1485
|
const topField = fieldPath.split('.')[0];
|
|
1486
|
+
if (topField === 'sessionLoop') {
|
|
1487
|
+
throw new ConfigError('SESSION_LOOP_MANAGED', 'sessionLoop is managed through the dedicated /loop or session Menu entry');
|
|
1488
|
+
}
|
|
1467
1489
|
if (scope === 'process')
|
|
1468
1490
|
return routeIn(DAEMON_SCHEMA_NAME, ConfigTarget.Process, topField);
|
|
1469
1491
|
if (scope === 'defaults') {
|
|
@@ -1541,8 +1563,10 @@ export function listFields(scope) {
|
|
|
1541
1563
|
const out = [];
|
|
1542
1564
|
const add = (name, target) => {
|
|
1543
1565
|
const s = loadSchema(name);
|
|
1544
|
-
for (const f of s.fields.keys())
|
|
1545
|
-
|
|
1566
|
+
for (const f of s.fields.keys()) {
|
|
1567
|
+
if (f !== 'sessionLoop')
|
|
1568
|
+
out.push(mkRoute(s, target, f));
|
|
1569
|
+
}
|
|
1546
1570
|
};
|
|
1547
1571
|
if (scope === 'process') {
|
|
1548
1572
|
add(DAEMON_SCHEMA_NAME, ConfigTarget.Process);
|
|
@@ -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);
|
|
@@ -90,6 +90,9 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
|
|
|
90
90
|
const field = business.values[0].trim();
|
|
91
91
|
if (!field)
|
|
92
92
|
return { ok: false, code: 'MISSING_ARG', reason: `config ${subcommand} requires a field` };
|
|
93
|
+
if (field === 'sessionLoop' || field.startsWith('sessionLoop.')) {
|
|
94
|
+
return { ok: false, code: 'SESSION_LOOP_MANAGED', reason: 'sessionLoop is managed through the dedicated /loop or session Menu entry' };
|
|
95
|
+
}
|
|
93
96
|
if (field === 'permissionMode') {
|
|
94
97
|
return {
|
|
95
98
|
ok: false,
|
|
@@ -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,21 @@ 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
|
+
}
|
|
76
|
+
export function isSkillWatchEnabled(config = loadDaemonConfig()) {
|
|
77
|
+
return config.skills?.watch === true;
|
|
78
|
+
}
|
|
63
79
|
let eckSnapshotsConfigCache;
|
|
64
80
|
/** Parse the process-level snapshot gate once for the current daemon lifecycle. */
|
|
65
81
|
export function initializeEckSnapshotsConfig(config = loadDaemonConfig()) {
|
|
@@ -89,6 +105,10 @@ export function saveDaemonConfig(value) {
|
|
|
89
105
|
|| current.aun?.defaultAidDomain !== value.aun?.defaultAidDomain;
|
|
90
106
|
const normalized = normalizeProcessConfigCompat(normalizeDaemonServices(stripLegacyAunGatewayConfig(value, configPath, message => logger.warn(message))));
|
|
91
107
|
atomicWriteJson(configPath, validateDaemonConfig({ ...normalized, $schema_version: currentVersion('daemon') }, validateAidDomain));
|
|
108
|
+
try {
|
|
109
|
+
registerFileIntegrity(configPath);
|
|
110
|
+
}
|
|
111
|
+
catch { /* best-effort */ }
|
|
92
112
|
}
|
|
93
113
|
function validateDaemonConfig(value, validateAidDomain) {
|
|
94
114
|
if (value.restartRecovery !== undefined) {
|
|
@@ -103,6 +123,31 @@ function validateDaemonConfig(value, validateAidDomain) {
|
|
|
103
123
|
throw new Error('daemon.json.restartRecovery.enabled must be a boolean');
|
|
104
124
|
}
|
|
105
125
|
}
|
|
126
|
+
if (value.skills !== undefined) {
|
|
127
|
+
if (!value.skills || typeof value.skills !== 'object' || Array.isArray(value.skills)) {
|
|
128
|
+
throw new Error('daemon.json.skills must be an object');
|
|
129
|
+
}
|
|
130
|
+
const unknownSkillsFields = Object.keys(value.skills).filter(key => key !== 'watch');
|
|
131
|
+
if (unknownSkillsFields.length > 0) {
|
|
132
|
+
throw new Error('daemon.json.skills contains unsupported fields: ' + unknownSkillsFields.join(', '));
|
|
133
|
+
}
|
|
134
|
+
if (value.skills.watch !== undefined && typeof value.skills.watch !== 'boolean') {
|
|
135
|
+
throw new Error('daemon.json.skills.watch must be a boolean');
|
|
136
|
+
}
|
|
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
|
+
}
|
|
106
151
|
const minEvolVersion = value.aun?.minEvolVersion;
|
|
107
152
|
if (minEvolVersion !== undefined && !parseStableSemver(minEvolVersion)) {
|
|
108
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);
|
|
@@ -5,6 +5,8 @@ import { formatPeerKey, parsePeerKey } from '../relation/peer-identity.js';
|
|
|
5
5
|
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
|
+
import { DebugDelegationRegistry, DEBUG_TOKEN_PREFIX } from './debug-context.js';
|
|
9
|
+
import { DelegationDiagnostics, } from './delegation-diagnostics.js';
|
|
8
10
|
export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
|
|
9
11
|
export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
|
|
10
12
|
export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
|
|
@@ -21,38 +23,82 @@ export function readDelegationCredential(env = process.env) {
|
|
|
21
23
|
}
|
|
22
24
|
export class AgentDelegationRegistry {
|
|
23
25
|
runtimeEpoch = crypto.randomBytes(16).toString('hex');
|
|
26
|
+
diagnostics;
|
|
24
27
|
grantsByHash = new Map();
|
|
25
28
|
activeHashBySession = new Map();
|
|
29
|
+
sessionTokenHashesBySession = new Map();
|
|
26
30
|
approvedCommands = new Map();
|
|
31
|
+
debugRegistry = new DebugDelegationRegistry();
|
|
32
|
+
constructor() {
|
|
33
|
+
this.diagnostics = new DelegationDiagnostics(this.runtimeEpoch);
|
|
34
|
+
}
|
|
27
35
|
getRuntimeEpoch() {
|
|
28
36
|
return this.runtimeEpoch;
|
|
29
37
|
}
|
|
38
|
+
/** 发行正常 task delegation token */
|
|
30
39
|
issue(input) {
|
|
31
40
|
if (input.executionIdentity && !isValidFullAccessExecutionIdentity(input.executionIdentity)) {
|
|
32
41
|
throw new Error('invalid fullaccess task execution identity');
|
|
33
42
|
}
|
|
34
|
-
this.revokeSession(input.sessionId);
|
|
43
|
+
this.revokeSession(input.sessionId, 'replaced');
|
|
35
44
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
36
45
|
const tokenHash = hashDelegationToken(token);
|
|
37
46
|
this.grantsByHash.set(tokenHash, { ...input, tokenHash });
|
|
38
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
|
+
});
|
|
39
58
|
return token;
|
|
40
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
|
+
}
|
|
82
|
+
/** 发行 debug token(dbg: 前缀),仅 debug 模式下调用 */
|
|
83
|
+
issueDebug(input) {
|
|
84
|
+
return this.debugRegistry.issue(input);
|
|
85
|
+
}
|
|
41
86
|
validate(token, sessionId, commandHash) {
|
|
87
|
+
if (token?.startsWith(DEBUG_TOKEN_PREFIX)) {
|
|
88
|
+
return this.debugRegistry.validate(token, sessionId);
|
|
89
|
+
}
|
|
42
90
|
return this.validateToken(token, sessionId, commandHash, true);
|
|
43
91
|
}
|
|
44
92
|
/** Authenticate a CLI context lookup without spending its approved operation. */
|
|
45
93
|
validateForContext(token, sessionId, commandHash) {
|
|
94
|
+
if (token?.startsWith(DEBUG_TOKEN_PREFIX)) {
|
|
95
|
+
return this.debugRegistry.validate(token, sessionId);
|
|
96
|
+
}
|
|
46
97
|
return this.validateToken(token, sessionId, commandHash, false);
|
|
47
98
|
}
|
|
48
99
|
validateToken(token, sessionId, commandHash, consumeApproval) {
|
|
49
100
|
if (!token) {
|
|
50
|
-
return
|
|
51
|
-
ok: false,
|
|
52
|
-
code: 'DELEGATION_REQUIRED',
|
|
53
|
-
reason: 'An active task delegation token is required',
|
|
54
|
-
diagnosticCause: 'missing_token',
|
|
55
|
-
};
|
|
101
|
+
return this.diagnostics.failure('missing_token', sessionId);
|
|
56
102
|
}
|
|
57
103
|
const tokenHash = hashDelegationToken(token);
|
|
58
104
|
const grant = this.grantsByHash.get(tokenHash);
|
|
@@ -64,10 +110,11 @@ export class AgentDelegationRegistry {
|
|
|
64
110
|
assertAgentPolicyAuthorization(grant.executionIdentity);
|
|
65
111
|
}
|
|
66
112
|
catch {
|
|
67
|
-
this.revokeSession(sessionId);
|
|
68
|
-
return
|
|
113
|
+
this.revokeSession(sessionId, 'policy_revoked');
|
|
114
|
+
return this.diagnostics.failure('task_revoked', sessionId, tokenHash);
|
|
69
115
|
}
|
|
70
116
|
}
|
|
117
|
+
this.diagnostics.accepted(sessionId, tokenHash, commandHash);
|
|
71
118
|
return { ok: true, grant };
|
|
72
119
|
}
|
|
73
120
|
const approved = commandHash
|
|
@@ -75,20 +122,9 @@ export class AgentDelegationRegistry {
|
|
|
75
122
|
: undefined;
|
|
76
123
|
if (approved)
|
|
77
124
|
return { ok: true, grant: approved };
|
|
78
|
-
const diagnosticCause =
|
|
79
|
-
? grant.sessionId !== sessionId
|
|
80
|
-
? 'session_mismatch'
|
|
81
|
-
: 'task_replaced'
|
|
82
|
-
: commandHash
|
|
83
|
-
? 'approved_command_unavailable'
|
|
84
|
-
: 'unknown_token';
|
|
125
|
+
const diagnosticCause = this.diagnostics.classify(tokenHash, sessionId, commandHash);
|
|
85
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'}`);
|
|
86
|
-
return
|
|
87
|
-
ok: false,
|
|
88
|
-
code: 'INVALID_DELEGATION',
|
|
89
|
-
reason: 'The task delegation token is invalid, revoked, or belongs to another session',
|
|
90
|
-
diagnosticCause,
|
|
91
|
-
};
|
|
127
|
+
return this.diagnostics.failure(diagnosticCause, sessionId, tokenHash, commandHash);
|
|
92
128
|
}
|
|
93
129
|
armApprovedCommand(input) {
|
|
94
130
|
const activeTokenHash = this.activeHashBySession.get(input.sessionId);
|
|
@@ -102,10 +138,15 @@ export class AgentDelegationRegistry {
|
|
|
102
138
|
return false;
|
|
103
139
|
this.deleteExpiredApprovedCommands();
|
|
104
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
|
+
}
|
|
105
145
|
const key = approvedCommandKey(sessionTokenHash, input.sessionId, input.commandHash);
|
|
106
146
|
const existing = this.approvedCommands.get(key);
|
|
107
147
|
const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
|
|
108
148
|
this.approvedCommands.set(key, {
|
|
149
|
+
sessionTokenHash,
|
|
109
150
|
sessionId: input.sessionId,
|
|
110
151
|
taskId: input.taskId,
|
|
111
152
|
activeTokenHash,
|
|
@@ -117,25 +158,42 @@ export class AgentDelegationRegistry {
|
|
|
117
158
|
? existing.remainingUses + 1
|
|
118
159
|
: 1,
|
|
119
160
|
});
|
|
161
|
+
this.recordApproval('approval.issued', this.approvedCommands.get(key), 'active');
|
|
120
162
|
return true;
|
|
121
163
|
}
|
|
122
|
-
revokeTask(sessionId, taskId) {
|
|
164
|
+
revokeTask(sessionId, taskId, reason = 'revoked') {
|
|
123
165
|
const tokenHash = this.activeHashBySession.get(sessionId);
|
|
124
166
|
if (!tokenHash)
|
|
125
167
|
return;
|
|
126
168
|
const grant = this.grantsByHash.get(tokenHash);
|
|
127
169
|
if (!grant || grant.taskId !== taskId)
|
|
128
170
|
return;
|
|
129
|
-
this.
|
|
130
|
-
this.activeHashBySession.delete(sessionId);
|
|
131
|
-
this.deleteApprovedCommandsForSession(sessionId);
|
|
171
|
+
this.revokeSession(sessionId, reason);
|
|
132
172
|
}
|
|
133
|
-
revokeSession(sessionId) {
|
|
173
|
+
revokeSession(sessionId, reason = 'revoked') {
|
|
134
174
|
const tokenHash = this.activeHashBySession.get(sessionId);
|
|
135
|
-
|
|
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
|
+
});
|
|
136
193
|
this.grantsByHash.delete(tokenHash);
|
|
194
|
+
}
|
|
137
195
|
this.activeHashBySession.delete(sessionId);
|
|
138
|
-
this.deleteApprovedCommandsForSession(sessionId);
|
|
196
|
+
this.deleteApprovedCommandsForSession(sessionId, reason);
|
|
139
197
|
}
|
|
140
198
|
readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
|
|
141
199
|
if (!isDelegationCommandHash(commandHash))
|
|
@@ -145,6 +203,7 @@ export class AgentDelegationRegistry {
|
|
|
145
203
|
if (!approved)
|
|
146
204
|
return undefined;
|
|
147
205
|
if (approved.expiresAt <= Date.now()) {
|
|
206
|
+
this.recordApproval('approval.expired', approved, 'expired');
|
|
148
207
|
this.approvedCommands.delete(key);
|
|
149
208
|
return undefined;
|
|
150
209
|
}
|
|
@@ -153,29 +212,56 @@ export class AgentDelegationRegistry {
|
|
|
153
212
|
if (!grant
|
|
154
213
|
|| activeTokenHash !== approved.activeTokenHash
|
|
155
214
|
|| grant.taskId !== approved.taskId) {
|
|
215
|
+
this.recordApproval('approval.revoked', approved, 'replaced', 'replaced');
|
|
156
216
|
this.approvedCommands.delete(key);
|
|
157
217
|
return undefined;
|
|
158
218
|
}
|
|
219
|
+
this.diagnostics.accepted(sessionId, sessionTokenHash, commandHash);
|
|
159
220
|
if (consume) {
|
|
160
|
-
|
|
221
|
+
approved.remainingUses--;
|
|
222
|
+
this.recordApproval('approval.consumed', approved, approved.remainingUses <= 0 ? 'consumed' : 'active');
|
|
223
|
+
if (approved.remainingUses <= 0)
|
|
161
224
|
this.approvedCommands.delete(key);
|
|
162
|
-
else
|
|
163
|
-
approved.remainingUses--;
|
|
164
225
|
}
|
|
165
226
|
return grant;
|
|
166
227
|
}
|
|
167
228
|
deleteExpiredApprovedCommands() {
|
|
168
229
|
const now = Date.now();
|
|
169
230
|
for (const [key, approved] of this.approvedCommands) {
|
|
170
|
-
if (approved.expiresAt <= now)
|
|
231
|
+
if (approved.expiresAt <= now) {
|
|
232
|
+
this.recordApproval('approval.expired', approved, 'expired');
|
|
171
233
|
this.approvedCommands.delete(key);
|
|
234
|
+
}
|
|
172
235
|
}
|
|
173
236
|
}
|
|
174
|
-
deleteApprovedCommandsForSession(sessionId) {
|
|
237
|
+
deleteApprovedCommandsForSession(sessionId, reason) {
|
|
175
238
|
for (const [key, approved] of this.approvedCommands) {
|
|
176
|
-
if (approved.sessionId === sessionId)
|
|
239
|
+
if (approved.sessionId === sessionId) {
|
|
240
|
+
this.recordApproval('approval.revoked', approved, reason === 'replaced' ? 'replaced' : 'revoked', reason);
|
|
177
241
|
this.approvedCommands.delete(key);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
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';
|
|
178
260
|
}
|
|
261
|
+
return this.diagnostics.failure(diagnosticCause, sessionId, tokenHash, commandHash);
|
|
262
|
+
}
|
|
263
|
+
close() {
|
|
264
|
+
this.diagnostics.close();
|
|
179
265
|
}
|
|
180
266
|
}
|
|
181
267
|
export function authorizeDelegatedAunMsgSend(registry, input) {
|
|
@@ -211,7 +297,18 @@ export function authorizeDelegatedAunMsgSend(registry, input) {
|
|
|
211
297
|
fromControlChannel: false,
|
|
212
298
|
...trustedExecutionIdentityInput(grant),
|
|
213
299
|
});
|
|
214
|
-
|
|
300
|
+
// 跨话题发送:目标 relation 由话题推断,按目标话题的会话类型和 relation 校验
|
|
301
|
+
// 「当前对端/当前群」约束;角色仍沿用来源会话,不因目标而提权。
|
|
302
|
+
const subject = input.threadRouted
|
|
303
|
+
? {
|
|
304
|
+
...builtSubject,
|
|
305
|
+
chatType: (input.scope === 'group' ? 'group' : 'private'),
|
|
306
|
+
channelId: input.to,
|
|
307
|
+
conversationId: input.to,
|
|
308
|
+
actorId: input.scope === 'group' ? builtSubject.actorId : input.to,
|
|
309
|
+
peerKey: formatPeerKey(grant.channelType, input.to),
|
|
310
|
+
}
|
|
311
|
+
: { ...builtSubject, peerKey: grant.peerKey };
|
|
215
312
|
const decision = authorizeOperation({
|
|
216
313
|
source: 'agent-tool',
|
|
217
314
|
intent: {
|