evolcore 0.0.2 → 0.0.4

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 (206) hide show
  1. package/CHANGELOG.md +93 -778
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +198 -48
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +203 -8
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/aun/outbox.js +8 -0
  14. package/dist/channels/aun.js +241 -116
  15. package/dist/channels/daemon.js +18 -4
  16. package/dist/channels/dingtalk.js +1005 -260
  17. package/dist/channels/feishu.js +184 -56
  18. package/dist/channels/qqbot.js +23 -1
  19. package/dist/channels/wechat.js +303 -155
  20. package/dist/channels/wecom.js +1132 -164
  21. package/dist/cli/agent-command.js +2 -1
  22. package/dist/cli/aun-commands.js +98 -43
  23. package/dist/cli/bench.js +2 -2
  24. package/dist/cli/contact.js +71 -0
  25. package/dist/cli/ctl-command.js +2 -2
  26. package/dist/cli/daemon-commands.js +77 -214
  27. package/dist/cli/handoff-command.js +4 -3
  28. package/dist/cli/help.js +9 -5
  29. package/dist/cli/index.js +132 -116
  30. package/dist/cli/init-channel.js +242 -129
  31. package/dist/cli/init.js +70 -31
  32. package/dist/cli/model.js +2 -1
  33. package/dist/cli/net-check.js +2 -2
  34. package/dist/cli/queue-command.js +30 -6
  35. package/dist/cli/raw-key-input.js +25 -0
  36. package/dist/cli/response.js +5 -6
  37. package/dist/cli/restart-monitor.js +25 -1
  38. package/dist/cli/stats.js +6 -4
  39. package/dist/cli/trigger-command.js +146 -25
  40. package/dist/cli/version.js +6 -1
  41. package/dist/config/access-policy-domain.js +38 -0
  42. package/dist/config/access-policy.js +134 -0
  43. package/dist/config/builtin-role-templates.js +42 -17
  44. package/dist/config/builtin-roles.js +31 -6
  45. package/dist/config/config-field-policy.js +17 -5
  46. package/dist/config/config-manager.js +409 -29
  47. package/dist/config/config-operation-service.js +35 -38
  48. package/dist/config/contact-alias.js +68 -0
  49. package/dist/config/contact-bind-code.js +274 -0
  50. package/dist/config/contact-book-store.js +622 -0
  51. package/dist/config/contact-book-v2-startup.js +35 -0
  52. package/dist/config/contact-book.js +185 -300
  53. package/dist/config/contact-operation-service.js +116 -0
  54. package/dist/config/contact-request-service.js +331 -0
  55. package/dist/config/peer-role-resolver.js +136 -55
  56. package/dist/config/role-ranks.js +18 -0
  57. package/dist/config/role-service.js +16 -19
  58. package/dist/config/role-store.js +16 -5
  59. package/dist/config/roles.js +10 -1
  60. package/dist/config/schema-registry.js +49 -24
  61. package/dist/config-store.js +14 -4
  62. package/dist/core/auth/agent-delegation.js +105 -11
  63. package/dist/core/auth/authorization-audit.js +16 -1
  64. package/dist/core/auth/operation-authorizer.js +10 -0
  65. package/dist/core/auth/operation-catalog.js +105 -1
  66. package/dist/core/auth/trigger-authorization.js +15 -0
  67. package/dist/core/bootstrap-service.js +2 -9
  68. package/dist/core/channel-loader.js +6 -2
  69. package/dist/core/command/command-handler.js +113 -21
  70. package/dist/core/command/connect-menu.js +599 -0
  71. package/dist/core/command/evol-menu-version-gate.js +38 -0
  72. package/dist/core/command/group-menu.js +421 -0
  73. package/dist/core/command/menu-handler.js +382 -100
  74. package/dist/core/command/menu-protocol.js +8 -2
  75. package/dist/core/command/menu-token-store.js +102 -0
  76. package/dist/core/command/role-menu.js +128 -29
  77. package/dist/core/command/slash-gate.js +1 -1
  78. package/dist/core/command/slash-handler.js +186 -49
  79. package/dist/core/daemon-file-cache.js +40 -6
  80. package/dist/core/event-catalog.js +99 -0
  81. package/dist/core/evolagent-registry.js +4 -40
  82. package/dist/core/evolagent.js +20 -9
  83. package/dist/core/handoff/runtime.js +162 -37
  84. package/dist/core/handoff/store.js +46 -2
  85. package/dist/core/handoff/types.js +1 -0
  86. package/dist/core/inference/text-inference.js +16 -74
  87. package/dist/core/message/file-markers.js +7 -0
  88. package/dist/core/message/im-renderer.js +20 -14
  89. package/dist/core/message/inbound-admission.js +124 -0
  90. package/dist/core/message/message-bridge.js +670 -57
  91. package/dist/core/message/message-log.js +1 -0
  92. package/dist/core/message/message-queue.js +195 -10
  93. package/dist/core/message/message-utils.js +8 -2
  94. package/dist/core/message/peer-mode.js +7 -8
  95. package/dist/core/message/response-engine.js +642 -96
  96. package/dist/core/message/send-receipt.js +24 -0
  97. package/dist/core/message/stream-debouncer.js +11 -2
  98. package/dist/core/permission/approval-gateway.js +17 -10
  99. package/dist/core/permission/ec-command-parser.js +101 -46
  100. package/dist/core/permission/tool-policy.js +115 -38
  101. package/dist/core/protected-paths.js +38 -11
  102. package/dist/core/session/session-fs-store.js +44 -3
  103. package/dist/core/session/session-manager.js +86 -8
  104. package/dist/core/session/session-mapper.js +2 -0
  105. package/dist/core/session/session-renew.js +125 -69
  106. package/dist/core/session/session-turn-coordinator.js +5 -1
  107. package/dist/eck/kit-renderer.js +12 -1
  108. package/dist/eck/message-renderer.js +79 -1
  109. package/dist/index.js +285 -94
  110. package/dist/ipc.js +114 -8
  111. package/dist/paths.js +3 -0
  112. package/dist/response-system/config-resolver.js +29 -0
  113. package/dist/response-system/coordinator.js +8 -32
  114. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  115. package/dist/response-system/index.js +1 -0
  116. package/dist/response-system/modes/single-session/index.js +7 -4
  117. package/dist/stats/billing.js +20 -8
  118. package/dist/trigger/manager.js +3 -0
  119. package/dist/trigger/parser.js +126 -11
  120. package/dist/trigger/patch.js +12 -2
  121. package/dist/trigger/scheduler.js +444 -40
  122. package/dist/trigger/validation.js +13 -0
  123. package/dist/utils/aid-bind.js +43 -29
  124. package/dist/utils/error-dict.json +7 -0
  125. package/dist/utils/evolcore-version.js +21 -0
  126. package/dist/utils/instance-registry.js +14 -7
  127. package/dist/utils/log-writer.js +46 -0
  128. package/dist/utils/logger.js +2 -2
  129. package/dist/utils/media-cache.js +4 -1
  130. package/dist/utils/model-prices.jsonl +6 -3
  131. package/dist/utils/restart-safety.js +31 -0
  132. package/dist/utils/stable-semver.js +21 -0
  133. package/dist/utils/stats.js +33 -9
  134. package/dist/utils/system-memory.js +62 -0
  135. package/kits/docs/INDEX.md +2 -1
  136. package/kits/docs/channels/aun.md +4 -13
  137. package/kits/docs/evolcore/INDEX.md +5 -3
  138. package/kits/docs/evolcore/agent.md +9 -1
  139. package/kits/docs/evolcore/aid.md +5 -2
  140. package/kits/docs/evolcore/config.md +47 -2
  141. package/kits/docs/evolcore/contact.md +61 -0
  142. package/kits/docs/evolcore/fs.md +9 -0
  143. package/kits/docs/evolcore/group-rules.md +46 -4
  144. package/kits/docs/evolcore/group.md +15 -6
  145. package/kits/docs/evolcore/model.md +4 -1
  146. package/kits/docs/evolcore/msg.md +12 -6
  147. package/kits/docs/evolcore/response.md +16 -21
  148. package/kits/docs/evolcore/rpc.md +2 -0
  149. package/kits/docs/evolcore/stats.md +15 -2
  150. package/kits/docs/evolcore/storage.md +1 -0
  151. package/kits/docs/evolcore/trigger.md +42 -10
  152. package/kits/docs/path-registry.md +36 -17
  153. package/kits/eck_manifest.json +12 -0
  154. package/kits/eck_message_manifest.json +12 -1
  155. package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
  156. package/kits/rules/01-overview.md +17 -7
  157. package/kits/rules/02-navigation.md +38 -18
  158. package/kits/rules/03-identity.md +28 -24
  159. package/kits/rules/04-relation.md +44 -28
  160. package/kits/rules/05-venue.md +31 -15
  161. package/kits/rules/06-channel.md +11 -7
  162. package/kits/schemas/_meta.json +21 -7
  163. package/kits/schemas/agent-config.schema.6.json +322 -0
  164. package/kits/schemas/agent-config.schema.7.json +303 -0
  165. package/kits/schemas/agent-config.schema.8.json +304 -0
  166. package/kits/schemas/agent-config.schema.9.json +374 -0
  167. package/kits/schemas/contact-book.schema.2.json +43 -0
  168. package/kits/schemas/contact-book.schema.3.json +67 -0
  169. package/kits/schemas/daemon.schema.1.json +3 -2
  170. package/kits/schemas/daemon.schema.2.json +101 -0
  171. package/kits/schemas/daemon.schema.3.json +123 -0
  172. package/kits/schemas/defaults.schema.2.json +85 -0
  173. package/kits/schemas/defaults.schema.3.json +73 -0
  174. package/kits/schemas/relation-config.schema.5.json +47 -0
  175. package/kits/schemas/relation-config.schema.6.json +46 -0
  176. package/kits/schemas/relation-config.schema.7.json +59 -0
  177. package/kits/schemas/role-config.schema.1.json +1 -0
  178. package/kits/schemas/role-registry.schema.1.json +2 -2
  179. package/kits/schemas/single-session.schema.2.json +57 -0
  180. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  181. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  182. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  183. package/kits/templates/roles/admin.json +47 -0
  184. package/kits/templates/roles/member.json +5 -0
  185. package/kits/templates/roles/owner.json +1 -0
  186. package/kits/templates/roles/visitor.json +5 -0
  187. package/kits/templates/system-fragments/channel.md +12 -2
  188. package/kits/templates/system-fragments/commands.md +3 -1
  189. package/kits/templates/system-fragments/identity.md +3 -1
  190. package/kits/templates/system-fragments/relation.md +1 -1
  191. package/kits/templates/system-fragments/session.md +6 -2
  192. package/package.json +7 -5
  193. package/MIGRATION-0.5.0.md +0 -378
  194. package/ROLE_ACCESS_CONTROL.md +0 -174
  195. package/assets/brand/evolcore/README.md +0 -19
  196. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  197. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  198. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  199. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  200. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  201. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  202. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  203. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  204. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  205. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  206. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
@@ -6,7 +6,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
6
6
  /^agents\/defaults\.json$/,
7
7
  /^agents\/[^/]+\/config\.json$/,
8
8
  /^agents\/[^/]+\/contact\.json$/,
9
- /^agents\/[^/]+\/roles(?:\/|$)/,
9
+ /^agents\/[^/]+\/contact-operations\.jsonl$/,
10
10
  /^agents\/[^/]+\/relations\/[^/]+\/config\.json$/,
11
11
  /^backups\/config(?:\/|$)/,
12
12
  /^\.snapshots(?:\/|$)/,
@@ -29,17 +29,18 @@ const H_CLASS_RELATIVE_PATTERNS = [
29
29
  ];
30
30
  const H_CLASS_REFERENCE_PATTERNS = [
31
31
  /(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
32
- /(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
33
- /(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
32
+ /(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
34
33
  /(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
35
34
  /(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|instance[\\/]control\.token|outbox|handoff))(?:[\\/]|$|[\s"';&|<>)},])/,
36
35
  /(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
37
36
  ];
38
37
  const L_CLASS_RELATIVE_PATTERNS = [
39
38
  /^data\/triggers(?:\/|$)/,
39
+ /^agents\/[^/]+\/roles(?:\/|$)/,
40
40
  ];
41
41
  const L_CLASS_REFERENCE_PATTERNS = [
42
42
  /(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
43
+ /(?:^|[^A-Za-z0-9_.-])agents[\\/][^/\\\s"']+[\\/]roles(?:[\\/]|$|[\s"';&|<>)},])/,
43
44
  ];
44
45
  function normalizeForComparison(value) {
45
46
  const resolved = path.resolve(value);
@@ -127,8 +128,7 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
127
128
  path.join('agents', 'defaults.json'),
128
129
  path.join('agents', '*', 'config.json'),
129
130
  path.join('agents', '*', 'contact.json'),
130
- path.join('agents', '*', 'roles'),
131
- path.join('agents', '*', 'roles', '**'),
131
+ path.join('agents', '*', 'contact-operations.jsonl'),
132
132
  path.join('agents', '*', 'relations', '*', 'config.json'),
133
133
  path.join('backups', 'config'),
134
134
  path.join('backups', 'config', '**'),
@@ -169,6 +169,7 @@ export function getLClassWritePatterns(root = resolveRoot()) {
169
169
  return [
170
170
  path.join(absoluteRoot, 'data', 'triggers'),
171
171
  path.join(absoluteRoot, 'data', 'triggers', '**'),
172
+ path.join(absoluteRoot, 'agents', '*', 'roles', '**'),
172
173
  ];
173
174
  }
174
175
  const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
@@ -213,10 +214,9 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
213
214
  if (!entry.isDirectory())
214
215
  continue;
215
216
  const agentRoot = path.join(agentsDir, entry.name);
216
- for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_']) {
217
+ for (const fileName of ['config.json', 'config.json_', 'contact.json', 'contact.json_', 'contact-operations.jsonl']) {
217
218
  addExistingTarget(targets, path.join(agentRoot, fileName));
218
219
  }
219
- addExistingTarget(targets, path.join(agentRoot, 'roles'));
220
220
  const relationsDir = path.join(agentRoot, 'relations');
221
221
  try {
222
222
  for (const relation of fs.readdirSync(relationsDir, { withFileTypes: true })) {
@@ -247,7 +247,17 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
247
247
  }
248
248
  export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
249
249
  const targets = new Map();
250
- addExistingTarget(targets, path.join(resolveThroughExistingAncestor(root), 'data', 'triggers'));
250
+ const absoluteRoot = resolveThroughExistingAncestor(root);
251
+ addExistingTarget(targets, path.join(absoluteRoot, 'data', 'triggers'));
252
+ const agentsDir = path.join(absoluteRoot, 'agents');
253
+ try {
254
+ for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
255
+ if (!entry.isDirectory())
256
+ continue;
257
+ addExistingTarget(targets, path.join(agentsDir, entry.name, 'roles'));
258
+ }
259
+ }
260
+ catch { }
251
261
  return [...targets.values()];
252
262
  }
253
263
  export function buildClaudeProtectedFilesystem(root = resolveRoot()) {
@@ -306,8 +316,9 @@ export function hClassGrantIncludesProtectedPath(value, options = {}) {
306
316
  return true;
307
317
  }
308
318
  export function lClassGrantIncludesProtectedPath(value, options = {}) {
319
+ if (!value.trim())
320
+ return false;
309
321
  const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());
310
- const protectedRoot = path.join(root, 'data', 'triggers');
311
322
  if (isLClassPath(value, { cwd: options.cwd, root }))
312
323
  return true;
313
324
  const normalized = value.replace(/\\/g, '/');
@@ -316,8 +327,24 @@ export function lClassGrantIncludesProtectedPath(value, options = {}) {
316
327
  ? value
317
328
  : normalized.slice(0, globIndex).replace(/\/+$/, '') || '.';
318
329
  const candidate = resolveProtectedCandidate(literalGrantRoot, options.cwd);
319
- return isSameOrDescendant(candidate, protectedRoot)
320
- || isSameOrDescendant(protectedRoot, candidate);
330
+ if (isSameOrDescendant(root, candidate))
331
+ return true;
332
+ if (!isSameOrDescendant(candidate, root))
333
+ return false;
334
+ const relative = path.relative(root, candidate);
335
+ const segments = relative.split(path.sep).filter(Boolean);
336
+ if (segments[0] === 'data') {
337
+ if (segments.length === 1)
338
+ return true;
339
+ return segments[1] === 'triggers';
340
+ }
341
+ if (segments[0] === 'agents') {
342
+ // The agents root and each individual Agent root contain a roles/ subtree.
343
+ if (segments.length <= 2)
344
+ return true;
345
+ return segments[2] === 'roles';
346
+ }
347
+ return false;
321
348
  }
322
349
  export function workspaceContainsHClassPaths(projectPath, root = resolveRoot()) {
323
350
  const project = resolveThroughExistingAncestor(projectPath);
@@ -13,12 +13,16 @@ function decodeSegment(s) {
13
13
  /**
14
14
  * 计算 chat 目录的完整路径。
15
15
  * - aun: sessionsDir/aun/<urlEncode(selfAID|'_unknown')>/<urlEncode(channelId)>/
16
+ * - wecom: sessionsDir/wecom/<urlEncode(channelKey)>/<urlEncode(channelId)>/
16
17
  * - 其它: sessionsDir/<channelType>/<urlEncode(channelId)>/
17
18
  */
18
- export function chatDirPath(sessionsDir, channelType, channelId, selfAID) {
19
+ export function chatDirPath(sessionsDir, channelType, channelId, selfAID, channelKey) {
19
20
  if (channelType === 'aun') {
20
21
  return path.join(sessionsDir, channelType, encodeSegment(selfAID || '_unknown'), encodeSegment(channelId));
21
22
  }
23
+ if (channelType === 'wecom' && channelKey?.startsWith('wecom#')) {
24
+ return path.join(sessionsDir, channelType, encodeSegment(channelKey), encodeSegment(channelId));
25
+ }
22
26
  return path.join(sessionsDir, channelType, encodeSegment(channelId));
23
27
  }
24
28
  /** 解码目录段(用于扫描时把目录名还原为原始 channelId/selfAID) */
@@ -126,6 +130,7 @@ export function readAllJsonlLines(filePath) {
126
130
  /**
127
131
  * 扫描所有 chat 目录。
128
132
  * - aun: channelType / selfAID / channelId (3层)
133
+ * - wecom: channelType / channelKey / channelId (3层;兼容旧的2层目录)
129
134
  * - 其它: channelType / channelId (2层)
130
135
  */
131
136
  export function scanChatDirs(sessionsDir) {
@@ -176,6 +181,42 @@ export function scanChatDirs(sessionsDir) {
176
181
  }
177
182
  }
178
183
  }
184
+ else if (channelType === 'wecom') {
185
+ for (const entry of level2Entries) {
186
+ if (!entry.isDirectory())
187
+ continue;
188
+ const decoded = decodeSegment(entry.name);
189
+ const entryDir = path.join(typeDir, entry.name);
190
+ if (decoded.startsWith('wecom#') && decoded.split('#').length === 3) {
191
+ let chatEntries;
192
+ try {
193
+ chatEntries = fs.readdirSync(entryDir, { withFileTypes: true });
194
+ }
195
+ catch {
196
+ continue;
197
+ }
198
+ for (const chatEntry of chatEntries) {
199
+ if (!chatEntry.isDirectory())
200
+ continue;
201
+ results.push({
202
+ channelType,
203
+ selfAID: '',
204
+ channelKey: decoded,
205
+ channelId: decodeSegment(chatEntry.name),
206
+ dirPath: path.join(entryDir, chatEntry.name),
207
+ });
208
+ }
209
+ }
210
+ else {
211
+ results.push({
212
+ channelType,
213
+ selfAID: '',
214
+ channelId: decoded,
215
+ dirPath: entryDir,
216
+ });
217
+ }
218
+ }
219
+ }
179
220
  else {
180
221
  // 2-layer: channelType/channelId
181
222
  for (const chatEntry of level2Entries) {
@@ -205,8 +246,8 @@ export function scanMetaFiles(chatDir) {
205
246
  throw e;
206
247
  }
207
248
  }
208
- export function ensureChatDir(sessionsDir, channelType, channelId, selfAID) {
209
- const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID);
249
+ export function ensureChatDir(sessionsDir, channelType, channelId, selfAID, channelKey) {
250
+ const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID, channelKey);
210
251
  fs.mkdirSync(dir, { recursive: true });
211
252
  fs.mkdirSync(path.join(dir, '_threads'), { recursive: true });
212
253
  fs.mkdirSync(path.join(dir, '_trash'), { recursive: true });
@@ -9,6 +9,22 @@ import { isSystemControlChannel } from '../system-channels.js';
9
9
  import path from 'path';
10
10
  import fs from 'fs';
11
11
  import os from 'os';
12
+ function newSessionStableMetadata(chatType, ...sources) {
13
+ const keys = chatType === 'group'
14
+ ? ['channelKey', 'groupId', 'groupName', 'dispatchMode']
15
+ : ['channelKey', 'peerId', 'peerName', 'peerType'];
16
+ const metadata = {};
17
+ for (const source of sources) {
18
+ if (!source)
19
+ continue;
20
+ for (const key of keys) {
21
+ const value = source[key];
22
+ if (value !== undefined && value !== null && value !== '')
23
+ metadata[key] = value;
24
+ }
25
+ }
26
+ return metadata;
27
+ }
12
28
  /** 判定用户是否为指定渠道的 admin */
13
29
  /** 判定用户是否为指定渠道的 member */
14
30
  export class SessionManager {
@@ -23,6 +39,7 @@ export class SessionManager {
23
39
  this.eventBus = eventBus;
24
40
  this.identityResolver = identityResolver;
25
41
  this.migrateChannelKeyFormat();
42
+ this.migrateWecomChannelDirs();
26
43
  }
27
44
  setIdentityResolver(resolver) {
28
45
  this.identityResolver = resolver;
@@ -69,20 +86,20 @@ export class SessionManager {
69
86
  * 需要明确的 channelType + selfAID 才能确定路径。
70
87
  */
71
88
  resolveChatDir(channel, channelId, channelType, selfAID) {
72
- return chatDirPath(this.sessionsDir, channelType, channelId, selfAID);
89
+ return chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel);
73
90
  }
74
91
  /**
75
92
  * 给定明确的 channelType + selfAID 时直接计算路径(不扫描)。
76
93
  * 用于 caller 已经知道完整路由信息的场景(如 message-bridge 透传)。
77
94
  */
78
95
  resolveChatDirExact(channel, channelId, channelType, selfAID) {
79
- return chatDirPath(this.sessionsDir, channelType, channelId, selfAID);
96
+ return chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel);
80
97
  }
81
98
  resolveChatDirFromSession(session) {
82
99
  if (!session.channelType) {
83
100
  throw new Error(`[SessionManager] missing channelType for session ${session.id}`);
84
101
  }
85
- return chatDirPath(this.sessionsDir, session.channelType, session.channelId, session.selfAID);
102
+ return chatDirPath(this.sessionsDir, session.channelType, session.channelId, session.selfAID, session.channel);
86
103
  }
87
104
  deriveChannelIdentity(channel, channelId) {
88
105
  const parsed = tryParseChannelKey(channel);
@@ -253,7 +270,7 @@ export class SessionManager {
253
270
  }
254
271
  ensureChatDirForSession(session) {
255
272
  const channelType = session.channelType || session.channel;
256
- return ensureChatDir(this.sessionsDir, channelType, session.channelId, session.selfAID);
273
+ return ensureChatDir(this.sessionsDir, channelType, session.channelId, session.selfAID, session.channel);
257
274
  }
258
275
  metaFilePath(chatDir, sessionId) {
259
276
  return path.join(chatDir, `${sessionId}.jsonl`);
@@ -678,7 +695,7 @@ export class SessionManager {
678
695
  getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, baseagent, selfAID, channelType, peerType, chatType) {
679
696
  // 使用精确路径(channelType + selfAID)
680
697
  const chatDir = (channelType && selfAID)
681
- ? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
698
+ ? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfAID, channel); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
682
699
  : this.ensureResolvedChatDirSafe(channel, channelId, channelType, selfAID);
683
700
  const threadIndex = readThreadIndex(chatDir);
684
701
  const existingEntry = threadIndex[threadId];
@@ -1097,18 +1114,20 @@ export class SessionManager {
1097
1114
  }
1098
1115
  }
1099
1116
  }
1100
- async createNewSession(channel, channelId, projectPath, name, baseagent) {
1117
+ async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata) {
1101
1118
  const inheritedChatType = this.getActiveChatType(channel, channelId);
1102
1119
  // Derive selfAID and channelType from existing sessions
1103
1120
  const existingDir = this.findExistingChatDir(channel, channelId);
1104
1121
  const identity = this.deriveChannelIdentity(channel, channelId);
1105
1122
  let channelType = identity.channelType;
1106
1123
  let selfAID = identity.selfAID;
1124
+ let activeMetadata;
1107
1125
  if (existingDir) {
1108
1126
  const active = readJsonFile(path.join(existingDir, 'active.json'));
1109
1127
  if (active) {
1110
1128
  channelType = active.channelType || channel;
1111
1129
  selfAID = active.selfAID || '';
1130
+ activeMetadata = active.metadata;
1112
1131
  // 兼容旧字段名
1113
1132
  const activeBaseagent = active.baseagent || active.agentType;
1114
1133
  if (!baseagent && activeBaseagent)
@@ -1129,7 +1148,7 @@ export class SessionManager {
1129
1148
  baseagent,
1130
1149
  sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
1131
1150
  chatType: inheritedChatType,
1132
- metadata: {},
1151
+ metadata: newSessionStableMetadata(inheritedChatType, activeMetadata, identityMetadata),
1133
1152
  name: name || '默认会话',
1134
1153
  createdAt: Date.now(),
1135
1154
  updatedAt: Date.now(),
@@ -1257,7 +1276,7 @@ export class SessionManager {
1257
1276
  * 为超时后的独立话题创建新会话。保留聊天路由和稳定 metadata,
1258
1277
  * 但不继承任何底层 agent session 或临时任务状态。
1259
1278
  */
1260
- async createRenewedSession(sourceSession) {
1279
+ async createRenewedSession(sourceSession, context) {
1261
1280
  const metadata = { ...(sourceSession.metadata || {}) };
1262
1281
  delete metadata.agentSessions;
1263
1282
  delete metadata.resumeAt;
@@ -1288,7 +1307,11 @@ export class SessionManager {
1288
1307
  projectPath: session.projectPath,
1289
1308
  name: session.name,
1290
1309
  chatType: session.chatType,
1310
+ cause: 'renew',
1311
+ sourceSessionId: sourceSession.id,
1312
+ renewSource: context?.source,
1291
1313
  timestamp: Date.now(),
1314
+ causation: context?.causation,
1292
1315
  });
1293
1316
  return session;
1294
1317
  }
@@ -1584,4 +1607,59 @@ export class SessionManager {
1584
1607
  }
1585
1608
  return null;
1586
1609
  }
1610
+ /**
1611
+ * Move legacy `sessions/wecom/<chatId>` directories under their persisted
1612
+ * channelKey. The stored SessionFile.channel is authoritative; ambiguous
1613
+ * directories are deliberately left untouched for manual recovery.
1614
+ */
1615
+ migrateWecomChannelDirs() {
1616
+ const legacyDirs = scanChatDirs(this.sessionsDir)
1617
+ .filter(entry => entry.channelType === 'wecom' && !entry.channelKey);
1618
+ let migrated = 0;
1619
+ for (const entry of legacyDirs) {
1620
+ const candidates = [];
1621
+ const active = readJsonFile(path.join(entry.dirPath, 'active.json'));
1622
+ if (active)
1623
+ candidates.push(active);
1624
+ for (const metaFile of scanMetaFiles(entry.dirPath)) {
1625
+ const meta = readLastJsonlLine(path.join(entry.dirPath, metaFile));
1626
+ if (meta)
1627
+ candidates.push(meta);
1628
+ }
1629
+ const threadDir = path.join(entry.dirPath, '_threads');
1630
+ for (const metaFile of scanMetaFiles(threadDir)) {
1631
+ const meta = readLastJsonlLine(path.join(threadDir, metaFile));
1632
+ if (meta)
1633
+ candidates.push(meta);
1634
+ }
1635
+ const channelKeys = new Set(candidates
1636
+ .map(item => item.channel)
1637
+ .filter((value) => typeof value === 'string' && value.startsWith('wecom#')));
1638
+ if (channelKeys.size !== 1) {
1639
+ if (channelKeys.size > 1) {
1640
+ logger.error(`[SessionManager] Cannot auto-migrate mixed WeCom session directory: ${entry.dirPath}`);
1641
+ }
1642
+ continue;
1643
+ }
1644
+ const channelKey = [...channelKeys][0];
1645
+ const target = chatDirPath(this.sessionsDir, 'wecom', entry.channelId, undefined, channelKey);
1646
+ if (target === entry.dirPath)
1647
+ continue;
1648
+ if (fs.existsSync(target)) {
1649
+ logger.error(`[SessionManager] Cannot migrate WeCom session directory because target exists: ${target}`);
1650
+ continue;
1651
+ }
1652
+ try {
1653
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1654
+ fs.renameSync(entry.dirPath, target);
1655
+ migrated++;
1656
+ }
1657
+ catch (error) {
1658
+ logger.error(`[SessionManager] Failed to migrate WeCom session directory ${entry.dirPath}: ${String(error)}`);
1659
+ }
1660
+ }
1661
+ if (migrated > 0) {
1662
+ logger.info(`[SessionManager] Migrated ${migrated} WeCom session director${migrated === 1 ? 'y' : 'ies'} to channelKey isolation`);
1663
+ }
1664
+ }
1587
1665
  }
@@ -3,6 +3,8 @@ import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
3
3
  export function sessionToFile(session) {
4
4
  const metadata = {};
5
5
  if (session.metadata) {
6
+ if (session.metadata.channelKey)
7
+ metadata.channelKey = session.metadata.channelKey;
6
8
  if (session.metadata.peerId)
7
9
  metadata.peerId = session.metadata.peerId;
8
10
  if (session.metadata.peerName)