evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `ec skill` 命令。
3
+ *
4
+ * 这是一层薄转发:真正的实现是内置技能 ec-skill 自己的脚本
5
+ * (`kits/skills/ec-skill/scripts/cli.mjs`)。CLI 不复制逻辑,
6
+ * 模型侧直接跑同一份脚本,两边行为天然一致。
7
+ *
8
+ * 用子进程而非 import,理由:脚本是 ESM、位于包的 kits/ 下,
9
+ * 而 kits/ 随包发布但不参与 tsc 编译;用 spawn 最直接,
10
+ * 也保留了脚本「自定位、不依赖 cwd」的约束。
11
+ *
12
+ * 权限门禁同样在脚本层的 skill.mjs `assertPermission()`,本文件不复制一份。
13
+ * 两个入口(这里的 spawn 与模型侧直接 node脚本)环境变量一致,
14
+ * 脚本自己读 EVOLCORE_TASK_RUNTIME_CONTEXT 即可得到相同判据;
15
+ * 若在 CLI 层再判一遍,只是多一处会与脚本漂移的实现。
16
+ */
17
+ import path from 'path';
18
+ import { spawn } from 'child_process';
19
+ import { getPackageRoot, agentDir } from '../paths.js';
20
+ import { readTaskRuntimeContextFromEnv } from './task-context.js';
21
+ export async function cmdSkill(args) {
22
+ const script = path.join(getPackageRoot(), 'kits', 'skills', 'ec-skill', 'scripts', 'cli.mjs');
23
+ // 脚本在包外运行,拿不到主程序的路径工具,这里把 Agent 目录显式传进去,
24
+ // 让它能定位 Agent 级 skills/ 与 .switches.json。
25
+ // selfAid 优先取任务运行时上下文(托管会话的权威来源),其次环境变量。
26
+ const env = { ...process.env };
27
+ const selfAid = readTaskRuntimeContextFromEnv()?.selfAid || process.env.EVOLCORE_SELF_AID;
28
+ if (selfAid && !env.EVOLCORE_AGENT_DIR) {
29
+ env.EVOLCORE_AGENT_DIR = agentDir(selfAid);
30
+ }
31
+ const code = await new Promise((resolve, reject) => {
32
+ const child = spawn(process.execPath, [script, ...args], {
33
+ stdio: 'inherit',
34
+ env,
35
+ });
36
+ child.on('error', reject);
37
+ child.on('exit', (c) => resolve(c ?? 0));
38
+ });
39
+ if (code !== 0)
40
+ process.exitCode = code;
41
+ }
@@ -34,6 +34,7 @@ function normalizeTaskRuntimeContext(value) {
34
34
  chatType: optionalString(value.chatType),
35
35
  selfAid: optionalString(value.selfAid),
36
36
  peerId: optionalString(value.peerId),
37
+ peerKey: optionalString(value.peerKey),
37
38
  peerName: optionalString(value.peerName),
38
39
  peerType: optionalString(value.peerType),
39
40
  peerRole: optionalString(value.peerRole),
@@ -51,6 +52,7 @@ function normalizeTaskRuntimeContext(value) {
51
52
  runtimeLockDir: optionalAbsolutePath(value.runtimeLockDir),
52
53
  handoffIds: handoffIds?.length ? handoffIds : undefined,
53
54
  causation: normalizeCausation(value.causation),
55
+ consolidationBatchDir: optionalString(value.consolidationBatchDir ?? value.consolidationLogBase),
54
56
  };
55
57
  }
56
58
  function optionalProcessRole(value) {
@@ -60,29 +62,6 @@ function optionalProcessRole(value) {
60
62
  function optionalDataScope(value) {
61
63
  return value === 'relation' || value === 'agent' || value === 'daemon' ? value : undefined;
62
64
  }
63
- /**
64
- * Codex's shell carrier can leave one JSON-style escape layer in a message
65
- * argument (for example, the two characters `\\` and `n`). Decode only that
66
- * layer, and only for a task that is explicitly running the Codex backend.
67
- * Actual line breaks and all non-Codex CLI input remain unchanged.
68
- */
69
- export function decodeCodexMessageText(text, runtime) {
70
- if (runtime?.baseagent?.trim().toLowerCase() !== 'codex')
71
- return text;
72
- return text.replace(/\\([nrt])/g, (_match, escape) => {
73
- if (escape === 'n')
74
- return '\n';
75
- if (escape === 'r')
76
- return '\r';
77
- return '\t';
78
- });
79
- }
80
- /** Apply the Codex-only normalization to a text message payload. */
81
- export function normalizeCodexTextPayload(payload, runtime) {
82
- if (payload.type !== 'text' || typeof payload.text !== 'string')
83
- return;
84
- payload.text = decodeCodexMessageText(payload.text, runtime);
85
- }
86
65
  function optionalAbsolutePath(value) {
87
66
  if (typeof value !== 'string' || !path.isAbsolute(value))
88
67
  return undefined;
@@ -5,9 +5,17 @@ import { ipcQuery } from '../ipc.js';
5
5
  import { isHelpFlag, wantsHelp } from './help.js';
6
6
  import { readDelegationCredential } from '../core/auth/agent-delegation.js';
7
7
  import { readTaskRuntimeContextFromEnv } from './task-context.js';
8
+ import { assertEcSingleLine } from './cli-argv.js';
9
+ import { canonicalizeEvolcoreCommandArgv } from '../core/permission/ec-command-parser.js';
8
10
  import { normalizeTriggerDefinition, resolveScriptPath, } from '../trigger/validation.js';
9
11
  export async function cmdTrigger(args) {
12
+ args = canonicalizeEvolcoreCommandArgv(['ec', 'trigger', ...args]).slice(2);
10
13
  const sub = args[0];
14
+ if (sub === 'create' || sub === 'set' || sub === 'update') {
15
+ const nameIndex = args.indexOf('--name');
16
+ if (nameIndex >= 0)
17
+ assertEcSingleLine(args[nameIndex + 1], '--name');
18
+ }
11
19
  if (!sub || isHelpFlag(sub)) {
12
20
  printHelp();
13
21
  return;
@@ -112,7 +120,7 @@ Create 参数模式支持:
112
120
  --execution-timeout <时长>(模型类 Trigger 单次执行总时限)
113
121
  --model <模型> --effort <low|medium|high|xhigh|max>
114
122
  --max-runs <次数> --max-duration <时长: 30s|15m|2h|1d>
115
- --permission <readonly|auto|request|bypass|fullaccess>(省略则继承当前身份配置;fullaccess 仅 daemon-owner 且 feature flag 开启时可用)
123
+ --permission <readonly|auto|request|bypass|fullaccess>(省略则继承当前身份配置;fullaccess 仅 daemon-owner 可用)
116
124
  --tz <时区> (仅 cron)
117
125
  --target-channel <channelKey> --target-channel-id <ID> (AUN 可简写为 aun)
118
126
  --target-chat-type <private|group> (AUN 目标必填;channelId 同时作为群聊 ID)
@@ -0,0 +1,209 @@
1
+ /**
2
+ * ec watch diag — 消息生命周期诊断
3
+ * 固定N行,每行一个环节,原地刷新,显示该环节的计数 + 最后处理的消息。
4
+ * 数据源:各处理环节直接打点写入的 msg-diag.jsonl,不分析其他日志。
5
+ */
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ import { resolvePaths } from '../paths.js';
10
+ // ==================== ANSI ====================
11
+ const isTTY = !!process.stdout.isTTY;
12
+ const RST = isTTY ? '\x1b[0m' : '';
13
+ const DIM = isTTY ? '\x1b[2m' : '';
14
+ const BOLD = isTTY ? '\x1b[1m' : '';
15
+ const CYAN = isTTY ? '\x1b[36m' : '';
16
+ const GREEN = isTTY ? '\x1b[32m' : '';
17
+ const YELLOW = isTTY ? '\x1b[33m' : '';
18
+ const BLUE = isTTY ? '\x1b[34m' : '';
19
+ const GRAY = isTTY ? '\x1b[38;5;244m' : '';
20
+ const UP = (n) => isTTY ? `\x1b[${n}A` : '';
21
+ const CLR = isTTY ? '\x1b[2K\r' : '';
22
+ // ==================== Stage definitions ====================
23
+ const STAGES = [
24
+ { stage: 'sdk_received', label: '0. SDK收到 ', color: GRAY },
25
+ { stage: 'aun_in', label: '1. AUN入站 ', color: GREEN },
26
+ { stage: 'queue_enqueue', label: '2. 队列入队 ', color: CYAN },
27
+ { stage: 'task_created', label: '3. 任务创建 ', color: YELLOW },
28
+ { stage: 'task_start', label: '4. 任务开始 ', color: YELLOW },
29
+ { stage: 'task_done', label: '5. 任务完成 ', color: BLUE },
30
+ { stage: 'aun_out', label: '6. AUN出站 ', color: GREEN },
31
+ ];
32
+ const state = new Map();
33
+ for (const s of STAGES)
34
+ state.set(s.stage, { count: 0 });
35
+ // ==================== Helpers ====================
36
+ function hhmmssms(iso) {
37
+ const d = new Date(iso);
38
+ return (String(d.getHours()).padStart(2, '0') + ':' +
39
+ String(d.getMinutes()).padStart(2, '0') + ':' +
40
+ String(d.getSeconds()).padStart(2, '0') + '.' +
41
+ String(d.getMilliseconds()).padStart(3, '0'));
42
+ }
43
+ function cut(s, n) {
44
+ if (!s)
45
+ return '';
46
+ return s.length <= n ? s : s.slice(0, n - 1) + '…';
47
+ }
48
+ function pad(s, n) {
49
+ return s.length >= n ? s : s + ' '.repeat(n - s.length);
50
+ }
51
+ // ==================== Render ====================
52
+ let firstRender = true;
53
+ const HEADER_LINES = 3;
54
+ function renderHeader() {
55
+ const cols = process.stdout.columns ?? 120;
56
+ let buf = '';
57
+ buf += `${BOLD}${CYAN}━━━ ec watch diag — 消息生命周期诊断 ━━━${RST}\n`;
58
+ buf += `${DIM}各环节实时显示处理计数和最后一条消息,Ctrl+C 退出${RST}\n`;
59
+ buf += `${GRAY}${'─'.repeat(Math.min(cols, 120))}${RST}\n`;
60
+ return buf;
61
+ }
62
+ function renderRow(stageDef, st) {
63
+ const countStr = `[${String(st.count).padStart(4)}]`;
64
+ const label = `${stageDef.color}${stageDef.label}${RST}`;
65
+ if (!st.last) {
66
+ return `${CLR}${label} ${DIM}${countStr}${RST} ${GRAY}(等待中...)${RST}`;
67
+ }
68
+ const e = st.last;
69
+ const time = hhmmssms(e.ts);
70
+ const from = cut(e.from || e.aid, 20);
71
+ const to = cut(e.to, 20);
72
+ const arrow = from && to ? `${from}${GRAY}→${RST}${to}` : (from || to || `${GRAY}---${RST}`);
73
+ const preview = e.preview ? `${DIM}"${cut(e.preview, 40)}"${RST}` : '';
74
+ const taskStr = e.taskId ? `${GRAY}[${cut(e.taskId, 22)}]${RST} ` : '';
75
+ const sessStr = e.sessionId ? `${GRAY}sess=${cut(e.sessionId, 18)}${RST} ` : '';
76
+ return `${CLR}${label} ${DIM}${countStr}${RST} ${GRAY}${time}${RST} ${pad(arrow, 42)} ${taskStr}${sessStr}${preview}`;
77
+ }
78
+ function render(_redrawAll) {
79
+ if (!isTTY)
80
+ return;
81
+ // 每次都清屏重绘,彻底避免因内容长度不一导致的行偏移问题
82
+ let buf = '\x1b[2J\x1b[H';
83
+ buf += `${BOLD}${CYAN}━━━ ec watch diag — 消息生命周期诊断 ━━━${RST}\n`;
84
+ const cols = process.stdout.columns ?? 120;
85
+ buf += `${DIM}各环节实时显示处理计数和最后一条消息,Ctrl+C 退出${RST}\n`;
86
+ buf += `${GRAY}${'─'.repeat(Math.min(cols, 120))}${RST}\n`;
87
+ for (const s of STAGES) {
88
+ buf += renderRow(s, state.get(s.stage)) + '\n';
89
+ }
90
+ process.stdout.write(buf);
91
+ firstRender = false;
92
+ }
93
+ // 非 TTY 模式打印新事件
94
+ function printNonTty(entry) {
95
+ const stageDef = STAGES.find(s => s.stage === entry.stage);
96
+ if (!stageDef)
97
+ return;
98
+ const st = state.get(entry.stage);
99
+ const time = hhmmssms(entry.ts);
100
+ const from = cut(entry.from || entry.aid, 14);
101
+ const to = cut(entry.to, 14);
102
+ console.log(`${stageDef.label.trim()} [${st.count}] ${time} ${from}→${to} "${entry.preview}"`);
103
+ }
104
+ // ==================== Log tailing ====================
105
+ function getDiagLogPath() {
106
+ const p = resolvePaths();
107
+ // resolvePaths().root 是 ~/.evolcore
108
+ const home = process.env.EVOLCORE_HOME ?? path.join(os.homedir(), '.evolcore');
109
+ return path.join(home, 'logs', 'msg-diag.jsonl');
110
+ }
111
+ function processLine(line) {
112
+ if (!line.trim())
113
+ return false;
114
+ try {
115
+ const entry = JSON.parse(line);
116
+ if (!entry.stage || !STAGES.find(s => s.stage === entry.stage))
117
+ return false;
118
+ const st = state.get(entry.stage);
119
+ if (!st)
120
+ return false;
121
+ st.count++;
122
+ st.last = entry;
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ // ==================== Main ====================
130
+ export async function cmdWatchMsgDiag(_args) {
131
+ const logPath = getDiagLogPath();
132
+ if (!isTTY) {
133
+ // 非 TTY:只打印新事件
134
+ console.log('# ec watch diag — 消息生命周期诊断(非 TTY 模式)');
135
+ }
136
+ else {
137
+ process.stdout.write('\x1b[?25l'); // 隐藏光标
138
+ }
139
+ // 初始加载:读取已有历史(最后 50KB)
140
+ let filePos = 0;
141
+ if (fs.existsSync(logPath)) {
142
+ const stat = fs.statSync(logPath);
143
+ filePos = Math.max(0, stat.size - 50000);
144
+ const fd = fs.openSync(logPath, 'r');
145
+ const buf = Buffer.alloc(stat.size - filePos);
146
+ fs.readSync(fd, buf, 0, buf.length, filePos);
147
+ fs.closeSync(fd);
148
+ const lines = buf.toString('utf-8').split('\n');
149
+ for (const line of lines)
150
+ processLine(line);
151
+ filePos = stat.size;
152
+ }
153
+ // 初始渲染
154
+ render(true);
155
+ let curLogPath = logPath;
156
+ function tick() {
157
+ // 检查文件是否存在(daemon 重启后才会创建)
158
+ if (!fs.existsSync(curLogPath)) {
159
+ if (!firstRender) {
160
+ // 文件消失了,说明 daemon 重启,重置状态
161
+ for (const s of STAGES)
162
+ state.set(s.stage, { count: 0 });
163
+ filePos = 0;
164
+ render(true);
165
+ }
166
+ return;
167
+ }
168
+ try {
169
+ const stat = fs.statSync(curLogPath);
170
+ if (stat.size <= filePos)
171
+ return; // 无新内容
172
+ const fd = fs.openSync(curLogPath, 'r');
173
+ const buf = Buffer.alloc(stat.size - filePos);
174
+ fs.readSync(fd, buf, 0, buf.length, filePos);
175
+ fs.closeSync(fd);
176
+ filePos = stat.size;
177
+ const lines = buf.toString('utf-8').split('\n');
178
+ let changed = false;
179
+ for (const line of lines) {
180
+ if (processLine(line)) {
181
+ changed = true;
182
+ if (!isTTY) {
183
+ try {
184
+ const entry = JSON.parse(line);
185
+ printNonTty(entry);
186
+ }
187
+ catch { }
188
+ }
189
+ }
190
+ }
191
+ // 批量处理完所有新行后,只渲染一次
192
+ if (changed && isTTY)
193
+ render(false);
194
+ }
195
+ catch {
196
+ // 文件轮转中,忽略
197
+ }
198
+ }
199
+ const interval = setInterval(tick, 300);
200
+ process.on('SIGINT', () => {
201
+ clearInterval(interval);
202
+ if (isTTY) {
203
+ process.stdout.write('\x1b[?25h\n');
204
+ }
205
+ console.log('\n诊断监控已停止');
206
+ process.exit(0);
207
+ });
208
+ await new Promise(() => { });
209
+ }
@@ -2,6 +2,8 @@ const BEHAVIOR_TOP_FIELDS = new Set([
2
2
  'active_baseagent',
3
3
  'chatmode',
4
4
  'mentionMode',
5
+ 'autoMentionQuietWindowMs',
6
+ 'sessionIdleSec',
5
7
  'groupSlashRequireMentions',
6
8
  'responseMode',
7
9
  'responseModeParams',
@@ -14,6 +16,7 @@ const BEHAVIOR_TOP_FIELDS = new Set([
14
16
  'render',
15
17
  'sessionManifests',
16
18
  'enable_rich_content',
19
+ 'memory',
17
20
  ]);
18
21
  const BASEAGENT_BEHAVIOR_FIELDS = new Set([
19
22
  'model',
@@ -59,6 +62,7 @@ const SENSITIVE_TOP_FIELDS = new Set([
59
62
  'roles',
60
63
  'role',
61
64
  'extra_backup',
65
+ 'decision_model',
62
66
  ]);
63
67
  const CRITICAL_AGENT_FIELDS = new Set(['owners', 'admins', 'roles', 'access']);
64
68
  const EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
@@ -75,7 +79,7 @@ export function isBehaviorConfigFieldPath(fieldPath) {
75
79
  }
76
80
  return BEHAVIOR_TOP_FIELDS.has(parts[0]);
77
81
  }
78
- export function resolveConfigFieldRule(fieldPath) {
82
+ export function resolveConfigFieldRule(fieldPath, scope) {
79
83
  const field = fieldPath.trim();
80
84
  if (!field)
81
85
  return { class: 'unknown' };
@@ -122,6 +126,10 @@ export function resolveConfigFieldRule(fieldPath) {
122
126
  return scalar(field, 'nonnegative-number');
123
127
  if (field === 'mentionMode')
124
128
  return scalar(field, 'mention-mode');
129
+ if (field === 'autoMentionQuietWindowMs')
130
+ return scalar(field, 'nonnegative-number');
131
+ if (field === 'sessionIdleSec')
132
+ return scalar(field, 'finite-number');
125
133
  if (field === 'groupSlashRequireMentions')
126
134
  return scalar(field, 'boolean');
127
135
  if (field === 'show_activities')
@@ -132,6 +140,8 @@ export function resolveConfigFieldRule(fieldPath) {
132
140
  return scalar(field, 'string');
133
141
  if (field === 'configSnapshots')
134
142
  return scalar('configSnapshots', 'boolean');
143
+ if (field === 'restartRecovery.enabled')
144
+ return scalar('restartRecovery.enabled', 'boolean');
135
145
  if (top === 'chatmode') {
136
146
  if (parts.length === 1)
137
147
  return readonlyObject('chatmode');
@@ -158,6 +168,8 @@ export function resolveConfigFieldRule(fieldPath) {
158
168
  return scalar('session_renew', 'boolean');
159
169
  if (parts[1] === 'after_hours')
160
170
  return scalar('session_renew', 'positive-number');
171
+ if (parts[1] === 'new_probability_threshold')
172
+ return scalar('session_renew', 'probability');
161
173
  if (parts[1] === 'fallback_action')
162
174
  return scalar('session_renew', 'session-renew-action');
163
175
  return { class: 'unknown' };
@@ -188,6 +200,31 @@ export function resolveConfigFieldRule(fieldPath) {
188
200
  return parts.length === 1
189
201
  ? readonlyObject('group_venue_sync')
190
202
  : { class: 'unknown' };
203
+ if (top === 'memory') {
204
+ if (parts.length === 1)
205
+ return readonlyObject('memory');
206
+ if (field === 'memory.enableAgent')
207
+ return scalar('memory', 'boolean', 'owner-only');
208
+ if (field === 'memory.enableOwner')
209
+ return scalar('memory', 'boolean', 'owner-only');
210
+ if (field === 'memory.enableRelation') {
211
+ // relation scope:对端自主控制自己这条关系的记忆开关,role-overridable
212
+ // agent scope:owner 级管控,owner-only
213
+ return scope === 'relation'
214
+ ? scalar('memory', 'boolean')
215
+ : scalar('memory', 'boolean', 'owner-only');
216
+ }
217
+ // 数值参数:整理节奏与容量上限,均 owner-only
218
+ if (field === 'memory.idleHours'
219
+ || field === 'memory.checkIntervalHours'
220
+ || field === 'memory.backlogHours'
221
+ || field === 'memory.indexMaxBytes'
222
+ || field === 'memory.forgetAfterDays'
223
+ || field === 'memory.profileRegenDays') {
224
+ return scalar('memory', 'positive-number', 'owner-only');
225
+ }
226
+ return { class: 'unknown' };
227
+ }
191
228
  return { class: 'unknown' };
192
229
  }
193
230
  export function isSafeBehaviorConfigField(fieldPath) {
@@ -262,6 +299,18 @@ export function parseConfigFieldValue(fieldPath, rawValue) {
262
299
  ? { ok: true, value }
263
300
  : { ok: false, reason: `Config field ${fieldPath} must be a positive number` };
264
301
  }
302
+ case 'finite-number': {
303
+ const value = Number(raw);
304
+ return Number.isFinite(value)
305
+ ? { ok: true, value }
306
+ : { ok: false, reason: `Config field ${fieldPath} must be a finite number` };
307
+ }
308
+ case 'probability': {
309
+ const value = Number(raw);
310
+ return raw && Number.isFinite(value) && value >= 0 && value <= 1
311
+ ? { ok: true, value }
312
+ : { ok: false, reason: `Config field ${fieldPath} must be a number between 0 and 1` };
313
+ }
265
314
  case 'effort':
266
315
  return EFFORTS.has(raw)
267
316
  ? { ok: true, value: raw }
@@ -271,9 +320,9 @@ export function parseConfigFieldValue(fieldPath, rawValue) {
271
320
  ? { ok: true, value: raw }
272
321
  : { ok: false, reason: `Config field ${fieldPath} must be interactive or proactive` };
273
322
  case 'mention-mode':
274
- return raw === 'disabled' || raw === 'mention-only'
323
+ return raw === 'disabled' || raw === 'mention-only' || raw === 'auto'
275
324
  ? { ok: true, value: raw }
276
- : { ok: false, reason: `Config field ${fieldPath} must be disabled or mention-only` };
325
+ : { ok: false, reason: `Config field ${fieldPath} must be disabled, mention-only, or auto` };
277
326
  case 'show-activities':
278
327
  return raw === 'all' || raw === 'text' || raw === 'none'
279
328
  ? { ok: true, value: raw }
@@ -747,6 +747,10 @@ export function normalizeProcessConfigCompat(value) {
747
747
  const debug = value.debug;
748
748
  let changed = false;
749
749
  let normalized = value;
750
+ if (Object.prototype.hasOwnProperty.call(normalized, 'fullaccess')) {
751
+ normalized = { ...normalized };
752
+ delete normalized.fullaccess;
753
+ }
750
754
  if (idleMonitor && typeof idleMonitor === 'object' && !Array.isArray(idleMonitor)) {
751
755
  const nextIdleMonitor = { ...idleMonitor };
752
756
  if (nextIdleMonitor.apiRetryTimeout === undefined && nextIdleMonitor.retryAttemptTimeout !== undefined) {
@@ -1337,7 +1341,7 @@ export function normalizeDefaultsBehaviorCompat(defaults) {
1337
1341
  if (!normalized.chatmode && (chatMode === 'interactive' || chatMode === 'proactive')) {
1338
1342
  normalized.chatmode = { private: chatMode, group: chatMode, nothuman: chatMode };
1339
1343
  }
1340
- if (normalized.mentionMode === undefined && (config.mentionMode === 'disabled' || config.mentionMode === 'mention-only')) {
1344
+ if (normalized.mentionMode === undefined && (config.mentionMode === 'disabled' || config.mentionMode === 'mention-only' || config.mentionMode === 'auto')) {
1341
1345
  normalized.mentionMode = config.mentionMode;
1342
1346
  }
1343
1347
  if (normalized.show_activities === undefined && ['all', 'text', 'none'].includes(config.show_activities)) {
@@ -1362,9 +1366,10 @@ function stripStaticOwnerField(config) {
1362
1366
  }
1363
1367
  function stripRelationRoleData(config) {
1364
1368
  if (!config || (!Object.prototype.hasOwnProperty.call(config, 'role')
1365
- && !Object.prototype.hasOwnProperty.call(config, 'roles')))
1369
+ && !Object.prototype.hasOwnProperty.call(config, 'roles')
1370
+ && !Object.prototype.hasOwnProperty.call(config, 'decision_model')))
1366
1371
  return config;
1367
- const { role: _role, roles: _legacyRoles, ...rest } = config;
1372
+ const { role: _role, roles: _legacyRoles, decision_model: _decisionModel, ...rest } = config;
1368
1373
  return rest;
1369
1374
  }
1370
1375
  // ── effective(合并视图)─────────────────────────────────────────────────
@@ -1374,6 +1379,7 @@ function stripRelationRoleData(config) {
1374
1379
  export function resolveEffective(sel, opts = {}) {
1375
1380
  const config = resolveAgentConfig(sel, opts);
1376
1381
  const observableDefault = loadSchema('agent-config').fields.get('observable')?.default;
1382
+ const autoMentionQuietWindowMsDefault = loadSchema('agent-config').fields.get('autoMentionQuietWindowMs')?.default;
1377
1383
  const effective = {
1378
1384
  $schema_version: config.$schema_version ?? currentVersion('agent-config'),
1379
1385
  aid: config.aid ?? sel.self ?? '',
@@ -1384,6 +1390,7 @@ export function resolveEffective(sel, opts = {}) {
1384
1390
  owners: config.owners,
1385
1391
  fullaccess: config.fullaccess,
1386
1392
  aun: config.aun,
1393
+ decision_model: config.decision_model,
1387
1394
  channels: config.channels ?? [],
1388
1395
  models: config.models,
1389
1396
  projects: config.projects,
@@ -1398,6 +1405,8 @@ export function resolveEffective(sel, opts = {}) {
1398
1405
  baseagents: config.baseagents,
1399
1406
  chatmode: config.chatmode,
1400
1407
  mentionMode: config.mentionMode,
1408
+ autoMentionQuietWindowMs: config.autoMentionQuietWindowMs
1409
+ ?? (typeof autoMentionQuietWindowMsDefault === 'number' ? autoMentionQuietWindowMsDefault : 3000),
1401
1410
  groupSlashRequireMentions: config.groupSlashRequireMentions ?? true,
1402
1411
  model: config.model,
1403
1412
  responseMode: config.responseMode,
@@ -1410,6 +1419,7 @@ export function resolveEffective(sel, opts = {}) {
1410
1419
  render: config.render,
1411
1420
  sessionManifests: config.sessionManifests,
1412
1421
  enable_rich_content: config.enable_rich_content,
1422
+ memory: config.memory,
1413
1423
  };
1414
1424
  return normalizeEffectiveCompatibility(effective);
1415
1425
  }
@@ -1430,6 +1440,7 @@ const EFFECTIVE_BEHAVIOR_FIELDS = [
1430
1440
  'baseagents',
1431
1441
  'chatmode',
1432
1442
  'mentionMode',
1443
+ 'autoMentionQuietWindowMs',
1433
1444
  'groupSlashRequireMentions',
1434
1445
  'flush_delay',
1435
1446
  'debounce',
@@ -1456,7 +1467,9 @@ export function routeFieldPath(fieldPath, scope) {
1456
1467
  if (scope === 'process')
1457
1468
  return routeIn(DAEMON_SCHEMA_NAME, ConfigTarget.Process, topField);
1458
1469
  if (scope === 'defaults') {
1459
- if (isBehaviorConfigFieldPath(fieldPath) && topField !== 'session_renew') {
1470
+ if (isBehaviorConfigFieldPath(fieldPath)
1471
+ && topField !== 'session_renew'
1472
+ && topField !== 'autoMentionQuietWindowMs') {
1460
1473
  throw new ConfigError('DEFAULT_BEHAVIOR_REJECT', `--default 不支持行为字段: ${fieldPath}`);
1461
1474
  }
1462
1475
  return routeIn('defaults', ConfigTarget.Defaults, topField);
@@ -3,7 +3,7 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { isValidAid } from '../aun/aid/validation.js';
5
5
  import { isExplicitGroupId } from '../aun/group/index.js';
6
- import { agentConfig, agentContactConfig, agentDir, resolvePaths } from '../paths.js';
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
9
  import { ensureManagedLockParent, resolveManagedLockPath } from '../core/runtime-lock.js';
@@ -601,7 +601,7 @@ function journalFile(selfAid) {
601
601
  return path.join(agentDir(selfAid), JOURNAL_NAME);
602
602
  }
603
603
  function appendAudit(selfAid, request, previousContactRevision, contactRevisionValue) {
604
- const file = path.join(agentDir(selfAid), 'data', 'contact-audit.jsonl');
604
+ const file = agentContactAuditPath(selfAid);
605
605
  fs.mkdirSync(path.dirname(file), { recursive: true });
606
606
  fs.appendFileSync(file, `${JSON.stringify({
607
607
  timestamp: new Date().toISOString(),
@@ -1,5 +1,4 @@
1
- /** AUN `mention_mode` and EvolCore `mentionMode` share these wire values. */
2
- export function normalizeMentionMode(mode) {
1
+ export function normalizeAunMentionMode(mode) {
3
2
  if (typeof mode !== 'string')
4
3
  return undefined;
5
4
  const normalized = mode.trim().toLowerCase();
@@ -9,3 +8,9 @@ export function normalizeMentionMode(mode) {
9
8
  return 'mention-only';
10
9
  return undefined;
11
10
  }
11
+ export function normalizeMentionMode(mode) {
12
+ const normalized = typeof mode === 'string' ? mode.trim().toLowerCase() : '';
13
+ if (normalized === 'auto')
14
+ return 'auto';
15
+ return normalizeAunMentionMode(mode);
16
+ }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * merge —— 类型驱动的覆盖链合并(design §三,全项目唯一实现点)+ ${VAR} 展开。
3
3
  *
4
- * 合并三规则(按字段的 x-merge 语义):
4
+ * 合并规则(按字段的 x-merge 语义):
5
5
  * - scalar:高优先级整体覆盖
6
6
  * - list: 并集追加去重(只能加,不能减)
7
- * - dict: 键并集;同键 → 高优先级值直接覆盖(**不递归**,合并粒度=第一层键)
7
+ * - dict: 键并集;同键 → 高优先级值直接覆盖(合并粒度=第一层键)
8
+ * 若字段在 schema 中声明了 x-merge-logic: AND 的子字段,
9
+ * 则该子字段做 AND 合并而非覆盖(任意层为 false 结果为 false)
8
10
  *
9
- * 合并代码永远只有这三条规则;所有"覆盖 vs 追加"通过 schema 的 x-merge 选择表达,不为字段写特判。
11
+ * 合并代码永远只有这几条规则;所有"覆盖 vs 追加 vs AND"通过 schema 的 x-merge / x-merge-logic 选择表达,不为字段写特判。
10
12
  */
11
13
  import fs from 'fs';
12
14
  import path from 'path';
@@ -24,11 +26,20 @@ function unionList(base, overlay) {
24
26
  }
25
27
  return out;
26
28
  }
27
- /** dict 第一层键合并:键并集,同键高优先级整体覆盖(不递归)。 */
28
- function mergeDict(base, overlay) {
29
+ /** dict 第一层键合并:键并集,同键按子字段 FieldSpec 决定合并语义。
30
+ * 若子字段声明了 mergeLogic: AND 且两值均为 boolean,做 AND;否则高优先级覆盖。 */
31
+ function mergeDictWithSchema(base, overlay, children) {
29
32
  const out = { ...base };
30
- for (const [k, v] of Object.entries(overlay))
31
- out[k] = v;
33
+ for (const [k, v] of Object.entries(overlay)) {
34
+ const childSpec = children?.get(k);
35
+ const prev = out[k];
36
+ if (prev !== undefined && childSpec?.mergeLogic === 'AND' && typeof prev === 'boolean' && typeof v === 'boolean') {
37
+ out[k] = prev && v;
38
+ }
39
+ else {
40
+ out[k] = v;
41
+ }
42
+ }
32
43
  return out;
33
44
  }
34
45
  function isPlainObject(v) {
@@ -38,6 +49,9 @@ function isPlainObject(v) {
38
49
  * 按 schema 字段表,对覆盖链上"存在的每一级"做类型驱动合并。
39
50
  * layers 从低优先级到高优先级;低优先级独有字段保留,冲突点高优先级胜。
40
51
  * 字段未在 schema 中(理论上 ajv 已拦)按 scalar 处理。
52
+ *
53
+ * x-merge-logic: AND 语义:所有层的布尔值做 AND 合并,任意层为 false 则结果为 false。
54
+ * 仅对 boolean 类型的 scalar 字段有意义;值缺失的层不参与 AND(按 true 处理)。
41
55
  */
42
56
  export function mergeLayers(layers, fields) {
43
57
  const out = {};
@@ -54,11 +68,16 @@ export function mergeLayers(layers, fields) {
54
68
  out[k] = v;
55
69
  continue;
56
70
  }
57
- if (kind === 'list' && Array.isArray(prev) && Array.isArray(v)) {
71
+ // x-merge-logic: AND — 任意层为 false 则结果为 false
72
+ if (spec?.mergeLogic === 'AND' && typeof v === 'boolean' && typeof prev === 'boolean') {
73
+ out[k] = prev && v;
74
+ }
75
+ else if (kind === 'list' && Array.isArray(prev) && Array.isArray(v)) {
58
76
  out[k] = unionList(prev, v);
59
77
  }
60
78
  else if (kind === 'dict' && isPlainObject(prev) && isPlainObject(v)) {
61
- out[k] = mergeDict(prev, v);
79
+ // 传入 children,让 dict 子字段能感知 x-merge-logic: AND
80
+ out[k] = mergeDictWithSchema(prev, v, spec?.children);
62
81
  }
63
82
  else {
64
83
  out[k] = v; // scalar 覆盖(或类型不一致时高优先级胜)
@@ -125,7 +125,7 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
125
125
  reason: 'fallbackModels is configured only by editing agents/defaults.json',
126
126
  };
127
127
  }
128
- const fieldRule = resolveConfigFieldRule(field);
128
+ const fieldRule = resolveConfigFieldRule(field, selector.scope);
129
129
  if (subcommand !== 'get' && fieldRule.class === 'safe-readonly-object') {
130
130
  return {
131
131
  ok: false,