evolcore 0.0.28 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -1,20 +1,26 @@
1
1
  import { loadAgent } from '../../config-store.js';
2
2
  import { initializationBlocksTasks } from '../../cli/agent-create.js';
3
3
  import { createHash, randomBytes } from 'crypto';
4
+ import fs from 'fs';
5
+ import path from 'path';
4
6
  import { agentStorageMenuService, isAgentStorageMenuRequest } from '../command/agent-storage-menu.js';
5
7
  import { logger } from '../../utils/logger.js';
6
8
  import { StreamDebouncer } from './stream-debouncer.js';
7
9
  import { appendMessageLog, appendMessageLogStrict, buildInboundEntry, isTransientProtocolMessage } from './message-log.js';
8
10
  import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from './message-utils.js';
9
11
  import { chatDirPath } from '../session/session-fs-store.js';
10
- import { readTopicLable } from '../session/session-mapper.js';
12
+ import { readTopicLabel, readTopicStatus } from '../session/session-mapper.js';
11
13
  import { formatChannelKey, tryParseChannelKey } from '../channel-loader.js';
12
14
  import { agentDir, resolvePaths } from '../../paths.js';
13
15
  import { listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, } from '../../config/peer-role-resolver.js';
14
16
  import { resolveContactView, resolvePeerDisplayLabel, resolvePrincipal } from '../../config/contact-book.js';
15
17
  import { findContactRequestNote, expirePendingContactRequests, reviewContactRequest, submitContactRequest, contactRequestSubmissionProtocolResult, queryOwnContactRequestStatus, } from '../../config/contact-request-service.js';
16
18
  import { isValidAid } from '../../aun/aid/validation.js';
17
- import { PeerIdentityCache } from '../relation/peer-identity.js';
19
+ import { PeerIdentityCache, formatPeerKey } from '../relation/peer-identity.js';
20
+ import { resolveEffective } from '../../config/config-manager.js';
21
+ import { createDecisionInferenceProvider } from '../inference/text-inference.js';
22
+ import { AutoMentionGate } from './auto-mention-gate.js';
23
+ import { syncGroupRulesContext } from '../../eck/group-rules-sync.js';
18
24
  import { handlePendingDingtalkContactBindMessage, registerPendingDingtalkContactBind } from '../../channels/dingtalk.js';
19
25
  import { handlePendingFeishuContactBindMessage, registerPendingFeishuContactBind } from '../../channels/feishu.js';
20
26
  import { handlePendingQQBotContactBindMessage, registerPendingQQBotContactBind } from '../../channels/qqbot.js';
@@ -30,7 +36,6 @@ import { recordCausationSpan } from '../causation/audit.js';
30
36
  import { renderActionAsText } from '../interaction-router.js';
31
37
  import { planApprovalRoute } from '../permission/approval-gateway.js';
32
38
  import { guardGroupSlashCommand, parseFullAccessCommand } from '../command/slash-gate.js';
33
- import { isFullAccessEnabled } from '../../config-store.js';
34
39
  import { createAgentPolicyAuthorization } from '../role/runtime-policy.js';
35
40
  import { auditFullAccessEvent } from '../auth/authorization-audit.js';
36
41
  import { DIAGNOSTIC_AGENT_AID, DiagnosticSessionRouter, parseDiagnosticEvent, isDiagnosticSessionActive } from '../command/diag-service.js';
@@ -105,6 +110,79 @@ export class MessageBridge {
105
110
  cmdHandler;
106
111
  eventBus;
107
112
  processOwnersProvider;
113
+ autoMentionGate = new AutoMentionGate({
114
+ resolveInference: item => {
115
+ const config = resolveEffective({
116
+ self: item.selfAid,
117
+ peerKey: formatPeerKey('aun', item.groupId),
118
+ }, { cache: true, expand: true });
119
+ const model = config.decision_model?.model?.trim();
120
+ const provider = createDecisionInferenceProvider(config.decision_model);
121
+ return model && provider ? { model, provider } : undefined;
122
+ },
123
+ resolveResources: async (item) => {
124
+ const config = resolveEffective({
125
+ self: item.selfAid,
126
+ peerKey: formatPeerKey('aun', item.groupId),
127
+ }, { cache: true, expand: true });
128
+ const resources = {};
129
+ if (config.groupRules?.mode !== 'ignore') {
130
+ try {
131
+ const synced = await syncGroupRulesContext({ selfAid: item.selfAid, groupId: item.groupId, channel: 'aun' });
132
+ if (synced.groupRulesPath && fs.existsSync(synced.groupRulesPath)) {
133
+ resources.groupRules = fs.readFileSync(synced.groupRulesPath, 'utf8');
134
+ }
135
+ }
136
+ catch (error) {
137
+ logger.debug(`[MessageBridge] Decision context group rules unavailable: ${String(error)}`);
138
+ }
139
+ }
140
+ const personalPath = path.join(agentDir(item.selfAid), 'personal', 'personal.md');
141
+ if (fs.existsSync(personalPath))
142
+ resources.personal = fs.readFileSync(personalPath, 'utf8');
143
+ if (item.projectPath) {
144
+ const files = [];
145
+ let current = path.resolve(item.projectPath);
146
+ while (true) {
147
+ const candidate = path.join(current, 'AGENTS.md');
148
+ if (fs.existsSync(candidate))
149
+ files.unshift(candidate);
150
+ const parent = path.dirname(current);
151
+ if (parent === current)
152
+ break;
153
+ current = parent;
154
+ }
155
+ if (files.length > 0) {
156
+ resources.projectInstructions = files.map(file => (`# ${path.relative(item.projectPath, file) || 'AGENTS.md'}\n${fs.readFileSync(file, 'utf8')}`)).join('\n\n');
157
+ }
158
+ }
159
+ return resources;
160
+ },
161
+ resolveQuietWindowMs: item => resolveEffective({
162
+ self: item.selfAid,
163
+ peerKey: formatPeerKey('aun', item.groupId),
164
+ }, { cache: true, expand: true }).autoMentionQuietWindowMs,
165
+ resolveMessageState: item => {
166
+ if (!item.messageId)
167
+ return 'missing';
168
+ if (item.sessionId) {
169
+ const state = this.messageQueue.getMessageState?.(item.sessionId, item.messageId, item.selfAid)?.state ?? 'missing';
170
+ if (state !== 'missing')
171
+ return state;
172
+ }
173
+ return this.messageQueue.getMessageStateByMessageId?.(item.messageId, item.selfAid, {
174
+ channelId: item.groupId,
175
+ groupId: item.groupId,
176
+ threadId: item.threadId || '',
177
+ })?.state ?? 'missing';
178
+ },
179
+ resolveProcessing: item => {
180
+ if (!item.sessionId)
181
+ return !!item.mainSessionBusy;
182
+ return (this.messageQueue.isProcessing?.(item.sessionId) ?? false)
183
+ || (this.messageQueue.getQueueLength?.(item.sessionId) ?? 0) > 0;
184
+ },
185
+ });
108
186
  debouncers = new Map();
109
187
  defaultDebounce;
110
188
  /** Serializes session selection/renewal with inbound log admission per route. */
@@ -529,6 +607,12 @@ export class MessageBridge {
529
607
  }
530
608
  return true;
531
609
  }
610
+ async sendAdmissionMenuResponse(context, adapter, channel, msg, response) {
611
+ if (!consumeAdmissionReplyBudget(context))
612
+ return false;
613
+ await this.sendMenuResponse(adapter, channel, msg, response);
614
+ return true;
615
+ }
532
616
  /** Controls handled before the normal slash handler still need structured group feedback. */
533
617
  async sendSlashControlReply(channel, msg, sendReply, payload, adapter = this.processor.getChannelInfo?.(channel)?.adapter) {
534
618
  if (msg.chatType === 'group' && adapter?.send) {
@@ -766,6 +850,12 @@ export class MessageBridge {
766
850
  const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
767
851
  const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
768
852
  const resolvedChannelType = msg.channelType || parsedChannelKey?.type || effectiveChannelType;
853
+ const menuControl = resolvedChannelType === 'aun'
854
+ ? parseMenuControl(content)
855
+ : { isMenu: false };
856
+ const isPrivateMenuTokenRequest = menuControl.isMenu
857
+ && menuControl.type === 'menu.token.request'
858
+ && chatType === 'private';
769
859
  const admissionRoute = [
770
860
  resolvedChannelType,
771
861
  selfAid || '',
@@ -802,7 +892,8 @@ export class MessageBridge {
802
892
  if (chatType === 'private' && !peerType) {
803
893
  peerType = PeerIdentityCache.get('aun', primaryId, agentDir(selfAid))?.type;
804
894
  }
805
- if (chatType === 'private'
895
+ if (!menuControl.isMenu
896
+ && chatType === 'private'
806
897
  && REJECTABLE_PEER_TYPES.has(String(peerType || '').trim().toLowerCase())) {
807
898
  await this.sendAdmissionControlReply(admissionPreflight.context, msg, sendReply, CONTACT_REJECTION_NOTICE);
808
899
  }
@@ -840,11 +931,23 @@ export class MessageBridge {
840
931
  return;
841
932
  }
842
933
  const admissionDecision = evaluateOrdinaryInboundAdmission(admissionPreflight.context);
843
- if (!admissionDecision.allow) {
934
+ if (!admissionDecision.allow && !isPrivateMenuTokenRequest) {
844
935
  if (chatType === 'private') {
845
- const notice = this.admissionDeniedNotice(admissionDecision.context, resolvedChannelType);
846
- if (notice) {
847
- await this.sendAdmissionControlReply(admissionDecision.context, msg, sendReply, notice);
936
+ if (menuControl.isMenu) {
937
+ if (hasValidMenuId(menuControl)) {
938
+ const header = { id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) };
939
+ await this.sendAdmissionMenuResponse(admissionDecision.context, adapter, channelName, msg, menuFailure(header, {
940
+ code: 'ROLE_ACCESS_DENIED',
941
+ message: this.admissionDeniedNotice(admissionDecision.context, resolvedChannelType)
942
+ ?? '当前不接受陌生人消息。',
943
+ }));
944
+ }
945
+ }
946
+ else {
947
+ const notice = this.admissionDeniedNotice(admissionDecision.context, resolvedChannelType);
948
+ if (notice) {
949
+ await this.sendAdmissionControlReply(admissionDecision.context, msg, sendReply, notice);
950
+ }
848
951
  }
849
952
  }
850
953
  logger.info(`[MessageBridge] Inbound admission denied before business routing: self=${selfAid} actor=${actorId ?? '<none>'} channel=${channelKey} reason=${admissionDecision.reason}`);
@@ -853,9 +956,6 @@ export class MessageBridge {
853
956
  // Menu is a protocol control plane. Its parsing and response path must
854
957
  // remain available throughout the agent lifecycle; lifecycle gates
855
958
  // ordinary model execution, not protocol handling.
856
- const menuControl = resolvedChannelType === 'aun'
857
- ? parseMenuControl(content)
858
- : { isMenu: false };
859
959
  let menuResponseWarning;
860
960
  if (menuControl.isMenu) {
861
961
  // Menu is an AUN private control plane. Group messages must never
@@ -1043,10 +1143,6 @@ export class MessageBridge {
1043
1143
  await reject('用法:/fa <提示词> 或 /fullaccess <提示词>');
1044
1144
  return;
1045
1145
  }
1046
- if (!isFullAccessEnabled()) {
1047
- await reject('fullaccess 功能未启用,请先设置 daemon.json.fullaccess.enabled=true');
1048
- return;
1049
- }
1050
1146
  if (!authSubject.isDaemonOwner || !authSubject.principalId) {
1051
1147
  await reject('仅可信 daemon-owner 可以使用 fullaccess');
1052
1148
  return;
@@ -1113,6 +1209,27 @@ export class MessageBridge {
1113
1209
  // 2. 命令快速路径(去除引用前缀后检查,兼容话题中引用上文的情况)
1114
1210
  const contentForCmd = content.replace(/^(>[^\n]*\n)+\n?/, '').trim();
1115
1211
  const cmdContent = contentForCmd || content;
1212
+ let autoMode = false;
1213
+ if (resolvedChannelType === 'aun' && chatType === 'group' && selfAid && msg.source !== 'handoff') {
1214
+ try {
1215
+ const config = resolveEffective({ self: selfAid, peerKey: formatPeerKey('aun', msg.groupId || msg.channelId) }, { cache: true, expand: true });
1216
+ autoMode = config.mentionMode === 'auto';
1217
+ }
1218
+ catch (error) {
1219
+ logger.warn(`[MessageBridge] Auto mention configuration unavailable: ${String(error)}`);
1220
+ }
1221
+ }
1222
+ if (autoMode && !msg.isMentioned && /^\//.test(cmdContent)) {
1223
+ const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', msg.channelId, selfAid || '', channelKey);
1224
+ appendMessageLog(chatDir, buildInboundEntry({
1225
+ from: msg.peerId, to: selfAid || '', chatType: 'group', groupId: msg.groupId,
1226
+ threadId: msg.threadId, msgId: msg.messageId, content: msg.messageLogContent ?? content,
1227
+ timestamp: receivedAt, msgType: 'command', groupAttention: 'command', groupDelivery: 'filtered',
1228
+ }));
1229
+ if (msg.messageId)
1230
+ adapter?.acknowledge?.(msg.messageId).catch(() => { });
1231
+ return;
1232
+ }
1116
1233
  if (msg.source !== 'handoff' && !executionPermissionOverride) {
1117
1234
  const groupGuard = guardGroupSlashCommand(cmdContent, chatType);
1118
1235
  if (groupGuard?.kind === 'command.error') {
@@ -1194,6 +1311,72 @@ export class MessageBridge {
1194
1311
  logger.info(`[MessageBridge] Thread creation denied (silent drop): channel=${channelName} channelId=${msg.channelId} thread=${msg.threadId} sender=${msg.peerId}`);
1195
1312
  return;
1196
1313
  }
1314
+ let autoResult;
1315
+ if (autoMode && selfAid && msg.groupId && !executionPermissionOverride
1316
+ && !isCmd && !/\[EvolCore\.(receive|reply|agent)\]/.test(content)) {
1317
+ const attentionItem = {
1318
+ value: msg, selfAid, groupId: msg.groupId, threadId: msg.threadId || '', channelKey,
1319
+ sessionId: msg.replyContext?.sessionId
1320
+ ?? this.sessionManager.getActiveSessionSync?.(channelName, msg.channelId, resolvedChannelType, selfAid)?.id,
1321
+ projectPath: owningAgent?.projectPath ?? this.defaultProjectPath,
1322
+ messageId: msg.messageId, sender: msg.peerId, role: identity.role,
1323
+ mentions: (msg.mentions ?? []).flatMap(mention => {
1324
+ if (typeof mention.aid === 'string')
1325
+ return [mention.aid];
1326
+ return mention.scope === 'all' ? ['all'] : [];
1327
+ }),
1328
+ content, receivedAt,
1329
+ mainSessionBusy: (() => {
1330
+ try {
1331
+ const active = this.sessionManager.getActiveSessionSync?.(channelName, msg.channelId, resolvedChannelType, selfAid);
1332
+ return !!active && ((this.messageQueue.isProcessing?.(active.id) ?? false)
1333
+ || (this.messageQueue.getQueueLength?.(active.id) ?? 0) > 0);
1334
+ }
1335
+ catch {
1336
+ return false;
1337
+ }
1338
+ })(),
1339
+ };
1340
+ if (msg.isMentioned) {
1341
+ autoResult = {
1342
+ decision: 'interrupt', source: 'mention', leader: true, batch: [attentionItem], background: [],
1343
+ current: [{ source: 'current', message_id: msg.messageId ?? null, sender: msg.peerId,
1344
+ role: identity.role, mentions: attentionItem.mentions,
1345
+ timestamp: new Date(receivedAt).toISOString(), content }],
1346
+ };
1347
+ }
1348
+ else {
1349
+ if (msg.messageId)
1350
+ adapter?.acknowledge?.(msg.messageId).catch(() => { });
1351
+ autoResult = await this.autoMentionGate.offer(attentionItem);
1352
+ if (autoResult.decision === 'ignore') {
1353
+ try {
1354
+ const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', msg.channelId, selfAid, channelKey);
1355
+ appendMessageLogStrict(chatDir, buildInboundEntry({
1356
+ from: msg.peerId, to: selfAid, chatType: 'group', groupId: msg.groupId,
1357
+ threadId: msg.threadId, msgId: msg.messageId, content: msg.messageLogContent ?? content,
1358
+ permMode: identity.role, timestamp: receivedAt, receivedAt: msg.receivedAt,
1359
+ gatewaySeq: msg.gatewaySeq, encrypt: msg.encrypted, peerName: msg.peerName,
1360
+ peerType: msg.peerType, msgType: msg.msgType, payloadType: msg.payloadType,
1361
+ payloadSummary: msg.payloadSummary,
1362
+ groupAttention: autoResult.source === 'fallback' ? 'auto-fallback' : 'auto-ignore',
1363
+ groupDelivery: 'filtered',
1364
+ attentionDecision: { ...autoResult.details, decision: 'ignore', source: autoResult.source },
1365
+ }), true);
1366
+ return;
1367
+ }
1368
+ catch (error) {
1369
+ logger.warn(`[MessageBridge] Auto ignore history append failed, responding: ${String(error)}`);
1370
+ autoResult = { ...autoResult, decision: 'interrupt', source: 'fallback', leader: true, batch: [attentionItem] };
1371
+ }
1372
+ }
1373
+ if (!autoResult.leader)
1374
+ return;
1375
+ }
1376
+ }
1377
+ const attentionDecision = autoResult
1378
+ ? { ...autoResult.details, decision: autoResult.decision, source: autoResult.source }
1379
+ : undefined;
1197
1380
  const metadata = {};
1198
1381
  // 话题会话创建时写入 replyContext(用于 threadId 路由);主会话不写(避免群聊覆盖)
1199
1382
  if (msg.threadId && msg.replyContext)
@@ -1229,7 +1412,7 @@ export class MessageBridge {
1229
1412
  if (!releaseAdmission)
1230
1413
  releaseAdmission = await this.acquireAdmission(admissionRoute);
1231
1414
  const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
1232
- let session = diagnosticSession ?? await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLable(msg) ?? readTopicLable(msg.replyContext?.metadata));
1415
+ let session = diagnosticSession ?? await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLabel(msg) ?? readTopicLabel(msg.replyContext?.metadata), readTopicStatus(msg) ?? readTopicStatus(msg.replyContext?.metadata));
1233
1416
  if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
1234
1417
  releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
1235
1418
  }
@@ -1259,6 +1442,32 @@ export class MessageBridge {
1259
1442
  causation: inboundCausation,
1260
1443
  });
1261
1444
  session = renewResult.session;
1445
+ if ('renewed' in renewResult && renewResult.renewed) {
1446
+ const notice = '已自动切换到新会话';
1447
+ try {
1448
+ if (adapter?.send) {
1449
+ await adapter.send(buildEnvelope({
1450
+ taskId: `session-renew-${randomBytes(5).toString('hex')}`,
1451
+ sessionId: session.id,
1452
+ channel: channelName,
1453
+ channelId: msg.channelId,
1454
+ agentName: owningAgent?.name ?? selfAid ?? '<unknown>',
1455
+ chatmode: 'interactive',
1456
+ replyContext: msg.replyContext,
1457
+ }), {
1458
+ kind: 'system.notice',
1459
+ text: notice,
1460
+ subtype: 'session.auto-renewed',
1461
+ });
1462
+ }
1463
+ else {
1464
+ await sendReply(msg.channelId, notice, msg.replyContext);
1465
+ }
1466
+ }
1467
+ catch (error) {
1468
+ logger.warn(`[MessageBridge] Failed to send automatic session-renew notice: ${error instanceof Error ? error.message : String(error)}`);
1469
+ }
1470
+ }
1262
1471
  // 4. 群聊发送者标注由消息渲染层(message-renderer)逐条承担,不再在此硬编码前缀,
1263
1472
  // 消息日志因此保存干净原文。policy.messagePrefix 暂保留(未来清理)。
1264
1473
  // 5. 构造完整消息(channel 字段存实例名,用于 session 精确匹配)
@@ -1285,15 +1494,45 @@ export class MessageBridge {
1285
1494
  mentions: msg.mentions, mentionAids: msg.mentionAids, threadId: msg.threadId,
1286
1495
  isMentioned: msg.isMentioned,
1287
1496
  topicName: this.extractTopicName(msg),
1288
- lable: session.lable,
1497
+ label: session.label,
1498
+ topicStatus: session.topicStatus,
1289
1499
  replyContext: msg.replyContext,
1290
1500
  source: msg.source,
1291
1501
  ...(executionPermissionOverride ? { executionPermissionOverride } : {}),
1292
1502
  boundSessionId: session.id,
1293
1503
  resolvedIdentity: identity,
1294
1504
  mentionMode: msg.mentionMode,
1505
+ ...(autoResult && autoResult.decision !== 'ignore' ? { attentionPolicy: autoResult.decision } : {}),
1506
+ ...(autoResult ? { autoMentionBatch: true } : {}),
1295
1507
  causation: inboundCausation,
1296
1508
  };
1509
+ if (autoResult && autoResult.batch.length > 1) {
1510
+ fullMessage.content = autoResult.batch.map(item => item.content).join('\n');
1511
+ fullMessage.sourceMessageIds = autoResult.batch.flatMap(item => item.messageId ? [item.messageId] : []);
1512
+ fullMessage.items = autoResult.batch.map(item => ({
1513
+ peerId: item.sender, peerName: item.value.peerName, peerType: item.value.peerType,
1514
+ peerRole: item.role, content: item.content,
1515
+ mentions: item.value.mentions, mentionAids: item.value.mentionAids,
1516
+ images: item.value.images, timestamp: item.receivedAt,
1517
+ receivedAt: item.value.receivedAt, gatewaySeq: item.value.gatewaySeq,
1518
+ encrypted: item.value.encrypted,
1519
+ sameDevice: item.value.sameDevice, sameNetwork: item.value.sameNetwork,
1520
+ sameEgressIp: item.value.sameEgressIp,
1521
+ }));
1522
+ fullMessage.images = autoResult.batch.flatMap(item => item.value.images ?? []);
1523
+ fullMessage.mentions = autoResult.batch.flatMap(item => item.value.mentions ?? []);
1524
+ fullMessage.timestamp = autoResult.batch[0].receivedAt;
1525
+ fullMessage.receivedAt = autoResult.batch[0].receivedAt;
1526
+ fullMessage.encrypted = autoResult.batch.some(item => item.value.encrypted === true) ? true : fullMessage.encrypted;
1527
+ if (fullMessage.encrypted && fullMessage.replyContext) {
1528
+ fullMessage.replyContext = {
1529
+ ...fullMessage.replyContext,
1530
+ metadata: { ...fullMessage.replyContext.metadata, encrypted: true },
1531
+ };
1532
+ }
1533
+ if (!autoResult.batch.every(item => item.role === identity.role))
1534
+ fullMessage.batchRole = 'visitor';
1535
+ }
1297
1536
  recordCausationSpan(inboundCausation, 'message.inbound', {
1298
1537
  status: 'completed',
1299
1538
  refs: { messageId: msg.messageId, sessionId: session.id },
@@ -1326,6 +1565,11 @@ export class MessageBridge {
1326
1565
  msgType: msg.msgType,
1327
1566
  payloadType: msg.payloadType,
1328
1567
  payloadSummary: msg.payloadSummary,
1568
+ groupAttention: autoResult?.source === 'mention' ? 'auto-mention'
1569
+ : autoResult?.source === 'fallback' ? 'auto-fallback'
1570
+ : autoResult?.decision === 'defer' ? 'auto-defer'
1571
+ : autoResult ? 'auto-interrupt' : undefined,
1572
+ attentionDecision,
1329
1573
  }) };
1330
1574
  })();
1331
1575
  let handoffCandidate = false;
@@ -1358,6 +1602,23 @@ export class MessageBridge {
1358
1602
  }
1359
1603
  if (!handoffCandidate)
1360
1604
  appendMessageLog(inboundEntry.chatDir, inboundEntry.entry);
1605
+ if (autoResult && autoResult.batch.length > 1) {
1606
+ for (const item of autoResult.batch.slice(0, -1)) {
1607
+ const member = item.value;
1608
+ appendMessageLog(inboundEntry.chatDir, buildInboundEntry({
1609
+ from: item.sender, to: selfAid || '', sessionId: session.id,
1610
+ threadId: session.threadId || null, chatType: 'group', groupId: msg.groupId,
1611
+ msgId: item.messageId, content: member.messageLogContent ?? item.content,
1612
+ permMode: item.role, timestamp: item.receivedAt,
1613
+ receivedAt: member.receivedAt, gatewaySeq: member.gatewaySeq,
1614
+ encrypt: member.encrypted, peerName: member.peerName, peerType: member.peerType,
1615
+ msgType: member.msgType, payloadType: member.payloadType,
1616
+ payloadSummary: member.payloadSummary,
1617
+ groupAttention: autoResult?.decision === 'defer' ? 'auto-defer' : 'auto-interrupt',
1618
+ attentionDecision,
1619
+ }));
1620
+ }
1621
+ }
1361
1622
  // The authoritative session choice is now paired with its first
1362
1623
  // conversation record. Release before waiting for queue completion so
1363
1624
  // later group messages can still exercise the normal interrupt/FIFO
@@ -1376,6 +1637,8 @@ export class MessageBridge {
1376
1637
  adapter?.acknowledge?.(fullMessage.messageId).catch(() => { });
1377
1638
  }
1378
1639
  const isInterrupt = chatType !== 'group' && !handoffCandidate;
1640
+ const attentionPolicy = fullMessage.attentionPolicy;
1641
+ const shouldInterrupt = isInterrupt || attentionPolicy === 'interrupt';
1379
1642
  const enqueueAgentName = owningAgent?.name ?? '<unknown>';
1380
1643
  const selfAID = session.selfAID;
1381
1644
  const doEnqueue = async (m) => {
@@ -1385,10 +1648,13 @@ export class MessageBridge {
1385
1648
  const enqueue = handoffCandidate ? this.messageQueue.enqueuePersisted.bind(this.messageQueue) : this.messageQueue.enqueue.bind(this.messageQueue);
1386
1649
  try {
1387
1650
  return await enqueue(session.id, m, session.projectPath, {
1388
- interruptible: isInterrupt,
1389
- interruptSamePeer: !isInterrupt, // 群聊:同人连发且队列无他人时打断
1651
+ interruptible: shouldInterrupt,
1652
+ attentionPolicy,
1653
+ interruptSamePeer: !shouldInterrupt && !attentionPolicy, // 旧群聊路径保留同人连发打断
1654
+ priority: attentionPolicy === 'interrupt',
1390
1655
  agentName: enqueueAgentName,
1391
- role: !isInterrupt ? (session.identity?.role ?? 'none') : undefined,
1656
+ role: chatType === 'group' ? (session.identity?.role ?? 'none')
1657
+ : (!shouldInterrupt ? (session.identity?.role ?? 'none') : undefined),
1392
1658
  sessionKeyField: session.sessionKey,
1393
1659
  selfAID, // ← 传递 selfAID,用于队列隔离
1394
1660
  onPersisted: releaseIngress,
@@ -1748,7 +2014,7 @@ export class MessageBridge {
1748
2014
  ...(msg.peerDeviceId ? { toDeviceId: msg.peerDeviceId } : {}),
1749
2015
  metadata: {
1750
2016
  ...(msg.replyContext?.metadata ?? {}),
1751
- ...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version),
2017
+ ...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version, msg.protectedHeaders?.menu_volatile_v1),
1752
2018
  ...(isAgentStorageMenuRequest(request) ? { encrypted: true } : {}),
1753
2019
  },
1754
2020
  };
@@ -345,6 +345,7 @@ export function buildInboundEntry(opts) {
345
345
  payloadSummary: opts.payloadSummary,
346
346
  groupAttention: opts.groupAttention,
347
347
  groupDelivery: opts.groupDelivery,
348
+ attentionDecision: opts.attentionDecision,
348
349
  content: opts.content,
349
350
  replyTo: opts.replyTo ?? null,
350
351
  agent: null,