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
@@ -12,37 +12,35 @@ import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { agentDir, resolveRoot } from '../../paths.js';
14
14
  import { serializeConsolidationContext } from './consolidation-context.js';
15
- import { normalizeTriggerDefinition } from '../../trigger/validation.js';
15
+ import { assertSystemSessionLaunchable } from '../system-session/registry.js';
16
+ import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
16
17
  import { formatChannelKey } from '../channel-loader.js';
17
- import { formatPeerKey } from '../relation/peer-identity.js';
18
18
  import { logger } from '../../utils/logger.js';
19
- import { appendOpsRecord } from './consolidation-ops-records.js';
19
+ import { appendOpsRecord, feedbackFilePath, readFeedbackText } from './consolidation-ops-records.js';
20
+ import { updateBatchMeta } from './batch-meta.js';
20
21
  import { checkConsolidationCandidates, shouldUseMetaIndex } from './consolidation-trigger.js';
21
22
  /**
22
- * 清理当前 agent 的旧整理批次。
23
- * 启动新整理时调用,保留最新的一个会话历史用于查看和调试。
23
+ * 清理旧整理批次,全局保留最新的 10 个(不区分 aid 或 mode)。
24
+ * 启动新整理时调用;selfAid 参数保留供日志上下文用,不参与过滤。
24
25
  */
25
- function cleanupPreviousConsolidationBatches(selfAid) {
26
+ function cleanupPreviousConsolidationBatches(_selfAid) {
26
27
  try {
27
28
  const batchRoot = path.join(resolveRoot(), 'memory', 'consolidation-batches');
28
29
  if (!fs.existsSync(batchRoot))
29
30
  return;
30
- // 匹配格式:YYYYMMDDTHHMMSS-<selfAid>-*
31
- const escapedAid = selfAid.replace(/\./g, '\\.');
32
- const pattern = new RegExp(`^\\d{8}T\\d{6}-${escapedAid}-`);
33
31
  const entries = fs.readdirSync(batchRoot, { withFileTypes: true })
34
- .filter(e => e.isDirectory() && pattern.test(e.name))
32
+ .filter(e => e.isDirectory())
35
33
  .map(e => ({ name: e.name, path: path.join(batchRoot, e.name) }))
36
- .sort((a, b) => b.name.localeCompare(a.name)); // 最新的在前
37
- // 保留最新的一个,删除其他
38
- for (let i = 1; i < entries.length; i++) {
34
+ .sort((a, b) => b.name.localeCompare(a.name)); // 时间戳前缀,字典序即时间序,最新的在前
35
+ const KEEP = 10;
36
+ for (let i = KEEP; i < entries.length; i++) {
39
37
  const dir = entries[i];
40
38
  try {
41
39
  fs.rmSync(dir.path, { recursive: true, force: true });
42
- logger.debug(`[Memory] 清理前一次整理批次: ${dir.name}`);
40
+ logger.debug(`[Memory] 清理旧整理批次: ${dir.name}`);
43
41
  }
44
42
  catch (e) {
45
- logger.warn(`[Memory] 清理前一次整理批次失败 ${dir.name}: ${e?.message}`);
43
+ logger.warn(`[Memory] 清理旧整理批次失败 ${dir.name}: ${e?.message}`);
46
44
  }
47
45
  }
48
46
  }
@@ -50,47 +48,6 @@ function cleanupPreviousConsolidationBatches(selfAid) {
50
48
  logger.warn(`[Memory] 清理整理批次时出错: ${e?.message}`);
51
49
  }
52
50
  }
53
- export function buildDebugTaskContext(input) {
54
- const { session } = input;
55
- const meta = (session.metadata ?? {});
56
- const sessionType = session.sessionType ?? meta.sessionType ?? 'main';
57
- let channel = session.channelType || session.channel;
58
- let chatType = session.chatType || 'private';
59
- let peerId = meta.peerId;
60
- let peerKeyId = chatType === 'group'
61
- ? (meta.groupId || session.channelId)
62
- : (meta.peerId || session.channelId);
63
- let threadId = session.threadId || undefined;
64
- if (session.channelId?.startsWith('trigger:') && session.selfAID && input.getTrigger) {
65
- const triggerId = session.channelId.slice('trigger:'.length);
66
- const origin = input.getTrigger(session.selfAID, triggerId)?.origin;
67
- if (origin?.channelType) {
68
- channel = origin.channelType;
69
- chatType = origin.delivery?.chatType ?? 'private';
70
- peerId = origin.peerId;
71
- peerKeyId = chatType === 'group' ? origin.channelId : (origin.peerId ?? origin.channelId);
72
- threadId = origin.threadId;
73
- }
74
- }
75
- return {
76
- sessionId: session.id,
77
- baseagent: session.baseagent,
78
- channel,
79
- channelId: session.channelId,
80
- chatType,
81
- selfAid: session.selfAID,
82
- peerId,
83
- peerKey: channel && peerKeyId ? formatPeerKey(channel, peerKeyId) : undefined,
84
- peerName: meta.peerName,
85
- peerType: meta.peerType,
86
- threadId,
87
- sessionType,
88
- memoryMode: meta.memoryMode,
89
- memoryScope: meta.memoryScope,
90
- consolidationBatchDir: meta.consolidationBatchDir ?? meta.consolidationLogBase,
91
- sessionRuntimeDir: input.sessionRuntimeDir,
92
- };
93
- }
94
51
  /**
95
52
  * 序列化上下文并直接创建整理会话。
96
53
  * 失败抛错,调用方负责决定是否记录诊断或回传错误。
@@ -103,10 +60,15 @@ export async function launchConsolidation(channel, params) {
103
60
  const selfAid = effectiveCtx.selfAid;
104
61
  if (!selfAid)
105
62
  throw new Error('无法确定当前 agent AID(selfAid 为空)');
63
+ // 系统会话标记:类型 / subtype 必须已注册,runner 必须受支持(Gemini 拒绝)。
64
+ // 放在认领 processing 之前,拒绝时不留副作用。
65
+ const systemSession = { type: 'memory', subtype: mode };
66
+ assertSystemSessionLaunchable(systemSession, effectiveCtx.baseagent ?? 'claude');
106
67
  // 清理旧的整理批次(保留最新的一个历史)
107
68
  cleanupPreviousConsolidationBatches(selfAid);
108
69
  // notes 模式:pending/ → processing/ 快照迁移(在序列化上下文之前执行,声明认领)
109
70
  let memoryDir;
71
+ let claimedProcessing = false;
110
72
  if (mode === 'notes') {
111
73
  memoryDir = effectiveCtx.peerKey
112
74
  ? path.join(resolveRoot(), 'agents', selfAid, 'relations', effectiveCtx.peerKey, 'memory')
@@ -114,13 +76,13 @@ export async function launchConsolidation(channel, params) {
114
76
  const pendingDir = path.join(memoryDir, 'pending');
115
77
  const processingDir = path.join(memoryDir, 'processing');
116
78
  if (fs.existsSync(processingDir)) {
117
- // 上一批还没收尾(会话仍在跑,或 daemon 中途重启丢了收尾),不覆盖、不合并
118
- logger.warn(`[Memory] processing/ 已存在,跳过本次迁移(上一批未收尾): ${processingDir}`);
79
+ throw new Error(`上一批整理尚未收尾,不能重新认领 processing: ${processingDir}`);
119
80
  }
120
81
  else if (fs.existsSync(pendingDir)) {
121
82
  try {
122
83
  const count = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md')).length;
123
84
  fs.renameSync(pendingDir, processingDir);
85
+ claimedProcessing = true;
124
86
  // 立即重建空 pending 目录,供主会话后续写 note
125
87
  fs.mkdirSync(pendingDir, { recursive: true });
126
88
  logger.info(`[Memory] pending → processing 快照完成(${count} 条 note): ${processingDir}`);
@@ -131,20 +93,28 @@ export async function launchConsolidation(channel, params) {
131
93
  }
132
94
  }
133
95
  }
134
- const ctxFile = serializeConsolidationContext({
135
- mode,
136
- triggeredBy,
137
- taskCtx: effectiveCtx,
138
- ownerAid,
139
- sessionRuntimeDir: effectiveCtx.sessionRuntimeDir,
140
- ownerInvolved: params.ownerInvolved,
141
- forgetAfterDays: params.forgetAfterDays,
142
- scopeOverride: params.scopeOverride,
143
- });
96
+ let ctxFile;
97
+ try {
98
+ ctxFile = serializeConsolidationContext({
99
+ mode,
100
+ triggeredBy,
101
+ taskCtx: effectiveCtx,
102
+ ownerAid,
103
+ ownerInvolved: params.ownerInvolved,
104
+ forgetAfterDays: params.forgetAfterDays,
105
+ scopeOverride: params.scopeOverride,
106
+ });
107
+ }
108
+ catch (error) {
109
+ if (claimedProcessing && memoryDir)
110
+ restorePendingNotes(memoryDir);
111
+ throw error;
112
+ }
144
113
  const baseagent = effectiveCtx.baseagent ?? 'claude';
145
114
  const now = Date.now();
146
115
  const scopeTag = params.scopeOverride ? `-${params.scopeOverride}` : '';
147
- const triggerId = `memory-consolidate-${mode}${scopeTag}-${now.toString(36)}`;
116
+ const kind = `memory-consolidate-${mode}${scopeTag}`;
117
+ const triggerId = `${kind}-${now.toString(36)}`;
148
118
  // 整理会话的渠道信息(origin):发起时抓取,供反馈时直接投递到原会话渠道。
149
119
  // 用 channelId/peerId/threadId 三要素——不依赖 evolcore sessionId,renew/`/new` 换会话也不影响送达。
150
120
  const feedbackOrigin = effectiveCtx.channel && effectiveCtx.peerId ? {
@@ -156,109 +126,76 @@ export async function launchConsolidation(channel, params) {
156
126
  ...(effectiveCtx.threadId ? { threadId: effectiveCtx.threadId } : {}),
157
127
  delivery: { chatType: effectiveCtx.chatType ?? 'private' },
158
128
  } : undefined;
159
- // 构造 TriggerDefinition 形状的数据载体。
160
- // daemonChannel.converse() 从 ctx.trigger 读 execution.memoryMode / id / name / agentAid。
161
- // 不再调 manager.create()——本函数直接创建会话,不进 trigger 系统的调度/持久化。
162
- const triggerDef = normalizeTriggerDefinition({
163
- $schema_version: 3.1,
164
- id: triggerId,
165
- agentAid: selfAid,
166
- enabled: true,
167
- name: triggerId,
168
- description: `记忆整理(mode=${mode}${effectiveCtx.peerKey ? `,peerKey=${effectiveCtx.peerKey}` : ''})`,
169
- createdAt: now,
170
- updatedAt: now,
171
- source: { type: 'once' },
172
- execution: {
173
- type: 'trigger_session',
174
- prompt: [
175
- '你正在执行记忆整理任务。',
176
- `本次整理模式:${mode}`,
177
- `整理上下文文件路径:${ctxFile.filePath}`,
178
- '',
179
- '请先读取上述上下文文件,再按照注入的整理规范完成本次整理。',
180
- ].join('\n'),
129
+ const projectPath = params.projectPath ?? path.join(resolveRoot(), 'agents', selfAid);
130
+ const prompt = [
131
+ '你正在执行记忆整理任务。',
132
+ `本次整理模式:${mode}`,
133
+ `整理上下文文件路径:${ctxFile.filePath}`,
134
+ '',
135
+ '请先读取上述上下文文件,再按照注入的整理规范完成本次整理。',
136
+ ].join('\n');
137
+ // 通过 BackgroundSessionLauncher 拉起独立会话。
138
+ // 不再构造 TriggerDefinition——与 trigger 调度系统完全解耦。
139
+ // on_next_launch 默认策略由 launchBackgroundSession() 负责清理 evolcore session chat 目录;
140
+ // cleanupPreviousConsolidationLogs()(上方已调用)负责清理 memory/consolidation-logs/ 日志目录。
141
+ // 两者清理的是不同的数据,互不重复。
142
+ // 详见 docs/background-session/background-session-launcher.md
143
+ let handle;
144
+ try {
145
+ handle = await channel.launch({
146
+ kind,
147
+ instanceId: now.toString(36),
181
148
  baseagent,
182
- thread: 'by_run',
183
- onError: 'retry',
184
- noopSentinel: '[[NOOP]]',
185
- memoryMode: mode,
186
- ...(params.scopeOverride ? { memoryScope: params.scopeOverride } : {}),
187
- // 透传到 session.metadata,供 ipc.ts 写 ops 流水、收尾流程读取
188
- consolidationBatchDir: ctxFile.batchDir,
189
- ...(ctxFile.chatDir ? { consolidationChatDir: ctxFile.chatDir } : {}),
190
- ...(memoryDir ? { consolidationMemoryDir: memoryDir } : {}),
191
- },
192
- // 不使用 trigger 的 feedback 机制;反馈由收尾流程直接调 adapter.send() 投递
193
- feedback: { strategy: 'silent' },
194
- ...(feedbackOrigin ? { origin: feedbackOrigin } : {}),
195
- reliability: {
196
- concurrency: 'forbid',
197
- missedPolicy: 'run_once',
198
- retry: { maxAttempts: 0, backoffMs: 30_000 },
199
- },
200
- });
201
- // normalizeTriggerDefinition 只保留白名单字段,自定义字段在其后补挂。
202
- // 反馈目标(发起时渠道三元组)与触发方式,收尾流程据此投递 / 判断是否反馈。
203
- if (feedbackOrigin) {
204
- triggerDef.execution.feedbackOrigin = feedbackOrigin;
149
+ agentAid: selfAid,
150
+ projectPath,
151
+ prompt,
152
+ metadata: {
153
+ // 系统会话的唯一判据:路径 / 工具 / ec 命令由 system-session/memory.ts 决定,与角色无关
154
+ systemSession,
155
+ sessionType: 'memory',
156
+ memoryMode: mode,
157
+ ...(params.scopeOverride ? { memoryScope: params.scopeOverride } : {}),
158
+ // 透传到 session.metadata,供 ipc.ts 写 ops 流水、收尾流程读取
159
+ consolidationBatchDir: ctxFile.batchDir,
160
+ ...(ctxFile.chatDir ? { consolidationChatDir: ctxFile.chatDir } : {}),
161
+ ...(claimedProcessing && memoryDir ? { consolidationMemoryDir: memoryDir } : {}),
162
+ ...(ownerAid ? { consolidationOwnerAid: ownerAid } : {}),
163
+ consolidationUsedThreadSnapshots: !!ctxFile.threadFiles?.length,
164
+ ...(ctxFile.snapshotThroughTs ? { consolidationSnapshotThroughTs: ctxFile.snapshotThroughTs } : {}),
165
+ ...(effectiveCtx.peerKey ? { peerKey: effectiveCtx.peerKey } : {}),
166
+ ...(feedbackOrigin ? { feedbackOrigin } : {}),
167
+ consolidationTriggeredBy: triggeredBy,
168
+ },
169
+ // cleanupPolicy 用默认 on_next_launch:清 evolcore session 记录;日志目录由 cleanupPreviousConsolidationLogs() 负责
170
+ });
205
171
  }
206
- triggerDef.execution.consolidationTriggeredBy = triggeredBy;
207
- // 直接创建整理会话并发送第一条消息——不再经 TriggerRuntimeScheduler。
208
- // 用 getOrCreateConversationSession + converse,与旧 trigger_session 执行路径同源(见 daemon.ts)。
209
- const projectPath = params.projectPath ?? path.join(resolveRoot(), 'agents', selfAid);
210
- const runId = `${triggerId}-run`;
211
- const session = await channel.getOrCreateConversationSession(triggerDef, projectPath, baseagent, runId);
212
- const prompt = triggerDef.execution.prompt;
213
- // 不 await:整理会话在后台跑,收尾由 task:completed/error/interrupted 事件驱动
214
- void channel.converse(prompt, {
215
- trigger: triggerDef,
216
- runId,
217
- firedAt: now,
218
- projectPath,
219
- baseagent,
220
- session,
221
- causation: undefined,
222
- }).catch((e) => {
223
- logger.warn(`[Memory] 整理会话执行失败(mode=${mode},session=${session.id}): ${e?.message}`);
172
+ catch (error) {
173
+ if (claimedProcessing && memoryDir)
174
+ restorePendingNotes(memoryDir);
175
+ recordConsolidationOutcome(ctxFile.contextFilePath, `启动失败: ${error instanceof Error ? error.message : String(error)}`);
176
+ throw error;
177
+ }
178
+ // launch() 在会话创建后即返回,sessionId 立即可用;不等会话结束。
179
+ // 收尾由 bg-session:completed/error/interrupted 事件驱动,这里只记录异常结局。
180
+ const executionSessionId = handle.sessionId;
181
+ void handle.result.then(r => {
182
+ if (r.outcome !== 'completed') {
183
+ logger.warn(`[Memory] 整理会话未正常结束(mode=${mode},outcome=${r.outcome}): ${r.error ?? ''}`);
184
+ }
224
185
  });
225
186
  logger.info(`[Memory] 已拉起整理会话(mode=${mode},triggeredBy=${triggeredBy},` +
226
- `selfAid=${selfAid}${effectiveCtx.peerKey ? `,peerKey=${effectiveCtx.peerKey}` : ''},` +
227
- `session=${session.id})`);
187
+ `selfAid=${selfAid}${effectiveCtx.peerKey ? `,peerKey=${effectiveCtx.peerKey}` : ''}` +
188
+ `${executionSessionId ? `,session=${executionSessionId}` : ''})`);
228
189
  return {
229
190
  triggerId,
230
191
  contextFile: ctxFile.filePath,
231
192
  contextFilePath: ctxFile.contextFilePath,
232
193
  logFile: ctxFile.contextFilePath,
233
194
  batchDir: ctxFile.batchDir,
234
- executionSessionId: session.id,
195
+ executionSessionId,
196
+ waitForAgentSessionId: handle.waitForAgentSessionId,
235
197
  };
236
198
  }
237
- /**
238
- * 手动 consolidate 用:在超时内轮询等 runner 分配 agentSessionId。
239
- * 拿到立即返回;超时返回已拿到的部分,不抛错。
240
- *
241
- * 会话创建后 executionSessionId 立刻可用,但 agentSessionId 要等 runner 初始化
242
- * 并开始第一轮才写入 session 记录,所以这里必须轮询等待。
243
- */
244
- export async function waitForConsolidationSession(opts) {
245
- const executionSessionId = opts.executionSessionId;
246
- if (!executionSessionId)
247
- return {};
248
- const deadline = Date.now() + (opts.timeoutMs ?? 10_000);
249
- const interval = opts.intervalMs ?? 250;
250
- while (true) {
251
- try {
252
- const agentSessionId = await opts.readAgentSessionId(executionSessionId);
253
- if (agentSessionId)
254
- return { executionSessionId, agentSessionId };
255
- }
256
- catch { /* 会话记录写到一半,下一轮再读 */ }
257
- if (Date.now() >= deadline)
258
- return { executionSessionId };
259
- await new Promise(r => setTimeout(r, interval));
260
- }
261
- }
262
199
  /** 把 entry 移进 destDir;重名时加时间戳后缀,不覆盖已有文件。 */
263
200
  function moveIntoDir(srcPath, destDir, stamp) {
264
201
  const name = path.basename(srcPath);
@@ -276,6 +213,17 @@ function moveIntoDir(srcPath, destDir, stamp) {
276
213
  }
277
214
  fs.renameSync(srcPath, dest);
278
215
  }
216
+ function restorePendingNotes(memoryDir) {
217
+ const processingDir = path.join(memoryDir, 'processing');
218
+ if (!fs.existsSync(processingDir))
219
+ return;
220
+ const pendingDir = path.join(memoryDir, 'pending');
221
+ fs.mkdirSync(pendingDir, { recursive: true });
222
+ const stamp = Date.now().toString(36);
223
+ for (const entry of fs.readdirSync(processingDir))
224
+ moveIntoDir(path.join(processingDir, entry), pendingDir, stamp);
225
+ fs.rmdirSync(processingDir);
226
+ }
279
227
  /**
280
228
  * 整理会话结束后的收尾,task:completed / task:error / task:interrupted 都走这里。
281
229
  * - processing/ 里的 note:成功 → archive/notes/,失败 → 归还 pending/(下次重试)
@@ -306,9 +254,10 @@ export async function onConsolidationComplete(params) {
306
254
  result.movedTo = noteDest;
307
255
  }
308
256
  }
309
- if (params.success && params.chatDir) {
257
+ // C3: update mode-specific checkpoint only when thread snapshots were used
258
+ if (params.success && params.chatDir && params.mode && params.usedThreadSnapshots) {
310
259
  const { writeCheckpoint } = await import('./consolidation-context.js');
311
- writeCheckpoint(params.chatDir, now);
260
+ writeCheckpoint(params.chatDir, params.snapshotThroughTs ?? now, params.mode, params.scopeOverride);
312
261
  result.checkpointWritten = true;
313
262
  }
314
263
  const { summarizeOpsRecords, appendOpsRecord } = await import('./consolidation-ops-records.js');
@@ -396,6 +345,7 @@ export async function rollbackConsolidation(batchDir, selfAid) {
396
345
  }
397
346
  const MAX_CONCURRENT_CONSOLIDATION = 5;
398
347
  export function createConsolidationSessionFinalizer({ sessionManager, getAgent, agentMap, releaseSlot, enqueueConsolidation, sendFeedback, }) {
348
+ const finalizedSessions = new Set();
399
349
  return async (sessionId, success) => {
400
350
  let releaseAid;
401
351
  try {
@@ -408,6 +358,9 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
408
358
  const batchDir = (meta.consolidationBatchDir ?? meta.consolidationLogBase);
409
359
  if (!batchDir)
410
360
  return;
361
+ if (finalizedSessions.has(sessionId))
362
+ return;
363
+ finalizedSessions.add(sessionId);
411
364
  releaseAid = session.selfAID;
412
365
  const { summarizeOpsRecords, collectAffectedScopes } = await import('./consolidation-ops-records.js');
413
366
  if (!success) {
@@ -418,6 +371,10 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
418
371
  batchDir,
419
372
  chatDir: meta.consolidationChatDir,
420
373
  memoryDir: meta.consolidationMemoryDir,
374
+ mode: meta.memoryMode,
375
+ scopeOverride: meta.memoryScope,
376
+ usedThreadSnapshots: meta.consolidationUsedThreadSnapshots === true,
377
+ snapshotThroughTs: meta.consolidationSnapshotThroughTs,
421
378
  });
422
379
  try {
423
380
  const affectedScopes = collectAffectedScopes(batchDir);
@@ -429,9 +386,8 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
429
386
  for (const [scope, memoryDir] of affectedScopes) {
430
387
  const consolidatedDir = path.join(memoryDir, 'consolidated');
431
388
  const indexPath = path.join(memoryDir, 'MEMORY.md');
432
- const invalidDir = path.join(memoryDir, 'archive', 'invalid');
433
389
  try {
434
- await rebuildIndex(consolidatedDir, indexPath, scope, invalidDir, undefined, maxBytes);
390
+ await rebuildIndex(consolidatedDir, indexPath, scope, maxBytes);
435
391
  }
436
392
  catch { }
437
393
  }
@@ -513,12 +469,28 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
513
469
  fs.writeFileSync(historyPath, JSON.stringify({ note: `runner ${baseagent} does not support getSessionMessages` }) + '\n', 'utf-8');
514
470
  }
515
471
  }
472
+ // feedback 只读一次:先落 batch.md,再清 scratch/,投递复用同一份文本
473
+ const customText = readFeedbackText(batchDir);
474
+ // batch.md 收尾更新:写入最终状态和 feedback 正文
475
+ try {
476
+ updateBatchMeta(batchDir, {
477
+ status: success ? 'completed' : 'failed',
478
+ finishedAt: new Date().toISOString(),
479
+ success,
480
+ ...(session.agentSessionId ? { agentSessionId: session.agentSessionId } : {}),
481
+ feedbackText: customText,
482
+ });
483
+ }
484
+ catch { /* 失败静默 */ }
485
+ // scratch/ 是会话期临时工作区,feedback 已落 batch.md,不分成败与触发方式一律清掉
486
+ try {
487
+ fs.rmSync(path.dirname(feedbackFilePath(batchDir)), { recursive: true, force: true });
488
+ }
489
+ catch { /* 静默,不影响投递 */ }
516
490
  const feedbackOrigin = meta.feedbackOrigin;
517
491
  const triggeredBy = meta.consolidationTriggeredBy;
518
492
  if (feedbackOrigin && triggeredBy && triggeredBy !== 'periodic-trigger' && session.selfAID) {
519
493
  try {
520
- const { readFeedbackText } = await import('./consolidation-ops-records.js');
521
- const customText = readFeedbackText(batchDir);
522
494
  const text = customText ?? renderDefaultConsolidationFeedback({
523
495
  mode: meta.memoryMode,
524
496
  triggeredBy,
@@ -537,8 +509,23 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
537
509
  logger.warn(`[Memory] 整理收尾失败(session=${sessionId}): ${error?.message}`);
538
510
  }
539
511
  finally {
540
- if (releaseAid)
541
- releaseSlot(releaseAid);
512
+ if (releaseAid && sessionId)
513
+ releaseSlot(releaseAid, sessionId);
514
+ // runtime 目录清理:immediate 策略下,finalizer 是最后一个要读它的消费者。
515
+ // 用 sessionId 无状态重算路径,不依赖 spec 传递——保证即使 spec 不传 runtimeDir
516
+ // 字段,目录也能在会话结束后被清掉。
517
+ if (sessionId) {
518
+ const runtimeDir = ensureSessionRuntimeDir(sessionId);
519
+ if (runtimeDir) {
520
+ try {
521
+ fs.rmSync(runtimeDir, { recursive: true, force: true });
522
+ logger.debug(`[Memory] 清理 runtime 目录: ${runtimeDir}`);
523
+ }
524
+ catch (cleanupError) {
525
+ logger.warn(`[Memory] runtime 目录清理失败(session=${sessionId}): ${cleanupError?.message}`);
526
+ }
527
+ }
528
+ }
542
529
  }
543
530
  };
544
531
  }
@@ -563,19 +550,48 @@ export function createConsolidationFeedbackSender(getChannel) {
563
550
  replyContext.threadId = threadId;
564
551
  if (chatType)
565
552
  replyContext.delivery = { chatType };
566
- await binding.adapter.send(buildEnvelope({
567
- taskId: `memory-feedback-${Date.now()}`,
553
+ // 对比期:同一份反馈 notice、text 各发一条,观察两种模式在对端的差异,
554
+ // 跑通后删掉 text 那条。两条 taskId 后缀不同,避免共用 operationId 被出站去重吞掉一条;
555
+ // 各自 try,一条失败不连累另一条。
556
+ const stamp = Date.now();
557
+ const envelopeFor = (suffix) => buildEnvelope({
558
+ taskId: `memory-feedback-${stamp}-${suffix}`,
568
559
  channel: binding.adapter.channelKey,
569
560
  channelId,
570
561
  agentName: binding.agentName,
571
562
  replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
572
- }), {
573
- kind: 'result.text',
574
- text,
575
- isFinal: true,
576
- format: 'markdown',
577
563
  });
578
- logger.info(`[Memory] 整理反馈已投递(aid=${selfAid},channelId=${channelId})`);
564
+ const attempts = [
565
+ {
566
+ mode: 'notice',
567
+ send: () => binding.adapter.send(envelopeFor('notice'), {
568
+ kind: 'system.notice',
569
+ text,
570
+ subtype: 'memory.consolidation.result',
571
+ }),
572
+ },
573
+ {
574
+ mode: 'text',
575
+ send: () => binding.adapter.send(envelopeFor('text'), {
576
+ kind: 'result.text',
577
+ text,
578
+ isFinal: true,
579
+ format: 'markdown',
580
+ }),
581
+ },
582
+ ];
583
+ const failures = [];
584
+ for (const { mode, send } of attempts) {
585
+ try {
586
+ await send();
587
+ logger.info(`[Memory] 整理反馈已投递(mode=${mode},aid=${selfAid},channelId=${channelId})`);
588
+ }
589
+ catch (error) {
590
+ failures.push(`${mode}: ${error?.message}`);
591
+ }
592
+ }
593
+ if (failures.length > 0)
594
+ throw new Error(failures.join('; '));
579
595
  };
580
596
  }
581
597
  export function registerConsolidationSessionEvents({ eventBus, permissionGateway, finalize, }) {
@@ -583,28 +599,43 @@ export function registerConsolidationSessionEvents({ eventBus, permissionGateway
583
599
  if ('sessionId' in event && event.sessionId) {
584
600
  permissionGateway.cancelAll(event.sessionId, event.reason || 'interrupted');
585
601
  }
586
- void finalize(event.sessionId, false);
587
602
  });
588
603
  eventBus.subscribe('task:error', event => {
589
604
  if ('sessionId' in event && event.sessionId) {
590
605
  permissionGateway.cancelAll(event.sessionId, 'task_error', false);
591
606
  }
592
- void finalize(event.sessionId, false);
593
607
  });
594
608
  eventBus.subscribe('task:completed', event => {
595
609
  if ('sessionId' in event && event.sessionId) {
596
610
  permissionGateway.cancelAll(event.sessionId, 'task_completed', false);
597
611
  }
598
- void finalize(event.sessionId, true);
612
+ });
613
+ eventBus.subscribe('bg-session:completed', event => {
614
+ if (event.type === 'bg-session:completed')
615
+ void finalize(event.sessionId, true);
616
+ });
617
+ eventBus.subscribe('bg-session:error', event => {
618
+ if (event.type === 'bg-session:error')
619
+ void finalize(event.sessionId, false);
620
+ });
621
+ eventBus.subscribe('bg-session:interrupted', event => {
622
+ if (event.type === 'bg-session:interrupted')
623
+ void finalize(event.sessionId, false);
599
624
  });
600
625
  }
601
626
  export class ConsolidationQueue {
602
627
  daemonChannel;
603
628
  activeAgents = new Set();
604
629
  queues = new Map();
630
+ activeSessions = new Map();
631
+ timers = new Map();
632
+ finalizer;
605
633
  constructor(daemonChannel) {
606
634
  this.daemonChannel = daemonChannel;
607
635
  }
636
+ setFinalizer(finalizer) {
637
+ this.finalizer = finalizer;
638
+ }
608
639
  async enqueue(params) {
609
640
  const aid = params.taskCtx.selfAid;
610
641
  if (!aid)
@@ -619,9 +650,30 @@ export class ConsolidationQueue {
619
650
  this.queues.set(aid, queue);
620
651
  logger.info(`[Memory] 整理请求入队(aid=${aid},mode=${params.mode},` +
621
652
  `队列长度=${queue.length},全局活跃=${this.activeAgents.size})`);
622
- return null;
653
+ return {
654
+ triggerId: '',
655
+ contextFile: '',
656
+ contextFilePath: '',
657
+ batchDir: '',
658
+ queued: {
659
+ position: queue.length,
660
+ agentQueueLength: queue.length,
661
+ agentBusy,
662
+ activeCount: this.activeAgents.size,
663
+ maxConcurrent: MAX_CONCURRENT_CONSOLIDATION,
664
+ totalQueued: [...this.queues.values()].reduce((total, pending) => total + pending.length, 0),
665
+ },
666
+ };
623
667
  }
624
- release(aid) {
668
+ release(aid, sessionId) {
669
+ const activeSessionId = this.activeSessions.get(aid);
670
+ if (sessionId && activeSessionId !== undefined && activeSessionId !== sessionId)
671
+ return;
672
+ const timer = this.timers.get(aid);
673
+ if (timer)
674
+ clearTimeout(timer);
675
+ this.timers.delete(aid);
676
+ this.activeSessions.delete(aid);
625
677
  this.activeAgents.delete(aid);
626
678
  void this.drain();
627
679
  }
@@ -631,7 +683,30 @@ export class ConsolidationQueue {
631
683
  return null;
632
684
  this.activeAgents.add(aid);
633
685
  try {
634
- return await launchConsolidation(this.daemonChannel, params);
686
+ const launched = await launchConsolidation(this.daemonChannel, params);
687
+ if (launched.executionSessionId) {
688
+ const sessionId = launched.executionSessionId;
689
+ this.activeSessions.set(aid, sessionId);
690
+ const timer = setTimeout(() => {
691
+ if (this.activeSessions.get(aid) !== sessionId)
692
+ return;
693
+ logger.warn(`[Memory] 整理会话超时收尾(aid=${aid},session=${sessionId})`);
694
+ if (this.finalizer)
695
+ void this.finalizer(sessionId, false).finally(() => this.release(aid, sessionId));
696
+ else
697
+ this.release(aid, sessionId);
698
+ }, 15 * 60_000);
699
+ timer.unref?.();
700
+ this.timers.set(aid, timer);
701
+ }
702
+ appendOpsRecord(launched.batchDir, {
703
+ ts: new Date().toISOString(),
704
+ op: 'session-start',
705
+ sessionId: launched.executionSessionId ?? launched.triggerId,
706
+ mode: params.mode,
707
+ triggeredBy: params.triggeredBy,
708
+ });
709
+ return launched;
635
710
  }
636
711
  catch (error) {
637
712
  logger.warn(`[Memory] 拉起整理会话失败(aid=${aid},mode=${params.mode}): ${error?.message}`);
@@ -659,33 +734,17 @@ export class ConsolidationQueue {
659
734
  }
660
735
  }
661
736
  }
662
- export function registerConsolidationLaunchExecutor({ ipcServer, sessionManager, enqueueConsolidation, }) {
737
+ export function registerConsolidationLaunchExecutor({ ipcServer, enqueueConsolidation, }) {
663
738
  ipcServer.setConsolidationLaunchExecutor(async (params) => {
664
739
  if (!params.taskCtx.selfAid)
665
740
  throw new Error('无法确定当前 agent AID(selfAid 为空)');
666
741
  const launched = await enqueueConsolidation(params);
667
- if (!launched) {
668
- return {
669
- triggerId: '',
670
- contextFile: '',
671
- contextFilePath: '',
672
- logFile: '',
673
- batchDir: '',
674
- queued: true,
675
- };
676
- }
677
- appendOpsRecord(launched.batchDir, {
678
- ts: new Date().toISOString(),
679
- op: 'session-start',
680
- sessionId: launched.triggerId,
681
- mode: params.mode,
682
- triggeredBy: params.triggeredBy,
683
- });
684
- const ids = await waitForConsolidationSession({
685
- executionSessionId: launched.executionSessionId,
686
- readAgentSessionId: async (id) => (await sessionManager.getSessionById(id))?.agentSessionId,
687
- });
688
- return { ...launched, ...ids };
742
+ if (!launched)
743
+ throw new Error('拉起整理会话失败,详见 daemon 日志');
744
+ if (launched.queued)
745
+ return launched;
746
+ const { waitForAgentSessionId, ...result } = launched;
747
+ return result;
689
748
  });
690
749
  }
691
750
  export function registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation, }) {