evolcore 0.0.12 → 0.0.13
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.
- package/CHANGELOG.md +22 -0
- package/bin/ec-safe-output.js +26 -7
- package/dist/agents/claude-runner.js +53 -7
- package/dist/agents/codex-app-server-client.js +53 -5
- package/dist/agents/codex-runner.js +104 -59
- package/dist/agents/ecagent-runner.js +4 -1
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +116 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +371 -136
- package/dist/cli/agent.js +9 -96
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +19 -10
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/config-manager.js +21 -1
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +29 -1
- package/dist/core/auth/authorization-audit.js +15 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +1 -0
- package/dist/core/command/command-handler.js +198 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +6 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +19 -0
- package/dist/core/message/message-bridge.js +63 -11
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +107 -41
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +93 -14
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +249 -27
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +161 -86
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +10 -0
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +5 -0
- package/kits/templates/roles/member.json +7 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
package/dist/channels/aun.js
CHANGED
|
@@ -7,7 +7,7 @@ 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, agentDir as agentDirPath, resolveRoot } from '../paths.js';
|
|
10
|
+
import { resolvePaths, agentDir as agentDirPath, resolveRoot, channelStatePath } 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, appendMessageLogStrict, buildOutboundEntry, buildInboundEntry, classifyAunPayloadForLog, hasMessageLogOperation } from '../core/message/message-log.js';
|
|
@@ -31,15 +31,36 @@ import { isHClassPath } from '../core/protected-paths.js';
|
|
|
31
31
|
import { consumeAunCausation, registerAunCausation } from '../core/causation/aun-association.js';
|
|
32
32
|
import { deriveCausation, normalizeCausation } from '../core/causation/context.js';
|
|
33
33
|
import { recordCausationSpan } from '../core/causation/audit.js';
|
|
34
|
-
import { isExplicitGroupId } from '../aun/group-identity.js';
|
|
35
34
|
import { refreshAgentDisplayName, resolveAgentDisplayName } from '../aun/aid/agentmd.js';
|
|
36
35
|
import { readInstalledEvolcoreVersion } from '../utils/evolcore-version.js';
|
|
37
36
|
import { bindPostBootstrapWelcomeOutboxSession, hasPendingPostBootstrapWelcomeOutbox, postBootstrapWelcomeOperationId, preparePostBootstrapWelcomeOutbox, } from '../core/bootstrap-messages.js';
|
|
38
37
|
import { hasMentionAll, mentionEntryAids, mentionEntryTargets, } from '../core/message/mention-schema.js';
|
|
39
38
|
import { normalizeAunMentionEntries, } from '../aun/msg/mention-schema.js';
|
|
39
|
+
import { isDeliveryTarget, sameDeliveryTarget } from '../core/message/message-utils.js';
|
|
40
40
|
export const AUN_HANDOFF_MARKER_FIELD = '_evolcore_handoff_id';
|
|
41
41
|
const AUN_INTERACTION_CARD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
42
|
+
const AUN_INBOUND_DEDUP_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
42
43
|
const IMAGE_MIME_TYPE = /^image\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/i;
|
|
44
|
+
function requireEnvelopeDelivery(channelId, envelopeDelivery, nestedDelivery) {
|
|
45
|
+
if (!isDeliveryTarget(envelopeDelivery)) {
|
|
46
|
+
throw Object.assign(new Error(`AUN outbound envelope requires a top-level delivery route for channelId=${channelId}`), {
|
|
47
|
+
code: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (nestedDelivery !== undefined) {
|
|
51
|
+
if (!isDeliveryTarget(nestedDelivery) || !sameDeliveryTarget(envelopeDelivery, nestedDelivery)) {
|
|
52
|
+
throw Object.assign(new Error(`AUN outbound envelope has conflicting delivery routes for channelId=${channelId}`), {
|
|
53
|
+
code: 'AUN_OUTBOUND_ROUTE_MISMATCH',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (envelopeDelivery.chatType === 'group' && envelopeDelivery.groupId !== channelId) {
|
|
58
|
+
throw Object.assign(new Error(`AUN group delivery does not match channelId=${channelId}`), {
|
|
59
|
+
code: 'AUN_OUTBOUND_ROUTE_MISMATCH',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return envelopeDelivery;
|
|
63
|
+
}
|
|
43
64
|
function normalizeImageMimeType(mimeType) {
|
|
44
65
|
const normalized = mimeType?.split(';', 1)[0].trim().toLowerCase();
|
|
45
66
|
if (!normalized)
|
|
@@ -133,6 +154,8 @@ export function aunOptsToInbound(opts, channel, channelType) {
|
|
|
133
154
|
encrypted: opts.encrypted,
|
|
134
155
|
protectedHeaders: opts.protectedHeaders,
|
|
135
156
|
messageId: opts.messageId,
|
|
157
|
+
receivedAt: opts.receivedAt,
|
|
158
|
+
gatewaySeq: opts.gatewaySeq,
|
|
136
159
|
causation: opts.causation,
|
|
137
160
|
mentions: opts.mentions,
|
|
138
161
|
mentionAids: opts.mentionAids,
|
|
@@ -289,14 +312,36 @@ export class AUNChannel {
|
|
|
289
312
|
throw e;
|
|
290
313
|
}
|
|
291
314
|
}
|
|
292
|
-
/**
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* - 兼容旧格式:grp_xxx、g-xxx.agentid.pub
|
|
315
|
+
/** Resolve the authenticated transport route for an ordinary outbound send.
|
|
316
|
+
* The channel id is an address only; it is intentionally never used to infer
|
|
317
|
+
* whether the address is a group.
|
|
296
318
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
319
|
+
requireDelivery(channelId, context, explicit) {
|
|
320
|
+
const supplied = explicit ?? context?.delivery;
|
|
321
|
+
if (supplied !== undefined && !isDeliveryTarget(supplied)) {
|
|
322
|
+
throw Object.assign(new Error(`AUN outbound send has an invalid delivery route for channelId=${channelId}`), {
|
|
323
|
+
code: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (!supplied) {
|
|
327
|
+
throw Object.assign(new Error(`AUN outbound send requires an explicit delivery route for channelId=${channelId}`), {
|
|
328
|
+
code: 'AUN_OUTBOUND_ROUTE_REQUIRED',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (supplied.chatType === 'group' && supplied.groupId !== channelId) {
|
|
332
|
+
throw Object.assign(new Error(`AUN group delivery does not match channelId=${channelId}`), {
|
|
333
|
+
code: 'AUN_OUTBOUND_ROUTE_MISMATCH',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return supplied;
|
|
337
|
+
}
|
|
338
|
+
requirePersistedDelivery(channelId, delivery) {
|
|
339
|
+
// Persisted sends only trust the top-level route. In particular, old
|
|
340
|
+
// outbox entries cannot recover routing from their nested reply context.
|
|
341
|
+
return this.requireDelivery(channelId, undefined, delivery);
|
|
342
|
+
}
|
|
343
|
+
withDelivery(context, delivery) {
|
|
344
|
+
return { ...(context ?? {}), delivery };
|
|
300
345
|
}
|
|
301
346
|
getShortAid(aid) {
|
|
302
347
|
if (!aid)
|
|
@@ -318,15 +363,19 @@ export class AUNChannel {
|
|
|
318
363
|
const name = cached?.name;
|
|
319
364
|
return name && name !== short ? `${short}(${name})` : short;
|
|
320
365
|
}
|
|
321
|
-
notifyCardActionFailure(channelId, text, operatorId, threadId) {
|
|
366
|
+
notifyCardActionFailure(channelId, text, operatorId, threadId, delivery) {
|
|
322
367
|
if (!channelId) {
|
|
323
368
|
logger.warn(`${this.logPrefix()} Card action failure without channelId: ${text}`);
|
|
324
369
|
return;
|
|
325
370
|
}
|
|
326
|
-
|
|
371
|
+
if (!delivery) {
|
|
372
|
+
logger.warn(`${this.logPrefix()} Card action failure without trusted delivery route: ${text}`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const context = { delivery };
|
|
327
376
|
if (threadId)
|
|
328
377
|
context.threadId = threadId;
|
|
329
|
-
if (operatorId &&
|
|
378
|
+
if (operatorId && delivery.chatType === 'group')
|
|
330
379
|
context.peerId = operatorId;
|
|
331
380
|
void this.sendMessage(channelId, text, context).catch((err) => {
|
|
332
381
|
logger.error(`${this.logPrefix()} Failed to send card action error:`, err);
|
|
@@ -336,7 +385,7 @@ export class AUNChannel {
|
|
|
336
385
|
logger.info(`${this.logPrefix()} ActionInteraction audit: behavior=${behavior ?? 'reply'} `
|
|
337
386
|
+ `cardId=${cardMessageId} interactionId=${interactionId} result=${result} textLength=${textLength}`);
|
|
338
387
|
}
|
|
339
|
-
extractTextPayload(payload, channelId, senderAid) {
|
|
388
|
+
extractTextPayload(payload, channelId, senderAid, delivery) {
|
|
340
389
|
if (typeof payload === 'string')
|
|
341
390
|
return payload;
|
|
342
391
|
if (payload && typeof payload === 'object') {
|
|
@@ -347,6 +396,7 @@ export class AUNChannel {
|
|
|
347
396
|
const cardMsgId = typeof obj.ref_message_id === 'string' ? obj.ref_message_id
|
|
348
397
|
: typeof obj.card_message_id === 'string' ? obj.card_message_id : '';
|
|
349
398
|
const cardInfo = cardMsgId ? this.cardMessageIdMap.get(cardMsgId) : undefined;
|
|
399
|
+
const cardDelivery = cardInfo?.delivery ?? delivery;
|
|
350
400
|
if (cardInfo) {
|
|
351
401
|
// compose 按钮由新版客户端在卡片内采集输入;旧版客户端可继续从主编辑器
|
|
352
402
|
// 采集。两者最终都回传 action_card_reply{behavior:"compose", text}。
|
|
@@ -371,25 +421,25 @@ export class AUNChannel {
|
|
|
371
421
|
// payloads omit senderAid. A group channelId is only a conversation
|
|
372
422
|
// identifier and must never be treated as the clicking operator.
|
|
373
423
|
const cardClickerAid = senderAid
|
|
374
|
-
|| (
|
|
424
|
+
|| (cardDelivery?.chatType === 'private' && channelId ? channelId : '')
|
|
375
425
|
|| '';
|
|
376
426
|
if (cardInfo.isCommandCard) {
|
|
377
427
|
// CommandCard:action_value 是完整 slash 命令,构造伪入站消息。
|
|
378
428
|
// 先完成点击者鉴权,再消费 mapping;否则群成员的一次误点会让发起者
|
|
379
429
|
// 无法继续操作同一张卡片。
|
|
380
|
-
const chatType =
|
|
430
|
+
const chatType = cardDelivery?.chatType ?? 'private';
|
|
381
431
|
if (this.messageHandler && actionValue.startsWith('/')) {
|
|
382
432
|
// Initiator 校验:仅群聊需要(私聊信道一对一,点击者恒为对端 = initiator)。
|
|
383
433
|
// 身份只信认证信封提取的 cardClickerAid,非 payload 自报。
|
|
384
434
|
if (chatType === 'group' && !cardClickerAid) {
|
|
385
435
|
logger.info(`${this.logPrefix()} CommandCard rejected: missing authenticated clicker mid=${cardMsgId}`);
|
|
386
|
-
this.notifyCardActionFailure(channelId, '⚠️ 无法验证卡片操作者身份', undefined, threadId);
|
|
436
|
+
this.notifyCardActionFailure(channelId, '⚠️ 无法验证卡片操作者身份', undefined, threadId, cardDelivery);
|
|
387
437
|
return '';
|
|
388
438
|
}
|
|
389
439
|
if (chatType === 'group' && cardInfo.initiatorAid
|
|
390
440
|
&& cardClickerAid !== cardInfo.initiatorAid) {
|
|
391
441
|
logger.info(`${this.logPrefix()} CommandCard rejected: clicker=${cardClickerAid} initiator=${cardInfo.initiatorAid} mid=${cardMsgId}`);
|
|
392
|
-
this.notifyCardActionFailure(channelId, '⚠️ 仅卡片发起者可操作', cardClickerAid, threadId);
|
|
442
|
+
this.notifyCardActionFailure(channelId, '⚠️ 仅卡片发起者可操作', cardClickerAid, threadId, cardDelivery);
|
|
393
443
|
return '';
|
|
394
444
|
}
|
|
395
445
|
// 已通过鉴权并即将进入处理流程;此时才消费 mapping,避免非发起者
|
|
@@ -408,28 +458,28 @@ export class AUNChannel {
|
|
|
408
458
|
})).catch((err) => {
|
|
409
459
|
logger.error(`${this.logPrefix()} CommandCard handler failed: action=${actionValue} mid=${cardMsgId}`, err);
|
|
410
460
|
const message = err instanceof Error && err.message ? err.message : String(err || '未知错误');
|
|
411
|
-
this.notifyCardActionFailure(channelId, `❌ 操作失败: ${message}`, cardClickerAid, threadId);
|
|
461
|
+
this.notifyCardActionFailure(channelId, `❌ 操作失败: ${message}`, cardClickerAid, threadId, cardDelivery);
|
|
412
462
|
});
|
|
413
463
|
}
|
|
414
464
|
else if (!this.messageHandler) {
|
|
415
|
-
this.notifyCardActionFailure(channelId, '❌ 操作失败:命令处理器未就绪', cardClickerAid, threadId);
|
|
465
|
+
this.notifyCardActionFailure(channelId, '❌ 操作失败:命令处理器未就绪', cardClickerAid, threadId, cardDelivery);
|
|
416
466
|
}
|
|
417
467
|
else {
|
|
418
|
-
this.notifyCardActionFailure(channelId, '❌ 操作失败:无效的卡片命令', cardClickerAid, threadId);
|
|
468
|
+
this.notifyCardActionFailure(channelId, '❌ 操作失败:无效的卡片命令', cardClickerAid, threadId, cardDelivery);
|
|
419
469
|
}
|
|
420
470
|
}
|
|
421
471
|
else {
|
|
422
472
|
// ActionInteraction:后端明确接受后才消费 mapping。撤权、过期或
|
|
423
473
|
// 重复点击返回 false 时保持 fail-closed,并提示用户卡片已失效。
|
|
424
|
-
if (
|
|
474
|
+
if (cardDelivery?.chatType === 'group' && !cardClickerAid) {
|
|
425
475
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'rejected_unauthenticated', text.length);
|
|
426
|
-
this.notifyCardActionFailure(channelId, '⚠️ 无法验证卡片操作者身份', undefined, threadId);
|
|
476
|
+
this.notifyCardActionFailure(channelId, '⚠️ 无法验证卡片操作者身份', undefined, threadId, cardDelivery);
|
|
427
477
|
return '';
|
|
428
478
|
}
|
|
429
|
-
if (
|
|
479
|
+
if (cardDelivery?.chatType === 'group' && cardInfo.initiatorAid
|
|
430
480
|
&& cardClickerAid !== cardInfo.initiatorAid) {
|
|
431
481
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'rejected_non_initiator', text.length);
|
|
432
|
-
this.notifyCardActionFailure(channelId, '⚠️ 仅卡片发起者可操作', cardClickerAid, threadId);
|
|
482
|
+
this.notifyCardActionFailure(channelId, '⚠️ 仅卡片发起者可操作', cardClickerAid, threadId, cardDelivery);
|
|
433
483
|
return '';
|
|
434
484
|
}
|
|
435
485
|
// Compose reply 入参校验:text 必须非空且未超长。
|
|
@@ -439,19 +489,19 @@ export class AUNChannel {
|
|
|
439
489
|
const MAX_COMPOSE_TEXT_LENGTH = 64 * 1024; // 64K 字符
|
|
440
490
|
if (!trimmedText) {
|
|
441
491
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'rejected_empty', text.length);
|
|
442
|
-
this.notifyCardActionFailure(channelId, '⚠️ 输入内容不能为空,请重新输入', cardClickerAid, threadId);
|
|
492
|
+
this.notifyCardActionFailure(channelId, '⚠️ 输入内容不能为空,请重新输入', cardClickerAid, threadId, cardDelivery);
|
|
443
493
|
return '';
|
|
444
494
|
}
|
|
445
495
|
if (text.length > MAX_COMPOSE_TEXT_LENGTH) {
|
|
446
496
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'rejected_too_long', text.length);
|
|
447
|
-
this.notifyCardActionFailure(channelId, `⚠️ 输入内容过长(${text.length} 字符,限制 ${MAX_COMPOSE_TEXT_LENGTH}),请缩短后重试`, cardClickerAid, threadId);
|
|
497
|
+
this.notifyCardActionFailure(channelId, `⚠️ 输入内容过长(${text.length} 字符,限制 ${MAX_COMPOSE_TEXT_LENGTH}),请缩短后重试`, cardClickerAid, threadId, cardDelivery);
|
|
448
498
|
return '';
|
|
449
499
|
}
|
|
450
500
|
}
|
|
451
501
|
if (this.interactionCallback) {
|
|
452
502
|
if (this.interactionActionsInFlight.has(cardMsgId)) {
|
|
453
503
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'rejected_in_flight', text.length);
|
|
454
|
-
this.notifyCardActionFailure(channelId, '⏳ 操作正在处理,请勿重复点击', cardClickerAid, threadId);
|
|
504
|
+
this.notifyCardActionFailure(channelId, '⏳ 操作正在处理,请勿重复点击', cardClickerAid, threadId, cardDelivery);
|
|
455
505
|
return '';
|
|
456
506
|
}
|
|
457
507
|
this.interactionActionsInFlight.add(cardMsgId);
|
|
@@ -484,7 +534,7 @@ export class AUNChannel {
|
|
|
484
534
|
// captured the message id before the callback returned.
|
|
485
535
|
this.forgetInteractionCard(cardMsgId, cardInfo.requestId);
|
|
486
536
|
}
|
|
487
|
-
this.notifyCardActionFailure(channelId, '⚠️ 卡片已失效,请重新发起', cardClickerAid, threadId);
|
|
537
|
+
this.notifyCardActionFailure(channelId, '⚠️ 卡片已失效,请重新发起', cardClickerAid, threadId, cardDelivery);
|
|
488
538
|
};
|
|
489
539
|
try {
|
|
490
540
|
const result = this.interactionCallback(response);
|
|
@@ -493,7 +543,7 @@ export class AUNChannel {
|
|
|
493
543
|
this.interactionActionsInFlight.delete(cardMsgId);
|
|
494
544
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'callback_error', text.length);
|
|
495
545
|
logger.error(`${this.logPrefix()} ActionInteraction callback failed: request=${cardInfo.requestId}`, err);
|
|
496
|
-
this.notifyCardActionFailure(channelId, '❌ 操作失败,请稍后重试', cardClickerAid, threadId);
|
|
546
|
+
this.notifyCardActionFailure(channelId, '❌ 操作失败,请稍后重试', cardClickerAid, threadId, cardDelivery);
|
|
497
547
|
});
|
|
498
548
|
}
|
|
499
549
|
else {
|
|
@@ -504,19 +554,19 @@ export class AUNChannel {
|
|
|
504
554
|
this.interactionActionsInFlight.delete(cardMsgId);
|
|
505
555
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'callback_error', text.length);
|
|
506
556
|
logger.error(`${this.logPrefix()} ActionInteraction callback failed: request=${cardInfo.requestId}`, err);
|
|
507
|
-
this.notifyCardActionFailure(channelId, '❌ 操作失败,请稍后重试', cardClickerAid, threadId);
|
|
557
|
+
this.notifyCardActionFailure(channelId, '❌ 操作失败,请稍后重试', cardClickerAid, threadId, cardDelivery);
|
|
508
558
|
}
|
|
509
559
|
}
|
|
510
560
|
else {
|
|
511
561
|
this.auditActionCardInteraction(behaviorRaw, cardMsgId, cardInfo.requestId, 'handler_unavailable', text.length);
|
|
512
|
-
this.notifyCardActionFailure(channelId, '❌ 操作失败:交互处理器未就绪', cardClickerAid, threadId);
|
|
562
|
+
this.notifyCardActionFailure(channelId, '❌ 操作失败:交互处理器未就绪', cardClickerAid, threadId, cardDelivery);
|
|
513
563
|
}
|
|
514
564
|
}
|
|
515
565
|
}
|
|
516
566
|
else if (this.ownedCardMsgIds.has(cardMsgId)) {
|
|
517
567
|
// 本 agent 发出的卡片,但 entry 已过期(24h TTL)
|
|
518
568
|
logger.debug(`${this.logPrefix()} action_card_reply expired: cardMsgId=${cardMsgId}`);
|
|
519
|
-
this.notifyCardActionFailure(channelId, '⚠️ 卡片已失效,请重新发起');
|
|
569
|
+
this.notifyCardActionFailure(channelId, '⚠️ 卡片已失效,请重新发起', undefined, undefined, delivery);
|
|
520
570
|
}
|
|
521
571
|
else {
|
|
522
572
|
// 非本 agent 发出的卡片(broadcast 模式下其他 agent 的卡)→ 静默忽略
|
|
@@ -751,8 +801,13 @@ export class AUNChannel {
|
|
|
751
801
|
return sanitized;
|
|
752
802
|
return undefined;
|
|
753
803
|
}
|
|
754
|
-
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName) {
|
|
755
|
-
|
|
804
|
+
buildGroupReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, groupId) {
|
|
805
|
+
if (!groupId)
|
|
806
|
+
throw new Error('buildGroupReplyContext requires groupId');
|
|
807
|
+
const replyContext = {
|
|
808
|
+
metadata: { encrypted, chatmode },
|
|
809
|
+
delivery: { chatType: 'group', groupId },
|
|
810
|
+
};
|
|
756
811
|
if (threadId)
|
|
757
812
|
replyContext.threadId = threadId;
|
|
758
813
|
replyContext.peerId = senderAid;
|
|
@@ -766,6 +821,20 @@ export class AUNChannel {
|
|
|
766
821
|
}
|
|
767
822
|
return replyContext;
|
|
768
823
|
}
|
|
824
|
+
buildPrivateReplyContext(threadId, senderAid, encrypted, messageId, chatmode, topicName, refMessageId) {
|
|
825
|
+
const replyContext = {
|
|
826
|
+
metadata: { encrypted, chatmode, refMessageId },
|
|
827
|
+
delivery: { chatType: 'private' },
|
|
828
|
+
peerId: senderAid,
|
|
829
|
+
};
|
|
830
|
+
if (threadId)
|
|
831
|
+
replyContext.threadId = threadId;
|
|
832
|
+
if (messageId)
|
|
833
|
+
replyContext.replyToMessageId = messageId;
|
|
834
|
+
if (topicName && threadId)
|
|
835
|
+
replyContext.metadata.topicName = topicName;
|
|
836
|
+
return replyContext;
|
|
837
|
+
}
|
|
769
838
|
claimInboundMessage(kind, messageId, seq) {
|
|
770
839
|
if (!messageId)
|
|
771
840
|
return true;
|
|
@@ -788,9 +857,72 @@ export class AUNChannel {
|
|
|
788
857
|
this.inboundSeenMessages.set(dedupeKey, { seenAt: Date.now(), seq });
|
|
789
858
|
if (seq !== undefined)
|
|
790
859
|
this.messageSeqMap.set(messageId, seq);
|
|
791
|
-
|
|
860
|
+
this.appendInboundDedupe(dedupeKey, messageId, kind, seq);
|
|
861
|
+
setTimeout(() => this.inboundSeenMessages.delete(dedupeKey), AUN_INBOUND_DEDUP_TTL_MS);
|
|
792
862
|
return true;
|
|
793
863
|
}
|
|
864
|
+
inboundDedupeFile() {
|
|
865
|
+
const channelName = this.config.channelName ?? 'aun';
|
|
866
|
+
return channelStatePath(channelName, 'dedup', 'aun', `${encodeURIComponent(this.config.aid)}.jsonl`);
|
|
867
|
+
}
|
|
868
|
+
loadInboundDedupe() {
|
|
869
|
+
const file = this.inboundDedupeFile();
|
|
870
|
+
const cutoff = Date.now() - AUN_INBOUND_DEDUP_TTL_MS;
|
|
871
|
+
let lines;
|
|
872
|
+
try {
|
|
873
|
+
lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
let needsRewrite = false;
|
|
879
|
+
for (const line of lines) {
|
|
880
|
+
try {
|
|
881
|
+
const entry = JSON.parse(line);
|
|
882
|
+
if (typeof entry.key !== 'string' || typeof entry.seenAt !== 'number' || !Number.isFinite(entry.seenAt) || entry.seenAt <= cutoff) {
|
|
883
|
+
needsRewrite = true;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
this.inboundSeenMessages.set(entry.key, {
|
|
887
|
+
seenAt: entry.seenAt,
|
|
888
|
+
seq: typeof entry.seq === 'number' && Number.isFinite(entry.seq) ? entry.seq : undefined,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
catch {
|
|
892
|
+
needsRewrite = true;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (needsRewrite)
|
|
896
|
+
this.rewriteInboundDedupe();
|
|
897
|
+
}
|
|
898
|
+
appendInboundDedupe(key, messageId, kind, seq) {
|
|
899
|
+
try {
|
|
900
|
+
const file = this.inboundDedupeFile();
|
|
901
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
902
|
+
fs.appendFileSync(file, JSON.stringify({ key, messageId, kind, seenAt: Date.now(), seq }) + '\n');
|
|
903
|
+
}
|
|
904
|
+
catch (error) {
|
|
905
|
+
logger.warn(`${this.logPrefix()} Failed to persist inbound dedup: ${error instanceof Error ? error.message : String(error)}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
rewriteInboundDedupe() {
|
|
909
|
+
const cutoff = Date.now() - AUN_INBOUND_DEDUP_TTL_MS;
|
|
910
|
+
for (const [key, value] of this.inboundSeenMessages) {
|
|
911
|
+
if (value.seenAt <= cutoff)
|
|
912
|
+
this.inboundSeenMessages.delete(key);
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const file = this.inboundDedupeFile();
|
|
916
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
917
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
918
|
+
const lines = [...this.inboundSeenMessages].map(([key, value]) => JSON.stringify({ key, seenAt: value.seenAt, seq: value.seq }));
|
|
919
|
+
fs.writeFileSync(tmp, lines.length > 0 ? `${lines.join('\n')}\n` : '');
|
|
920
|
+
fs.renameSync(tmp, file);
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
logger.warn(`${this.logPrefix()} Failed to compact inbound dedup: ${error instanceof Error ? error.message : String(error)}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
794
926
|
acknowledgeImmediately(messageId, _seq) {
|
|
795
927
|
// SDK internally manages seq tracking and ack — do not call message.ack RPC directly,
|
|
796
928
|
// as it corrupts the SDK's seqTracker state and breaks V2 e2ee message pull.
|
|
@@ -875,6 +1007,7 @@ export class AUNChannel {
|
|
|
875
1007
|
constructor(config) {
|
|
876
1008
|
this.config = config;
|
|
877
1009
|
this.agentDir = agentDirPath(config.aid);
|
|
1010
|
+
this.loadInboundDedupe();
|
|
878
1011
|
if (config.aunTrace) {
|
|
879
1012
|
this.traceWriter = new LogWriter({
|
|
880
1013
|
baseName: 'aun',
|
|
@@ -972,7 +1105,7 @@ export class AUNChannel {
|
|
|
972
1105
|
// gateway metadata cache when well-known is temporarily unavailable.
|
|
973
1106
|
const configuredGateway = this.config.gatewayUrl || '';
|
|
974
1107
|
logger.info(`${this.logPrefix()} Initializing: aid=${aidName}, gateway=${configuredGateway || '<sdk-discovery>'}, aun_path=${aunPath}`);
|
|
975
|
-
// 构造 AIDStore
|
|
1108
|
+
// 构造 AIDStore。daemon 使用独立 slot,CLI/netcheck 不能触碰业务入站游标。
|
|
976
1109
|
// encryptionSeed / rootCaPath 由 getAidStore 内部注入
|
|
977
1110
|
const store = await getAidStore({
|
|
978
1111
|
slotId: SLOT.daemon,
|
|
@@ -1025,7 +1158,7 @@ export class AUNChannel {
|
|
|
1025
1158
|
for (const id of ids) {
|
|
1026
1159
|
if (typeof id === 'string') {
|
|
1027
1160
|
logger.info(`${this.logPrefix()} Message recalled: ${id}`);
|
|
1028
|
-
this.recallHandler?.(id);
|
|
1161
|
+
this.recallHandler?.(id, this._aid || this.config.aid);
|
|
1029
1162
|
}
|
|
1030
1163
|
}
|
|
1031
1164
|
}
|
|
@@ -1057,7 +1190,7 @@ export class AUNChannel {
|
|
|
1057
1190
|
for (const id of ids) {
|
|
1058
1191
|
if (typeof id === 'string') {
|
|
1059
1192
|
logger.info(`${this.logPrefix()} Group message recalled: group=${env.group_id ?? ''} mid=${id}`);
|
|
1060
|
-
this.recallHandler?.(id);
|
|
1193
|
+
this.recallHandler?.(id, this._aid || this.config.aid);
|
|
1061
1194
|
}
|
|
1062
1195
|
}
|
|
1063
1196
|
}
|
|
@@ -1095,7 +1228,7 @@ export class AUNChannel {
|
|
|
1095
1228
|
});
|
|
1096
1229
|
this.trace('OUT', 'client.connect', { gateway: this.gatewayUrl, extra_info: extraInfo });
|
|
1097
1230
|
await client.connect({
|
|
1098
|
-
// connection_kind 默认 long;slot 已由 AID 携带(evolcore
|
|
1231
|
+
// connection_kind 默认 long;slot 已由 AID 携带(evolcore-daemon)
|
|
1099
1232
|
// extra_info:互踢诊断名片(0.4.3 公开 connect 已支持透传)
|
|
1100
1233
|
extra_info: extraInfo,
|
|
1101
1234
|
// max_attempts=0 = 无限重试(与 Go/Python 对齐),交由 SDK 自己跑指数退避
|
|
@@ -1155,7 +1288,9 @@ export class AUNChannel {
|
|
|
1155
1288
|
}
|
|
1156
1289
|
const operationId = postBootstrapWelcomeOperationId(aidName);
|
|
1157
1290
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aidName);
|
|
1158
|
-
|
|
1291
|
+
const prepared = outbox.findByDedupeKey(aidName, operationId);
|
|
1292
|
+
if (hasMessageLogOperation(chatDir, operationId)
|
|
1293
|
+
|| (prepared?.delivery?.chatType === 'private' && prepared.channelId === owner)) {
|
|
1159
1294
|
return true;
|
|
1160
1295
|
}
|
|
1161
1296
|
const ownerAidClean = owner.startsWith('@') ? owner.slice(1) : owner;
|
|
@@ -1211,6 +1346,11 @@ export class AUNChannel {
|
|
|
1211
1346
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', owner, aid);
|
|
1212
1347
|
return hasMessageLogOperation(chatDir, operationId);
|
|
1213
1348
|
}
|
|
1349
|
+
if (entry.delivery?.chatType !== 'private') {
|
|
1350
|
+
logger.warn(`${this.logPrefix()} Discarding unrouted legacy post-bootstrap welcome: id=${entry.id}`);
|
|
1351
|
+
outbox.remove(aid, entry.id);
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1214
1354
|
const agentConfig = loadAgent(aid);
|
|
1215
1355
|
if (!agentConfig || normalizeAgentLifecycle(agentConfig).lifecycle !== 'active') {
|
|
1216
1356
|
logger.info(`${this.logPrefix()} Post-bootstrap welcome prepared; waiting for lifecycle=active`);
|
|
@@ -1234,7 +1374,7 @@ export class AUNChannel {
|
|
|
1234
1374
|
* @param channelId 下载归属(私聊 fromAid / 群聊 groupId)
|
|
1235
1375
|
* @param preCollected 已收集的附件(群聊路径会提前 collect,避免重复)
|
|
1236
1376
|
*/
|
|
1237
|
-
async processAttachments(payload, baseText, channelId, preCollected) {
|
|
1377
|
+
async processAttachments(payload, baseText, channelId, preCollected, delivery) {
|
|
1238
1378
|
const rawAttachments = preCollected ?? this.collectAllAttachments(payload);
|
|
1239
1379
|
const images = [];
|
|
1240
1380
|
let finalText = baseText;
|
|
@@ -1243,7 +1383,7 @@ export class AUNChannel {
|
|
|
1243
1383
|
}
|
|
1244
1384
|
const fileParts = [];
|
|
1245
1385
|
for (const att of rawAttachments) {
|
|
1246
|
-
const filePath = await this.downloadAttachment(att, channelId);
|
|
1386
|
+
const filePath = await this.downloadAttachment(att, channelId, delivery);
|
|
1247
1387
|
if (!filePath)
|
|
1248
1388
|
continue;
|
|
1249
1389
|
const name = sanitizeFileName(att.filename || att.object_key?.split('/').pop() || 'file');
|
|
@@ -1275,8 +1415,11 @@ export class AUNChannel {
|
|
|
1275
1415
|
logger.info(`${this.logPrefix()} [attachments] count=${rawAttachments.length} images=${images.length} files=${fileParts.length}`);
|
|
1276
1416
|
return { finalText, images };
|
|
1277
1417
|
}
|
|
1278
|
-
async downloadAttachment(att, channelId) {
|
|
1279
|
-
const ownerAid = att.owner_aid
|
|
1418
|
+
async downloadAttachment(att, channelId, delivery) {
|
|
1419
|
+
const ownerAid = att.owner_aid
|
|
1420
|
+
|| (delivery?.chatType === 'private' ? channelId : '')
|
|
1421
|
+
|| this._aid
|
|
1422
|
+
|| '';
|
|
1280
1423
|
const objectKey = att.object_key;
|
|
1281
1424
|
if (!objectKey) {
|
|
1282
1425
|
logger.warn(`${this.logPrefix()} Attachment missing object_key, skipping`);
|
|
@@ -1448,6 +1591,7 @@ export class AUNChannel {
|
|
|
1448
1591
|
if (!data || typeof data !== 'object')
|
|
1449
1592
|
return;
|
|
1450
1593
|
const msg = data;
|
|
1594
|
+
const receivedAt = Date.now();
|
|
1451
1595
|
// Claim before any payload extraction or command parsing. Retransmits may
|
|
1452
1596
|
// carry a new seq, but the application identity remains message_id.
|
|
1453
1597
|
const messageId = typeof msg.message_id === 'string' ? msg.message_id : '';
|
|
@@ -1467,7 +1611,7 @@ export class AUNChannel {
|
|
|
1467
1611
|
? payload._evolcore_causation_ref
|
|
1468
1612
|
: undefined;
|
|
1469
1613
|
const logDescriptor = classifyAunPayloadForLog(payload);
|
|
1470
|
-
const text = this.extractTextPayload(payload, fromAid, fromAid);
|
|
1614
|
+
const text = this.extractTextPayload(payload, fromAid, fromAid, { chatType: 'private' });
|
|
1471
1615
|
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
1472
1616
|
const refMessageId = typeof payload === 'object' && payload !== null && typeof payload.ref_message_id === 'string'
|
|
1473
1617
|
? payload.ref_message_id
|
|
@@ -1513,7 +1657,7 @@ export class AUNChannel {
|
|
|
1513
1657
|
mentions.push({ aid: this._aid });
|
|
1514
1658
|
}
|
|
1515
1659
|
// Process attachments (顶层 + 嵌套在 merge.items / quote.quote 中的)
|
|
1516
|
-
const { finalText, images: inboundImages } = await this.processAttachments(payload, text, fromAid);
|
|
1660
|
+
const { finalText, images: inboundImages } = await this.processAttachments(payload, text, fromAid, undefined, { chatType: 'private' });
|
|
1517
1661
|
// 私聊 channelId = 对端 AID(不再读 payload.chat_id 含 device 三段式)
|
|
1518
1662
|
// device_id 仅 SDK 内部多实例去重用,evolcore session 层面跨端共享会话
|
|
1519
1663
|
const chatId = fromAid;
|
|
@@ -1529,7 +1673,7 @@ export class AUNChannel {
|
|
|
1529
1673
|
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))}`);
|
|
1530
1674
|
// action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
|
|
1531
1675
|
if (p2pPayloadType === 'action_card_reply') {
|
|
1532
|
-
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId);
|
|
1676
|
+
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
1533
1677
|
return;
|
|
1534
1678
|
}
|
|
1535
1679
|
// menu.* 与联系人本人状态查询:需要原始 payload JSON 传递给 bridge。
|
|
@@ -1537,18 +1681,18 @@ export class AUNChannel {
|
|
|
1537
1681
|
|| p2pPayloadType === AUNChannel.CONTACT_REQUEST_QUERY_TYPE
|
|
1538
1682
|
|| p2pPayloadType === AUNChannel.CONTACT_REQUEST_SUBMIT_TYPE) {
|
|
1539
1683
|
this.acknowledgeImmediately(messageId, seq);
|
|
1540
|
-
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId);
|
|
1684
|
+
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
1541
1685
|
this.dispatchMessage({
|
|
1542
1686
|
channelId: chatId, userId: fromAid,
|
|
1543
1687
|
text: JSON.stringify(payload),
|
|
1544
|
-
chatType: 'private', messageId, seq,
|
|
1688
|
+
chatType: 'private', messageId, seq, receivedAt, gatewaySeq: seq,
|
|
1545
1689
|
peerName: displayName || undefined,
|
|
1546
1690
|
peerType: peerIdentity.type,
|
|
1547
1691
|
encrypted: msgEncrypted,
|
|
1548
1692
|
protectedHeaders,
|
|
1549
1693
|
mentions,
|
|
1550
1694
|
mentionAids: mentionEntryAids(mentions),
|
|
1551
|
-
replyContext: this.
|
|
1695
|
+
replyContext: this.buildPrivateReplyContext(threadId, fromAid, msgEncrypted, messageId),
|
|
1552
1696
|
msgType: logDescriptor.msgType,
|
|
1553
1697
|
payloadType: logDescriptor.payloadType,
|
|
1554
1698
|
payloadSummary: logDescriptor.payloadSummary,
|
|
@@ -1568,11 +1712,11 @@ export class AUNChannel {
|
|
|
1568
1712
|
// 透传 encrypted:bind.response 须与请求的加密/明文对称(见 onMessage 处理)。
|
|
1569
1713
|
if (p2pPayloadType === AUNChannel.BIND_REQUEST_TYPE) {
|
|
1570
1714
|
this.acknowledgeImmediately(messageId, seq);
|
|
1571
|
-
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId);
|
|
1715
|
+
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
1572
1716
|
this.dispatchMessage({
|
|
1573
1717
|
channelId: chatId, userId: fromAid,
|
|
1574
1718
|
text: JSON.stringify(payload),
|
|
1575
|
-
chatType: 'private', messageId, seq,
|
|
1719
|
+
chatType: 'private', messageId, seq, receivedAt, gatewaySeq: seq,
|
|
1576
1720
|
peerName: displayName || undefined,
|
|
1577
1721
|
peerType: peerIdentity.type,
|
|
1578
1722
|
encrypted: msgEncrypted,
|
|
@@ -1588,7 +1732,7 @@ export class AUNChannel {
|
|
|
1588
1732
|
// payload 类型白名单:信号类消息(status / event / thought 等)不进 Agent
|
|
1589
1733
|
if (p2pPayloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(p2pPayloadType)) {
|
|
1590
1734
|
this.acknowledgeImmediately(messageId, seq);
|
|
1591
|
-
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId);
|
|
1735
|
+
this.appendInboundPayloadJsonl(chatId, fromAid, messageId, msgEncrypted, false, logDescriptor, threadId, receivedAt, seq);
|
|
1592
1736
|
logger.info(`${this.logPrefix()} P2P dropped (type deny): type=${p2pPayloadType} from=${shortAid}(${displayName}) mid=${messageId}`);
|
|
1593
1737
|
return;
|
|
1594
1738
|
}
|
|
@@ -1597,24 +1741,15 @@ export class AUNChannel {
|
|
|
1597
1741
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_in', aid: this.config.aid, from: fromAid, msgId: messageId, kind: 'text', len: finalText.length });
|
|
1598
1742
|
const isSystemP2P = p2pPayloadType === 'event';
|
|
1599
1743
|
this.aidStatsCollector?.recordInbound(this.config.aid, fromAid, Buffer.byteLength(finalText, 'utf-8'), finalText, isSystemP2P, msgEncrypted, msgChatmode, isSystemP2P ? 'notify' : 'send');
|
|
1600
|
-
const replyContext =
|
|
1601
|
-
if (threadId)
|
|
1602
|
-
replyContext.threadId = threadId;
|
|
1603
|
-
replyContext.peerId = fromAid;
|
|
1604
|
-
if (messageId)
|
|
1605
|
-
replyContext.replyToMessageId = messageId;
|
|
1606
|
-
// 协议 v2.4 §6.4/§12.1: 话题会话创建时传入 topicName
|
|
1607
|
-
if (topicName && threadId) {
|
|
1608
|
-
if (!replyContext.metadata)
|
|
1609
|
-
replyContext.metadata = {};
|
|
1610
|
-
replyContext.metadata.topicName = topicName;
|
|
1611
|
-
}
|
|
1744
|
+
const replyContext = this.buildPrivateReplyContext(threadId, fromAid, msgEncrypted, messageId, msgChatmode, topicName, refMessageId);
|
|
1612
1745
|
this.dispatchMessage({
|
|
1613
1746
|
channelId: chatId,
|
|
1614
1747
|
userId: fromAid,
|
|
1615
1748
|
text: finalText,
|
|
1616
1749
|
chatType: 'private',
|
|
1617
1750
|
messageId,
|
|
1751
|
+
receivedAt,
|
|
1752
|
+
gatewaySeq: seq,
|
|
1618
1753
|
causation,
|
|
1619
1754
|
seq,
|
|
1620
1755
|
threadId,
|
|
@@ -1637,6 +1772,7 @@ export class AUNChannel {
|
|
|
1637
1772
|
if (!data || typeof data !== 'object')
|
|
1638
1773
|
return;
|
|
1639
1774
|
const msg = data;
|
|
1775
|
+
const receivedAt = Date.now();
|
|
1640
1776
|
// Deduplicate at the adapter boundary, before mention/slash routing.
|
|
1641
1777
|
const messageId = typeof msg.message_id === 'string' ? msg.message_id : '';
|
|
1642
1778
|
const seq = typeof msg.seq === 'number' ? msg.seq : undefined;
|
|
@@ -1654,7 +1790,7 @@ export class AUNChannel {
|
|
|
1654
1790
|
return;
|
|
1655
1791
|
const payloadMentionAids = mentionEntryAids(payloadMentionEntries);
|
|
1656
1792
|
const logDescriptor = classifyAunPayloadForLog(payload);
|
|
1657
|
-
const text = this.extractTextPayload(payload, groupId, senderAid);
|
|
1793
|
+
const text = this.extractTextPayload(payload, groupId, senderAid, { chatType: 'group', groupId });
|
|
1658
1794
|
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
1659
1795
|
const topicName = (typeof payload === 'object' && payload !== null)
|
|
1660
1796
|
? this.sanitizeTopicName(payload.topicName)
|
|
@@ -1710,7 +1846,7 @@ export class AUNChannel {
|
|
|
1710
1846
|
peerName: displayName,
|
|
1711
1847
|
peerType: peerInfo?.type || 'unknown',
|
|
1712
1848
|
seq,
|
|
1713
|
-
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncryptedFast, messageId, msgChatmodeFast, topicName),
|
|
1849
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncryptedFast, messageId, msgChatmodeFast, topicName, groupId),
|
|
1714
1850
|
createdAt,
|
|
1715
1851
|
});
|
|
1716
1852
|
return;
|
|
@@ -1720,7 +1856,7 @@ export class AUNChannel {
|
|
|
1720
1856
|
{
|
|
1721
1857
|
const payloadType = (payload && typeof payload === 'object') ? payload.type ?? '' : '';
|
|
1722
1858
|
if (payloadType === 'action_card_reply') {
|
|
1723
|
-
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, !!env.encrypted, true, logDescriptor, threadId);
|
|
1859
|
+
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, !!env.encrypted, true, logDescriptor, threadId, receivedAt, seq);
|
|
1724
1860
|
return;
|
|
1725
1861
|
}
|
|
1726
1862
|
}
|
|
@@ -1733,18 +1869,18 @@ export class AUNChannel {
|
|
|
1733
1869
|
if (payloadType.startsWith('menu.')) {
|
|
1734
1870
|
const menuEncrypted = !!env.encrypted;
|
|
1735
1871
|
this.acknowledgeImmediately(messageId, seq);
|
|
1736
|
-
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, menuEncrypted, true, logDescriptor, threadId);
|
|
1872
|
+
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, menuEncrypted, true, logDescriptor, threadId, receivedAt, seq);
|
|
1737
1873
|
this.dispatchMessage({
|
|
1738
1874
|
channelId: groupId, userId: senderAid,
|
|
1739
1875
|
text: JSON.stringify(payload),
|
|
1740
|
-
chatType: 'group', messageId, seq, groupId,
|
|
1876
|
+
chatType: 'group', messageId, seq, receivedAt, gatewaySeq: seq, groupId,
|
|
1741
1877
|
encrypted: menuEncrypted,
|
|
1742
1878
|
protectedHeaders,
|
|
1743
1879
|
mentions: payloadMentionEntries,
|
|
1744
1880
|
mentionAids: payloadMentionAids,
|
|
1745
1881
|
isMentioned: hasMentionAll(payloadMentionEntries)
|
|
1746
1882
|
|| (!!this._aid && mentionEntryTargets(payloadMentionEntries).includes(this._aid)),
|
|
1747
|
-
replyContext: this.buildGroupReplyContext(threadId, senderAid, menuEncrypted, messageId, payloadObj?.chatmode, topicName),
|
|
1883
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, menuEncrypted, messageId, payloadObj?.chatmode, topicName, groupId),
|
|
1748
1884
|
msgType: logDescriptor.msgType,
|
|
1749
1885
|
payloadType: logDescriptor.payloadType,
|
|
1750
1886
|
payloadSummary: logDescriptor.payloadSummary,
|
|
@@ -1754,7 +1890,7 @@ export class AUNChannel {
|
|
|
1754
1890
|
}
|
|
1755
1891
|
if (payloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(payloadType)) {
|
|
1756
1892
|
this.acknowledgeImmediately(messageId, seq);
|
|
1757
|
-
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, !!env.encrypted, true, logDescriptor, threadId);
|
|
1893
|
+
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, !!env.encrypted, true, logDescriptor, threadId, receivedAt, seq);
|
|
1758
1894
|
logger.info(`${this.logPrefix()} Group dropped (type deny): type=${payloadType} group=${groupId} sender=${senderAid} mid=${messageId}`);
|
|
1759
1895
|
return;
|
|
1760
1896
|
}
|
|
@@ -1795,7 +1931,7 @@ export class AUNChannel {
|
|
|
1795
1931
|
this.pendingEchoMessages.set(messageId, {
|
|
1796
1932
|
text: echoText,
|
|
1797
1933
|
channelId: groupId,
|
|
1798
|
-
context: this.buildGroupReplyContext(undefined, senderAid, msgEncrypted, messageId, msgChatmodeEcho, undefined),
|
|
1934
|
+
context: this.buildGroupReplyContext(undefined, senderAid, msgEncrypted, messageId, msgChatmodeEcho, undefined, groupId),
|
|
1799
1935
|
receiveTs: Date.now(),
|
|
1800
1936
|
});
|
|
1801
1937
|
// 继续走正常 Agent 流程(下面的代码会 dispatch)
|
|
@@ -1817,7 +1953,7 @@ export class AUNChannel {
|
|
|
1817
1953
|
// TODO: Phase 6 实施 selective-response 模式后,移除此过滤,只保留 isMentioned 标记
|
|
1818
1954
|
if (enforceMention && !isMentioned && !isOwnHandoff) {
|
|
1819
1955
|
this.acknowledgeImmediately(messageId, seq);
|
|
1820
|
-
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, msgEncrypted, true, logDescriptor, threadId);
|
|
1956
|
+
this.appendInboundPayloadJsonl(groupId, senderAid, messageId, msgEncrypted, true, logDescriptor, threadId, receivedAt, seq);
|
|
1821
1957
|
logger.info(`${this.logPrefix()} Group dropped: unmentioned (group=${groupId} sender=${senderAid} mid=${messageId} mentionMode=${mentionMode} isCommand=${isCommandMsg} textPreview=${JSON.stringify(text.slice(0, 80))})`);
|
|
1822
1958
|
return;
|
|
1823
1959
|
}
|
|
@@ -1838,7 +1974,7 @@ export class AUNChannel {
|
|
|
1838
1974
|
}
|
|
1839
1975
|
const mentions = payloadMentionEntries;
|
|
1840
1976
|
// Process attachments
|
|
1841
|
-
const { finalText, images: inboundImages } = await this.processAttachments(payload, strippedText, groupId, rawAttachments);
|
|
1977
|
+
const { finalText, images: inboundImages } = await this.processAttachments(payload, strippedText, groupId, rawAttachments, { chatType: 'group', groupId });
|
|
1842
1978
|
const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
|
|
1843
1979
|
const peerIdentity = await PeerIdentityCache.resolve('aun', senderAid, selfAgentDir, this.store, false);
|
|
1844
1980
|
const shortAid = this.getShortAid(senderAid);
|
|
@@ -1882,6 +2018,8 @@ export class AUNChannel {
|
|
|
1882
2018
|
text: finalText,
|
|
1883
2019
|
chatType: 'group',
|
|
1884
2020
|
messageId,
|
|
2021
|
+
receivedAt,
|
|
2022
|
+
gatewaySeq: seq,
|
|
1885
2023
|
seq,
|
|
1886
2024
|
threadId,
|
|
1887
2025
|
mentions,
|
|
@@ -1889,7 +2027,7 @@ export class AUNChannel {
|
|
|
1889
2027
|
isMentioned: isOwnHandoff || mentionedSelf || mentionedAll,
|
|
1890
2028
|
source: isOwnHandoff ? 'handoff' : undefined,
|
|
1891
2029
|
encrypted: msgEncrypted,
|
|
1892
|
-
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode, topicName),
|
|
2030
|
+
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode, topicName, groupId),
|
|
1893
2031
|
mentionMode: serverMentionMode,
|
|
1894
2032
|
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
1895
2033
|
msgType: logDescriptor.msgType,
|
|
@@ -1899,6 +2037,19 @@ export class AUNChannel {
|
|
|
1899
2037
|
});
|
|
1900
2038
|
}
|
|
1901
2039
|
dispatchMessage(event) {
|
|
2040
|
+
const authenticatedDelivery = event.chatType === 'group'
|
|
2041
|
+
? { chatType: 'group', groupId: event.groupId ?? event.channelId }
|
|
2042
|
+
: { chatType: 'private' };
|
|
2043
|
+
const suppliedDelivery = event.replyContext?.delivery;
|
|
2044
|
+
const suppliedMatches = suppliedDelivery?.chatType === 'private'
|
|
2045
|
+
? authenticatedDelivery.chatType === 'private'
|
|
2046
|
+
: suppliedDelivery?.chatType === 'group'
|
|
2047
|
+
&& authenticatedDelivery.chatType === 'group'
|
|
2048
|
+
&& suppliedDelivery.groupId === authenticatedDelivery.groupId;
|
|
2049
|
+
const delivery = suppliedDelivery && suppliedMatches ? suppliedDelivery : authenticatedDelivery;
|
|
2050
|
+
if (suppliedDelivery && delivery !== suppliedDelivery) {
|
|
2051
|
+
logger.warn(`${this.logPrefix()} Ignoring mismatched inbound reply route: channel=${event.channelId} chatType=${event.chatType}`);
|
|
2052
|
+
}
|
|
1902
2053
|
// Inbound events are claimed in handleIncomingPrivateMessage / GroupMessage
|
|
1903
2054
|
// before parsing. Keeping dedupe there prevents duplicate slash commands
|
|
1904
2055
|
// from reaching routing and mention policy code.
|
|
@@ -1926,19 +2077,24 @@ export class AUNChannel {
|
|
|
1926
2077
|
this.pendingEchoMessages.set(event.messageId, {
|
|
1927
2078
|
text: echoText,
|
|
1928
2079
|
channelId: event.channelId,
|
|
1929
|
-
context:
|
|
2080
|
+
context: {
|
|
2081
|
+
...(event.replyContext?.metadata ? { metadata: event.replyContext.metadata } : {}),
|
|
2082
|
+
delivery,
|
|
2083
|
+
},
|
|
1930
2084
|
receiveTs: Date.now(),
|
|
1931
2085
|
});
|
|
1932
2086
|
logger.info(`${this.logPrefix()} [Echo] long echo stored: mid=${event.messageId} channelId=${event.channelId}`);
|
|
1933
2087
|
}
|
|
1934
2088
|
if (!this.messageHandler)
|
|
1935
2089
|
return;
|
|
1936
|
-
//
|
|
1937
|
-
//
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
replyContext
|
|
1941
|
-
|
|
2090
|
+
// Preserve the caller-supplied context (group paths add mention fields),
|
|
2091
|
+
// but always attach the route derived from this authenticated event when
|
|
2092
|
+
// a synthetic/test event omitted it. Never infer the route from the AID.
|
|
2093
|
+
const replyContext = {
|
|
2094
|
+
...(event.replyContext ?? {}),
|
|
2095
|
+
delivery,
|
|
2096
|
+
...(event.threadId && !event.replyContext?.threadId ? { threadId: event.threadId } : {}),
|
|
2097
|
+
};
|
|
1942
2098
|
this.messageHandler({
|
|
1943
2099
|
channelId: event.channelId || '',
|
|
1944
2100
|
channelType: 'aun',
|
|
@@ -1955,6 +2111,8 @@ export class AUNChannel {
|
|
|
1955
2111
|
encrypted: event.encrypted,
|
|
1956
2112
|
protectedHeaders: event.protectedHeaders,
|
|
1957
2113
|
messageId: event.messageId,
|
|
2114
|
+
receivedAt: event.receivedAt,
|
|
2115
|
+
gatewaySeq: event.gatewaySeq ?? event.seq,
|
|
1958
2116
|
causation: event.causation,
|
|
1959
2117
|
threadId: event.threadId,
|
|
1960
2118
|
mentions: event.mentions,
|
|
@@ -1972,7 +2130,7 @@ export class AUNChannel {
|
|
|
1972
2130
|
logger.error(`${this.logPrefix()} Message handler error:`, err);
|
|
1973
2131
|
});
|
|
1974
2132
|
}
|
|
1975
|
-
appendInboundPayloadJsonl(channelId, fromAid, msgId, encrypt, isGroup, descriptor, threadId) {
|
|
2133
|
+
appendInboundPayloadJsonl(channelId, fromAid, msgId, encrypt, isGroup, descriptor, threadId, receivedAt, gatewaySeq) {
|
|
1976
2134
|
try {
|
|
1977
2135
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', channelId, this.config.aid);
|
|
1978
2136
|
appendMessageLog(chatDir, buildInboundEntry({
|
|
@@ -1987,6 +2145,9 @@ export class AUNChannel {
|
|
|
1987
2145
|
msgType: descriptor.msgType,
|
|
1988
2146
|
payloadType: descriptor.payloadType,
|
|
1989
2147
|
payloadSummary: descriptor.payloadSummary,
|
|
2148
|
+
timestamp: receivedAt,
|
|
2149
|
+
receivedAt,
|
|
2150
|
+
gatewaySeq,
|
|
1990
2151
|
}));
|
|
1991
2152
|
}
|
|
1992
2153
|
catch (e) {
|
|
@@ -2190,7 +2351,14 @@ export class AUNChannel {
|
|
|
2190
2351
|
}
|
|
2191
2352
|
const replyTarget = event.channelId;
|
|
2192
2353
|
// 不传 peerId,避免 sendMessage 在头部追加 @peer
|
|
2193
|
-
const
|
|
2354
|
+
const authenticatedDelivery = event.chatType === 'group'
|
|
2355
|
+
? { chatType: 'group', groupId: event.channelId }
|
|
2356
|
+
: { chatType: 'private' };
|
|
2357
|
+
const context = {
|
|
2358
|
+
threadId: event.threadId,
|
|
2359
|
+
metadata: event.replyContext?.metadata,
|
|
2360
|
+
delivery: authenticatedDelivery,
|
|
2361
|
+
};
|
|
2194
2362
|
const sendStart = Date.now();
|
|
2195
2363
|
this.isEchoSending = true;
|
|
2196
2364
|
this.sendMessage(replyTarget, echoText, context).then(() => {
|
|
@@ -2611,14 +2779,18 @@ export class AUNChannel {
|
|
|
2611
2779
|
const timer = setTimeout(() => this.invalidatedInteractions.delete(interactionId), 24 * 60 * 60 * 1000);
|
|
2612
2780
|
timer.unref?.();
|
|
2613
2781
|
}
|
|
2614
|
-
async recallInteractionCard(messageId, channelId) {
|
|
2782
|
+
async recallInteractionCard(messageId, channelId, delivery) {
|
|
2615
2783
|
if (!this.client || !this.connected)
|
|
2616
2784
|
return false;
|
|
2617
2785
|
try {
|
|
2618
|
-
|
|
2786
|
+
if (!channelId || !delivery) {
|
|
2787
|
+
logger.warn(`${this.logPrefix()} Cannot recall interaction card without trusted delivery route: mid=${messageId}`);
|
|
2788
|
+
return false;
|
|
2789
|
+
}
|
|
2790
|
+
const isGroup = delivery.chatType === 'group';
|
|
2619
2791
|
const method = isGroup ? 'group.recall' : 'message.recall';
|
|
2620
2792
|
const params = isGroup
|
|
2621
|
-
? { group_id:
|
|
2793
|
+
? { group_id: delivery.groupId, message_ids: [messageId] }
|
|
2622
2794
|
: { message_ids: [messageId] };
|
|
2623
2795
|
await this.callAndTrace(method, params, { silentError: true });
|
|
2624
2796
|
return true;
|
|
@@ -2641,9 +2813,10 @@ export class AUNChannel {
|
|
|
2641
2813
|
}
|
|
2642
2814
|
const messageIds = [...(this.interactionCardMessageIds.get(interactionId) ?? [])];
|
|
2643
2815
|
for (const messageId of messageIds) {
|
|
2644
|
-
const
|
|
2816
|
+
const card = this.cardMessageIdMap.get(messageId);
|
|
2817
|
+
const channelId = card?.channelId;
|
|
2645
2818
|
this.forgetInteractionCard(messageId, interactionId);
|
|
2646
|
-
await this.recallInteractionCard(messageId, channelId);
|
|
2819
|
+
await this.recallInteractionCard(messageId, channelId, card?.delivery);
|
|
2647
2820
|
}
|
|
2648
2821
|
}
|
|
2649
2822
|
forgetInteractionCard(messageId, interactionId) {
|
|
@@ -2653,7 +2826,7 @@ export class AUNChannel {
|
|
|
2653
2826
|
if (ids && ids.size === 0)
|
|
2654
2827
|
this.interactionCardMessageIds.delete(interactionId);
|
|
2655
2828
|
}
|
|
2656
|
-
registerCardPostSend(messageId, action, channelId) {
|
|
2829
|
+
registerCardPostSend(messageId, action, channelId, delivery) {
|
|
2657
2830
|
if (action.type !== 'register_interaction_card')
|
|
2658
2831
|
return;
|
|
2659
2832
|
this.cardMessageIdMap.set(messageId, {
|
|
@@ -2661,6 +2834,7 @@ export class AUNChannel {
|
|
|
2661
2834
|
isCommandCard: action.isCommandCard,
|
|
2662
2835
|
initiatorAid: action.initiatorAid,
|
|
2663
2836
|
channelId,
|
|
2837
|
+
delivery: delivery ?? action.delivery,
|
|
2664
2838
|
});
|
|
2665
2839
|
this.ownedCardMsgIds.add(messageId);
|
|
2666
2840
|
let messageIds = this.interactionCardMessageIds.get(action.requestId);
|
|
@@ -2686,17 +2860,18 @@ export class AUNChannel {
|
|
|
2686
2860
|
const invalidationReason = this.invalidatedInteractions.get(action.requestId);
|
|
2687
2861
|
if (invalidationReason) {
|
|
2688
2862
|
this.cardMessageIdMap.delete(messageId);
|
|
2689
|
-
void this.recallInteractionCard(messageId, channelId);
|
|
2863
|
+
void this.recallInteractionCard(messageId, channelId, delivery ?? action.delivery);
|
|
2690
2864
|
}
|
|
2691
2865
|
}
|
|
2692
2866
|
runPostSend(entry, messageId) {
|
|
2693
2867
|
if (!entry.postSend)
|
|
2694
2868
|
return;
|
|
2695
2869
|
if (entry.postSend.type === 'register_interaction_card') {
|
|
2696
|
-
this.registerCardPostSend(messageId, entry.postSend, entry.channelId);
|
|
2870
|
+
this.registerCardPostSend(messageId, entry.postSend, entry.channelId, entry.delivery);
|
|
2697
2871
|
}
|
|
2698
2872
|
}
|
|
2699
2873
|
async sendAunPayload(channelId, payload, context, label) {
|
|
2874
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
2700
2875
|
let normalizedPayload;
|
|
2701
2876
|
try {
|
|
2702
2877
|
normalizedPayload = this.normalizeAunPayloadMentions(payload);
|
|
@@ -2709,7 +2884,7 @@ export class AUNChannel {
|
|
|
2709
2884
|
}
|
|
2710
2885
|
if (!this.client || !this.connected)
|
|
2711
2886
|
return { ok: false };
|
|
2712
|
-
const isGroup =
|
|
2887
|
+
const isGroup = delivery.chatType === 'group';
|
|
2713
2888
|
const targetAid = channelId;
|
|
2714
2889
|
const encryptTarget = isGroup ? channelId : targetAid;
|
|
2715
2890
|
const encrypt = context?.metadata?.encrypted != null
|
|
@@ -2727,7 +2902,7 @@ export class AUNChannel {
|
|
|
2727
2902
|
const truncatedPayload = this.truncatePayloadIfNeeded(payloadWithCausationRef, label);
|
|
2728
2903
|
const params = { payload: truncatedPayload, encrypt };
|
|
2729
2904
|
if (isGroup)
|
|
2730
|
-
params.group_id =
|
|
2905
|
+
params.group_id = delivery.groupId;
|
|
2731
2906
|
else
|
|
2732
2907
|
params.to = targetAid;
|
|
2733
2908
|
const callOnce = async (sendParams, fallback) => {
|
|
@@ -2797,6 +2972,8 @@ export class AUNChannel {
|
|
|
2797
2972
|
this.forwardOutbound(result);
|
|
2798
2973
|
}
|
|
2799
2974
|
async sendContentPayload(channelId, payload, opts) {
|
|
2975
|
+
const delivery = this.requireDelivery(channelId, opts.context);
|
|
2976
|
+
const context = this.withDelivery(opts.context, delivery);
|
|
2800
2977
|
// Validate the caller's raw mentions before any early-return policy (such
|
|
2801
2978
|
// as a cancelled interaction) can hide a malformed wire payload.
|
|
2802
2979
|
const validatedPayload = this.normalizeAunPayloadMentions(payload);
|
|
@@ -2812,14 +2989,15 @@ export class AUNChannel {
|
|
|
2812
2989
|
// Validate the caller's raw mentions before deep-cleaning: that helper
|
|
2813
2990
|
// intentionally removes undefined array elements, which would otherwise
|
|
2814
2991
|
// turn malformed `mentions: [undefined]` into an apparently valid `[]`.
|
|
2815
|
-
const finalPayload = this.normalizeAunPayloadMentions(this.applyReplyContextToPayload(validatedPayload,
|
|
2992
|
+
const finalPayload = this.normalizeAunPayloadMentions(this.applyReplyContextToPayload(validatedPayload, context));
|
|
2816
2993
|
const logText = opts.logText ?? this.payloadLogText(finalPayload, opts.contentKind);
|
|
2817
2994
|
const entry = outbox.enqueue(this.config.aid, {
|
|
2818
2995
|
channelId,
|
|
2996
|
+
delivery,
|
|
2819
2997
|
type: 'payload',
|
|
2820
2998
|
contentKind: opts.contentKind,
|
|
2821
2999
|
payload: finalPayload,
|
|
2822
|
-
context
|
|
3000
|
+
context,
|
|
2823
3001
|
logText,
|
|
2824
3002
|
ttl: opts.ttl,
|
|
2825
3003
|
postSend: opts.postSend,
|
|
@@ -2966,13 +3144,15 @@ export class AUNChannel {
|
|
|
2966
3144
|
logger.warn(`${this.logPrefix()} Attempted to send empty message, skipping`);
|
|
2967
3145
|
return;
|
|
2968
3146
|
}
|
|
3147
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
3148
|
+
const routedContext = this.withDelivery(context, delivery);
|
|
2969
3149
|
// 长 echo: agent 首次回复前先发 echo trace(echo 自身发送时跳过)
|
|
2970
3150
|
if (!this.isEchoSending) {
|
|
2971
|
-
await this.flushPendingEcho(channelId);
|
|
3151
|
+
await this.flushPendingEcho(channelId, delivery);
|
|
2972
3152
|
}
|
|
2973
3153
|
let finalText = text;
|
|
2974
3154
|
this.sentCount.set(channelId, (this.sentCount.get(channelId) || 0) + 1);
|
|
2975
|
-
if (
|
|
3155
|
+
if (delivery.chatType === 'group') {
|
|
2976
3156
|
const requestedMentions = Array.from(new Set((context?.mentionUserIds ?? [])
|
|
2977
3157
|
.filter((id) => typeof id === 'string' && id.trim().length > 0)
|
|
2978
3158
|
.map(id => id.trim())));
|
|
@@ -2995,8 +3175,8 @@ export class AUNChannel {
|
|
|
2995
3175
|
}
|
|
2996
3176
|
}
|
|
2997
3177
|
}
|
|
2998
|
-
const operationId = typeof
|
|
2999
|
-
?
|
|
3178
|
+
const operationId = typeof routedContext.metadata?.operationId === 'string'
|
|
3179
|
+
? routedContext.metadata.operationId
|
|
3000
3180
|
: undefined;
|
|
3001
3181
|
if (operationId) {
|
|
3002
3182
|
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', channelId, this.config.aid);
|
|
@@ -3006,13 +3186,14 @@ export class AUNChannel {
|
|
|
3006
3186
|
// Write-ahead: persist to outbox before attempting send
|
|
3007
3187
|
const entry = outbox.enqueue(this.config.aid, {
|
|
3008
3188
|
channelId,
|
|
3189
|
+
delivery,
|
|
3009
3190
|
dedupeKey: operationId,
|
|
3010
|
-
critical:
|
|
3191
|
+
critical: routedContext.metadata?.criticalDelivery === true,
|
|
3011
3192
|
type: 'text',
|
|
3012
3193
|
text: finalText,
|
|
3013
|
-
context,
|
|
3014
|
-
ttl: typeof
|
|
3015
|
-
?
|
|
3194
|
+
context: routedContext,
|
|
3195
|
+
ttl: typeof routedContext.metadata?.outboxTtl === 'number'
|
|
3196
|
+
? routedContext.metadata.outboxTtl
|
|
3016
3197
|
: undefined,
|
|
3017
3198
|
});
|
|
3018
3199
|
logger.debug(`${this.logPrefix()} Outbox enqueued: id=${entry.id} channel=${channelId} text=${finalText.slice(0, 40)}`);
|
|
@@ -3036,8 +3217,8 @@ export class AUNChannel {
|
|
|
3036
3217
|
}
|
|
3037
3218
|
/** Daemon-side transport for `ec msg send` running inside an agent task. */
|
|
3038
3219
|
async sendDaemonMsg(args) {
|
|
3039
|
-
if (!args.to
|
|
3040
|
-
return { ok: false, error: 'aun-msg-send
|
|
3220
|
+
if (!args.to) {
|
|
3221
|
+
return { ok: false, error: 'aun-msg-send requires a private target', code: 'UNSUPPORTED_TARGET' };
|
|
3041
3222
|
}
|
|
3042
3223
|
if (!args.payload || typeof args.payload !== 'object' || Array.isArray(args.payload)) {
|
|
3043
3224
|
return { ok: false, error: 'payload must be an object', code: 'INVALID_PAYLOAD' };
|
|
@@ -3161,8 +3342,8 @@ export class AUNChannel {
|
|
|
3161
3342
|
}
|
|
3162
3343
|
/** Daemon-side transport for `ec group send` running inside an agent task. */
|
|
3163
3344
|
async sendDaemonGroupMsg(args) {
|
|
3164
|
-
if (!args.groupId
|
|
3165
|
-
return { ok: false, error: 'aun
|
|
3345
|
+
if (!args.groupId) {
|
|
3346
|
+
return { ok: false, error: 'aun group send requires a group target', code: 'UNSUPPORTED_TARGET' };
|
|
3166
3347
|
}
|
|
3167
3348
|
if (!args.payload || typeof args.payload !== 'object' || Array.isArray(args.payload)) {
|
|
3168
3349
|
return { ok: false, error: 'payload must be an object', code: 'INVALID_PAYLOAD' };
|
|
@@ -3246,7 +3427,7 @@ export class AUNChannel {
|
|
|
3246
3427
|
return { ok: false, error: e?.message || String(e), code: e?.code };
|
|
3247
3428
|
}
|
|
3248
3429
|
}
|
|
3249
|
-
async flushPendingEcho(channelId) {
|
|
3430
|
+
async flushPendingEcho(channelId, delivery) {
|
|
3250
3431
|
// 查找该 channelId 是否有 pending echo(长 echo 等待 agent 首次回复)
|
|
3251
3432
|
for (const [key, echo] of this.pendingEchoMessages) {
|
|
3252
3433
|
if (echo.channelId === channelId) {
|
|
@@ -3270,18 +3451,32 @@ export class AUNChannel {
|
|
|
3270
3451
|
ts: Date.now(),
|
|
3271
3452
|
aid: this.config.aid,
|
|
3272
3453
|
channelId,
|
|
3454
|
+
delivery,
|
|
3273
3455
|
type: 'text',
|
|
3274
3456
|
text: echoText,
|
|
3457
|
+
context: this.withDelivery(echo.context, delivery),
|
|
3275
3458
|
ttl: 300_000,
|
|
3276
3459
|
};
|
|
3277
3460
|
const ok = await this.deliverTextEntry(echoEntry);
|
|
3278
3461
|
if (!ok) {
|
|
3279
|
-
outbox.enqueue(this.config.aid, {
|
|
3462
|
+
outbox.enqueue(this.config.aid, {
|
|
3463
|
+
channelId,
|
|
3464
|
+
delivery,
|
|
3465
|
+
type: 'text',
|
|
3466
|
+
text: echoText,
|
|
3467
|
+
context: this.withDelivery(echo.context, delivery),
|
|
3468
|
+
});
|
|
3280
3469
|
}
|
|
3281
3470
|
logger.info(`${this.logPrefix()} [Echo] long echo trace delivered=${ok} to ${channelId} (agent ${agentDuration}ms)`);
|
|
3282
3471
|
}
|
|
3283
3472
|
else {
|
|
3284
|
-
outbox.enqueue(this.config.aid, {
|
|
3473
|
+
outbox.enqueue(this.config.aid, {
|
|
3474
|
+
channelId,
|
|
3475
|
+
delivery,
|
|
3476
|
+
type: 'text',
|
|
3477
|
+
text: echoText,
|
|
3478
|
+
context: this.withDelivery(echo.context, delivery),
|
|
3479
|
+
});
|
|
3285
3480
|
logger.warn(`${this.logPrefix()} [Echo] not connected, echo trace queued in outbox`);
|
|
3286
3481
|
}
|
|
3287
3482
|
break;
|
|
@@ -3291,7 +3486,8 @@ export class AUNChannel {
|
|
|
3291
3486
|
async deliverTextEntry(entry) {
|
|
3292
3487
|
const channelId = entry.channelId;
|
|
3293
3488
|
const finalText = entry.text;
|
|
3294
|
-
const
|
|
3489
|
+
const delivery = this.requirePersistedDelivery(channelId, entry.delivery);
|
|
3490
|
+
const context = this.withDelivery(entry.context, delivery);
|
|
3295
3491
|
const operationId = entry.dedupeKey
|
|
3296
3492
|
|| (typeof context?.metadata?.operationId === 'string' ? context.metadata.operationId : undefined);
|
|
3297
3493
|
if (operationId) {
|
|
@@ -3309,7 +3505,7 @@ export class AUNChannel {
|
|
|
3309
3505
|
// 从 context.metadata.source 读取 source,默认为 'daemon'
|
|
3310
3506
|
const source = context?.metadata?.source ?? 'daemon';
|
|
3311
3507
|
const payload = { type: 'text', text: finalText };
|
|
3312
|
-
if (
|
|
3508
|
+
if (delivery.chatType === 'group') {
|
|
3313
3509
|
const extracted = this.extractMentionEntriesFromText(finalText);
|
|
3314
3510
|
if (extracted.length > 0)
|
|
3315
3511
|
payload.mentions = extracted;
|
|
@@ -3323,7 +3519,7 @@ export class AUNChannel {
|
|
|
3323
3519
|
if (context?.metadata?.chatmode)
|
|
3324
3520
|
payload.chatmode = context.metadata.chatmode;
|
|
3325
3521
|
const causation = normalizeCausation(context?.metadata?.causation);
|
|
3326
|
-
const causationRef =
|
|
3522
|
+
const causationRef = delivery.chatType !== 'group' && causation
|
|
3327
3523
|
? `caus_${crypto.randomUUID().replace(/-/g, '')}`
|
|
3328
3524
|
: undefined;
|
|
3329
3525
|
if (causationRef && causation) {
|
|
@@ -3332,7 +3528,7 @@ export class AUNChannel {
|
|
|
3332
3528
|
}
|
|
3333
3529
|
// 诊断日志:记录 payload 构造结果(含 task_id / thread_id / chatmode)
|
|
3334
3530
|
logger.info(`${this.logPrefix()} deliverTextEntry: channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} chatmode=${payload.chatmode ?? 'none'} source=${source} textLen=${finalText.length}`);
|
|
3335
|
-
const isGroup =
|
|
3531
|
+
const isGroup = delivery.chatType === 'group';
|
|
3336
3532
|
const targetAid = channelId;
|
|
3337
3533
|
if (operationId && entry.deliveryReceipt) {
|
|
3338
3534
|
this.appendOutboundJsonl(channelId, {
|
|
@@ -3506,7 +3702,8 @@ export class AUNChannel {
|
|
|
3506
3702
|
}
|
|
3507
3703
|
const contentKind = entry.contentKind;
|
|
3508
3704
|
const logText = entry.logText ?? this.payloadLogText(payload, contentKind);
|
|
3509
|
-
const
|
|
3705
|
+
const delivery = this.requirePersistedDelivery(channelId, entry.delivery);
|
|
3706
|
+
const context = this.withDelivery(entry.context, delivery);
|
|
3510
3707
|
logger.info(`${this.logPrefix()} deliverPayloadEntry: id=${entry.id} kind=${contentKind ?? payload.type ?? 'payload'} channelId=${channelId} thread_id=${payload.thread_id ?? 'none'} task_id=${payload.task_id ?? 'none'} textLen=${logText.length}`);
|
|
3511
3708
|
const sent = await this.sendAunPayload(channelId, payload, context, `${contentKind ?? payload.type ?? 'payload'}`);
|
|
3512
3709
|
if (sent.discarded) {
|
|
@@ -3515,7 +3712,7 @@ export class AUNChannel {
|
|
|
3515
3712
|
}
|
|
3516
3713
|
if (!sent.ok || !sent.messageId)
|
|
3517
3714
|
return sent;
|
|
3518
|
-
const isGroup =
|
|
3715
|
+
const isGroup = delivery.chatType === 'group';
|
|
3519
3716
|
const statsContext = isGroup ? await this.groupStatsContext(channelId) : undefined;
|
|
3520
3717
|
this.logAunSendAccepted(isGroup ? 'group.send' : 'message.send', isGroup ? channelId : this.peerLabel(channelId), sent.messageId, !!sent.encrypt, sent.result, logText);
|
|
3521
3718
|
this.recordDurableOutbound(channelId, payload, sent.messageId, !!sent.encrypt, context, isGroup, contentKind, logText, sent.result, statsContext);
|
|
@@ -3577,6 +3774,8 @@ export class AUNChannel {
|
|
|
3577
3774
|
async sendThought(channelId, taskId, payload, context) {
|
|
3578
3775
|
if (!taskId)
|
|
3579
3776
|
return;
|
|
3777
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
3778
|
+
context = this.withDelivery(context, delivery);
|
|
3580
3779
|
const validatedPayload = this.normalizeAunPayloadMentions(payload);
|
|
3581
3780
|
const finalPayload = this.normalizeAunPayloadMentions(this.stripUndefinedDeep(validatedPayload));
|
|
3582
3781
|
if (!this.connected || !this.client)
|
|
@@ -3621,8 +3820,8 @@ export class AUNChannel {
|
|
|
3621
3820
|
else {
|
|
3622
3821
|
thoughtText = this.activityLogText(finalPayload);
|
|
3623
3822
|
}
|
|
3624
|
-
if (
|
|
3625
|
-
params.group_id =
|
|
3823
|
+
if (delivery.chatType === 'group') {
|
|
3824
|
+
params.group_id = delivery.groupId;
|
|
3626
3825
|
const putRes = await this.callAndTrace('group.thought.put', params);
|
|
3627
3826
|
const tid = putRes?.thought_id;
|
|
3628
3827
|
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
@@ -3652,13 +3851,15 @@ export class AUNChannel {
|
|
|
3652
3851
|
}
|
|
3653
3852
|
/** 发送结构化 AUN payload;transient 协议载荷不写本地 messages.jsonl。 */
|
|
3654
3853
|
async sendStructured(channelId, payload, context) {
|
|
3854
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
3855
|
+
context = this.withDelivery(context, delivery);
|
|
3655
3856
|
// Validate before checking connection state so malformed producer payloads
|
|
3656
3857
|
// are reported deterministically instead of being hidden by a disconnect.
|
|
3657
3858
|
const validatedPayload = this.normalizeAunPayloadMentions(payload);
|
|
3658
3859
|
const finalPayload = this.stripUndefinedDeep(validatedPayload);
|
|
3659
3860
|
if (!this.connected || !this.client)
|
|
3660
3861
|
return null;
|
|
3661
|
-
const isGroup =
|
|
3862
|
+
const isGroup = delivery.chatType === 'group';
|
|
3662
3863
|
const targetAid = channelId;
|
|
3663
3864
|
const encryptTarget = isGroup ? channelId : targetAid;
|
|
3664
3865
|
const encrypt = context?.metadata?.encrypted != null
|
|
@@ -3684,7 +3885,7 @@ export class AUNChannel {
|
|
|
3684
3885
|
}
|
|
3685
3886
|
try {
|
|
3686
3887
|
if (isGroup) {
|
|
3687
|
-
params.group_id =
|
|
3888
|
+
params.group_id = delivery.groupId;
|
|
3688
3889
|
const result = await this.callAndTrace('group.send', params);
|
|
3689
3890
|
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
3690
3891
|
logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
|
|
@@ -3708,6 +3909,8 @@ export class AUNChannel {
|
|
|
3708
3909
|
}
|
|
3709
3910
|
}
|
|
3710
3911
|
async sendFile(channelId, filePath, context) {
|
|
3912
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
3913
|
+
context = this.withDelivery(context, delivery);
|
|
3711
3914
|
const absPath = path.resolve(filePath);
|
|
3712
3915
|
if (!fs.existsSync(absPath)) {
|
|
3713
3916
|
logger.warn(`${this.logPrefix()} sendFile: file not found: ${absPath}`);
|
|
@@ -3725,6 +3928,7 @@ export class AUNChannel {
|
|
|
3725
3928
|
// Write-ahead: persist to outbox
|
|
3726
3929
|
const entry = outbox.enqueue(this.config.aid, {
|
|
3727
3930
|
channelId,
|
|
3931
|
+
delivery,
|
|
3728
3932
|
type: 'file',
|
|
3729
3933
|
filePath: absPath,
|
|
3730
3934
|
context,
|
|
@@ -3743,6 +3947,8 @@ export class AUNChannel {
|
|
|
3743
3947
|
}
|
|
3744
3948
|
}
|
|
3745
3949
|
async sendImage(channelId, data, mimeType, alt, context) {
|
|
3950
|
+
const delivery = this.requireDelivery(channelId, context);
|
|
3951
|
+
context = this.withDelivery(context, delivery);
|
|
3746
3952
|
if (!Buffer.isBuffer(data)) {
|
|
3747
3953
|
throw new Error('result.image data must be a Buffer');
|
|
3748
3954
|
}
|
|
@@ -3764,6 +3970,7 @@ export class AUNChannel {
|
|
|
3764
3970
|
const logText = alt ? `[image] ${alt}` : '[image]';
|
|
3765
3971
|
const entry = outbox.enqueue(this.config.aid, {
|
|
3766
3972
|
channelId,
|
|
3973
|
+
delivery,
|
|
3767
3974
|
type: 'image',
|
|
3768
3975
|
contentKind: 'image',
|
|
3769
3976
|
image: { dataBase64: data.toString('base64'), mimeType: normalizedMimeType, alt },
|
|
@@ -3822,6 +4029,9 @@ export class AUNChannel {
|
|
|
3822
4029
|
logger.warn(`${this.logPrefix()} deliverImageEntry: missing image data (outbox id=${entry.id})`);
|
|
3823
4030
|
return true;
|
|
3824
4031
|
}
|
|
4032
|
+
const delivery = this.requirePersistedDelivery(entry.channelId, entry.delivery);
|
|
4033
|
+
entry.delivery = delivery;
|
|
4034
|
+
entry.context = this.withDelivery(entry.context, delivery);
|
|
3825
4035
|
const data = Buffer.from(image.dataBase64, 'base64');
|
|
3826
4036
|
if (data.length === 0) {
|
|
3827
4037
|
logger.warn(`${this.logPrefix()} deliverImageEntry: invalid image data (outbox id=${entry.id})`);
|
|
@@ -3839,6 +4049,7 @@ export class AUNChannel {
|
|
|
3839
4049
|
if (!outbox.replace(this.config.aid, entry)) {
|
|
3840
4050
|
payloadEntry = outbox.enqueue(this.config.aid, {
|
|
3841
4051
|
channelId: entry.channelId,
|
|
4052
|
+
delivery,
|
|
3842
4053
|
type: 'payload',
|
|
3843
4054
|
contentKind: 'image',
|
|
3844
4055
|
payload: entry.payload,
|
|
@@ -3856,7 +4067,8 @@ export class AUNChannel {
|
|
|
3856
4067
|
async deliverFileEntry(entry) {
|
|
3857
4068
|
const channelId = entry.channelId;
|
|
3858
4069
|
const absPath = entry.filePath;
|
|
3859
|
-
const
|
|
4070
|
+
const delivery = this.requirePersistedDelivery(channelId, entry.delivery);
|
|
4071
|
+
const context = this.withDelivery(entry.context, delivery);
|
|
3860
4072
|
if (!fs.existsSync(absPath)) {
|
|
3861
4073
|
logger.warn(`${this.logPrefix()} deliverFileEntry: file gone: ${absPath}`);
|
|
3862
4074
|
return true; // remove from outbox, file no longer exists
|
|
@@ -3914,7 +4126,7 @@ export class AUNChannel {
|
|
|
3914
4126
|
attachment,
|
|
3915
4127
|
context,
|
|
3916
4128
|
});
|
|
3917
|
-
const isGroup =
|
|
4129
|
+
const isGroup = delivery.chatType === 'group';
|
|
3918
4130
|
const fileTargetAid = channelId;
|
|
3919
4131
|
const encryptTarget = isGroup ? channelId : fileTargetAid;
|
|
3920
4132
|
const encrypt = context?.metadata?.encrypted != null
|
|
@@ -3925,7 +4137,7 @@ export class AUNChannel {
|
|
|
3925
4137
|
let sentMid = null;
|
|
3926
4138
|
try {
|
|
3927
4139
|
if (isGroup) {
|
|
3928
|
-
params.group_id =
|
|
4140
|
+
params.group_id = delivery.groupId;
|
|
3929
4141
|
this.trace('OUT', 'group.send.file', params);
|
|
3930
4142
|
const result = await this.client.call('group.send', params);
|
|
3931
4143
|
sendResult = result;
|
|
@@ -4033,6 +4245,10 @@ export class AUNChannel {
|
|
|
4033
4245
|
return;
|
|
4034
4246
|
logger.info(`${this.logPrefix()} Draining outbox...`);
|
|
4035
4247
|
const result = await outbox.drain(this.config.aid, async (entry) => {
|
|
4248
|
+
if (!isDeliveryTarget(entry.delivery)) {
|
|
4249
|
+
logger.warn(`${this.logPrefix()} Discarding legacy/malformed outbox entry without a trusted delivery route: id=${entry.id} channel=${entry.channelId}`);
|
|
4250
|
+
return true;
|
|
4251
|
+
}
|
|
4036
4252
|
if (entry.type === 'text') {
|
|
4037
4253
|
return this.withOutboxInFlight(entry, () => this.deliverTextEntry(entry), false);
|
|
4038
4254
|
}
|
|
@@ -4058,11 +4274,23 @@ export class AUNChannel {
|
|
|
4058
4274
|
this.messageSeqMap.delete(messageId);
|
|
4059
4275
|
}
|
|
4060
4276
|
sendProcessingStatus(channelId, status, sessionId, taskId, context, extraMeta) {
|
|
4277
|
+
let delivery;
|
|
4278
|
+
try {
|
|
4279
|
+
delivery = this.requireDelivery(channelId, context);
|
|
4280
|
+
}
|
|
4281
|
+
catch (error) {
|
|
4282
|
+
logger.warn(`${this.logPrefix()} task.${status} notify skipped without trusted delivery route: channel=${channelId} task=${taskId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
4283
|
+
return;
|
|
4284
|
+
}
|
|
4285
|
+
context = this.withDelivery(context, delivery);
|
|
4061
4286
|
if (status === 'start')
|
|
4062
4287
|
this.sentCount.delete(channelId); // 新任务开始,重置计数
|
|
4063
4288
|
if (!this.client || !this.connected)
|
|
4064
4289
|
return;
|
|
4065
|
-
const
|
|
4290
|
+
const requestedSeverity = extraMeta?.severity;
|
|
4291
|
+
const severity = requestedSeverity === 'warn'
|
|
4292
|
+
? 'warn'
|
|
4293
|
+
: status === 'error' || status === 'timeout' ? 'error' : 'info';
|
|
4066
4294
|
const stateMap = {
|
|
4067
4295
|
start: 'started',
|
|
4068
4296
|
done: 'completed',
|
|
@@ -4081,7 +4309,10 @@ export class AUNChannel {
|
|
|
4081
4309
|
severity,
|
|
4082
4310
|
terminal: ['done', 'interrupted', 'error', 'timeout'].includes(status),
|
|
4083
4311
|
};
|
|
4084
|
-
const
|
|
4312
|
+
const metadataInput = extraMeta ? { ...extraMeta } : undefined;
|
|
4313
|
+
if (metadataInput)
|
|
4314
|
+
delete metadataInput.severity;
|
|
4315
|
+
const metadata = metadataInput ? this.stripUndefinedDeep(metadataInput) : undefined;
|
|
4085
4316
|
if (metadata && Object.keys(metadata).length > 0)
|
|
4086
4317
|
statusPayload.metadata = metadata;
|
|
4087
4318
|
if (context?.threadId)
|
|
@@ -4093,8 +4324,8 @@ export class AUNChannel {
|
|
|
4093
4324
|
if (context?.replyToMessageId)
|
|
4094
4325
|
statusPayload.ref_message_id = context.replyToMessageId;
|
|
4095
4326
|
const notifyOptions = { ttlMs: 60_000 };
|
|
4096
|
-
if (
|
|
4097
|
-
notifyOptions.groupId =
|
|
4327
|
+
if (delivery.chatType === 'group')
|
|
4328
|
+
notifyOptions.groupId = delivery.groupId;
|
|
4098
4329
|
else
|
|
4099
4330
|
notifyOptions.to = channelId;
|
|
4100
4331
|
this.trace('OUT', 'notify.task_status', {
|
|
@@ -4115,7 +4346,7 @@ export class AUNChannel {
|
|
|
4115
4346
|
}).catch(() => { });
|
|
4116
4347
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, JSON.stringify(statusPayload).length, undefined, true, undefined, undefined, 'notify');
|
|
4117
4348
|
// 群聊显示 group id 简称,P2P 显示 peer label;从 context.metadata 读取 chatmode
|
|
4118
|
-
const targetLabel =
|
|
4349
|
+
const targetLabel = delivery.chatType === 'group' ? delivery.groupId : this.peerLabel(channelId);
|
|
4119
4350
|
const chatmode = context?.metadata?.chatmode ?? '?';
|
|
4120
4351
|
const initiator = statusPayload.initiator ?? '';
|
|
4121
4352
|
const refMsgId = statusPayload.ref_message_id ?? '';
|
|
@@ -4127,8 +4358,6 @@ export class AUNChannel {
|
|
|
4127
4358
|
logger.info(logTaskStatus);
|
|
4128
4359
|
}
|
|
4129
4360
|
async sendCustomPayload(channelId, payload, context) {
|
|
4130
|
-
if (!this.client || !this.connected)
|
|
4131
|
-
return;
|
|
4132
4361
|
// SDK 0.3.0 E2EE requires payload to be an object
|
|
4133
4362
|
let payloadObj;
|
|
4134
4363
|
try {
|
|
@@ -4362,21 +4591,25 @@ export class AUNChannelPlugin {
|
|
|
4362
4591
|
channelKey: inst.name, // channelName 实际上就是 channelKey
|
|
4363
4592
|
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: true, status: true, thread: true, authenticatedApproval: true },
|
|
4364
4593
|
send: async (envelope, payload) => {
|
|
4594
|
+
const channelId = envelope?.channelId;
|
|
4595
|
+
const delivery = requireEnvelopeDelivery(channelId, envelope?.delivery, envelope?.replyContext?.delivery);
|
|
4365
4596
|
const parentCausation = normalizeCausation(envelope.causation ?? envelope.replyContext?.metadata?.causation);
|
|
4366
4597
|
const outboundCausation = parentCausation ? deriveCausation(parentCausation) : undefined;
|
|
4367
4598
|
const replyCtx = outboundCausation
|
|
4368
4599
|
? {
|
|
4369
4600
|
...(envelope.replyContext ?? {}),
|
|
4601
|
+
delivery,
|
|
4370
4602
|
metadata: { ...(envelope.replyContext?.metadata ?? {}), causation: outboundCausation },
|
|
4371
4603
|
}
|
|
4372
|
-
: envelope.replyContext
|
|
4604
|
+
: (envelope.replyContext || envelope.delivery)
|
|
4605
|
+
? { ...(envelope.replyContext ?? {}), delivery }
|
|
4606
|
+
: undefined;
|
|
4373
4607
|
if (outboundCausation) {
|
|
4374
4608
|
recordCausationSpan(outboundCausation, 'message.outbound', {
|
|
4375
4609
|
status: 'started',
|
|
4376
4610
|
refs: { taskId: envelope.taskId, sessionId: envelope.sessionId },
|
|
4377
4611
|
});
|
|
4378
4612
|
}
|
|
4379
|
-
const channelId = envelope.channelId;
|
|
4380
4613
|
const taskBase = () => channel.buildTaskPayloadBase(envelope, replyCtx);
|
|
4381
4614
|
switch (payload.kind) {
|
|
4382
4615
|
case 'result.text':
|
|
@@ -4505,7 +4738,7 @@ export class AUNChannelPlugin {
|
|
|
4505
4738
|
aunCard.text = action.body;
|
|
4506
4739
|
setIfDefined(aunCard, 'format', action.bodyFormat);
|
|
4507
4740
|
}
|
|
4508
|
-
if (req.initiatorId &&
|
|
4741
|
+
if (req.initiatorId && replyCtx?.delivery?.chatType === 'group')
|
|
4509
4742
|
aunCard.initiator = req.initiatorId;
|
|
4510
4743
|
if (replyCtx?.threadId)
|
|
4511
4744
|
aunCard.thread_id = replyCtx.threadId;
|
|
@@ -4518,6 +4751,7 @@ export class AUNChannelPlugin {
|
|
|
4518
4751
|
requestId: req.id,
|
|
4519
4752
|
isCommandCard: false,
|
|
4520
4753
|
initiatorAid: req.initiatorId,
|
|
4754
|
+
delivery: replyCtx?.delivery,
|
|
4521
4755
|
expiresAt: Date.now() + cardTtlMs,
|
|
4522
4756
|
},
|
|
4523
4757
|
});
|
|
@@ -4542,7 +4776,7 @@ export class AUNChannelPlugin {
|
|
|
4542
4776
|
aunCard.text = card.body;
|
|
4543
4777
|
setIfDefined(aunCard, 'format', card.bodyFormat);
|
|
4544
4778
|
}
|
|
4545
|
-
if (req.initiatorId &&
|
|
4779
|
+
if (req.initiatorId && replyCtx?.delivery?.chatType === 'group')
|
|
4546
4780
|
aunCard.initiator = req.initiatorId;
|
|
4547
4781
|
if (replyCtx?.threadId)
|
|
4548
4782
|
aunCard.thread_id = replyCtx.threadId;
|
|
@@ -4555,6 +4789,7 @@ export class AUNChannelPlugin {
|
|
|
4555
4789
|
requestId: req.id,
|
|
4556
4790
|
isCommandCard: true,
|
|
4557
4791
|
initiatorAid: req.initiatorId,
|
|
4792
|
+
delivery: replyCtx?.delivery,
|
|
4558
4793
|
expiresAt: Date.now() + cardTtlMs,
|
|
4559
4794
|
},
|
|
4560
4795
|
});
|