evolcore 0.0.32 → 0.0.33

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 (150) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/install-codex-managed-hooks.mjs +124 -37
  3. package/dist/agents/claude-runner.js +29 -2
  4. package/dist/agents/codex/app-server-client.js +3 -0
  5. package/dist/agents/codex/diagnostics.js +76 -44
  6. package/dist/agents/codex-runner.js +23 -19
  7. package/dist/agents/ecagent-runner.js +5 -13
  8. package/dist/aun/group/operations.js +0 -1
  9. package/dist/aun/msg/history.js +1 -0
  10. package/dist/aun/msg/p2p.js +0 -1
  11. package/dist/aun/outbox.js +50 -1
  12. package/dist/channels/aun.js +28 -2
  13. package/dist/channels/daemon.js +257 -2
  14. package/dist/cli/agent-command.js +58 -24
  15. package/dist/cli/agent.js +1 -1
  16. package/dist/cli/aun-commands.js +365 -29
  17. package/dist/cli/contact.js +8 -2
  18. package/dist/cli/daemon-commands.js +81 -12
  19. package/dist/cli/debug-mode.js +154 -0
  20. package/dist/cli/handoff-command.js +3 -0
  21. package/dist/cli/index.js +81 -64
  22. package/dist/cli/init.js +2 -2
  23. package/dist/cli/managed-command-guard.js +5 -0
  24. package/dist/cli/memory-command.js +8 -1
  25. package/dist/cli/queue-command.js +9 -4
  26. package/dist/cli/session-command.js +194 -0
  27. package/dist/cli/skill-command.js +78 -2
  28. package/dist/cli/task-context.js +1 -0
  29. package/dist/cli/thread-command.js +620 -0
  30. package/dist/cli/trigger-command.js +4 -2
  31. package/dist/config/builtin-roles.js +1 -0
  32. package/dist/config/config-field-policy.js +2 -3
  33. package/dist/config/config-manager.js +7 -2
  34. package/dist/config/resolved-config-op.js +3 -0
  35. package/dist/config-store.js +15 -0
  36. package/dist/core/auth/agent-delegation.js +25 -1
  37. package/dist/core/auth/debug-context.js +139 -0
  38. package/dist/core/auth/operation-authorizer.js +24 -1
  39. package/dist/core/auth/operation-catalog.js +33 -0
  40. package/dist/core/background-session/session-cleanup.js +212 -0
  41. package/dist/core/background-session/types.js +10 -0
  42. package/dist/core/bootstrap-messages.js +1 -1
  43. package/dist/core/bootstrap-service.js +6 -3
  44. package/dist/core/command/command-handler.js +60 -24
  45. package/dist/core/command/memory-slash.js +6 -2
  46. package/dist/core/command/menu-catalog.js +48 -1
  47. package/dist/core/command/menu-handler.js +92 -16
  48. package/dist/core/command/menu-protocol.js +24 -1
  49. package/dist/core/command/skill-slash.js +322 -0
  50. package/dist/core/command/slash-gate.js +6 -4
  51. package/dist/core/command/slash-handler.js +193 -13
  52. package/dist/core/command/topic-slash.js +528 -0
  53. package/dist/core/evolagent-registry.js +20 -5
  54. package/dist/core/handoff/runtime.js +5 -11
  55. package/dist/core/handoff/store.js +5 -0
  56. package/dist/core/ipc-registration.js +139 -2
  57. package/dist/core/memory/consolidation-context.js +49 -16
  58. package/dist/core/memory/consolidation-ops-records.js +35 -0
  59. package/dist/core/memory/consolidation-queue-format.js +21 -0
  60. package/dist/core/memory/consolidation.js +174 -180
  61. package/dist/core/message/aun-ipc-sender.js +161 -6
  62. package/dist/core/message/im-renderer.js +149 -6
  63. package/dist/core/message/message-bridge.js +13 -3
  64. package/dist/core/message/message-log.js +1 -0
  65. package/dist/core/message/peer-mode.js +6 -1
  66. package/dist/core/message/response-engine.js +349 -67
  67. package/dist/core/message/subtask-tool-projection.js +140 -0
  68. package/dist/core/model/catalog-service.js +17 -12
  69. package/dist/core/permission/ec-command-parser.js +126 -7
  70. package/dist/core/session/session-idle-monitor.js +249 -22
  71. package/dist/core/session/session-manager.js +54 -9
  72. package/dist/core/skill/command-parser.js +13 -0
  73. package/dist/core/skill/index-generator.js +4 -1
  74. package/dist/core/skill/startup.js +41 -0
  75. package/dist/core/skill/switch-resolver.js +55 -29
  76. package/dist/core/thread/command-parser.js +125 -0
  77. package/dist/core/thread/index.js +1 -0
  78. package/dist/core/thread/ipc-operation.js +95 -0
  79. package/dist/core/thread/thread-context-storage.js +131 -0
  80. package/dist/core/thread/thread-operation.js +258 -0
  81. package/dist/core/thread/thread-route.js +33 -0
  82. package/dist/core/thread/thread-service.js +777 -0
  83. package/dist/core/thread/types.js +1 -0
  84. package/dist/eck/message-renderer.js +35 -0
  85. package/dist/index.js +8 -5
  86. package/dist/ipc.js +89 -10
  87. package/dist/paths.js +23 -0
  88. package/dist/response-system/engines/v1/proactive-flow.js +169 -80
  89. package/dist/trigger/authorization.js +57 -28
  90. package/dist/trigger/feedback.js +7 -1
  91. package/dist/trigger/parser.js +63 -1
  92. package/dist/trigger/validation.js +14 -0
  93. package/dist/utils/error-utils.js +22 -1
  94. package/kits/docs/context-assembly.md +1 -0
  95. package/kits/docs/evolcore/INDEX.md +1 -0
  96. package/kits/docs/evolcore/agent-create.md +39 -28
  97. package/kits/docs/evolcore/agent.md +19 -4
  98. package/kits/docs/evolcore/ec-capabilities.md +4 -0
  99. package/kits/docs/evolcore/group-collaboration.md +3 -5
  100. package/kits/docs/evolcore/group.md +3 -4
  101. package/kits/docs/evolcore/memory-consolidation.md +1 -1
  102. package/kits/docs/evolcore/menu-exec-schema-commands.md +2 -2
  103. package/kits/docs/evolcore/menu-protocol.md +7 -0
  104. package/kits/docs/evolcore/msg.md +5 -8
  105. package/kits/docs/evolcore/thread-chain.md +267 -0
  106. package/kits/docs/prompt-loading-architecture.md +1 -0
  107. package/kits/eck_manifest.json +61 -26
  108. package/kits/eck_message_manifest.json +169 -10
  109. package/kits/rules/05-venue.md +1 -1
  110. package/kits/rules/07-memory-and-thread.md +202 -0
  111. package/kits/schemas/relation-config.schema.9.json +18 -3
  112. package/kits/schemas/role-config.schema.1.json +6 -6
  113. package/kits/schemas/single-session.schema.1.json +6 -6
  114. package/kits/schemas/single-session.schema.2.json +6 -6
  115. package/kits/schemas/single-session.schema.3.json +6 -6
  116. package/kits/skills/.switches.json +12 -1
  117. package/kits/skills/ec-group/SKILL.md +234 -0
  118. package/kits/skills/ec-skill/SKILL.md +10 -1
  119. package/kits/skills/ec-skill/scripts/cli.mjs +15 -1
  120. package/kits/skills/scrapling-article-fetch/SKILL.md +128 -0
  121. package/kits/skills/scrapling-article-fetch/scripts/check_python_env.py +54 -0
  122. package/kits/skills/scrapling-article-fetch/scripts/render_markdown_reply.py +35 -0
  123. package/kits/skills/scrapling-article-fetch/scripts/run_pipeline.sh +16 -0
  124. package/kits/skills/scrapling-article-fetch/scripts/scrapling_fetch.py +344 -0
  125. package/kits/skills/thread-ops/SKILL.md +492 -0
  126. package/kits/skills/wechat-article-media-download/SKILL.md +65 -0
  127. package/kits/skills/wechat-article-media-download/scripts/download.py +163 -0
  128. package/kits/skills/wechat-article-reader/SKILL.md +97 -0
  129. package/kits/skills/wechat-article-reader/scripts/fetch.mjs +256 -0
  130. package/kits/templates/bootstrap-welcome.md +7 -7
  131. package/kits/templates/memory-consolidation/forget.md +5 -0
  132. package/kits/templates/memory-consolidation/generalize.md +5 -0
  133. package/kits/templates/memory-consolidation/profile.md +5 -0
  134. package/kits/templates/memory-consolidation/promote.md +5 -0
  135. package/kits/templates/message-fragments/item.md +2 -1
  136. package/kits/templates/message-fragments/thread-context.md +6 -0
  137. package/kits/templates/message-fragments/trigger-session.md +2 -0
  138. package/kits/templates/message-fragments/trigger-thread-check.md +2 -0
  139. package/kits/templates/roles/member.json +2 -0
  140. package/kits/templates/roles/visitor.json +2 -0
  141. package/kits/templates/system-fragments/bootstrap.md +66 -72
  142. package/kits/templates/system-fragments/channel.md +3 -6
  143. package/kits/templates/system-fragments/commands.md +1 -0
  144. package/kits/templates/system-fragments/managed-runtime.md +15 -0
  145. package/kits/templates/system-fragments/reply.md +3 -2
  146. package/kits/templates/system-fragments/session.md +5 -14
  147. package/package.json +3 -1
  148. package/kits/docs/evolcore/group-fs.md +0 -17
  149. package/kits/docs/venues/aun-group.md +0 -45
  150. package/kits/docs/venues/group.md +0 -43
@@ -12,9 +12,7 @@ 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';
16
15
  import { formatChannelKey } from '../channel-loader.js';
17
- import { formatPeerKey } from '../relation/peer-identity.js';
18
16
  import { logger } from '../../utils/logger.js';
19
17
  import { appendOpsRecord } from './consolidation-ops-records.js';
20
18
  import { checkConsolidationCandidates, shouldUseMetaIndex } from './consolidation-trigger.js';
@@ -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
  * 失败抛错,调用方负责决定是否记录诊断或回传错误。
@@ -107,6 +64,7 @@ export async function launchConsolidation(channel, params) {
107
64
  cleanupPreviousConsolidationBatches(selfAid);
108
65
  // notes 模式:pending/ → processing/ 快照迁移(在序列化上下文之前执行,声明认领)
109
66
  let memoryDir;
67
+ let claimedProcessing = false;
110
68
  if (mode === 'notes') {
111
69
  memoryDir = effectiveCtx.peerKey
112
70
  ? path.join(resolveRoot(), 'agents', selfAid, 'relations', effectiveCtx.peerKey, 'memory')
@@ -114,13 +72,13 @@ export async function launchConsolidation(channel, params) {
114
72
  const pendingDir = path.join(memoryDir, 'pending');
115
73
  const processingDir = path.join(memoryDir, 'processing');
116
74
  if (fs.existsSync(processingDir)) {
117
- // 上一批还没收尾(会话仍在跑,或 daemon 中途重启丢了收尾),不覆盖、不合并
118
- logger.warn(`[Memory] processing/ 已存在,跳过本次迁移(上一批未收尾): ${processingDir}`);
75
+ throw new Error(`上一批整理尚未收尾,不能重新认领 processing: ${processingDir}`);
119
76
  }
120
77
  else if (fs.existsSync(pendingDir)) {
121
78
  try {
122
79
  const count = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md')).length;
123
80
  fs.renameSync(pendingDir, processingDir);
81
+ claimedProcessing = true;
124
82
  // 立即重建空 pending 目录,供主会话后续写 note
125
83
  fs.mkdirSync(pendingDir, { recursive: true });
126
84
  logger.info(`[Memory] pending → processing 快照完成(${count} 条 note): ${processingDir}`);
@@ -131,20 +89,29 @@ export async function launchConsolidation(channel, params) {
131
89
  }
132
90
  }
133
91
  }
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
- });
92
+ let ctxFile;
93
+ try {
94
+ ctxFile = serializeConsolidationContext({
95
+ mode,
96
+ triggeredBy,
97
+ taskCtx: effectiveCtx,
98
+ ownerAid,
99
+ sessionRuntimeDir: effectiveCtx.sessionRuntimeDir,
100
+ ownerInvolved: params.ownerInvolved,
101
+ forgetAfterDays: params.forgetAfterDays,
102
+ scopeOverride: params.scopeOverride,
103
+ });
104
+ }
105
+ catch (error) {
106
+ if (claimedProcessing && memoryDir)
107
+ restorePendingNotes(memoryDir);
108
+ throw error;
109
+ }
144
110
  const baseagent = effectiveCtx.baseagent ?? 'claude';
145
111
  const now = Date.now();
146
112
  const scopeTag = params.scopeOverride ? `-${params.scopeOverride}` : '';
147
- const triggerId = `memory-consolidate-${mode}${scopeTag}-${now.toString(36)}`;
113
+ const kind = `memory-consolidate-${mode}${scopeTag}`;
114
+ const triggerId = `${kind}-${now.toString(36)}`;
148
115
  // 整理会话的渠道信息(origin):发起时抓取,供反馈时直接投递到原会话渠道。
149
116
  // 用 channelId/peerId/threadId 三要素——不依赖 evolcore sessionId,renew/`/new` 换会话也不影响送达。
150
117
  const feedbackOrigin = effectiveCtx.channel && effectiveCtx.peerId ? {
@@ -156,109 +123,73 @@ export async function launchConsolidation(channel, params) {
156
123
  ...(effectiveCtx.threadId ? { threadId: effectiveCtx.threadId } : {}),
157
124
  delivery: { chatType: effectiveCtx.chatType ?? 'private' },
158
125
  } : 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'),
126
+ const projectPath = params.projectPath ?? path.join(resolveRoot(), 'agents', selfAid);
127
+ const prompt = [
128
+ '你正在执行记忆整理任务。',
129
+ `本次整理模式:${mode}`,
130
+ `整理上下文文件路径:${ctxFile.filePath}`,
131
+ '',
132
+ '请先读取上述上下文文件,再按照注入的整理规范完成本次整理。',
133
+ ].join('\n');
134
+ // 通过 BackgroundSessionLauncher 拉起独立会话。
135
+ // 不再构造 TriggerDefinition——与 trigger 调度系统完全解耦。
136
+ // on_next_launch 默认策略由 launchBackgroundSession() 负责清理 evolcore session chat 目录;
137
+ // cleanupPreviousConsolidationLogs()(上方已调用)负责清理 memory/consolidation-logs/ 日志目录。
138
+ // 两者清理的是不同的数据,互不重复。
139
+ // 详见 docs/background-session/background-session-launcher.md
140
+ let handle;
141
+ try {
142
+ handle = await channel.launch({
143
+ kind,
144
+ instanceId: now.toString(36),
181
145
  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;
146
+ agentAid: selfAid,
147
+ projectPath,
148
+ prompt,
149
+ metadata: {
150
+ sessionType: 'memory',
151
+ memoryMode: mode,
152
+ ...(params.scopeOverride ? { memoryScope: params.scopeOverride } : {}),
153
+ // 透传到 session.metadata,供 ipc.ts 写 ops 流水、收尾流程读取
154
+ consolidationBatchDir: ctxFile.batchDir,
155
+ ...(ctxFile.chatDir ? { consolidationChatDir: ctxFile.chatDir } : {}),
156
+ ...(claimedProcessing && memoryDir ? { consolidationMemoryDir: memoryDir } : {}),
157
+ consolidationUsedThreadSnapshots: !!ctxFile.threadFiles?.length,
158
+ ...(ctxFile.snapshotThroughTs ? { consolidationSnapshotThroughTs: ctxFile.snapshotThroughTs } : {}),
159
+ ...(effectiveCtx.peerKey ? { peerKey: effectiveCtx.peerKey } : {}),
160
+ ...(feedbackOrigin ? { feedbackOrigin } : {}),
161
+ consolidationTriggeredBy: triggeredBy,
162
+ },
163
+ // cleanupPolicy 用默认 on_next_launch:清 evolcore session 记录;日志目录由 cleanupPreviousConsolidationLogs() 负责
164
+ });
205
165
  }
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}`);
166
+ catch (error) {
167
+ if (claimedProcessing && memoryDir)
168
+ restorePendingNotes(memoryDir);
169
+ recordConsolidationOutcome(ctxFile.contextFilePath, `启动失败: ${error instanceof Error ? error.message : String(error)}`);
170
+ throw error;
171
+ }
172
+ // launch() 在会话创建后即返回,sessionId 立即可用;不等会话结束。
173
+ // 收尾由 bg-session:completed/error/interrupted 事件驱动,这里只记录异常结局。
174
+ const executionSessionId = handle.sessionId;
175
+ void handle.result.then(r => {
176
+ if (r.outcome !== 'completed') {
177
+ logger.warn(`[Memory] 整理会话未正常结束(mode=${mode},outcome=${r.outcome}): ${r.error ?? ''}`);
178
+ }
224
179
  });
225
180
  logger.info(`[Memory] 已拉起整理会话(mode=${mode},triggeredBy=${triggeredBy},` +
226
- `selfAid=${selfAid}${effectiveCtx.peerKey ? `,peerKey=${effectiveCtx.peerKey}` : ''},` +
227
- `session=${session.id})`);
181
+ `selfAid=${selfAid}${effectiveCtx.peerKey ? `,peerKey=${effectiveCtx.peerKey}` : ''}` +
182
+ `${executionSessionId ? `,session=${executionSessionId}` : ''})`);
228
183
  return {
229
184
  triggerId,
230
185
  contextFile: ctxFile.filePath,
231
186
  contextFilePath: ctxFile.contextFilePath,
232
187
  logFile: ctxFile.contextFilePath,
233
188
  batchDir: ctxFile.batchDir,
234
- executionSessionId: session.id,
189
+ executionSessionId,
190
+ waitForAgentSessionId: handle.waitForAgentSessionId,
235
191
  };
236
192
  }
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
193
  /** 把 entry 移进 destDir;重名时加时间戳后缀,不覆盖已有文件。 */
263
194
  function moveIntoDir(srcPath, destDir, stamp) {
264
195
  const name = path.basename(srcPath);
@@ -276,6 +207,17 @@ function moveIntoDir(srcPath, destDir, stamp) {
276
207
  }
277
208
  fs.renameSync(srcPath, dest);
278
209
  }
210
+ function restorePendingNotes(memoryDir) {
211
+ const processingDir = path.join(memoryDir, 'processing');
212
+ if (!fs.existsSync(processingDir))
213
+ return;
214
+ const pendingDir = path.join(memoryDir, 'pending');
215
+ fs.mkdirSync(pendingDir, { recursive: true });
216
+ const stamp = Date.now().toString(36);
217
+ for (const entry of fs.readdirSync(processingDir))
218
+ moveIntoDir(path.join(processingDir, entry), pendingDir, stamp);
219
+ fs.rmdirSync(processingDir);
220
+ }
279
221
  /**
280
222
  * 整理会话结束后的收尾,task:completed / task:error / task:interrupted 都走这里。
281
223
  * - processing/ 里的 note:成功 → archive/notes/,失败 → 归还 pending/(下次重试)
@@ -306,9 +248,10 @@ export async function onConsolidationComplete(params) {
306
248
  result.movedTo = noteDest;
307
249
  }
308
250
  }
309
- if (params.success && params.chatDir) {
251
+ // C3: update mode-specific checkpoint only when thread snapshots were used
252
+ if (params.success && params.chatDir && params.mode && params.usedThreadSnapshots) {
310
253
  const { writeCheckpoint } = await import('./consolidation-context.js');
311
- writeCheckpoint(params.chatDir, now);
254
+ writeCheckpoint(params.chatDir, params.snapshotThroughTs ?? now, params.mode, params.scopeOverride);
312
255
  result.checkpointWritten = true;
313
256
  }
314
257
  const { summarizeOpsRecords, appendOpsRecord } = await import('./consolidation-ops-records.js');
@@ -396,6 +339,7 @@ export async function rollbackConsolidation(batchDir, selfAid) {
396
339
  }
397
340
  const MAX_CONCURRENT_CONSOLIDATION = 5;
398
341
  export function createConsolidationSessionFinalizer({ sessionManager, getAgent, agentMap, releaseSlot, enqueueConsolidation, sendFeedback, }) {
342
+ const finalizedSessions = new Set();
399
343
  return async (sessionId, success) => {
400
344
  let releaseAid;
401
345
  try {
@@ -408,6 +352,9 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
408
352
  const batchDir = (meta.consolidationBatchDir ?? meta.consolidationLogBase);
409
353
  if (!batchDir)
410
354
  return;
355
+ if (finalizedSessions.has(sessionId))
356
+ return;
357
+ finalizedSessions.add(sessionId);
411
358
  releaseAid = session.selfAID;
412
359
  const { summarizeOpsRecords, collectAffectedScopes } = await import('./consolidation-ops-records.js');
413
360
  if (!success) {
@@ -418,6 +365,10 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
418
365
  batchDir,
419
366
  chatDir: meta.consolidationChatDir,
420
367
  memoryDir: meta.consolidationMemoryDir,
368
+ mode: meta.memoryMode,
369
+ scopeOverride: meta.memoryScope,
370
+ usedThreadSnapshots: meta.consolidationUsedThreadSnapshots === true,
371
+ snapshotThroughTs: meta.consolidationSnapshotThroughTs,
421
372
  });
422
373
  try {
423
374
  const affectedScopes = collectAffectedScopes(batchDir);
@@ -537,8 +488,8 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
537
488
  logger.warn(`[Memory] 整理收尾失败(session=${sessionId}): ${error?.message}`);
538
489
  }
539
490
  finally {
540
- if (releaseAid)
541
- releaseSlot(releaseAid);
491
+ if (releaseAid && sessionId)
492
+ releaseSlot(releaseAid, sessionId);
542
493
  }
543
494
  };
544
495
  }
@@ -583,28 +534,43 @@ export function registerConsolidationSessionEvents({ eventBus, permissionGateway
583
534
  if ('sessionId' in event && event.sessionId) {
584
535
  permissionGateway.cancelAll(event.sessionId, event.reason || 'interrupted');
585
536
  }
586
- void finalize(event.sessionId, false);
587
537
  });
588
538
  eventBus.subscribe('task:error', event => {
589
539
  if ('sessionId' in event && event.sessionId) {
590
540
  permissionGateway.cancelAll(event.sessionId, 'task_error', false);
591
541
  }
592
- void finalize(event.sessionId, false);
593
542
  });
594
543
  eventBus.subscribe('task:completed', event => {
595
544
  if ('sessionId' in event && event.sessionId) {
596
545
  permissionGateway.cancelAll(event.sessionId, 'task_completed', false);
597
546
  }
598
- void finalize(event.sessionId, true);
547
+ });
548
+ eventBus.subscribe('bg-session:completed', event => {
549
+ if (event.type === 'bg-session:completed')
550
+ void finalize(event.sessionId, true);
551
+ });
552
+ eventBus.subscribe('bg-session:error', event => {
553
+ if (event.type === 'bg-session:error')
554
+ void finalize(event.sessionId, false);
555
+ });
556
+ eventBus.subscribe('bg-session:interrupted', event => {
557
+ if (event.type === 'bg-session:interrupted')
558
+ void finalize(event.sessionId, false);
599
559
  });
600
560
  }
601
561
  export class ConsolidationQueue {
602
562
  daemonChannel;
603
563
  activeAgents = new Set();
604
564
  queues = new Map();
565
+ activeSessions = new Map();
566
+ timers = new Map();
567
+ finalizer;
605
568
  constructor(daemonChannel) {
606
569
  this.daemonChannel = daemonChannel;
607
570
  }
571
+ setFinalizer(finalizer) {
572
+ this.finalizer = finalizer;
573
+ }
608
574
  async enqueue(params) {
609
575
  const aid = params.taskCtx.selfAid;
610
576
  if (!aid)
@@ -619,9 +585,30 @@ export class ConsolidationQueue {
619
585
  this.queues.set(aid, queue);
620
586
  logger.info(`[Memory] 整理请求入队(aid=${aid},mode=${params.mode},` +
621
587
  `队列长度=${queue.length},全局活跃=${this.activeAgents.size})`);
622
- return null;
588
+ return {
589
+ triggerId: '',
590
+ contextFile: '',
591
+ contextFilePath: '',
592
+ batchDir: '',
593
+ queued: {
594
+ position: queue.length,
595
+ agentQueueLength: queue.length,
596
+ agentBusy,
597
+ activeCount: this.activeAgents.size,
598
+ maxConcurrent: MAX_CONCURRENT_CONSOLIDATION,
599
+ totalQueued: [...this.queues.values()].reduce((total, pending) => total + pending.length, 0),
600
+ },
601
+ };
623
602
  }
624
- release(aid) {
603
+ release(aid, sessionId) {
604
+ const activeSessionId = this.activeSessions.get(aid);
605
+ if (sessionId && activeSessionId !== undefined && activeSessionId !== sessionId)
606
+ return;
607
+ const timer = this.timers.get(aid);
608
+ if (timer)
609
+ clearTimeout(timer);
610
+ this.timers.delete(aid);
611
+ this.activeSessions.delete(aid);
625
612
  this.activeAgents.delete(aid);
626
613
  void this.drain();
627
614
  }
@@ -631,7 +618,30 @@ export class ConsolidationQueue {
631
618
  return null;
632
619
  this.activeAgents.add(aid);
633
620
  try {
634
- return await launchConsolidation(this.daemonChannel, params);
621
+ const launched = await launchConsolidation(this.daemonChannel, params);
622
+ if (launched.executionSessionId) {
623
+ const sessionId = launched.executionSessionId;
624
+ this.activeSessions.set(aid, sessionId);
625
+ const timer = setTimeout(() => {
626
+ if (this.activeSessions.get(aid) !== sessionId)
627
+ return;
628
+ logger.warn(`[Memory] 整理会话超时收尾(aid=${aid},session=${sessionId})`);
629
+ if (this.finalizer)
630
+ void this.finalizer(sessionId, false).finally(() => this.release(aid, sessionId));
631
+ else
632
+ this.release(aid, sessionId);
633
+ }, 15 * 60_000);
634
+ timer.unref?.();
635
+ this.timers.set(aid, timer);
636
+ }
637
+ appendOpsRecord(launched.batchDir, {
638
+ ts: new Date().toISOString(),
639
+ op: 'session-start',
640
+ sessionId: launched.executionSessionId ?? launched.triggerId,
641
+ mode: params.mode,
642
+ triggeredBy: params.triggeredBy,
643
+ });
644
+ return launched;
635
645
  }
636
646
  catch (error) {
637
647
  logger.warn(`[Memory] 拉起整理会话失败(aid=${aid},mode=${params.mode}): ${error?.message}`);
@@ -659,33 +669,17 @@ export class ConsolidationQueue {
659
669
  }
660
670
  }
661
671
  }
662
- export function registerConsolidationLaunchExecutor({ ipcServer, sessionManager, enqueueConsolidation, }) {
672
+ export function registerConsolidationLaunchExecutor({ ipcServer, enqueueConsolidation, }) {
663
673
  ipcServer.setConsolidationLaunchExecutor(async (params) => {
664
674
  if (!params.taskCtx.selfAid)
665
675
  throw new Error('无法确定当前 agent AID(selfAid 为空)');
666
676
  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 };
677
+ if (!launched)
678
+ throw new Error('拉起整理会话失败,详见 daemon 日志');
679
+ if (launched.queued)
680
+ return launched;
681
+ const { waitForAgentSessionId, ...result } = launched;
682
+ return result;
689
683
  });
690
684
  }
691
685
  export function registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation, }) {