evolcore 0.0.3 → 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 (143) hide show
  1. package/CHANGELOG.md +64 -0
  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 +1 -31
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +157 -5
  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/channels/aun.js +220 -112
  14. package/dist/channels/daemon.js +18 -4
  15. package/dist/channels/dingtalk.js +52 -137
  16. package/dist/channels/feishu.js +56 -4
  17. package/dist/channels/qqbot.js +23 -1
  18. package/dist/channels/wechat.js +303 -155
  19. package/dist/channels/wecom.js +383 -7
  20. package/dist/cli/aun-commands.js +11 -11
  21. package/dist/cli/handoff-command.js +2 -1
  22. package/dist/cli/init-channel.js +185 -67
  23. package/dist/cli/init.js +7 -5
  24. package/dist/cli/trigger-command.js +92 -11
  25. package/dist/config/access-policy-domain.js +38 -0
  26. package/dist/config/access-policy.js +134 -0
  27. package/dist/config/builtin-role-templates.js +27 -14
  28. package/dist/config/builtin-roles.js +25 -6
  29. package/dist/config/config-field-policy.js +17 -5
  30. package/dist/config/config-manager.js +198 -22
  31. package/dist/config/config-operation-service.js +35 -38
  32. package/dist/config/contact-bind-code.js +274 -0
  33. package/dist/config/contact-book-store.js +173 -5
  34. package/dist/config/contact-book.js +32 -0
  35. package/dist/config/contact-operation-service.js +9 -3
  36. package/dist/config/contact-request-service.js +331 -0
  37. package/dist/config/peer-role-resolver.js +3 -1
  38. package/dist/config/schema-registry.js +49 -24
  39. package/dist/config-store.js +14 -2
  40. package/dist/core/auth/agent-delegation.js +105 -11
  41. package/dist/core/auth/authorization-audit.js +6 -0
  42. package/dist/core/auth/operation-authorizer.js +8 -0
  43. package/dist/core/auth/operation-catalog.js +51 -3
  44. package/dist/core/auth/trigger-authorization.js +15 -0
  45. package/dist/core/bootstrap-service.js +2 -9
  46. package/dist/core/channel-loader.js +6 -2
  47. package/dist/core/command/command-handler.js +10 -13
  48. package/dist/core/command/connect-menu.js +249 -24
  49. package/dist/core/command/evol-menu-version-gate.js +38 -0
  50. package/dist/core/command/group-menu.js +421 -0
  51. package/dist/core/command/menu-handler.js +269 -85
  52. package/dist/core/command/menu-protocol.js +8 -2
  53. package/dist/core/command/menu-token-store.js +102 -0
  54. package/dist/core/command/slash-gate.js +1 -1
  55. package/dist/core/command/slash-handler.js +158 -31
  56. package/dist/core/daemon-file-cache.js +28 -0
  57. package/dist/core/event-catalog.js +29 -0
  58. package/dist/core/evolagent-registry.js +4 -39
  59. package/dist/core/handoff/runtime.js +162 -37
  60. package/dist/core/handoff/store.js +46 -2
  61. package/dist/core/handoff/types.js +1 -0
  62. package/dist/core/inference/text-inference.js +16 -74
  63. package/dist/core/message/file-markers.js +7 -0
  64. package/dist/core/message/im-renderer.js +18 -14
  65. package/dist/core/message/inbound-admission.js +124 -0
  66. package/dist/core/message/message-bridge.js +616 -74
  67. package/dist/core/message/message-log.js +1 -0
  68. package/dist/core/message/message-queue.js +185 -10
  69. package/dist/core/message/peer-mode.js +7 -8
  70. package/dist/core/message/response-engine.js +381 -79
  71. package/dist/core/permission/approval-gateway.js +17 -10
  72. package/dist/core/permission/ec-command-parser.js +101 -46
  73. package/dist/core/permission/tool-policy.js +69 -24
  74. package/dist/core/protected-paths.js +36 -11
  75. package/dist/core/session/session-manager.js +25 -3
  76. package/dist/core/session/session-mapper.js +2 -0
  77. package/dist/core/session/session-renew.js +125 -69
  78. package/dist/core/session/session-turn-coordinator.js +5 -1
  79. package/dist/eck/kit-renderer.js +12 -1
  80. package/dist/eck/message-renderer.js +79 -1
  81. package/dist/index.js +224 -89
  82. package/dist/ipc.js +81 -4
  83. package/dist/paths.js +3 -0
  84. package/dist/response-system/config-resolver.js +29 -0
  85. package/dist/response-system/coordinator.js +8 -32
  86. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  87. package/dist/response-system/index.js +1 -0
  88. package/dist/response-system/modes/single-session/index.js +7 -4
  89. package/dist/trigger/parser.js +55 -15
  90. package/dist/trigger/patch.js +4 -1
  91. package/dist/trigger/scheduler.js +441 -39
  92. package/dist/utils/error-dict.json +7 -0
  93. package/dist/utils/evolcore-version.js +21 -0
  94. package/dist/utils/logger.js +2 -2
  95. package/dist/utils/stable-semver.js +21 -0
  96. package/dist/utils/stats.js +33 -9
  97. package/kits/docs/channels/aun.md +4 -13
  98. package/kits/docs/evolcore/INDEX.md +1 -1
  99. package/kits/docs/evolcore/config.md +47 -2
  100. package/kits/docs/evolcore/contact.md +7 -3
  101. package/kits/docs/evolcore/group-rules.md +46 -4
  102. package/kits/docs/evolcore/group.md +4 -4
  103. package/kits/docs/evolcore/msg.md +5 -5
  104. package/kits/docs/evolcore/trigger.md +25 -8
  105. package/kits/docs/path-registry.md +36 -17
  106. package/kits/eck_message_manifest.json +12 -1
  107. package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
  108. package/kits/rules/01-overview.md +17 -7
  109. package/kits/rules/02-navigation.md +38 -18
  110. package/kits/rules/03-identity.md +28 -24
  111. package/kits/rules/04-relation.md +44 -28
  112. package/kits/rules/05-venue.md +31 -15
  113. package/kits/rules/06-channel.md +11 -7
  114. package/kits/schemas/_meta.json +18 -7
  115. package/kits/schemas/agent-config.schema.7.json +303 -0
  116. package/kits/schemas/agent-config.schema.8.json +304 -0
  117. package/kits/schemas/agent-config.schema.9.json +374 -0
  118. package/kits/schemas/contact-book.schema.3.json +67 -0
  119. package/kits/schemas/daemon.schema.1.json +3 -2
  120. package/kits/schemas/daemon.schema.2.json +101 -0
  121. package/kits/schemas/daemon.schema.3.json +123 -0
  122. package/kits/schemas/defaults.schema.2.json +85 -0
  123. package/kits/schemas/defaults.schema.3.json +73 -0
  124. package/kits/schemas/relation-config.schema.6.json +46 -0
  125. package/kits/schemas/relation-config.schema.7.json +59 -0
  126. package/kits/schemas/single-session.schema.2.json +57 -0
  127. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  128. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  129. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  130. package/kits/templates/roles/admin.json +46 -0
  131. package/kits/templates/roles/member.json +4 -0
  132. package/kits/templates/roles/visitor.json +4 -0
  133. package/kits/templates/system-fragments/channel.md +12 -2
  134. package/kits/templates/system-fragments/identity.md +3 -1
  135. package/kits/templates/system-fragments/relation.md +1 -1
  136. package/kits/templates/system-fragments/session.md +6 -2
  137. package/package.json +4 -2
  138. package/MIGRATION-0.5.0.md +0 -378
  139. package/ROLE_ACCESS_CONTROL.md +0 -174
  140. package/dist/channels/contact-bind-code.js +0 -134
  141. package/dist/channels/wecom-card.js +0 -101
  142. package/dist/channels/wecom-onboarding.js +0 -82
  143. package/dist/channels/wecom-state.js +0 -191
@@ -7,10 +7,11 @@ import { logger, localTimestamp } from '../utils/logger.js';
7
7
  import { LogWriter } from '../utils/log-writer.js';
8
8
  import { middleOutputModePolicy, resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
9
9
  import { DEFAULT_FLUSH_DELAY_SECONDS } from '../types.js';
10
- import { resolvePaths, getPackageRoot, agentMdPath as agentMdPathFn, agentDir as agentDirPath, resolveRoot } from '../paths.js';
10
+ import { resolvePaths, agentMdPath as agentMdPathFn, agentDir as agentDirPath, resolveRoot } from '../paths.js';
11
11
  import { saveToUploads, sanitizeFileName, bufferToInboundImage, safeFetch } from '../utils/media-cache.js';
12
12
  import { appendAidEvent } from '../utils/instance-registry.js';
13
13
  import { appendMessageLog, buildOutboundEntry, buildInboundEntry, classifyAunPayloadForLog } from '../core/message/message-log.js';
14
+ import { createSendFileMarkerPattern } from '../core/message/file-markers.js';
14
15
  import { chatDirPath } from '../core/session/session-fs-store.js';
15
16
  import { appendHintAdd, appendHintRemove, parseInjectRequest } from '../core/message/pending-hints.js';
16
17
  import { appendAidLifecycle } from '../aun/aid/identity.js';
@@ -31,6 +32,8 @@ import { consumeAunCausation, registerAunCausation } from '../core/causation/aun
31
32
  import { deriveCausation, normalizeCausation } from '../core/causation/context.js';
32
33
  import { recordCausationSpan } from '../core/causation/audit.js';
33
34
  import { isExplicitGroupId } from '../aun/group-identity.js';
35
+ import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
36
+ import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
34
37
  export const AUN_HANDOFF_MARKER_FIELD = '_evolcore_handoff_id';
35
38
  /**
36
39
  * 构造 connect extra_info:自描述本进程身份。
@@ -45,7 +48,7 @@ function buildConnectExtraInfo(opts) {
45
48
  const startedAt = getProcessStartTime(process.pid) ?? Date.now();
46
49
  return {
47
50
  app: 'evolcore',
48
- version: getEvolcoreVersion(),
51
+ version: readInstalledEvolcoreVersion(),
49
52
  pid: process.pid,
50
53
  started_at: startedAt,
51
54
  started_at_iso: new Date(startedAt).toISOString(),
@@ -59,23 +62,14 @@ function buildConnectExtraInfo(opts) {
59
62
  channel_name: opts.channelName ?? '',
60
63
  };
61
64
  }
62
- let _cachedVersion = null;
63
- function getEvolcoreVersion() {
64
- if (_cachedVersion !== null)
65
- return _cachedVersion;
66
- try {
67
- const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
68
- _cachedVersion = String(pkg.version ?? '');
69
- }
70
- catch {
71
- _cachedVersion = '';
72
- }
73
- return _cachedVersion;
74
- }
75
65
  export function buildAunFilePayload(params) {
76
66
  const correlationId = params.context?.metadata?.correlationId;
77
67
  const payload = {
78
- type: correlationId ? 'result.file' : 'file',
68
+ // `result.file` is EvolCore's internal outbound kind. On the AUN wire this
69
+ // must remain the standard `file` payload type so clients route it through
70
+ // their attachment/download pipeline; correlation_id carries the control
71
+ // request association without changing the media type.
72
+ type: 'file',
79
73
  text: `📎 ${params.filename} (${formatSize(params.size)})`,
80
74
  attachments: [params.attachment],
81
75
  };
@@ -121,6 +115,7 @@ export function aunOptsToInbound(opts, channel, channelType) {
121
115
  sameNetwork: opts.sameNetwork,
122
116
  sameEgressIp: opts.sameEgressIp,
123
117
  encrypted: opts.encrypted,
118
+ protectedHeaders: opts.protectedHeaders,
124
119
  messageId: opts.messageId,
125
120
  causation: opts.causation,
126
121
  mentions: opts.mentions,
@@ -133,6 +128,54 @@ export function aunOptsToInbound(opts, channel, channelType) {
133
128
  dispatchMode: opts.dispatchMode,
134
129
  };
135
130
  }
131
+ function verifiedProtectedHeaders(msg, env) {
132
+ const candidates = [
133
+ msg.e2ee?.protected_headers,
134
+ env.protected_headers,
135
+ msg.protected_headers,
136
+ ];
137
+ for (const candidate of candidates) {
138
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
139
+ continue;
140
+ const headers = {};
141
+ for (const [key, value] of Object.entries(candidate)) {
142
+ const name = key.trim().toLowerCase();
143
+ if (!/^[a-z0-9_-]+$/.test(name) || typeof value !== 'string')
144
+ continue;
145
+ headers[name] = value;
146
+ }
147
+ return Object.keys(headers).length > 0 ? headers : undefined;
148
+ }
149
+ return undefined;
150
+ }
151
+ function redactMenuTokensForTrace(value, seen = new WeakSet()) {
152
+ if (!value || typeof value !== 'object')
153
+ return value;
154
+ if (seen.has(value))
155
+ return '[Circular]';
156
+ seen.add(value);
157
+ if (Array.isArray(value))
158
+ return value.map(item => redactMenuTokensForTrace(item, seen));
159
+ const redacted = {};
160
+ for (const [key, item] of Object.entries(value)) {
161
+ redacted[key] = key.toLowerCase() === 'menu_token'
162
+ ? '[REDACTED]'
163
+ : redactMenuTokensForTrace(item, seen);
164
+ }
165
+ return redacted;
166
+ }
167
+ function payloadTypeForObserver(value) {
168
+ if (!value || typeof value !== 'object' || Array.isArray(value))
169
+ return undefined;
170
+ const record = value;
171
+ const payload = record.payload
172
+ ?? (record.message && typeof record.message === 'object' ? record.message.payload : undefined)
173
+ ?? (record.envelope && typeof record.envelope === 'object' ? record.envelope.payload : undefined);
174
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
175
+ return undefined;
176
+ const type = payload.type;
177
+ return typeof type === 'string' ? type : undefined;
178
+ }
136
179
  export class AUNChannel {
137
180
  config;
138
181
  client = null;
@@ -157,7 +200,8 @@ export class AUNChannel {
157
200
  return;
158
201
  // 自动从 data 推断顶层字段(self_aid / peer_aid / group_id / task_id / chatmode),
159
202
  // 便于 jq 过滤:`jq 'select(.task_id == "task-xxx")'`
160
- const d = (data && typeof data === 'object') ? data : {};
203
+ const safeData = redactMenuTokensForTrace(data);
204
+ const d = (safeData && typeof safeData === 'object') ? safeData : {};
161
205
  const payload = d.payload ?? {};
162
206
  // 入站事件(IN)路由字段在 d.envelope.*(SDK 0.5.*);出站 params(OUT)字段在顶层。两者兼顾。
163
207
  const env = (d.envelope && typeof d.envelope === 'object') ? d.envelope : {};
@@ -178,7 +222,7 @@ export class AUNChannel {
178
222
  const chatmode = payload.chatmode;
179
223
  if (chatmode)
180
224
  topContext.chatmode = chatmode;
181
- const line = JSON.stringify({ ts: localTimestamp(), dir, event, ...topContext, data });
225
+ const line = JSON.stringify({ ts: localTimestamp(), dir, event, ...topContext, data: safeData });
182
226
  this.traceWriter.write(line);
183
227
  }
184
228
  /** 日志前缀(含 self aid 简称,多实例可识别) */
@@ -285,8 +329,20 @@ export class AUNChannel {
285
329
  : typeof obj.card_message_id === 'string' ? obj.card_message_id : '';
286
330
  const cardInfo = cardMsgId ? this.cardMessageIdMap.get(cardMsgId) : undefined;
287
331
  if (cardInfo) {
288
- const actionValue = typeof obj.value === 'string' ? obj.value
289
- : typeof obj.action_value === 'string' ? obj.action_value : text;
332
+ // compose 按钮点击后客户端收卡聚焦输入框,用户随后发送的自由文本由客户端结构化
333
+ // 回传为 action_card_reply{behavior:"compose", text},没有按钮 value/action_value
334
+ // 映射为 _custom_input + values.custom_text,复用 claude-runner 已有的自定义输入分支
335
+ // (原为多选表单提交而设),不需要新增 action 类型,也不必拦截消息队列。
336
+ const behaviorRaw = typeof obj.behavior === 'string' ? obj.behavior : undefined;
337
+ if (behaviorRaw && behaviorRaw !== 'reply' && behaviorRaw !== 'compose' && behaviorRaw !== 'dismiss') {
338
+ // 非法 behavior 值:按协议兜底为 reply 处理,但记录下来便于排查不合规客户端
339
+ // (例如本该是 compose 手动输入却被误判为普通按钮点击)。
340
+ logger.warn(`${this.logPrefix()} action_card_reply with unrecognized behavior="${behaviorRaw}" mid=${cardMsgId}, treating as reply`);
341
+ }
342
+ const isComposeReply = behaviorRaw === 'compose';
343
+ const actionValue = isComposeReply ? '_custom_input'
344
+ : typeof obj.value === 'string' ? obj.value
345
+ : typeof obj.action_value === 'string' ? obj.action_value : text;
290
346
  const threadId = typeof obj.thread_id === 'string' ? obj.thread_id : undefined;
291
347
  // 卡片点击者身份:只信认证信封(senderAid 参数,由调用方从 msg.from / msg.sender_aid 提取)。
292
348
  // payload 自报字段(from / sender_aid / user_id)不可信,可被客户端伪造,不读取。
@@ -361,7 +417,13 @@ export class AUNChannel {
361
417
  type: 'interaction.response',
362
418
  id: cardInfo.requestId,
363
419
  action: actionValue,
364
- values: { text, action_label: obj.label ?? obj.action_label, behavior: obj.behavior, card_message_id: cardMsgId },
420
+ values: {
421
+ text,
422
+ action_label: obj.label ?? obj.action_label,
423
+ behavior: obj.behavior,
424
+ card_message_id: cardMsgId,
425
+ ...(isComposeReply ? { custom_text: text } : {}),
426
+ },
365
427
  operatorId: cardClickerAid || undefined,
366
428
  };
367
429
  const settle = (accepted) => {
@@ -653,10 +715,6 @@ export class AUNChannel {
653
715
  'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
654
716
  'merge', 'link', 'location', 'personal_card',
655
717
  ]);
656
- /** Menu protocol 请求类型:自定义消息快速路径,绕过白名单直接分发到 bridge */
657
- static MENU_REQUEST_TYPES = new Set([
658
- 'menu.list', 'menu.query', 'menu.options', 'menu.update', 'menu.action',
659
- ]);
660
718
  /** 观察者插话请求类型(owner → agent.AID)。详见 docs/observer-insert-design.md。 */
661
719
  static INJECT_REQUEST_TYPE = 'observer.inject';
662
720
  /** 绑定请求类型(Evol App → 控制 AID):快速路径,绕过白名单透传原始 payload JSON 给 bridge。 */
@@ -1316,6 +1374,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1316
1374
  // SDK 0.5.* 移除了顶层 from/to/group_id/encrypted 等别名,统一从 msg.envelope.* 读取。
1317
1375
  // message_id / seq / payload / same_* 等仍是顶层独立字段,不在 envelope 内。
1318
1376
  const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
1377
+ const protectedHeaders = verifiedProtectedHeaders(msg, env);
1319
1378
  const fromAid = env.from ?? '';
1320
1379
  const payload = msg.payload ?? '';
1321
1380
  const causationRef = payload && typeof payload === 'object' && typeof payload._evolcore_causation_ref === 'string'
@@ -1371,7 +1430,9 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1371
1430
  const shortAid = this.getShortAid(fromAid);
1372
1431
  const displayName = peerIdentity.name || shortAid;
1373
1432
  // 详细 dispatch 决策日志:记录消息为何被路由到 agent
1374
- const p2pPayloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
1433
+ const p2pPayloadType = (payload && typeof payload === 'object' && typeof payload.type === 'string')
1434
+ ? payload.type
1435
+ : '';
1375
1436
  logger.info(`${this.logPrefix()} P2P dispatch decision: mid=${messageId} from=${shortAid}(${displayName}) peerType=${peerIdentity.type} payloadType=${p2pPayloadType} chatId=${chatId} encrypt=${msgEncrypted} textPreview=${JSON.stringify(text.slice(0, 80))}`);
1376
1437
  // action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
1377
1438
  if (p2pPayloadType === 'action_card_reply') {
@@ -1379,7 +1440,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1379
1440
  return;
1380
1441
  }
1381
1442
  // menu.* 协议:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
1382
- if (AUNChannel.MENU_REQUEST_TYPES.has(p2pPayloadType)) {
1443
+ if (p2pPayloadType.startsWith('menu.')) {
1383
1444
  this.acknowledgeImmediately(messageId, seq);
1384
1445
  this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId);
1385
1446
  this.dispatchMessage({
@@ -1388,6 +1449,9 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1388
1449
  chatType: 'private', messageId, seq,
1389
1450
  peerName: displayName || undefined,
1390
1451
  peerType: peerIdentity.type,
1452
+ encrypted: msgEncrypted,
1453
+ protectedHeaders,
1454
+ replyContext: this.buildGroupReplyContext(threadId, fromAid, msgEncrypted, messageId),
1391
1455
  msgType: logDescriptor.msgType,
1392
1456
  payloadType: logDescriptor.payloadType,
1393
1457
  payloadSummary: logDescriptor.payloadSummary,
@@ -1477,6 +1541,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1477
1541
  // SDK 0.5.* 移除了顶层 from/sender_aid/group_id/encrypted 等别名,统一从 msg.envelope.* 读取。
1478
1542
  // message_id / seq / payload / same_* / dispatch_mode 等仍是顶层独立字段,不在 envelope 内。
1479
1543
  const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
1544
+ const protectedHeaders = verifiedProtectedHeaders(msg, env);
1480
1545
  const groupId = env.group_id ?? '';
1481
1546
  const senderAid = env.from ?? '';
1482
1547
  const payload = msg.payload ?? '';
@@ -1554,15 +1619,19 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1554
1619
  // 信号类消息(status / event / thought / task.update 等)不进 Agent
1555
1620
  {
1556
1621
  const payloadObj = (payload && typeof payload === 'object') ? payload : null;
1557
- const payloadType = payloadObj?.type ?? '';
1622
+ const payloadType = typeof payloadObj?.type === 'string' ? payloadObj.type : '';
1558
1623
  // menu.* 协议:自定义消息快速路径
1559
- if (AUNChannel.MENU_REQUEST_TYPES.has(payloadType)) {
1624
+ if (payloadType.startsWith('menu.')) {
1625
+ const menuEncrypted = !!env.encrypted;
1560
1626
  this.acknowledgeImmediately(messageId, seq);
1561
- this.appendInboundPayloadJsonl(groupId, senderAid, messageId, !!env.encrypted, true, logDescriptor, threadId);
1627
+ this.appendInboundPayloadJsonl(groupId, senderAid, messageId, menuEncrypted, true, logDescriptor, threadId);
1562
1628
  this.dispatchMessage({
1563
1629
  channelId: groupId, userId: senderAid,
1564
1630
  text: JSON.stringify(payload),
1565
1631
  chatType: 'group', messageId, seq, groupId,
1632
+ encrypted: menuEncrypted,
1633
+ protectedHeaders,
1634
+ replyContext: this.buildGroupReplyContext(threadId, senderAid, menuEncrypted, messageId, payloadObj?.chatmode, topicName),
1566
1635
  msgType: logDescriptor.msgType,
1567
1636
  payloadType: logDescriptor.payloadType,
1568
1637
  payloadSummary: logDescriptor.payloadSummary,
@@ -1781,6 +1850,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1781
1850
  sameNetwork: event.sameNetwork,
1782
1851
  sameEgressIp: event.sameEgressIp,
1783
1852
  encrypted: event.encrypted,
1853
+ protectedHeaders: event.protectedHeaders,
1784
1854
  messageId: event.messageId,
1785
1855
  causation: event.causation,
1786
1856
  threadId: event.threadId,
@@ -1849,6 +1919,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1849
1919
  forwardInbound(data) {
1850
1920
  if (!this.connected || !this.client)
1851
1921
  return;
1922
+ if (payloadTypeForObserver(data)?.startsWith('menu.'))
1923
+ return;
1852
1924
  const { observable, owners } = this.getObserverConfig();
1853
1925
  if (!observable || owners.length === 0)
1854
1926
  return;
@@ -1870,6 +1942,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1870
1942
  forwardOutbound(result) {
1871
1943
  if (!this.connected || !this.client)
1872
1944
  return;
1945
+ if (payloadTypeForObserver(result)?.startsWith('menu.'))
1946
+ return;
1873
1947
  const { observable, owners } = this.getObserverConfig();
1874
1948
  if (!observable || owners.length === 0)
1875
1949
  return;
@@ -2362,6 +2436,30 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2362
2436
  messageIdFromSendResult(result) {
2363
2437
  return result?.message?.message_id ?? result?.message_id ?? null;
2364
2438
  }
2439
+ /**
2440
+ * An AUN send result is only a gateway transport result. It is not a
2441
+ * receipt that the peer client has pulled, decrypted, stored, or rendered it.
2442
+ * Preserve the compact fields for later correlation with the receiving side.
2443
+ */
2444
+ sendReceiptFromResult(result) {
2445
+ if (!result || typeof result !== 'object')
2446
+ return {};
2447
+ const response = result;
2448
+ return {
2449
+ status: typeof response.status === 'string' ? response.status : undefined,
2450
+ seq: typeof response.seq === 'number' ? response.seq : undefined,
2451
+ timestamp: typeof response.timestamp === 'number' ? response.timestamp : undefined,
2452
+ deliveryMode: typeof response.delivery_mode === 'string' ? response.delivery_mode : undefined,
2453
+ };
2454
+ }
2455
+ receiptLogFields(result) {
2456
+ const receipt = this.sendReceiptFromResult(result);
2457
+ return `aun_status=${receipt.status ?? 'unknown'} seq=${receipt.seq ?? 'unknown'} delivery_mode=${receipt.deliveryMode ?? 'unknown'} gateway_timestamp=${receipt.timestamp ?? 'unknown'}`;
2458
+ }
2459
+ logAunSendAccepted(method, target, messageId, encrypt, result, text) {
2460
+ const preview = text === undefined ? '' : ` text=${text.slice(0, 60)}`;
2461
+ logger.info(`${this.logPrefix()} ${method} accepted by AUN: target=${target} mid=${messageId} encrypt=${encrypt} ${this.receiptLogFields(result)}${preview}`);
2462
+ }
2365
2463
  stripUndefinedDeep(value) {
2366
2464
  if (Array.isArray(value)) {
2367
2465
  return value
@@ -2554,7 +2652,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2554
2652
  return { ok: false };
2555
2653
  }
2556
2654
  }
2557
- recordDurableOutbound(channelId, payload, messageId, encrypt, context, isGroup, contentKind, logText, result) {
2655
+ recordDurableOutbound(channelId, payload, messageId, encrypt, context, isGroup, contentKind, logText, result, statsContext) {
2558
2656
  const causation = normalizeCausation(context?.metadata?.causation);
2559
2657
  if (!isGroup && causation)
2560
2658
  registerAunCausation(messageId, this.config.aid, channelId, causation);
@@ -2576,10 +2674,11 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2576
2674
  len: Buffer.byteLength(logText, 'utf-8'),
2577
2675
  ...(isGroup && { groupId: channelId }),
2578
2676
  });
2579
- this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, this.payloadByteLength(payload, logText), logText, false, encrypt, context?.metadata?.chatmode);
2677
+ this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, this.payloadByteLength(payload, logText), logText, false, encrypt, context?.metadata?.chatmode, 'send', statsContext);
2580
2678
  const source = context?.metadata?.source ?? 'daemon';
2581
2679
  this.appendOutboundJsonl(channelId, {
2582
2680
  ...classifyAunPayloadForLog(payload), msgId: messageId, encrypt, context, isGroup, source,
2681
+ transport: this.sendReceiptFromResult(result),
2583
2682
  });
2584
2683
  this.forwardOutbound(result);
2585
2684
  }
@@ -2817,7 +2916,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2817
2916
  if (!messageId) {
2818
2917
  return { ok: false, error: `message.send returned no message_id: ${JSON.stringify(result)}`, code: 'MISSING_MESSAGE_ID' };
2819
2918
  }
2820
- logger.info(`${this.logPrefix()} daemon msg.send ok: to=${this.peerLabel(args.to)} mid=${messageId} encrypt=${encrypt} chatmode=${chatmode}`);
2919
+ this.logAunSendAccepted('message.send', this.peerLabel(args.to), messageId, encrypt, result);
2821
2920
  if (outboundCausation) {
2822
2921
  registerAunCausation(messageId, this.config.aid, args.to, outboundCausation);
2823
2922
  recordCausationSpan(outboundCausation, 'message.outbound', {
@@ -2846,6 +2945,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2846
2945
  payloadSummary: classified.payloadSummary ?? args.log.payloadSummary,
2847
2946
  source: args.log.source ?? 'msg',
2848
2947
  handoff_trace: args.log.handoffTrace,
2948
+ transport: this.sendReceiptFromResult(result),
2849
2949
  }));
2850
2950
  this.aidStatsCollector?.recordOutbound(this.config.aid, args.to, Buffer.byteLength(classified.content || args.log.content, 'utf-8'), classified.content || args.log.content, false, encrypt, chatmode, 'send');
2851
2951
  logWritten = true;
@@ -2853,10 +2953,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2853
2953
  return {
2854
2954
  ok: true,
2855
2955
  message_id: messageId,
2856
- seq: result?.seq,
2857
- timestamp: result?.timestamp,
2858
- status: result?.status,
2859
- delivery_mode: result?.delivery_mode,
2956
+ seq: this.sendReceiptFromResult(result).seq,
2957
+ timestamp: this.sendReceiptFromResult(result).timestamp,
2958
+ status: this.sendReceiptFromResult(result).status,
2959
+ delivery_mode: this.sendReceiptFromResult(result).deliveryMode,
2860
2960
  encrypt,
2861
2961
  chatmode,
2862
2962
  log_written: logWritten,
@@ -2920,7 +3020,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2920
3020
  if (!messageId) {
2921
3021
  return { ok: false, error: `group.send returned no message_id: ${JSON.stringify(result)}`, code: 'MISSING_MESSAGE_ID' };
2922
3022
  }
2923
- logger.info(`${this.logPrefix()} daemon group.send ok: group=${args.groupId} mid=${messageId} encrypt=${encrypt}`);
3023
+ this.logAunSendAccepted('group.send', args.groupId, messageId, encrypt, result);
2924
3024
  this.forwardOutbound(result);
2925
3025
  let logWritten = false;
2926
3026
  if (args.log?.content !== undefined) {
@@ -2944,8 +3044,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2944
3044
  payloadSummary: classified.payloadSummary ?? args.log.payloadSummary,
2945
3045
  source: args.log.source ?? 'msg',
2946
3046
  handoff_trace: args.log.handoffTrace,
3047
+ transport: this.sendReceiptFromResult(result),
2947
3048
  }));
2948
- this.aidStatsCollector?.recordOutbound(this.config.aid, args.groupId, Buffer.byteLength(content, 'utf-8'), content, false, encrypt, typeof finalPayload.chatmode === 'string' ? finalPayload.chatmode : undefined, 'send');
3049
+ const statsContext = await this.groupStatsContext(args.groupId);
3050
+ this.aidStatsCollector?.recordOutbound(this.config.aid, args.groupId, Buffer.byteLength(content, 'utf-8'), content, false, encrypt, typeof finalPayload.chatmode === 'string' ? finalPayload.chatmode : undefined, 'send', statsContext);
2949
3051
  logWritten = true;
2950
3052
  }
2951
3053
  return {
@@ -2954,6 +3056,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2954
3056
  group_id: result?.group_id ?? args.groupId,
2955
3057
  message: result?.message,
2956
3058
  event: result?.event,
3059
+ seq: this.sendReceiptFromResult(result).seq,
3060
+ timestamp: this.sendReceiptFromResult(result).timestamp,
3061
+ status: this.sendReceiptFromResult(result).status,
3062
+ delivery_mode: this.sendReceiptFromResult(result).deliveryMode,
2957
3063
  encrypt,
2958
3064
  log_written: logWritten,
2959
3065
  };
@@ -3057,11 +3163,13 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3057
3163
  return false;
3058
3164
  }
3059
3165
  else {
3060
- logger.info(`${this.logPrefix()} group.send ok: group=${channelId} mid=${mid} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
3166
+ this.logAunSendAccepted('group.send', channelId, mid, encrypt, result, finalText);
3061
3167
  appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: mid, kind: 'text', len: finalText.length, groupId: channelId });
3062
- this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode, 'send');
3168
+ const statsContext = await this.groupStatsContext(channelId);
3169
+ this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode, 'send', statsContext);
3063
3170
  this.appendOutboundJsonl(channelId, {
3064
3171
  ...classifyAunPayloadForLog(payload), msgId: mid, encrypt, context, isGroup: true, source,
3172
+ transport: this.sendReceiptFromResult(result),
3065
3173
  });
3066
3174
  // Observer forward: outbound (group) — 原样转发 SDK SendResult(含 envelope + payload)
3067
3175
  this.forwardOutbound(result);
@@ -3075,7 +3183,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3075
3183
  return false;
3076
3184
  }
3077
3185
  else {
3078
- logger.info(`${this.logPrefix()} message.send ok: to=${this.peerLabel(targetAid)} mid=${result.message_id} encrypt=${encrypt} text=${finalText.slice(0, 60)}`);
3186
+ this.logAunSendAccepted('message.send', this.peerLabel(targetAid), result.message_id, encrypt, result, finalText);
3079
3187
  const causation = normalizeCausation(context?.metadata?.causation);
3080
3188
  if (causation) {
3081
3189
  registerAunCausation(result.message_id, this.config.aid, targetAid, causation);
@@ -3088,6 +3196,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3088
3196
  this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode, 'send');
3089
3197
  this.appendOutboundJsonl(targetAid, {
3090
3198
  ...classifyAunPayloadForLog(payload), msgId: result.message_id, encrypt, context, isGroup: false, source,
3199
+ transport: this.sendReceiptFromResult(result),
3091
3200
  });
3092
3201
  // Observer forward: outbound (private) — 原样转发 SDK SendResult(含 envelope + payload)
3093
3202
  this.forwardOutbound(result);
@@ -3111,9 +3220,11 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3111
3220
  return false;
3112
3221
  }
3113
3222
  appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: mid, kind: 'text', len: finalText.length, groupId: channelId });
3114
- this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, false, context?.metadata?.chatmode);
3223
+ const statsContext = await this.groupStatsContext(channelId);
3224
+ this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, false, context?.metadata?.chatmode, 'send', statsContext);
3115
3225
  this.appendOutboundJsonl(channelId, {
3116
3226
  ...classifyAunPayloadForLog(payload), msgId: mid, encrypt: false, context, isGroup: true, source,
3227
+ transport: this.sendReceiptFromResult(result),
3117
3228
  });
3118
3229
  this.forwardOutbound(result);
3119
3230
  }
@@ -3138,6 +3249,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3138
3249
  this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, false, context?.metadata?.chatmode);
3139
3250
  this.appendOutboundJsonl(targetAid, {
3140
3251
  ...classifyAunPayloadForLog(payload), msgId: mid, encrypt: false, context, isGroup: false, source,
3252
+ transport: this.sendReceiptFromResult(result),
3141
3253
  });
3142
3254
  this.forwardOutbound(result);
3143
3255
  }
@@ -3178,15 +3290,16 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3178
3290
  if (!sent.ok || !sent.messageId)
3179
3291
  return sent;
3180
3292
  const isGroup = this.isGroupId(channelId);
3181
- logger.info(`${this.logPrefix()} durable payload sent: kind=${contentKind ?? payload.type ?? 'payload'} target=${isGroup ? channelId : this.peerLabel(channelId)} mid=${sent.messageId} encrypt=${sent.encrypt} text=${logText.slice(0, 60)}`);
3182
- this.recordDurableOutbound(channelId, payload, sent.messageId, !!sent.encrypt, context, isGroup, contentKind, logText, sent.result);
3293
+ const statsContext = isGroup ? await this.groupStatsContext(channelId) : undefined;
3294
+ this.logAunSendAccepted(isGroup ? 'group.send' : 'message.send', isGroup ? channelId : this.peerLabel(channelId), sent.messageId, !!sent.encrypt, sent.result, logText);
3295
+ this.recordDurableOutbound(channelId, payload, sent.messageId, !!sent.encrypt, context, isGroup, contentKind, logText, sent.result, statsContext);
3183
3296
  this.runPostSend(entry, sent.messageId);
3184
3297
  return sent;
3185
3298
  }
3186
3299
  /** 有效会话正文写入 messages.jsonl(message.send/group.send 成功后调用)。 */
3187
3300
  appendOutboundJsonl(channelId, descriptor) {
3188
3301
  try {
3189
- const { content, msgType, payloadType, payloadSummary, msgId, encrypt, context, isGroup, source = 'daemon' } = descriptor;
3302
+ const { content, msgType, payloadType, payloadSummary, msgId, encrypt, transport, context, isGroup, source = 'daemon' } = descriptor;
3190
3303
  const sessionsDir = resolvePaths().sessionsDir;
3191
3304
  const selfAID = this.config.aid;
3192
3305
  const chatDir = chatDirPath(sessionsDir, 'aun', channelId, selfAID);
@@ -3206,6 +3319,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3206
3319
  durationMs: null,
3207
3320
  encrypt,
3208
3321
  chatmode,
3322
+ transport,
3209
3323
  msgType,
3210
3324
  payloadType,
3211
3325
  payloadSummary,
@@ -3278,7 +3392,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3278
3392
  this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
3279
3393
  this.forwardOutbound(putRes);
3280
3394
  if (thoughtText) {
3281
- this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive', 'thought');
3395
+ const statsContext = await this.groupStatsContext(channelId);
3396
+ this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive', 'thought', statsContext);
3282
3397
  }
3283
3398
  }
3284
3399
  else {
@@ -3313,21 +3428,36 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3313
3428
  finalPayload.thread_id = context.threadId;
3314
3429
  if (context?.replyToMessageId && !finalPayload.ref_message_id)
3315
3430
  finalPayload.ref_message_id = context.replyToMessageId;
3431
+ const isMenuPayload = typeof finalPayload.type === 'string' && finalPayload.type.startsWith('menu.');
3316
3432
  const params = { payload: finalPayload, encrypt };
3433
+ const rawHeaders = context?.metadata?.protectedHeaders;
3434
+ if (encrypt && rawHeaders && typeof rawHeaders === 'object' && !Array.isArray(rawHeaders)) {
3435
+ const protectedHeaders = {};
3436
+ for (const [key, value] of Object.entries(rawHeaders)) {
3437
+ const name = key.trim().toLowerCase();
3438
+ if (!/^[a-z0-9_-]+$/.test(name) || typeof value !== 'string')
3439
+ continue;
3440
+ protectedHeaders[name] = value;
3441
+ }
3442
+ if (Object.keys(protectedHeaders).length > 0)
3443
+ params.protected_headers = protectedHeaders;
3444
+ }
3317
3445
  try {
3318
3446
  if (isGroup) {
3319
3447
  params.group_id = channelId;
3320
3448
  const result = await this.callAndTrace('group.send', params);
3321
3449
  const mid = result?.message?.message_id ?? result?.message_id ?? null;
3322
3450
  logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
3323
- this.forwardOutbound(result);
3451
+ if (!isMenuPayload)
3452
+ this.forwardOutbound(result);
3324
3453
  return mid;
3325
3454
  }
3326
3455
  else {
3327
3456
  params.to = targetAid;
3328
3457
  const result = await this.callAndTrace('message.send', params);
3329
3458
  logger.info(`${this.logPrefix()} message.send (${payload.type}) ok: to=${this.peerLabel(targetAid)} mid=${result?.message_id} encrypt=${encrypt}`);
3330
- this.forwardOutbound(result);
3459
+ if (!isMenuPayload)
3460
+ this.forwardOutbound(result);
3331
3461
  return result?.message_id ?? null;
3332
3462
  }
3333
3463
  }
@@ -3510,7 +3640,8 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3510
3640
  if (sentMid) {
3511
3641
  const fileText = filePayload.text;
3512
3642
  appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: channelId, msgId: sentMid, kind: 'file', len: fileText.length, ...(isGroup && { groupId: channelId }) });
3513
- this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(fileText, 'utf-8'), fileText, false, !!params.encrypt, context?.metadata?.chatmode);
3643
+ const statsContext = isGroup ? await this.groupStatsContext(channelId) : undefined;
3644
+ this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(fileText, 'utf-8'), fileText, false, !!params.encrypt, context?.metadata?.chatmode, 'send', statsContext);
3514
3645
  const source = context?.metadata?.source ?? 'daemon';
3515
3646
  this.appendOutboundJsonl(channelId, {
3516
3647
  ...classifyAunPayloadForLog(filePayload), msgId: sentMid, encrypt: !!params.encrypt,
@@ -3635,9 +3766,13 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3635
3766
  const initiator = statusPayload.initiator ?? '';
3636
3767
  const refMsgId = statusPayload.ref_message_id ?? '';
3637
3768
  const metaStr = statusPayload.metadata ? ` meta=${JSON.stringify(statusPayload.metadata)}` : '';
3638
- logger.info(`${this.logPrefix()} task.${status} task=${taskId} session=${sessionId} chatmode=${chatmode} target=${targetLabel} initiator=${initiator} ref_msg=${refMsgId}${metaStr}`);
3769
+ const logTaskStatus = `${this.logPrefix()} task.${status} task=${taskId} session=${sessionId} chatmode=${chatmode} target=${targetLabel} initiator=${initiator} ref_msg=${refMsgId}${metaStr}`;
3770
+ if (status === 'error' || status === 'timeout')
3771
+ logger.error(logTaskStatus);
3772
+ else
3773
+ logger.info(logTaskStatus);
3639
3774
  }
3640
- sendCustomPayload(channelId, payload) {
3775
+ async sendCustomPayload(channelId, payload, context) {
3641
3776
  if (!this.client || !this.connected)
3642
3777
  return;
3643
3778
  // SDK 0.3.0 E2EE requires payload to be an object
@@ -3650,19 +3785,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3650
3785
  catch {
3651
3786
  payloadObj = { text: payload };
3652
3787
  }
3653
- // 私聊 channelId = 对端 AID(不含 device_id)
3654
- const customTargetAid = channelId;
3655
- const sendParams = {
3656
- to: customTargetAid, payload: payloadObj,
3657
- encrypt: true,
3658
- };
3659
- this.trace('OUT', 'message.send.custom', sendParams);
3660
- this.client.call('message.send', sendParams).then((result) => {
3661
- this.trace('OUT', 'message.send.custom.ok', { message_id: result?.message_id });
3662
- }).catch(e => {
3663
- this.trace('OUT', 'message.send.custom.error', { error: String(e) });
3664
- logger.warn(`${this.logPrefix()} Custom payload failed: ${e}`);
3665
- });
3788
+ await this.sendStructured(channelId, payloadObj, context);
3666
3789
  }
3667
3790
  async disconnect() {
3668
3791
  this.intentionalDisconnect = true;
@@ -3738,44 +3861,18 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3738
3861
  }
3739
3862
  /** 读取本地 agent.md 中的 name(用于身份上下文展示),若本地不存在则尝试远程拉取 */
3740
3863
  loadSelfName(aid) {
3741
- try {
3742
- const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
3743
- const mdPath = agentMdPathFn(aidName);
3744
- if (!fs.existsSync(mdPath)) {
3745
- // 异步拉取,不阻塞连接流程
3746
- this.fetchAndCacheSelfName(aidName);
3747
- return undefined;
3748
- }
3749
- const content = fs.readFileSync(mdPath, 'utf-8');
3750
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
3751
- if (!fmMatch)
3752
- return undefined;
3753
- const nameMatch = fmMatch[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
3754
- return nameMatch?.[1]?.trim() || undefined;
3755
- }
3756
- catch {
3757
- return undefined;
3758
- }
3864
+ const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
3865
+ const name = resolveAgentDisplayName(aidName);
3866
+ if (!name)
3867
+ void this.fetchAndCacheSelfName(aidName);
3868
+ return name;
3759
3869
  }
3760
3870
  async fetchAndCacheSelfName(aidName) {
3761
- try {
3762
- const { agentmdGet } = await import('../aun/aid/index.js');
3763
- const content = await agentmdGet(aidName);
3764
- if (content) {
3765
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
3766
- if (fmMatch) {
3767
- const nameMatch = fmMatch[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
3768
- const name = nameMatch?.[1]?.trim();
3769
- if (name) {
3770
- this._selfName = name;
3771
- if (this.aidStatsCollector)
3772
- this.aidStatsCollector.setSelfName(this.config.aid, name);
3773
- }
3774
- }
3775
- }
3776
- }
3777
- catch {
3778
- // ignore — name will remain undefined
3871
+ const name = await refreshAgentDisplayName(aidName);
3872
+ if (name) {
3873
+ this._selfName = name;
3874
+ if (this.aidStatsCollector)
3875
+ this.aidStatsCollector.setSelfName(this.config.aid, name);
3779
3876
  }
3780
3877
  }
3781
3878
  getSelfName() {
@@ -3826,7 +3923,7 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3826
3923
  return result.content ?? '';
3827
3924
  }
3828
3925
  /**
3829
- * 取群显示名(group.get → group.name),进程内缓存。
3926
+ * 取群显示名(group.get_info → group.name),进程内缓存。
3830
3927
  * 走长连接 callAndTrace,失败/未连接返回 undefined —— 绝不抛出阻塞消息处理。
3831
3928
  */
3832
3929
  async getGroupName(groupId) {
@@ -3838,9 +3935,10 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3838
3935
  if (!this.client)
3839
3936
  return undefined;
3840
3937
  try {
3841
- const result = await this.callAndTrace('group.get', { group_id: groupId });
3842
- const name = result?.group?.name;
3843
- if (typeof name === 'string' && name) {
3938
+ const result = await this.callAndTrace('group.get_info', { group_id: groupId });
3939
+ const group = result?.group ?? result;
3940
+ const name = typeof group?.name === 'string' ? group.name.trim() : '';
3941
+ if (name) {
3844
3942
  this.groupNameCache.set(groupId, name);
3845
3943
  return name;
3846
3944
  }
@@ -3851,6 +3949,13 @@ EvolCore AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
3851
3949
  return undefined; // 不写缓存,下次仍可重试
3852
3950
  }
3853
3951
  }
3952
+ /** 统计关系键继续使用群 ID,仅为智能体预览补充可读群名。 */
3953
+ async groupStatsContext(groupId) {
3954
+ return {
3955
+ channelType: 'aun',
3956
+ peerLabel: await this.getGroupName(groupId) ?? groupId,
3957
+ };
3958
+ }
3854
3959
  /**
3855
3960
  * 查询某成员在群里的角色(经 group.get_admins)。
3856
3961
  * 仅用于话题创建权限校验(稀有事件),故不缓存:每次查权威源,结果天然最新。
@@ -4015,6 +4120,7 @@ export class AUNChannelPlugin {
4015
4120
  return;
4016
4121
  case 'interaction': {
4017
4122
  const req = payload.interaction;
4123
+ const cardTtlMs = 20 * 60 * 1000;
4018
4124
  if (req.kind.kind === 'action') {
4019
4125
  const action = req.kind;
4020
4126
  const aunCard = {
@@ -4024,10 +4130,12 @@ export class AUNChannelPlugin {
4024
4130
  label: btn.label, value: btn.key, style: btn.style ?? 'default', behavior: 'reply',
4025
4131
  })),
4026
4132
  };
4027
- // AUN action_card 无法内嵌输入框(只有按钮),手动输入降级为「✏️ 手动输入」按钮:
4028
- // 点击回传 _show_input → claude-runner 用 interceptNextMessage 拦截下一条消息作为答案。
4133
+ // AUN action_card 无法内嵌输入框(只有按钮),手动输入用协议原生的 compose
4134
+ // behavior:客户端收到点击后自行收卡+聚焦输入框,用户在 timeout 内的下一条
4135
+ // 消息由客户端结构化回传为 action_card_reply{behavior:"compose"}(见
4136
+ // extractTextPayload 的 compose 分支),不依赖 interceptNextMessage 拦截消息队列。
4029
4137
  if (action.allowCustomInput) {
4030
- aunCard.actions.push({ label: '✏️ 手动输入', value: '_show_input', style: 'default', behavior: 'reply' });
4138
+ aunCard.actions.push({ label: '✏️ 手动输入', style: 'default', behavior: 'compose', compose_timeout_ms: cardTtlMs });
4031
4139
  }
4032
4140
  if (action.body) {
4033
4141
  aunCard.text = action.body;
@@ -4046,7 +4154,7 @@ export class AUNChannelPlugin {
4046
4154
  requestId: req.id,
4047
4155
  isCommandCard: false,
4048
4156
  initiatorAid: req.initiatorId,
4049
- expiresAt: Date.now() + 20 * 60 * 1000,
4157
+ expiresAt: Date.now() + cardTtlMs,
4050
4158
  },
4051
4159
  });
4052
4160
  }
@@ -4081,7 +4189,7 @@ export class AUNChannelPlugin {
4081
4189
  requestId: req.id,
4082
4190
  isCommandCard: true,
4083
4191
  initiatorAid: req.initiatorId,
4084
- expiresAt: Date.now() + 20 * 60 * 1000,
4192
+ expiresAt: Date.now() + cardTtlMs,
4085
4193
  },
4086
4194
  });
4087
4195
  }
@@ -4092,7 +4200,7 @@ export class AUNChannelPlugin {
4092
4200
  }
4093
4201
  case 'custom': {
4094
4202
  const text = typeof payload.payload === 'string' ? payload.payload : JSON.stringify(payload.payload);
4095
- channel.sendCustomPayload(channelId, text);
4203
+ await channel.sendCustomPayload(channelId, text, replyCtx);
4096
4204
  return;
4097
4205
  }
4098
4206
  default:
@@ -4124,7 +4232,7 @@ export class AUNChannelPlugin {
4124
4232
  return {
4125
4233
  channelType: 'aun', adapter, channel,
4126
4234
  policy,
4127
- options: { flushDelay: inst.flushDelay ?? DEFAULT_FLUSH_DELAY_SECONDS, fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g },
4235
+ options: { flushDelay: inst.flushDelay ?? DEFAULT_FLUSH_DELAY_SECONDS, fileMarkerPattern: createSendFileMarkerPattern() },
4128
4236
  connect: () => channel.connect(),
4129
4237
  disconnect: () => channel.disconnect(),
4130
4238
  onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),