evolcore 0.0.33 → 0.0.35-test.0

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 (152) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bin/ec.js +3 -2
  3. package/bin/install-codex-managed-hooks.mjs +38 -4
  4. package/dist/agents/baseagent-availability.js +20 -36
  5. package/dist/agents/claude-runner.js +93 -14
  6. package/dist/agents/codex/diagnostics.js +79 -22
  7. package/dist/agents/codex-runner.js +245 -47
  8. package/dist/agents/ecagent-runner.js +90 -27
  9. package/dist/agents/runner-types.js +5 -6
  10. package/dist/aun/aid/agentmd.js +13 -2
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/identity.js +1 -1
  13. package/dist/aun/msg/managed-operation.js +16 -0
  14. package/dist/aun/msg/p2p.js +18 -2
  15. package/dist/channels/aun.js +123 -7
  16. package/dist/channels/daemon.js +6 -3
  17. package/dist/cli/aun-commands.js +39 -14
  18. package/dist/cli/ctl-command.js +3 -1
  19. package/dist/cli/daemon-commands.js +130 -23
  20. package/dist/cli/fs-command.js +2 -1
  21. package/dist/cli/getmeta-command.js +211 -0
  22. package/dist/cli/index.js +26 -6
  23. package/dist/cli/integrity-command.js +196 -0
  24. package/dist/cli/managed-command-guard.js +3 -0
  25. package/dist/cli/memory-command.js +200 -156
  26. package/dist/cli/thread-command.js +17 -7
  27. package/dist/cli/trigger-command.js +6 -1
  28. package/dist/config/config-field-policy.js +4 -0
  29. package/dist/config/config-manager.js +19 -0
  30. package/dist/config/contact-book-store.js +5 -0
  31. package/dist/config/resolved-config-op.js +25 -8
  32. package/dist/config/role-service.js +13 -0
  33. package/dist/config/runtime-env.js +16 -0
  34. package/dist/config-store.js +30 -0
  35. package/dist/core/audit/log-integrity.js +3 -1
  36. package/dist/core/auth/agent-delegation.js +108 -35
  37. package/dist/core/auth/delegation-diagnostics.js +414 -0
  38. package/dist/core/auth/operation-authorizer.js +22 -3
  39. package/dist/core/auth/operation-catalog.js +16 -0
  40. package/dist/core/channel-runtime.js +1 -1
  41. package/dist/core/command/agent-control.js +18 -26
  42. package/dist/core/command/command-handler.js +145 -5
  43. package/dist/core/command/daemon-diagnostics.js +1 -0
  44. package/dist/core/command/menu-catalog.js +3 -0
  45. package/dist/core/command/menu-handler.js +24 -7
  46. package/dist/core/command/menu-protocol.js +1 -0
  47. package/dist/core/command/slash-handler.js +2 -0
  48. package/dist/core/command/topic-slash.js +4 -7
  49. package/dist/core/daemon-bootstrap.js +4 -7
  50. package/dist/core/event-catalog.js +14 -0
  51. package/dist/core/file-integrity/read.js +61 -0
  52. package/dist/core/file-integrity/store.js +262 -0
  53. package/dist/core/ipc-registration.js +47 -2
  54. package/dist/core/memory/batch-meta.js +133 -0
  55. package/dist/core/memory/consolidation-context.js +132 -28
  56. package/dist/core/memory/consolidation-ops-records.js +14 -16
  57. package/dist/core/memory/consolidation-trigger.js +15 -7
  58. package/dist/core/memory/consolidation.js +146 -26
  59. package/dist/core/memory/index-manager.js +19 -28
  60. package/dist/core/memory/memory-admin.js +19 -14
  61. package/dist/core/memory/memory-document.js +53 -9
  62. package/dist/core/memory/memory-writer.js +50 -19
  63. package/dist/core/message/aun-ipc-sender.js +19 -1
  64. package/dist/core/message/im-renderer.js +20 -4
  65. package/dist/core/message/message-bridge.js +5 -4
  66. package/dist/core/message/message-log.js +1 -0
  67. package/dist/core/message/peer-mode.js +17 -0
  68. package/dist/core/message/response-engine.js +598 -50
  69. package/dist/core/permission/ec-command-parser.js +169 -44
  70. package/dist/core/permission/protected-paths.js +5 -1
  71. package/dist/core/permission/readonly-shell-query.js +43 -5
  72. package/dist/core/permission/session-path-grants.js +334 -0
  73. package/dist/core/permission/tool-canonical.js +226 -0
  74. package/dist/core/permission/tool-policy.js +60 -62
  75. package/dist/core/relation/peer-identity.js +87 -12
  76. package/dist/core/role/runtime-policy.js +21 -10
  77. package/dist/core/session/session-fs-store.js +123 -8
  78. package/dist/core/session/session-manager.js +131 -6
  79. package/dist/core/session/session-title.js +26 -1
  80. package/dist/core/skill/index-generator.js +17 -3
  81. package/dist/core/system-session/grants.js +40 -0
  82. package/dist/core/system-session/memory.js +289 -0
  83. package/dist/core/system-session/registry.js +74 -0
  84. package/dist/core/system-session/types.js +14 -0
  85. package/dist/core/thread/command-parser.js +6 -1
  86. package/dist/core/thread/ipc-operation.js +33 -16
  87. package/dist/core/thread/thread-operation.js +62 -24
  88. package/dist/core/thread/thread-service.js +258 -51
  89. package/dist/eck/kit-renderer.js +7 -2
  90. package/dist/eck/manifest-engine.js +24 -4
  91. package/dist/index.js +11 -2
  92. package/dist/ipc.js +51 -40
  93. package/dist/paths.js +70 -0
  94. package/dist/stats/billing.js +20 -8
  95. package/dist/trigger/feedback.js +6 -1
  96. package/dist/trigger/parser.js +8 -1
  97. package/dist/trigger/system-definitions.js +3 -3
  98. package/dist/utils/cross-platform.js +17 -5
  99. package/dist/utils/error-dict.json +18 -4
  100. package/dist/utils/error-utils.js +121 -0
  101. package/dist/utils/instance-registry.js +13 -8
  102. package/dist/utils/log-writer.js +12 -0
  103. package/dist/utils/process-introspect.js +11 -0
  104. package/kits/docs/context-assembly.md +0 -1
  105. package/kits/docs/evolcore/agent-create.md +5 -4
  106. package/kits/docs/evolcore/agent.md +2 -2
  107. package/kits/docs/evolcore/config.md +2 -2
  108. package/kits/docs/evolcore/ctl.md +2 -2
  109. package/kits/docs/evolcore/ec-capabilities.md +1 -1
  110. package/kits/docs/evolcore/fs-architecture.md +9 -10
  111. package/kits/docs/evolcore/fs.md +2 -2
  112. package/kits/docs/evolcore/group-collaboration.md +3 -3
  113. package/kits/docs/evolcore/group.md +14 -12
  114. package/kits/docs/evolcore/memory-architecture.md +4 -5
  115. package/kits/docs/evolcore/memory-consolidation.md +19 -16
  116. package/kits/docs/evolcore/memory-tags.md +3 -1
  117. package/kits/docs/evolcore/memory.md +24 -12
  118. package/kits/docs/evolcore/model.md +16 -0
  119. package/kits/docs/evolcore/msg.md +7 -7
  120. package/kits/docs/evolcore/stats.md +1 -1
  121. package/kits/docs/evolcore/thread-chain.md +30 -14
  122. package/kits/docs/prompt-loading-architecture.md +0 -1
  123. package/kits/eck_manifest.auxiliary.json +9 -0
  124. package/kits/eck_manifest.json +20 -20
  125. package/kits/eck_manifest.memory.json +20 -12
  126. package/kits/rules/01-overview.md +11 -0
  127. package/kits/rules/03-identity.md +6 -3
  128. package/kits/rules/06-channel.md +1 -1
  129. package/kits/rules/07-memory-and-thread.md +13 -16
  130. package/kits/schemas/_meta.json +7 -1
  131. package/kits/schemas/agent-config.schema.13.json +1 -1
  132. package/kits/schemas/daemon.schema.7.json +140 -0
  133. package/kits/skills/ec-group/SKILL.md +8 -5
  134. package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
  135. package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
  136. package/kits/skills/thread-ops/SKILL.md +18 -18
  137. package/kits/templates/memory-consolidation/forget.md +2 -2
  138. package/kits/templates/memory-consolidation/generalize.md +4 -4
  139. package/kits/templates/memory-consolidation/notes.md +12 -12
  140. package/kits/templates/memory-consolidation/profile.md +3 -3
  141. package/kits/templates/memory-consolidation/promote.md +3 -3
  142. package/kits/templates/memory-consolidation/workflow.md +42 -7
  143. package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
  144. package/kits/templates/system-fragments/bash-description.md +9 -0
  145. package/kits/templates/system-fragments/bootstrap.md +4 -3
  146. package/kits/templates/system-fragments/channel.md +2 -2
  147. package/kits/templates/system-fragments/memory.md +16 -9
  148. package/kits/templates/system-fragments/reply.md +6 -6
  149. package/kits/templates/system-fragments/session.md +1 -1
  150. package/package.json +4 -3
  151. package/dist/core/memory/memory-session-policy.js +0 -41
  152. package/dist/core/memory/memory-signature.js +0 -257
@@ -128,9 +128,13 @@ function printItem(item) {
128
128
  const running = obj.running ? ' [运行中]' : '';
129
129
  // 内部话题(daemon 会话记录)不是真实联系人,标注出来避免误认
130
130
  const internal = obj.internal ? ' [内部会话记录·无消息]' : '';
131
- console.log(`${obj.peer}/${obj.threadId}${running}${internal}`);
132
- // 名称只放名称本身:状态、标签各自单独一行,避免污染标题(标题会被 summary set --name 引用)
133
- console.log(` 名称: ${obj.name}`);
131
+ // 主会话的 threadId 是空串;显示成 /main 比留一个空尾巴好读。
132
+ console.log(`${obj.peer}/${obj.threadId || 'main'}${running}${internal}`);
133
+ // 名称只放名称本身:状态、标签各自单独一行,避免污染标题(标题会被 summary set --name 引用)。
134
+ // 主会话(threadId 为空)加 `[主会话]` 前缀,与普通话题区分开;前缀只在展示层加,
135
+ // 不进索引与 session 的 name 字段,避免被 summary set --name 当成真实标题。
136
+ const namePrefix = obj.threadId ? '' : '[主会话] ';
137
+ console.log(` 名称: ${namePrefix}${obj.name}`);
134
138
  if (obj.topicStatus)
135
139
  console.log(` 状态: ${obj.topicStatus}`);
136
140
  if (obj.label)
@@ -150,6 +154,8 @@ function printItem(item) {
150
154
  console.log(` 最后消息: ${obj.lastMsgSnippet}`);
151
155
  if (obj.msgTotal !== undefined)
152
156
  console.log(` 摘要: ${obj.summarySnippet ?? '未总结'}`);
157
+ if (obj.sessionId)
158
+ console.log(` 会话: ${obj.sessionId}`);
153
159
  console.log('');
154
160
  return;
155
161
  }
@@ -228,7 +234,11 @@ export async function runThreadCommand(args) {
228
234
  const slash = ref.lastIndexOf('/');
229
235
  if (slash < 0)
230
236
  return { peer: ref };
231
- return { peer: ref.slice(0, slash), threadId: ref.slice(slash + 1) };
237
+ const raw = ref.slice(slash + 1);
238
+ // 主会话的 threadId 是空串;`<peer>/main` 是同义写法,`list` 也这样显示。
239
+ if (raw === 'main')
240
+ return { peer: ref.slice(0, slash), threadId: '' };
241
+ return { peer: ref.slice(0, slash), threadId: raw };
232
242
  }
233
243
  switch (sub) {
234
244
  case 'list': {
@@ -308,7 +318,7 @@ export async function runThreadCommand(args) {
308
318
  case 'create': {
309
319
  const peer = args[1];
310
320
  if (!peer || peer.startsWith('-')) {
311
- console.error('用法: ec thread create <peer> --name "..."');
321
+ console.error(`用法: ec thread create <peer> --name '...'`);
312
322
  process.exitCode = 1;
313
323
  return;
314
324
  }
@@ -334,7 +344,7 @@ export async function runThreadCommand(args) {
334
344
  const ref = args[1];
335
345
  const newName = args[2];
336
346
  if (!ref || newName === undefined) {
337
- console.error('用法: ec thread rename <ref> "<名称>"');
347
+ console.error(`用法: ec thread rename <ref> '<名称>'`);
338
348
  process.exitCode = 1;
339
349
  return;
340
350
  }
@@ -524,7 +534,7 @@ export async function runThreadCommand(args) {
524
534
  if (newName === undefined) {
525
535
  console.error('✗ 必须指定 --name <新标题>');
526
536
  console.error(' 不改名请显式传 --name -');
527
- console.error(` 例: ec thread summary set ${ref} "摘要内容" --name "话题的核心动作"`);
537
+ console.error(` 例: ec thread summary set ${ref} '<摘要内容>' --name '<话题的核心动作>'`);
528
538
  process.exitCode = 1;
529
539
  return;
530
540
  }
@@ -146,7 +146,7 @@ Create 参数模式支持:
146
146
  --target-chat-type <private|group> (AUN 目标必填;channelId 同时作为群聊 ID)
147
147
  --target-session <main|thread> [--target-thread-id <threadId>]
148
148
  --trigger-thread <by-run|by-trigger> (兼容旧值 per-run)
149
- --name <名称>
149
+ --name <名称> --description <文本>
150
150
 
151
151
  通用输出参数:
152
152
  --json | --format json
@@ -187,6 +187,8 @@ async function listTriggers(args, json, response) {
187
187
  }
188
188
  for (const t of triggers) {
189
189
  console.log(`${t.enabled ? 'on ' : 'off'} ${t.id} ${t.name} ${sourceLabel(t)}`);
190
+ if (t.description)
191
+ console.log(` ${t.description}`);
190
192
  }
191
193
  }
192
194
  async function showTrigger(args, json, response) {
@@ -199,6 +201,8 @@ async function showTrigger(args, json, response) {
199
201
  console.log(`${t.name} (${t.id})`);
200
202
  console.log(`agent: ${t.agentAid}`);
201
203
  console.log(`enabled: ${t.enabled}`);
204
+ if (t.description)
205
+ console.log(`description: ${t.description}`);
202
206
  console.log(`source: ${sourceLabel(t)}`);
203
207
  if (t.limits?.maxRuns !== undefined || t.limits?.maxDuration !== undefined) {
204
208
  console.log(`limits: ${[
@@ -331,6 +335,7 @@ export async function buildTriggerCreateRequest(args, fileScope, defaultAgentAid
331
335
  $schema_version: 3.1,
332
336
  agentAid,
333
337
  name: parsed.name || `trigger-${now.toString(36)}`,
338
+ ...(parsed.description ? { description: parsed.description } : {}),
334
339
  enabled: hasFlag(args, '--enable'),
335
340
  createdAt: now,
336
341
  updatedAt: now,
@@ -1,3 +1,4 @@
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',
@@ -183,6 +184,9 @@ export function resolveConfigFieldRule(fieldPath, scope) {
183
184
  if (top === 'sessionManifests') {
184
185
  if (parts.length === 1)
185
186
  return readonlyObject('sessionManifests');
187
+ // 系统会话类型的 manifest 由代码决定,不可配置
188
+ if (parts.length === 2 && SYSTEM_SESSION_TYPES.includes(parts[1]))
189
+ return { class: 'unknown' };
186
190
  if (parts.length === 2 && /^[A-Za-z0-9_-]+$/.test(parts[1]))
187
191
  return scalar('sessionManifests', 'manifest-file');
188
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 {
@@ -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);
@@ -208,7 +208,10 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
208
208
  else {
209
209
  if (!route)
210
210
  return { ok: false, code: 'UNKNOWN_FIELD', reason: `Unknown config field: ${field}` };
211
- value = coerceConfigValue(rawValue, route);
211
+ const coerced = coerceConfigValue(rawValue, route, field);
212
+ if (!coerced.ok)
213
+ return { ok: false, code: 'INVALID_CONFIG_VALUE', reason: coerced.reason };
214
+ value = coerced.value;
212
215
  }
213
216
  }
214
217
  return {
@@ -506,18 +509,32 @@ function buildGlobalCanonicalArgv(command) {
506
509
  argv.push('--format', command.format);
507
510
  return argv;
508
511
  }
509
- function coerceConfigValue(raw, route) {
512
+ function coerceConfigValue(raw, route, field) {
510
513
  if (route.merge === 'list')
511
- return [raw];
514
+ return { ok: true, value: [raw] };
512
515
  if (route.enum)
513
- return raw;
516
+ return { ok: true, value: raw };
517
+ if (route.merge === 'dict' && field === route.field) {
518
+ const trimmed = raw.trim();
519
+ let parsed;
520
+ try {
521
+ parsed = JSON.parse(trimmed);
522
+ }
523
+ catch {
524
+ return { ok: false, reason: `Config field ${route.field} requires a JSON object value` };
525
+ }
526
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
527
+ return { ok: false, reason: `Config field ${route.field} requires a JSON object value` };
528
+ }
529
+ return { ok: true, value: parsed };
530
+ }
514
531
  if (raw === 'true')
515
- return true;
532
+ return { ok: true, value: true };
516
533
  if (raw === 'false')
517
- return false;
534
+ return { ok: true, value: false };
518
535
  if (/^-?\d+(\.\d+)?$/.test(raw))
519
- return Number(raw);
520
- return raw;
536
+ return { ok: true, value: Number(raw) };
537
+ return { ok: true, value: raw };
521
538
  }
522
539
  function parseOwnersValue(raw) {
523
540
  let values;
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { atomicReadJson, atomicWriteJson } from '../utils/atomic-write.js';
4
4
  import { agentRelationConfig, agentRelationsDir, agentRoleConfig, agentRolesIndex } from '../paths.js';
5
+ import { registerFile, unregisterFile } from '../core/file-integrity/store.js';
5
6
  import { loadSchema } from './schema-registry.js';
6
7
  import { clearRoleStoreCache, getStoredRoleRegistry, isRoleName, readRoleStore, roleRegistryExists, } from './role-store.js';
7
8
  import { expectedRoleRank, isManagementRole } from './builtin-roles.js';
@@ -38,6 +39,10 @@ export function writeRoleRegistry(selfAid, value) {
38
39
  const file = agentRolesIndex(selfAid);
39
40
  fs.mkdirSync(path.dirname(file), { recursive: true });
40
41
  atomicWriteJson(file, { ...value, $schema_version: 1 });
42
+ try {
43
+ registerFile(file);
44
+ }
45
+ catch { /* best-effort */ }
41
46
  clearRoleStoreCache(selfAid);
42
47
  }
43
48
  export function writeRoleDefinition(selfAid, roleId, value) {
@@ -52,6 +57,10 @@ export function writeRoleDefinition(selfAid, roleId, value) {
52
57
  const file = agentRoleConfig(selfAid, roleId);
53
58
  fs.mkdirSync(path.dirname(file), { recursive: true });
54
59
  atomicWriteJson(file, normalized);
60
+ try {
61
+ registerFile(file);
62
+ }
63
+ catch { /* best-effort */ }
55
64
  clearRoleStoreCache(selfAid);
56
65
  }
57
66
  /** Remove an agent-local override so the builtin role template is used again. */
@@ -62,6 +71,10 @@ export function resetRoleDefinition(selfAid, roleId) {
62
71
  const changed = fs.existsSync(file);
63
72
  if (changed) {
64
73
  fs.rmSync(file, { force: true });
74
+ try {
75
+ unregisterFile(file);
76
+ }
77
+ catch { /* best-effort */ }
65
78
  clearRoleStoreCache(selfAid);
66
79
  }
67
80
  return changed;
@@ -43,9 +43,23 @@ function envFileValue(file, name) {
43
43
  catch { }
44
44
  return undefined;
45
45
  }
46
+ // Registry values do not depend on the agent/project scope. Without this, every
47
+ // agent pays its own reg.exe launches (2 hives x 3 names, ~50ms each), which
48
+ // serializes daemon startup at roughly 150ms per Claude runner.
49
+ const windowsValueCache = new Map();
46
50
  function windowsValue(hive, name) {
47
51
  if (process.platform !== 'win32')
48
52
  return undefined;
53
+ const key = `${hive}:${name}`;
54
+ const now = Date.now();
55
+ const hit = windowsValueCache.get(key);
56
+ if (hit && hit.expires > now)
57
+ return hit.value;
58
+ const value = readWindowsValue(hive, name);
59
+ windowsValueCache.set(key, { value, expires: now + 3000 });
60
+ return value;
61
+ }
62
+ function readWindowsValue(hive, name) {
49
63
  const registryPath = hive === 'user' ? 'HKCU\\Environment'
50
64
  : 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment';
51
65
  try {
@@ -129,9 +143,11 @@ export function setRuntimeEnvironmentOverride(aid, values) {
129
143
  else
130
144
  overrides.delete(scope);
131
145
  cache.clear();
146
+ windowsValueCache.clear();
132
147
  }
133
148
  export function resetRuntimeEnvironmentForTests() {
134
149
  overrides.clear();
135
150
  cache.clear();
151
+ windowsValueCache.clear();
136
152
  captureStartupValues();
137
153
  }
@@ -19,6 +19,7 @@ import fs from 'fs';
19
19
  import path from 'path';
20
20
  import { resolvePaths, agentDir, } from './paths.js';
21
21
  import { atomicReadJson, atomicWriteJson } from './utils/atomic-write.js';
22
+ import { registerFile as registerFileIntegrity } from './core/file-integrity/store.js';
22
23
  import { checkAgentDir, isValidAid } from './aun/aid/validation.js';
23
24
  import { normalizeAidDomain } from './aun/aid/domain.js';
24
25
  import { isValidChannelName } from './core/channel-loader.js';
@@ -60,6 +61,18 @@ export function loadDaemonConfig() {
60
61
  export function isRestartRecoveryEnabled(config = loadDaemonConfig()) {
61
62
  return config.restartRecovery?.enabled === true;
62
63
  }
64
+ export function isDelegationDiagnosticsEnabled() {
65
+ const config = cfgRead(ConfigTarget.Process, undefined, { cache: true });
66
+ const diagnostics = config?.delegationDiagnostics;
67
+ if (diagnostics !== undefined && (!diagnostics || typeof diagnostics !== 'object' || Array.isArray(diagnostics))) {
68
+ throw new Error('daemon.json.delegationDiagnostics must be an object');
69
+ }
70
+ const enabled = diagnostics?.enabled;
71
+ if (enabled !== undefined && typeof enabled !== 'boolean') {
72
+ throw new Error('daemon.json.delegationDiagnostics.enabled must be a boolean');
73
+ }
74
+ return enabled !== false;
75
+ }
63
76
  export function isSkillWatchEnabled(config = loadDaemonConfig()) {
64
77
  return config.skills?.watch === true;
65
78
  }
@@ -92,6 +105,10 @@ export function saveDaemonConfig(value) {
92
105
  || current.aun?.defaultAidDomain !== value.aun?.defaultAidDomain;
93
106
  const normalized = normalizeProcessConfigCompat(normalizeDaemonServices(stripLegacyAunGatewayConfig(value, configPath, message => logger.warn(message))));
94
107
  atomicWriteJson(configPath, validateDaemonConfig({ ...normalized, $schema_version: currentVersion('daemon') }, validateAidDomain));
108
+ try {
109
+ registerFileIntegrity(configPath);
110
+ }
111
+ catch { /* best-effort */ }
95
112
  }
96
113
  function validateDaemonConfig(value, validateAidDomain) {
97
114
  if (value.restartRecovery !== undefined) {
@@ -118,6 +135,19 @@ function validateDaemonConfig(value, validateAidDomain) {
118
135
  throw new Error('daemon.json.skills.watch must be a boolean');
119
136
  }
120
137
  }
138
+ if (value.delegationDiagnostics !== undefined) {
139
+ if (!value.delegationDiagnostics || typeof value.delegationDiagnostics !== 'object' || Array.isArray(value.delegationDiagnostics)) {
140
+ throw new Error('daemon.json.delegationDiagnostics must be an object');
141
+ }
142
+ const unknownFields = Object.keys(value.delegationDiagnostics)
143
+ .filter(key => key !== 'enabled');
144
+ if (unknownFields.length > 0) {
145
+ throw new Error('daemon.json.delegationDiagnostics contains unsupported fields: ' + unknownFields.join(', '));
146
+ }
147
+ if (value.delegationDiagnostics.enabled !== undefined && typeof value.delegationDiagnostics.enabled !== 'boolean') {
148
+ throw new Error('daemon.json.delegationDiagnostics.enabled must be a boolean');
149
+ }
150
+ }
121
151
  const minEvolVersion = value.aun?.minEvolVersion;
122
152
  if (minEvolVersion !== undefined && !parseStableSemver(minEvolVersion)) {
123
153
  throw new Error('daemon.json.aun.minEvolVersion must use stable X.Y.Z format');
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { buildToolLifecycleEventKey, normalizeToolCorrelation } from './event-key.js';
5
5
  // Logger rotation is hourly/daily, but the suffix is intentionally generic so
6
6
  // this check also covers future rotation formats without changing the audit.
7
- const STRUCTURED_LOG_RE = /^(?:daemon|events|channel-in|channel-out|messages|command-audit)(?:-[^.]+)?\.log$/;
7
+ const STRUCTURED_LOG_RE = /^(?:daemon|events|channel-in|channel-out|messages|command-audit|delegation-audit)(?:-[^.]+)?\.log$/;
8
8
  const MIXED_TEXT_LOG_RE = /^daemon(?:-[^.]+)?\.log$/;
9
9
  const LEGACY_JSONL_FILE = /\.jsonl$/i;
10
10
  /** Validate and de-duplicate structured event streams by lifecycle identity. */
@@ -80,6 +80,8 @@ export function inspectStructuredLogs(logDir, options = {}) {
80
80
  return { files: files.length, records: records.length, uniqueRecords: records.length - duplicates, duplicates, issues };
81
81
  }
82
82
  function structuredRecordKey(value) {
83
+ if (typeof value.eventId === 'string' && value.eventId.startsWith('del-audit-'))
84
+ return value.eventId;
83
85
  const nested = nestedRecord(value);
84
86
  const eventKey = firstDefined(value, nested, ['eventKey', 'event_key']);
85
87
  const eventPhase = recordPhase(value);
@@ -6,6 +6,7 @@ import { logger } from '../../utils/logger.js';
6
6
  import { canonicalizeEvolcoreCommandArgv } from '../permission/ec-command-parser.js';
7
7
  import { MAIN_CLI_BIN } from '../../product.js';
8
8
  import { DebugDelegationRegistry, DEBUG_TOKEN_PREFIX } from './debug-context.js';
9
+ import { DelegationDiagnostics, } from './delegation-diagnostics.js';
9
10
  export const AGENT_DELEGATION_TOKEN_ENV = 'EVOLCORE_DELEGATION_TOKEN';
10
11
  export const CODEX_SESSION_TOKEN_ENV = 'EVOLCORE_CODEX_SESSION_TOKEN';
11
12
  export const AGENT_DELEGATION_COMMAND_TTL_MS = 5 * 60_000;
@@ -22,10 +23,15 @@ export function readDelegationCredential(env = process.env) {
22
23
  }
23
24
  export class AgentDelegationRegistry {
24
25
  runtimeEpoch = crypto.randomBytes(16).toString('hex');
26
+ diagnostics;
25
27
  grantsByHash = new Map();
26
28
  activeHashBySession = new Map();
29
+ sessionTokenHashesBySession = new Map();
27
30
  approvedCommands = new Map();
28
31
  debugRegistry = new DebugDelegationRegistry();
32
+ constructor() {
33
+ this.diagnostics = new DelegationDiagnostics(this.runtimeEpoch);
34
+ }
29
35
  getRuntimeEpoch() {
30
36
  return this.runtimeEpoch;
31
37
  }
@@ -34,13 +40,45 @@ export class AgentDelegationRegistry {
34
40
  if (input.executionIdentity && !isValidFullAccessExecutionIdentity(input.executionIdentity)) {
35
41
  throw new Error('invalid fullaccess task execution identity');
36
42
  }
37
- this.revokeSession(input.sessionId);
43
+ this.revokeSession(input.sessionId, 'replaced');
38
44
  const token = crypto.randomBytes(32).toString('base64url');
39
45
  const tokenHash = hashDelegationToken(token);
40
46
  this.grantsByHash.set(tokenHash, { ...input, tokenHash });
41
47
  this.activeHashBySession.set(input.sessionId, tokenHash);
48
+ this.diagnostics.put('task.issued', {
49
+ kind: 'task', tokenHash, sessionId: input.sessionId, taskId: input.taskId,
50
+ runtimeEpoch: this.runtimeEpoch, status: 'active', credentialType: 'task',
51
+ ...(input.selfAid ? { selfAid: input.selfAid } : {}),
52
+ ...(input.peerKey ? { peerKey: input.peerKey } : {}),
53
+ ...(input.actorId ? { actorId: input.actorId } : {}),
54
+ ...(input.issuedRole ? { issuedRole: input.issuedRole } : {}),
55
+ ...(input.permissionMode ? { permissionMode: input.permissionMode } : {}),
56
+ ...(input.messageId ? { messageId: input.messageId } : {}),
57
+ });
42
58
  return token;
43
59
  }
60
+ registerSessionCredential(input) {
61
+ const activeTokenHash = this.activeHashBySession.get(input.sessionId);
62
+ const grant = activeTokenHash ? this.grantsByHash.get(activeTokenHash) : undefined;
63
+ if (!grant || grant.taskId !== input.taskId || !input.sessionToken)
64
+ return false;
65
+ const tokenHash = hashDelegationToken(input.sessionToken);
66
+ let sessionTokens = this.sessionTokenHashesBySession.get(input.sessionId);
67
+ if (!sessionTokens) {
68
+ sessionTokens = new Set();
69
+ this.sessionTokenHashesBySession.set(input.sessionId, sessionTokens);
70
+ }
71
+ sessionTokens.add(tokenHash);
72
+ this.diagnostics.put('session.bound', {
73
+ kind: 'session', tokenHash,
74
+ sessionId: input.sessionId, taskId: input.taskId, runtimeEpoch: this.runtimeEpoch,
75
+ status: 'active', credentialType: 'codex-session', activeTokenHash,
76
+ threadId: input.threadId, selfAid: grant.selfAid, peerKey: grant.peerKey,
77
+ actorId: grant.actorId, issuedRole: grant.issuedRole, permissionMode: grant.permissionMode,
78
+ ...(grant.messageId ? { messageId: grant.messageId } : {}),
79
+ });
80
+ return true;
81
+ }
44
82
  /** 发行 debug token(dbg: 前缀),仅 debug 模式下调用 */
45
83
  issueDebug(input) {
46
84
  return this.debugRegistry.issue(input);
@@ -60,12 +98,7 @@ export class AgentDelegationRegistry {
60
98
  }
61
99
  validateToken(token, sessionId, commandHash, consumeApproval) {
62
100
  if (!token) {
63
- return {
64
- ok: false,
65
- code: 'DELEGATION_REQUIRED',
66
- reason: 'An active task delegation token is required',
67
- diagnosticCause: 'missing_token',
68
- };
101
+ return this.diagnostics.failure('missing_token', sessionId);
69
102
  }
70
103
  const tokenHash = hashDelegationToken(token);
71
104
  const grant = this.grantsByHash.get(tokenHash);
@@ -77,10 +110,11 @@ export class AgentDelegationRegistry {
77
110
  assertAgentPolicyAuthorization(grant.executionIdentity);
78
111
  }
79
112
  catch {
80
- this.revokeSession(sessionId);
81
- return { ok: false, code: 'INVALID_DELEGATION', reason: 'Agent fullaccess policy was revoked', diagnosticCause: 'task_replaced' };
113
+ this.revokeSession(sessionId, 'policy_revoked');
114
+ return this.diagnostics.failure('task_revoked', sessionId, tokenHash);
82
115
  }
83
116
  }
117
+ this.diagnostics.accepted(sessionId, tokenHash, commandHash);
84
118
  return { ok: true, grant };
85
119
  }
86
120
  const approved = commandHash
@@ -88,20 +122,9 @@ export class AgentDelegationRegistry {
88
122
  : undefined;
89
123
  if (approved)
90
124
  return { ok: true, grant: approved };
91
- const diagnosticCause = grant
92
- ? grant.sessionId !== sessionId
93
- ? 'session_mismatch'
94
- : 'task_replaced'
95
- : commandHash
96
- ? 'approved_command_unavailable'
97
- : 'unknown_token';
125
+ const diagnosticCause = this.diagnostics.classify(tokenHash, sessionId, commandHash);
98
126
  logger.warn(`[AgentDelegation] validate failed: cause=${diagnosticCause} session=${sessionId} grantSession=${grant?.sessionId ?? 'none'} taskId=${grant?.taskId ?? 'none'} hasCommandHash=${!!commandHash} tokenKnown=${!!grant} activeForSession=${this.activeHashBySession.get(sessionId) ?? 'none'} grantTokenHash=${grant?.tokenHash ?? 'none'}`);
99
- return {
100
- ok: false,
101
- code: 'INVALID_DELEGATION',
102
- reason: 'The task delegation token is invalid, revoked, or belongs to another session',
103
- diagnosticCause,
104
- };
127
+ return this.diagnostics.failure(diagnosticCause, sessionId, tokenHash, commandHash);
105
128
  }
106
129
  armApprovedCommand(input) {
107
130
  const activeTokenHash = this.activeHashBySession.get(input.sessionId);
@@ -115,10 +138,15 @@ export class AgentDelegationRegistry {
115
138
  return false;
116
139
  this.deleteExpiredApprovedCommands();
117
140
  const sessionTokenHash = hashDelegationToken(sessionToken);
141
+ const credential = this.diagnostics.find('session', sessionTokenHash, input.sessionId);
142
+ if (credential?.taskId !== input.taskId || credential.runtimeEpoch !== this.runtimeEpoch) {
143
+ this.registerSessionCredential({ sessionToken, sessionId: input.sessionId, taskId: input.taskId });
144
+ }
118
145
  const key = approvedCommandKey(sessionTokenHash, input.sessionId, input.commandHash);
119
146
  const existing = this.approvedCommands.get(key);
120
147
  const ttlMs = Math.max(1, Math.min(input.ttlMs ?? AGENT_DELEGATION_COMMAND_TTL_MS, AGENT_DELEGATION_COMMAND_TTL_MS));
121
148
  this.approvedCommands.set(key, {
149
+ sessionTokenHash,
122
150
  sessionId: input.sessionId,
123
151
  taskId: input.taskId,
124
152
  activeTokenHash,
@@ -130,25 +158,42 @@ export class AgentDelegationRegistry {
130
158
  ? existing.remainingUses + 1
131
159
  : 1,
132
160
  });
161
+ this.recordApproval('approval.issued', this.approvedCommands.get(key), 'active');
133
162
  return true;
134
163
  }
135
- revokeTask(sessionId, taskId) {
164
+ revokeTask(sessionId, taskId, reason = 'revoked') {
136
165
  const tokenHash = this.activeHashBySession.get(sessionId);
137
166
  if (!tokenHash)
138
167
  return;
139
168
  const grant = this.grantsByHash.get(tokenHash);
140
169
  if (!grant || grant.taskId !== taskId)
141
170
  return;
142
- this.grantsByHash.delete(tokenHash);
143
- this.activeHashBySession.delete(sessionId);
144
- this.deleteApprovedCommandsForSession(sessionId);
171
+ this.revokeSession(sessionId, reason);
145
172
  }
146
- revokeSession(sessionId) {
173
+ revokeSession(sessionId, reason = 'revoked') {
147
174
  const tokenHash = this.activeHashBySession.get(sessionId);
148
- if (tokenHash)
175
+ for (const sessionTokenHash of this.sessionTokenHashesBySession.get(sessionId) ?? []) {
176
+ const credential = this.diagnostics.find('session', sessionTokenHash, sessionId);
177
+ if (credential)
178
+ this.diagnostics.put('session.revoked', {
179
+ ...credential, status: reason === 'replaced' ? 'replaced' : 'revoked', revocationReason: reason,
180
+ });
181
+ }
182
+ this.sessionTokenHashesBySession.delete(sessionId);
183
+ if (tokenHash) {
184
+ const grant = this.grantsByHash.get(tokenHash);
185
+ if (grant)
186
+ this.diagnostics.put('task.revoked', {
187
+ kind: 'task', tokenHash, sessionId, taskId: grant.taskId, runtimeEpoch: this.runtimeEpoch,
188
+ status: reason === 'replaced' ? 'replaced' : 'revoked', revocationReason: reason,
189
+ credentialType: 'task', selfAid: grant.selfAid, peerKey: grant.peerKey,
190
+ actorId: grant.actorId, issuedRole: grant.issuedRole, permissionMode: grant.permissionMode,
191
+ ...(grant.messageId ? { messageId: grant.messageId } : {}),
192
+ });
149
193
  this.grantsByHash.delete(tokenHash);
194
+ }
150
195
  this.activeHashBySession.delete(sessionId);
151
- this.deleteApprovedCommandsForSession(sessionId);
196
+ this.deleteApprovedCommandsForSession(sessionId, reason);
152
197
  }
153
198
  readApprovedCommand(sessionTokenHash, sessionId, commandHash, consume) {
154
199
  if (!isDelegationCommandHash(commandHash))
@@ -158,6 +203,7 @@ export class AgentDelegationRegistry {
158
203
  if (!approved)
159
204
  return undefined;
160
205
  if (approved.expiresAt <= Date.now()) {
206
+ this.recordApproval('approval.expired', approved, 'expired');
161
207
  this.approvedCommands.delete(key);
162
208
  return undefined;
163
209
  }
@@ -166,30 +212,57 @@ export class AgentDelegationRegistry {
166
212
  if (!grant
167
213
  || activeTokenHash !== approved.activeTokenHash
168
214
  || grant.taskId !== approved.taskId) {
215
+ this.recordApproval('approval.revoked', approved, 'replaced', 'replaced');
169
216
  this.approvedCommands.delete(key);
170
217
  return undefined;
171
218
  }
219
+ this.diagnostics.accepted(sessionId, sessionTokenHash, commandHash);
172
220
  if (consume) {
173
- if (approved.remainingUses <= 1)
221
+ approved.remainingUses--;
222
+ this.recordApproval('approval.consumed', approved, approved.remainingUses <= 0 ? 'consumed' : 'active');
223
+ if (approved.remainingUses <= 0)
174
224
  this.approvedCommands.delete(key);
175
- else
176
- approved.remainingUses--;
177
225
  }
178
226
  return grant;
179
227
  }
180
228
  deleteExpiredApprovedCommands() {
181
229
  const now = Date.now();
182
230
  for (const [key, approved] of this.approvedCommands) {
183
- if (approved.expiresAt <= now)
231
+ if (approved.expiresAt <= now) {
232
+ this.recordApproval('approval.expired', approved, 'expired');
184
233
  this.approvedCommands.delete(key);
234
+ }
185
235
  }
186
236
  }
187
- deleteApprovedCommandsForSession(sessionId) {
237
+ deleteApprovedCommandsForSession(sessionId, reason) {
188
238
  for (const [key, approved] of this.approvedCommands) {
189
- if (approved.sessionId === sessionId)
239
+ if (approved.sessionId === sessionId) {
240
+ this.recordApproval('approval.revoked', approved, reason === 'replaced' ? 'replaced' : 'revoked', reason);
190
241
  this.approvedCommands.delete(key);
242
+ }
191
243
  }
192
244
  }
245
+ recordApproval(event, approved, status, revocationReason) {
246
+ this.diagnostics.put(event, {
247
+ kind: 'approval', tokenHash: approved.sessionTokenHash, sessionId: approved.sessionId,
248
+ taskId: approved.taskId, runtimeEpoch: this.runtimeEpoch, status, credentialType: 'codex-session',
249
+ activeTokenHash: approved.activeTokenHash, commandHash: approved.commandHash,
250
+ expiresAt: approved.expiresAt, remainingUses: approved.remainingUses,
251
+ ...(revocationReason ? { revocationReason } : {}),
252
+ });
253
+ }
254
+ recordDiagnosticFailure(cause, sessionId, token, commandHash) {
255
+ const tokenHash = token ? hashDelegationToken(token) : undefined;
256
+ let diagnosticCause = cause;
257
+ if (cause === 'command_hash_mismatch') {
258
+ diagnosticCause = !tokenHash ? 'missing_token'
259
+ : sessionId ? this.diagnostics.classifyCommandHashMismatch(tokenHash, sessionId) : 'unknown_token';
260
+ }
261
+ return this.diagnostics.failure(diagnosticCause, sessionId, tokenHash, commandHash);
262
+ }
263
+ close() {
264
+ this.diagnostics.close();
265
+ }
193
266
  }
194
267
  export function authorizeDelegatedAunMsgSend(registry, input) {
195
268
  if (!input.sessionId) {