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.
Files changed (214) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +135 -37
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +111 -15
  6. package/dist/agents/codex/app-server-client.js +3 -0
  7. package/dist/agents/codex/diagnostics.js +112 -54
  8. package/dist/agents/codex-runner.js +241 -63
  9. package/dist/agents/ecagent-runner.js +78 -33
  10. package/dist/agents/runner-types.js +5 -6
  11. package/dist/aun/aid/identity.js +1 -1
  12. package/dist/aun/group/operations.js +0 -1
  13. package/dist/aun/msg/history.js +1 -0
  14. package/dist/aun/msg/p2p.js +0 -1
  15. package/dist/aun/outbox.js +50 -1
  16. package/dist/channels/aun.js +44 -8
  17. package/dist/channels/daemon.js +261 -3
  18. package/dist/cli/agent-command.js +58 -24
  19. package/dist/cli/agent.js +1 -1
  20. package/dist/cli/aun-commands.js +368 -31
  21. package/dist/cli/contact.js +8 -2
  22. package/dist/cli/ctl-command.js +3 -1
  23. package/dist/cli/daemon-commands.js +197 -27
  24. package/dist/cli/debug-mode.js +154 -0
  25. package/dist/cli/fs-command.js +2 -1
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +98 -67
  28. package/dist/cli/init.js +2 -2
  29. package/dist/cli/integrity-command.js +196 -0
  30. package/dist/cli/managed-command-guard.js +5 -0
  31. package/dist/cli/memory-command.js +166 -146
  32. package/dist/cli/queue-command.js +9 -4
  33. package/dist/cli/session-command.js +194 -0
  34. package/dist/cli/skill-command.js +78 -2
  35. package/dist/cli/task-context.js +1 -0
  36. package/dist/cli/thread-command.js +622 -0
  37. package/dist/cli/trigger-command.js +10 -3
  38. package/dist/config/builtin-roles.js +1 -0
  39. package/dist/config/config-field-policy.js +6 -3
  40. package/dist/config/config-manager.js +26 -2
  41. package/dist/config/contact-book-store.js +5 -0
  42. package/dist/config/resolved-config-op.js +3 -0
  43. package/dist/config/role-service.js +13 -0
  44. package/dist/config/runtime-env.js +16 -0
  45. package/dist/config-store.js +45 -0
  46. package/dist/core/audit/log-integrity.js +3 -1
  47. package/dist/core/auth/agent-delegation.js +133 -36
  48. package/dist/core/auth/debug-context.js +139 -0
  49. package/dist/core/auth/delegation-diagnostics.js +414 -0
  50. package/dist/core/auth/operation-authorizer.js +25 -2
  51. package/dist/core/auth/operation-catalog.js +49 -0
  52. package/dist/core/background-session/session-cleanup.js +212 -0
  53. package/dist/core/background-session/types.js +10 -0
  54. package/dist/core/bootstrap-messages.js +1 -1
  55. package/dist/core/bootstrap-service.js +6 -3
  56. package/dist/core/command/agent-control.js +18 -26
  57. package/dist/core/command/command-handler.js +197 -27
  58. package/dist/core/command/daemon-diagnostics.js +1 -0
  59. package/dist/core/command/memory-slash.js +6 -2
  60. package/dist/core/command/menu-catalog.js +51 -1
  61. package/dist/core/command/menu-handler.js +114 -23
  62. package/dist/core/command/menu-protocol.js +25 -1
  63. package/dist/core/command/skill-slash.js +322 -0
  64. package/dist/core/command/slash-gate.js +6 -4
  65. package/dist/core/command/slash-handler.js +195 -13
  66. package/dist/core/command/topic-slash.js +528 -0
  67. package/dist/core/daemon-bootstrap.js +4 -7
  68. package/dist/core/evolagent-registry.js +20 -5
  69. package/dist/core/file-integrity/read.js +61 -0
  70. package/dist/core/file-integrity/store.js +262 -0
  71. package/dist/core/handoff/runtime.js +5 -11
  72. package/dist/core/handoff/store.js +5 -0
  73. package/dist/core/ipc-registration.js +175 -3
  74. package/dist/core/memory/batch-meta.js +132 -0
  75. package/dist/core/memory/consolidation-context.js +83 -29
  76. package/dist/core/memory/consolidation-ops-records.js +49 -16
  77. package/dist/core/memory/consolidation-queue-format.js +21 -0
  78. package/dist/core/memory/consolidation.js +264 -205
  79. package/dist/core/memory/index-manager.js +19 -28
  80. package/dist/core/memory/memory-admin.js +19 -14
  81. package/dist/core/memory/memory-document.js +53 -9
  82. package/dist/core/memory/memory-writer.js +30 -15
  83. package/dist/core/message/aun-ipc-sender.js +161 -6
  84. package/dist/core/message/im-renderer.js +169 -10
  85. package/dist/core/message/message-bridge.js +15 -4
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/peer-mode.js +6 -1
  88. package/dist/core/message/response-engine.js +929 -110
  89. package/dist/core/message/subtask-tool-projection.js +140 -0
  90. package/dist/core/model/catalog-service.js +17 -12
  91. package/dist/core/permission/ec-command-parser.js +217 -9
  92. package/dist/core/permission/protected-paths.js +5 -1
  93. package/dist/core/permission/readonly-shell-query.js +43 -5
  94. package/dist/core/permission/session-path-grants.js +334 -0
  95. package/dist/core/permission/tool-canonical.js +226 -0
  96. package/dist/core/permission/tool-policy.js +30 -62
  97. package/dist/core/role/runtime-policy.js +21 -10
  98. package/dist/core/session/session-fs-store.js +123 -8
  99. package/dist/core/session/session-idle-monitor.js +249 -22
  100. package/dist/core/session/session-manager.js +129 -14
  101. package/dist/core/skill/command-parser.js +13 -0
  102. package/dist/core/skill/index-generator.js +21 -4
  103. package/dist/core/skill/startup.js +41 -0
  104. package/dist/core/skill/switch-resolver.js +55 -29
  105. package/dist/core/system-session/grants.js +40 -0
  106. package/dist/core/system-session/memory.js +222 -0
  107. package/dist/core/system-session/registry.js +74 -0
  108. package/dist/core/system-session/types.js +14 -0
  109. package/dist/core/thread/command-parser.js +125 -0
  110. package/dist/core/thread/index.js +1 -0
  111. package/dist/core/thread/ipc-operation.js +95 -0
  112. package/dist/core/thread/thread-context-storage.js +131 -0
  113. package/dist/core/thread/thread-operation.js +258 -0
  114. package/dist/core/thread/thread-route.js +33 -0
  115. package/dist/core/thread/thread-service.js +770 -0
  116. package/dist/core/thread/types.js +1 -0
  117. package/dist/eck/kit-renderer.js +7 -2
  118. package/dist/eck/manifest-engine.js +24 -4
  119. package/dist/eck/message-renderer.js +35 -0
  120. package/dist/index.js +19 -7
  121. package/dist/ipc.js +140 -50
  122. package/dist/paths.js +93 -0
  123. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  124. package/dist/stats/billing.js +20 -8
  125. package/dist/trigger/authorization.js +57 -28
  126. package/dist/trigger/feedback.js +13 -2
  127. package/dist/trigger/parser.js +70 -1
  128. package/dist/trigger/system-definitions.js +3 -3
  129. package/dist/trigger/validation.js +14 -0
  130. package/dist/utils/cross-platform.js +11 -2
  131. package/dist/utils/error-dict.json +18 -4
  132. package/dist/utils/error-utils.js +143 -1
  133. package/dist/utils/instance-registry.js +13 -8
  134. package/dist/utils/log-writer.js +12 -0
  135. package/dist/utils/process-introspect.js +11 -0
  136. package/kits/docs/context-assembly.md +1 -1
  137. package/kits/docs/evolcore/INDEX.md +1 -0
  138. package/kits/docs/evolcore/agent-create.md +40 -28
  139. package/kits/docs/evolcore/agent.md +21 -6
  140. package/kits/docs/evolcore/config.md +2 -2
  141. package/kits/docs/evolcore/ctl.md +2 -2
  142. package/kits/docs/evolcore/ec-capabilities.md +5 -1
  143. package/kits/docs/evolcore/fs-architecture.md +4 -5
  144. package/kits/docs/evolcore/fs.md +1 -1
  145. package/kits/docs/evolcore/group-collaboration.md +4 -6
  146. package/kits/docs/evolcore/group.md +11 -12
  147. package/kits/docs/evolcore/memory-architecture.md +4 -5
  148. package/kits/docs/evolcore/memory-consolidation.md +19 -15
  149. package/kits/docs/evolcore/memory-tags.md +3 -1
  150. package/kits/docs/evolcore/memory.md +24 -12
  151. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  152. package/kits/docs/evolcore/menu-protocol.md +7 -0
  153. package/kits/docs/evolcore/model.md +16 -0
  154. package/kits/docs/evolcore/msg.md +8 -11
  155. package/kits/docs/evolcore/thread-chain.md +267 -0
  156. package/kits/docs/prompt-loading-architecture.md +1 -1
  157. package/kits/eck_manifest.json +55 -40
  158. package/kits/eck_manifest.memory.json +0 -12
  159. package/kits/eck_message_manifest.json +169 -10
  160. package/kits/rules/01-overview.md +11 -0
  161. package/kits/rules/03-identity.md +6 -3
  162. package/kits/rules/05-venue.md +1 -1
  163. package/kits/rules/06-channel.md +1 -1
  164. package/kits/rules/07-memory-and-thread.md +199 -0
  165. package/kits/schemas/_meta.json +7 -1
  166. package/kits/schemas/agent-config.schema.13.json +1 -1
  167. package/kits/schemas/daemon.schema.7.json +140 -0
  168. package/kits/schemas/relation-config.schema.9.json +18 -3
  169. package/kits/schemas/role-config.schema.1.json +6 -6
  170. package/kits/schemas/single-session.schema.1.json +6 -6
  171. package/kits/schemas/single-session.schema.2.json +6 -6
  172. package/kits/schemas/single-session.schema.3.json +6 -6
  173. package/kits/skills/.switches.json +12 -1
  174. package/kits/skills/ec-group/SKILL.md +234 -0
  175. package/kits/skills/ec-skill/SKILL.md +10 -1
  176. package/kits/skills/ec-skill/scripts/cli.mjs +16 -2
  177. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  178. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  179. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  180. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  181. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  182. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  183. package/kits/skills/thread-ops/SKILL.md +492 -0
  184. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  185. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  186. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  187. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  188. package/kits/templates/bootstrap-welcome.md +7 -7
  189. package/kits/templates/memory-consolidation/forget.md +7 -2
  190. package/kits/templates/memory-consolidation/generalize.md +9 -4
  191. package/kits/templates/memory-consolidation/notes.md +11 -11
  192. package/kits/templates/memory-consolidation/profile.md +8 -3
  193. package/kits/templates/memory-consolidation/promote.md +8 -3
  194. package/kits/templates/memory-consolidation/workflow.md +6 -6
  195. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  196. package/kits/templates/message-fragments/item.md +2 -1
  197. package/kits/templates/message-fragments/thread-context.md +6 -0
  198. package/kits/templates/message-fragments/trigger-session.md +2 -0
  199. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  200. package/kits/templates/roles/member.json +2 -0
  201. package/kits/templates/roles/visitor.json +2 -0
  202. package/kits/templates/system-fragments/bootstrap.md +67 -72
  203. package/kits/templates/system-fragments/channel.md +3 -6
  204. package/kits/templates/system-fragments/commands.md +1 -0
  205. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  206. package/kits/templates/system-fragments/memory.md +16 -9
  207. package/kits/templates/system-fragments/reply.md +7 -6
  208. package/kits/templates/system-fragments/session.md +5 -14
  209. package/package.json +6 -3
  210. package/dist/core/memory/memory-session-policy.js +0 -41
  211. package/dist/core/memory/memory-signature.js +0 -257
  212. package/kits/docs/evolcore/group-fs.md +0 -17
  213. package/kits/docs/venues/aun-group.md +0 -45
  214. 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
- out.push(mkRoute(s, target, f));
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
  }
@@ -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 { 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);
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 = grant
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.grantsByHash.delete(tokenHash);
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
- 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
+ });
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
- if (approved.remainingUses <= 1)
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
- const subject = { ...builtSubject, peerKey: grant.peerKey };
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: {