evolclaw 3.2.0 → 3.3.0
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 +17 -0
- package/README.md +1 -2
- package/dist/agents/{resolve.js → baseagent.js} +34 -5
- package/dist/agents/claude-runner.js +120 -27
- package/dist/agents/codex-app-server-client.js +364 -0
- package/dist/agents/codex-runner.js +1069 -141
- package/dist/agents/gemini-runner.js +2 -2
- package/dist/agents/runner-types.js +28 -0
- package/dist/aun/aid/store.js +1 -1
- package/dist/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +406 -293
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +97 -150
- package/dist/channels/qqbot.js +75 -138
- package/dist/channels/wechat.js +75 -136
- package/dist/channels/wecom.js +75 -138
- package/dist/cli/agent.js +8 -5
- package/dist/cli/index.js +177 -44
- package/dist/cli/init.js +33 -6
- package/dist/cli/model.js +1 -1
- package/dist/cli/stats.js +558 -0
- package/dist/cli/version.js +87 -0
- package/dist/cli/watch-msg.js +5 -2
- package/dist/config-store.js +12 -6
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +473 -114
- package/dist/core/evolagent-registry.js +1 -0
- package/dist/core/evolagent.js +1 -1
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +63 -1
- package/dist/core/message/im-renderer.js +35 -13
- package/dist/core/message/items-formatter.js +9 -1
- package/dist/core/message/message-bridge.js +49 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +295 -35
- package/dist/core/message/message-queue.js +2 -2
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +56 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-scope.js +2 -2
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
- package/dist/core/session/session-manager.js +27 -13
- package/dist/core/session/session-title.js +26 -0
- package/dist/core/stats/billing.js +151 -0
- package/dist/core/stats/budget.js +93 -0
- package/dist/core/stats/db.js +314 -0
- package/dist/core/stats/eck-vars.js +84 -0
- package/dist/core/stats/index.js +10 -0
- package/dist/core/stats/normalizer.js +78 -0
- package/dist/core/stats/query.js +760 -0
- package/dist/core/stats/writer.js +115 -0
- package/dist/core/trigger/manager.js +34 -0
- package/dist/core/trigger/parser.js +9 -3
- package/dist/core/trigger/scheduler.js +20 -17
- package/dist/{agents → eck}/manifest-engine.js +20 -1
- package/dist/{agents → eck}/message-renderer.js +24 -1
- package/dist/index.js +130 -8
- package/dist/ipc.js +17 -1
- package/dist/utils/cross-platform.js +23 -5
- package/dist/utils/ecweb-pair.js +20 -0
- package/dist/utils/stats.js +14 -0
- package/kits/docs/evolclaw/INDEX.md +3 -1
- package/kits/docs/evolclaw/fs-architecture.md +1215 -0
- package/kits/docs/evolclaw/fs.md +131 -0
- package/kits/docs/evolclaw/group-fs.md +209 -0
- package/kits/docs/evolclaw/stats.md +70 -0
- package/kits/docs/venues/aun-group.md +29 -6
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +30 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/system-fragments/response-depth.md +16 -0
- package/package.json +4 -4
- package/dist/agents/baseagent-normalize.js +0 -19
- package/dist/core/relation/peer-key.js +0 -16
- package/dist/evolclaw-config.js +0 -11
- package/dist/utils/channel-helpers.js +0 -46
- /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
- /package/dist/{agents → eck}/kit-renderer.js +0 -0
package/dist/channels/aun.js
CHANGED
|
@@ -5,12 +5,13 @@ import path from 'path';
|
|
|
5
5
|
import os from 'os';
|
|
6
6
|
import { logger, localTimestamp } from '../utils/logger.js';
|
|
7
7
|
import { LogWriter } from '../utils/log-writer.js';
|
|
8
|
-
import {
|
|
8
|
+
import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
|
|
9
9
|
import { resolvePaths, getPackageRoot, agentMdPath as agentMdPathFn, agentDir as agentDirPath, resolveRoot } from '../paths.js';
|
|
10
10
|
import { saveToUploads, sanitizeFileName, bufferToInboundImage } from '../utils/media-cache.js';
|
|
11
11
|
import { appendAidEvent } from '../utils/instance-registry.js';
|
|
12
|
-
import { appendMessageLog, buildOutboundEntry } from '../core/message/message-log.js';
|
|
12
|
+
import { appendMessageLog, buildOutboundEntry, buildInboundEntry } from '../core/message/message-log.js';
|
|
13
13
|
import { chatDirPath } from '../core/session/session-fs-store.js';
|
|
14
|
+
import { appendHintAdd, appendHintRemove, parseInjectRequest } from '../core/message/pending-hints.js';
|
|
14
15
|
import { appendAidLifecycle } from '../aun/aid/identity.js';
|
|
15
16
|
import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
16
17
|
import { loadAgent, saveAgent } from '../config-store.js';
|
|
@@ -87,6 +88,7 @@ export function aunOptsToInbound(opts, channel, channelType) {
|
|
|
87
88
|
replyContext: opts.replyContext,
|
|
88
89
|
source: opts.source,
|
|
89
90
|
images: opts.images,
|
|
91
|
+
dispatchMode: opts.dispatchMode,
|
|
90
92
|
};
|
|
91
93
|
}
|
|
92
94
|
export class AUNChannel {
|
|
@@ -115,14 +117,16 @@ export class AUNChannel {
|
|
|
115
117
|
// 便于 jq 过滤:`jq 'select(.task_id == "task-xxx")'`
|
|
116
118
|
const d = (data && typeof data === 'object') ? data : {};
|
|
117
119
|
const payload = d.payload ?? {};
|
|
120
|
+
// 入站事件(IN)路由字段在 d.envelope.*(SDK 0.5.*);出站 params(OUT)字段在顶层。两者兼顾。
|
|
121
|
+
const env = (d.envelope && typeof d.envelope === 'object') ? d.envelope : {};
|
|
118
122
|
const topContext = {
|
|
119
123
|
self_aid: this._aid ?? this.config.aid,
|
|
120
124
|
};
|
|
121
125
|
// peer / group 识别
|
|
122
|
-
const peerAid = d.to ?? d.from ?? d.sender_aid ?? payload.to;
|
|
126
|
+
const peerAid = env.to ?? env.from ?? d.to ?? d.from ?? d.sender_aid ?? payload.to;
|
|
123
127
|
if (peerAid)
|
|
124
128
|
topContext.peer_aid = peerAid;
|
|
125
|
-
const groupId = d.group_id ?? payload.group_id;
|
|
129
|
+
const groupId = env.group_id ?? d.group_id ?? payload.group_id;
|
|
126
130
|
if (groupId)
|
|
127
131
|
topContext.group_id = groupId;
|
|
128
132
|
// task_id / chatmode(message.send / thought.put / status 都可能有)
|
|
@@ -175,6 +179,7 @@ export class AUNChannel {
|
|
|
175
179
|
* - 数字群号:{group_no}.{issuer}(如 11117.agentid.pub)
|
|
176
180
|
* - 兼容旧格式:grp_xxx、g-xxx.agentid.pub
|
|
177
181
|
*/
|
|
182
|
+
/** 判断 channelId 是否群组 ID(public:plugin adapter 闭包需调用) */
|
|
178
183
|
isGroupId(id) {
|
|
179
184
|
return (id.startsWith('group.') && id.includes('/'))
|
|
180
185
|
|| /^\d+\./.test(id)
|
|
@@ -215,23 +220,19 @@ export class AUNChannel {
|
|
|
215
220
|
if (cardInfo) {
|
|
216
221
|
const actionValue = typeof obj.value === 'string' ? obj.value
|
|
217
222
|
: typeof obj.action_value === 'string' ? obj.action_value : text;
|
|
223
|
+
// 卡片点击者身份:只信认证信封(senderAid 参数,由调用方从 msg.from / msg.sender_aid 提取)。
|
|
224
|
+
// payload 自报字段(from / sender_aid / user_id)不可信,可被客户端伪造,不读取。
|
|
225
|
+
// 两类卡片共用:CommandCard → 伪入站消息的 peerId,ActionInteraction → operatorId。
|
|
226
|
+
const cardClickerAid = senderAid || channelId || '';
|
|
218
227
|
if (cardInfo.isCommandCard) {
|
|
219
228
|
// CommandCard:action_value 是完整 slash 命令,构造伪入站消息
|
|
220
229
|
this.cardMessageIdMap.delete(cardMsgId);
|
|
221
230
|
if (this.messageHandler && actionValue.startsWith('/')) {
|
|
222
231
|
const chatType = channelId ? (this.isGroupId(channelId) ? 'group' : 'private') : 'private';
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|| (typeof obj.user_id === 'string' && obj.user_id)
|
|
228
|
-
|| senderAid
|
|
229
|
-
|| cardInfo.initiatorAid
|
|
230
|
-
|| channelId || '';
|
|
231
|
-
// Initiator 校验:群聊中仅卡片发起者可操作(与飞书行为对齐)
|
|
232
|
-
if (cardInfo.initiatorAid && cardClickerAid
|
|
233
|
-
&& cardClickerAid !== cardInfo.initiatorAid
|
|
234
|
-
&& !this.isGroupId(cardClickerAid)) {
|
|
232
|
+
// Initiator 校验:仅群聊需要(私聊信道一对一,点击者恒为对端 = initiator)。
|
|
233
|
+
// 身份只信认证信封提取的 cardClickerAid,非 payload 自报。
|
|
234
|
+
if (chatType === 'group' && cardInfo.initiatorAid && cardClickerAid
|
|
235
|
+
&& cardClickerAid !== cardInfo.initiatorAid) {
|
|
235
236
|
logger.info(`${this.logPrefix()} CommandCard rejected: clicker=${cardClickerAid} initiator=${cardInfo.initiatorAid} mid=${cardMsgId}`);
|
|
236
237
|
return '';
|
|
237
238
|
}
|
|
@@ -256,6 +257,7 @@ export class AUNChannel {
|
|
|
256
257
|
id: cardInfo.requestId,
|
|
257
258
|
action: actionValue,
|
|
258
259
|
values: { text, action_label: obj.label ?? obj.action_label, behavior: obj.behavior },
|
|
260
|
+
operatorId: cardClickerAid || undefined,
|
|
259
261
|
});
|
|
260
262
|
}
|
|
261
263
|
}
|
|
@@ -477,6 +479,8 @@ export class AUNChannel {
|
|
|
477
479
|
static MENU_REQUEST_TYPES = new Set([
|
|
478
480
|
'menu.list', 'menu.query', 'menu.options', 'menu.update', 'menu.action',
|
|
479
481
|
]);
|
|
482
|
+
/** 观察者插话请求类型(owner → agent.AID)。详见 docs/observer-insert-design.md。 */
|
|
483
|
+
static INJECT_REQUEST_TYPE = 'observer.inject';
|
|
480
484
|
// Reconnect state
|
|
481
485
|
// SDK 自己跑无限指数退避(1s → 5min);TS 层只在 SDK 够不到的两类场景下接管:
|
|
482
486
|
// 1. flap:短命 connected 反复出现(SDK 不记忆跨轮 base delay,会从 1s 重新开始)
|
|
@@ -627,8 +631,9 @@ export class AUNChannel {
|
|
|
627
631
|
if (!this.config.pureIdentity) {
|
|
628
632
|
client.on('group.message_created', (data) => {
|
|
629
633
|
this.trace('IN', 'group.message_created', data);
|
|
630
|
-
const
|
|
631
|
-
const
|
|
634
|
+
const env = (data && typeof data === 'object') ? data.envelope ?? {} : {};
|
|
635
|
+
const gid = env.group_id ?? '';
|
|
636
|
+
const sender = env.from ?? '';
|
|
632
637
|
logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
633
638
|
this.handleIncomingGroupMessage(data);
|
|
634
639
|
});
|
|
@@ -659,14 +664,34 @@ export class AUNChannel {
|
|
|
659
664
|
client.on('message.undecryptable', (data) => {
|
|
660
665
|
this.trace('IN', 'message.undecryptable', data);
|
|
661
666
|
const d = data;
|
|
662
|
-
|
|
667
|
+
const env = (d.envelope && typeof d.envelope === 'object') ? d.envelope : {};
|
|
668
|
+
logger.warn(`${this.logPrefix()} Message undecryptable: from=${env.from} mid=${d.message_id} err=${d._decrypt_error}`);
|
|
663
669
|
});
|
|
664
670
|
// pureIdentity(控制 AID):不注册 group 解密失败监听
|
|
665
671
|
if (!this.config.pureIdentity) {
|
|
666
672
|
client.on('group.message_undecryptable', (data) => {
|
|
667
673
|
this.trace('IN', 'group.message_undecryptable', data);
|
|
668
674
|
const d = data;
|
|
669
|
-
|
|
675
|
+
const env = (d.envelope && typeof d.envelope === 'object') ? d.envelope : {};
|
|
676
|
+
logger.warn(`${this.logPrefix()} Group message undecryptable: group=${env.group_id} from=${env.from} mid=${d.message_id} err=${d._decrypt_error}`);
|
|
677
|
+
});
|
|
678
|
+
// 群消息撤回(SDK 0.4.10 在线 push 通道):与私聊 message.recalled 同构,
|
|
679
|
+
// 逐个 message_id 交给 recallHandler → msgBridge.cancel(排队中删除 / 处理中中断)。
|
|
680
|
+
client.on('group.message_recalled', (data) => {
|
|
681
|
+
this.trace('IN', 'group.message_recalled', data);
|
|
682
|
+
if (data && typeof data === 'object') {
|
|
683
|
+
const d = data;
|
|
684
|
+
const env = (d.envelope && typeof d.envelope === 'object') ? d.envelope : {};
|
|
685
|
+
const ids = d.message_ids;
|
|
686
|
+
if (Array.isArray(ids)) {
|
|
687
|
+
for (const id of ids) {
|
|
688
|
+
if (typeof id === 'string') {
|
|
689
|
+
logger.info(`${this.logPrefix()} Group message recalled: group=${env.group_id ?? ''} mid=${id}`);
|
|
690
|
+
this.recallHandler?.(id);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
670
695
|
});
|
|
671
696
|
}
|
|
672
697
|
// Authenticate(拿权威 gateway 用于日志/状态;connect 内部也会复用 token)
|
|
@@ -1004,15 +1029,23 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1004
1029
|
if (!data || typeof data !== 'object')
|
|
1005
1030
|
return;
|
|
1006
1031
|
const msg = data;
|
|
1007
|
-
|
|
1032
|
+
// SDK 0.5.* 移除了顶层 from/to/group_id/encrypted 等别名,统一从 msg.envelope.* 读取。
|
|
1033
|
+
// message_id / seq / payload / same_* 等仍是顶层独立字段,不在 envelope 内。
|
|
1034
|
+
const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
|
|
1035
|
+
const fromAid = env.from ?? '';
|
|
1008
1036
|
const payload = msg.payload ?? '';
|
|
1009
|
-
const text = this.extractTextPayload(payload, fromAid);
|
|
1037
|
+
const text = this.extractTextPayload(payload, fromAid, fromAid);
|
|
1010
1038
|
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
1011
1039
|
const messageId = msg.message_id ?? '';
|
|
1012
1040
|
const seq = msg.seq;
|
|
1013
1041
|
// Observer forward (inbound):在所有过滤之前转发原始明文 payload。
|
|
1014
1042
|
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
1015
|
-
|
|
1043
|
+
// 显式排除 observer.inject:它是 owner 对本 agent 的控制消息,不应镜像给观察者
|
|
1044
|
+
// (即便日后 from-owner 排除规则调整,也不会泄漏)。
|
|
1045
|
+
const inboundType = (payload && typeof payload === 'object') ? payload.type : undefined;
|
|
1046
|
+
if (inboundType !== AUNChannel.INJECT_REQUEST_TYPE) {
|
|
1047
|
+
this.forwardInbound(msg);
|
|
1048
|
+
}
|
|
1016
1049
|
// 回声过滤:自己发出的消息会被 gateway fanout 回来,
|
|
1017
1050
|
// 只有 from_aid == self 且 chat_id 不匹配时才丢弃(说明是其它实例发的)
|
|
1018
1051
|
const msgChatId = typeof payload === 'object' && payload !== null && payload.chat_id;
|
|
@@ -1022,7 +1055,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1022
1055
|
return;
|
|
1023
1056
|
}
|
|
1024
1057
|
// 记录入站消息加密状态,透传到出站 ReplyContext
|
|
1025
|
-
const msgEncrypted = !!
|
|
1058
|
+
const msgEncrypted = !!env.encrypted;
|
|
1026
1059
|
if (!msgEncrypted)
|
|
1027
1060
|
this.plaintextRecv++;
|
|
1028
1061
|
// Detect @mentions
|
|
@@ -1058,6 +1091,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1058
1091
|
});
|
|
1059
1092
|
return;
|
|
1060
1093
|
}
|
|
1094
|
+
// observer.inject:owner 插话。鉴权 from∈owners 后,以 target.channel_id 选 agent↔对端 会话,
|
|
1095
|
+
// observer 插话(v0.3):只落盘到 pending-hints,不进 Agent、不回 owner。详见 docs/observer-insert-design.md。
|
|
1096
|
+
if (p2pPayloadType === AUNChannel.INJECT_REQUEST_TYPE) {
|
|
1097
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
1098
|
+
this.handleObserverInject(fromAid, payload, displayName, peerIdentity.type);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1061
1101
|
// payload 类型白名单:信号类消息(status / event / thought 等)不进 Agent
|
|
1062
1102
|
if (p2pPayloadType && !AUNChannel.PROACTIVE_ALLOW_TYPES.has(p2pPayloadType)) {
|
|
1063
1103
|
this.acknowledgeImmediately(messageId, seq);
|
|
@@ -1094,8 +1134,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1094
1134
|
if (!data || typeof data !== 'object')
|
|
1095
1135
|
return;
|
|
1096
1136
|
const msg = data;
|
|
1097
|
-
|
|
1098
|
-
|
|
1137
|
+
// SDK 0.5.* 移除了顶层 from/sender_aid/group_id/encrypted 等别名,统一从 msg.envelope.* 读取。
|
|
1138
|
+
// message_id / seq / payload / same_* / dispatch_mode 等仍是顶层独立字段,不在 envelope 内。
|
|
1139
|
+
const env = (msg.envelope && typeof msg.envelope === 'object') ? msg.envelope : {};
|
|
1140
|
+
const groupId = env.group_id ?? '';
|
|
1141
|
+
const senderAid = env.from ?? '';
|
|
1099
1142
|
const payload = msg.payload ?? '';
|
|
1100
1143
|
const text = this.extractTextPayload(payload, groupId, senderAid);
|
|
1101
1144
|
const threadId = typeof payload === 'object' && payload !== null ? payload.thread_id : undefined;
|
|
@@ -1103,7 +1146,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1103
1146
|
const seq = msg.seq;
|
|
1104
1147
|
// Observer forward (inbound):群聊消息在所有过滤之前转发原始明文 payload。
|
|
1105
1148
|
// forwardInbound 内部排除 self-echo 与 from-owner。
|
|
1106
|
-
this.forwardInbound(
|
|
1149
|
+
this.forwardInbound(msg);
|
|
1107
1150
|
// Extract structured mentions from payload (e.g. payload.mentions: ["evolai.agentid.pub"])
|
|
1108
1151
|
const payloadMentions = Array.isArray(payload?.mentions)
|
|
1109
1152
|
? payload.mentions.filter((m) => typeof m === 'string')
|
|
@@ -1125,7 +1168,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1125
1168
|
const hasEvolClawTrace = /\[EvolClaw\.(receive|reply|agent)\]/.test(text);
|
|
1126
1169
|
if (/echo/i.test(firstLineFast) && firstLineFast.trim().length <= 10 && !hasEvolClawTrace) {
|
|
1127
1170
|
this.acknowledgeImmediately(messageId, seq);
|
|
1128
|
-
const msgEncryptedFast = !!
|
|
1171
|
+
const msgEncryptedFast = !!env.encrypted;
|
|
1129
1172
|
const msgChatmodeFast = (payload && typeof payload === 'object') ? payload.chatmode : undefined;
|
|
1130
1173
|
const peerInfo = this.peerInfoCached(senderAid);
|
|
1131
1174
|
const shortAid = this.getShortAid(senderAid);
|
|
@@ -1176,7 +1219,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1176
1219
|
}
|
|
1177
1220
|
}
|
|
1178
1221
|
// 记录入站消息加密状态,透传到出站 ReplyContext
|
|
1179
|
-
const msgEncrypted = !!
|
|
1222
|
+
const msgEncrypted = !!env.encrypted;
|
|
1180
1223
|
if (!msgEncrypted)
|
|
1181
1224
|
this.plaintextRecv++;
|
|
1182
1225
|
// dispatch_mode: 本地设置优先,fallback 到服务器参数
|
|
@@ -1287,6 +1330,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1287
1330
|
mentions,
|
|
1288
1331
|
mentionAids: renderMentionAids.length > 0 ? renderMentionAids : undefined,
|
|
1289
1332
|
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode),
|
|
1333
|
+
dispatchMode,
|
|
1290
1334
|
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
1291
1335
|
});
|
|
1292
1336
|
}
|
|
@@ -1358,6 +1402,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1358
1402
|
mentions: mentionObjects,
|
|
1359
1403
|
mentionAids: event.mentionAids,
|
|
1360
1404
|
replyContext,
|
|
1405
|
+
source: event.source,
|
|
1406
|
+
dispatchMode: event.dispatchMode,
|
|
1361
1407
|
images: event.images,
|
|
1362
1408
|
}).catch(err => {
|
|
1363
1409
|
logger.error(`${this.logPrefix()} Message handler error:`, err);
|
|
@@ -1384,57 +1430,161 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1384
1430
|
* 若消息发送方本身是 owner,则不转发给该 owner,但仍转发给其他 owner。
|
|
1385
1431
|
* 调用点须在所有过滤逻辑之前,payload 为 SDK 解密后的明文。
|
|
1386
1432
|
*/
|
|
1387
|
-
|
|
1433
|
+
/**
|
|
1434
|
+
* 入站转发:把对端发来的消息原样转发给 owner。
|
|
1435
|
+
* data 为 SDK message.received / group.message_created 回调的整个对象,
|
|
1436
|
+
* 不拆解、不重组——SDK 信封结构变化不影响此处。
|
|
1437
|
+
*/
|
|
1438
|
+
forwardInbound(data) {
|
|
1388
1439
|
if (!this.connected || !this.client)
|
|
1389
1440
|
return;
|
|
1390
1441
|
const { observable, owners } = this.getObserverConfig();
|
|
1391
1442
|
if (!observable || owners.length === 0)
|
|
1392
1443
|
return;
|
|
1444
|
+
const env = (data?.envelope && typeof data.envelope === 'object') ? data.envelope : {};
|
|
1445
|
+
const from = env.from ?? '';
|
|
1393
1446
|
if (this._aid && from === this._aid)
|
|
1394
1447
|
return; // self-echo:已在出站转过
|
|
1395
1448
|
// 排除来源 owner(不把"owner A 发来的"再转回 A),但仍转给其他 owner。
|
|
1396
1449
|
const recipientOwners = owners.filter(o => o !== from);
|
|
1397
1450
|
if (recipientOwners.length === 0)
|
|
1398
1451
|
return;
|
|
1399
|
-
this.emitForward('inbound',
|
|
1452
|
+
this.emitForward('inbound', data, recipientOwners);
|
|
1400
1453
|
}
|
|
1401
1454
|
/**
|
|
1402
|
-
* 出站转发:Agent 经 AUN
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1455
|
+
* 出站转发:Agent 经 AUN 真实发出的消息原样转发给 owner。
|
|
1456
|
+
* result 为 SDK message.send / group.send 的 SendResult(已 attach envelope + payload)。
|
|
1457
|
+
* 若对端本身是 owner,排除该 owner(不把"回复 A"转发给 A 自己)。
|
|
1405
1458
|
*/
|
|
1406
|
-
forwardOutbound(
|
|
1459
|
+
forwardOutbound(result) {
|
|
1407
1460
|
if (!this.connected || !this.client)
|
|
1408
1461
|
return;
|
|
1409
1462
|
const { observable, owners } = this.getObserverConfig();
|
|
1410
1463
|
if (!observable || owners.length === 0)
|
|
1411
1464
|
return;
|
|
1412
|
-
|
|
1465
|
+
const env = (result?.envelope && typeof result.envelope === 'object') ? result.envelope : {};
|
|
1466
|
+
const to = env.to ?? env.group_id ?? '';
|
|
1467
|
+
// 过滤:若对端本身是 owner,不转发给该 owner(避免"回复你"转给你自己);
|
|
1413
1468
|
// 但仍转发给其他 owner。
|
|
1414
1469
|
const recipientOwners = owners.filter(o => o !== to);
|
|
1415
1470
|
if (recipientOwners.length === 0)
|
|
1416
1471
|
return;
|
|
1417
|
-
this.emitForward('outbound',
|
|
1472
|
+
this.emitForward('outbound', result, recipientOwners);
|
|
1418
1473
|
}
|
|
1419
|
-
/**
|
|
1474
|
+
/**
|
|
1475
|
+
* 实际投递 observer.forward 给每个 owner,外层一律明文。
|
|
1476
|
+
* original 为 SDK 给到的原始对象(入站回调 data / 出站 SendResult),整体透传,
|
|
1477
|
+
* 不挑字段、不改字段——SDK 加任何字段都会自动一并转发给 owner。
|
|
1478
|
+
*/
|
|
1420
1479
|
emitForward(direction, original, owners) {
|
|
1421
1480
|
const forwardPayload = {
|
|
1422
1481
|
type: 'observer.forward',
|
|
1423
1482
|
direction,
|
|
1424
1483
|
agent_aid: this.config.aid,
|
|
1425
|
-
original
|
|
1426
|
-
from: original.from,
|
|
1427
|
-
to: original.to,
|
|
1428
|
-
...(original.seq != null ? { seq: original.seq } : {}),
|
|
1429
|
-
timestamp: Date.now(),
|
|
1430
|
-
payload: original.payload,
|
|
1431
|
-
},
|
|
1484
|
+
original,
|
|
1432
1485
|
};
|
|
1433
1486
|
for (const ownerAid of owners) {
|
|
1434
1487
|
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt: false })
|
|
1435
1488
|
.catch(e => logger.debug(`${this.logPrefix()} observer.forward to ${ownerAid} failed: ${e}`));
|
|
1436
1489
|
}
|
|
1437
1490
|
}
|
|
1491
|
+
// ── 观察者插话(Observer Insert,v0.3 待用上下文提示) ──────────────
|
|
1492
|
+
//
|
|
1493
|
+
// owner 经 message.send 给 agent 自身 AID 发 observer.inject(payload 为对象)。
|
|
1494
|
+
// 鉴权 from∈owners 后,把提示【只落盘】到 agent↔对端 会话的 pending-hints.jsonl
|
|
1495
|
+
// (不 dispatch、不跑 LLM、不回 owner);下一条对端消息到达时由 message-processor
|
|
1496
|
+
// 回放消费、注入渲染层。action=add 加提示 / remove 撤销。
|
|
1497
|
+
// 详见 docs/observer-insert-design.md 第一部分。
|
|
1498
|
+
/** 回 observer.inject.ack 给 owner(明文)。accepted 在成功写盘之后发出。 */
|
|
1499
|
+
emitInjectAck(ownerAid, injectId, data, error) {
|
|
1500
|
+
if (!this.connected || !this.client)
|
|
1501
|
+
return;
|
|
1502
|
+
const ackPayload = { type: 'observer.inject.ack' };
|
|
1503
|
+
if (injectId)
|
|
1504
|
+
ackPayload.id = injectId;
|
|
1505
|
+
if (data)
|
|
1506
|
+
ackPayload.data = data;
|
|
1507
|
+
if (error)
|
|
1508
|
+
ackPayload.error = error;
|
|
1509
|
+
this.callAndTrace('message.send', { to: ownerAid, payload: ackPayload, encrypt: false })
|
|
1510
|
+
.catch(e => logger.debug(`${this.logPrefix()} observer.inject.ack to ${ownerAid} failed: ${e}`));
|
|
1511
|
+
}
|
|
1512
|
+
/** 处理 observer.inject:鉴权 + 校验 + 只落盘到 pending-hints(不触发处理、不回 owner)。 */
|
|
1513
|
+
handleObserverInject(fromAid, payload, displayName, peerType) {
|
|
1514
|
+
void peerType;
|
|
1515
|
+
const { owners } = this.getObserverConfig();
|
|
1516
|
+
const ts = Date.now();
|
|
1517
|
+
const req = parseInjectRequest(payload, fromAid, owners, ts);
|
|
1518
|
+
if (req.kind === 'reject') {
|
|
1519
|
+
logger.warn(`${this.logPrefix()} observer.inject rejected: ${this.getShortAid(fromAid)} ${req.code}`);
|
|
1520
|
+
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.action }, { code: req.code, message: req.message });
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
const selfAID = this.config.aid;
|
|
1524
|
+
const sessionsDir = resolvePaths().sessionsDir;
|
|
1525
|
+
let ok;
|
|
1526
|
+
if (req.kind === 'remove') {
|
|
1527
|
+
ok = appendHintRemove(sessionsDir, 'aun', req.channelId, selfAID, { targetId: req.targetId, threadId: req.threadId, ts });
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
ok = appendHintAdd(sessionsDir, 'aun', req.channelId, selfAID, { id: req.id, text: req.text, threadId: req.threadId, ownerAid: req.ownerAid, ts });
|
|
1531
|
+
}
|
|
1532
|
+
if (!ok) {
|
|
1533
|
+
this.emitInjectAck(fromAid, req.injectId, { status: 'rejected', action: req.kind }, { code: 'STORE_FAILED', message: '提示落盘失败' });
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
logger.info(`${this.logPrefix()} observer.inject ${req.kind} stored: from=${this.getShortAid(fromAid)}(${displayName}) target=${req.channelId} chatType=${req.chatType} thread=${req.threadId ?? 'main'}${req.kind === 'add' ? ` textLen=${req.text.length}` : `${req.targetId ? ` targetId=${req.targetId}` : ' (clear-all)'}`}`);
|
|
1537
|
+
this.emitInjectAck(fromAid, req.injectId, { status: 'accepted', action: req.kind });
|
|
1538
|
+
// 记录到 watch(被观察的 agent↔对端 会话),带 owner-inject 标记区分对端真实消息。
|
|
1539
|
+
// v0.3:只记"提示已添加/已撤销",不触发处理、不产生 agent→owner 回应。
|
|
1540
|
+
const watchText = req.kind === 'remove' ? `[撤销提示]${req.targetId ? ` id=${req.targetId}` : '(全部)'}` : req.text;
|
|
1541
|
+
const synthId = `inject-${req.injectId || ts}`;
|
|
1542
|
+
this.recordInjectWatch('in', fromAid, req.channelId, req.chatType, synthId, watchText);
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* 把 observer 插话 / 对插话的回应记录到 watch(被观察的 agent↔对端 会话),
|
|
1546
|
+
* 带 source='owner-inject' 标记,与对端真实消息区分。
|
|
1547
|
+
* 写三处:messages.jsonl(watch msg)、appendAidEvent(watch aid 事件流)、aidStatsCollector(统计)。
|
|
1548
|
+
* @param dir 'in'=owner→agent 插话;'out'=agent→owner 对插话的回应
|
|
1549
|
+
* @param peerChannelId 被观察会话的对端(agent↔对端),日志落点 = sessions/aun/<self>/<peerChannelId>/
|
|
1550
|
+
*/
|
|
1551
|
+
recordInjectWatch(dir, ownerAid, peerChannelId, chatType, msgId, text) {
|
|
1552
|
+
try {
|
|
1553
|
+
const selfAID = this.config.aid;
|
|
1554
|
+
const isGroup = chatType === 'group';
|
|
1555
|
+
const chatDir = chatDirPath(resolvePaths().sessionsDir, 'aun', peerChannelId, selfAID);
|
|
1556
|
+
const entry = dir === 'in'
|
|
1557
|
+
? buildInboundEntry({
|
|
1558
|
+
from: ownerAid, to: selfAID, chatType,
|
|
1559
|
+
groupId: isGroup ? peerChannelId : null, msgId, content: text,
|
|
1560
|
+
permMode: 'owner', source: 'owner-inject',
|
|
1561
|
+
})
|
|
1562
|
+
: buildOutboundEntry({
|
|
1563
|
+
from: selfAID, to: ownerAid, chatType,
|
|
1564
|
+
groupId: isGroup ? peerChannelId : null, msgId, content: text,
|
|
1565
|
+
source: 'owner-inject',
|
|
1566
|
+
});
|
|
1567
|
+
appendMessageLog(chatDir, entry);
|
|
1568
|
+
}
|
|
1569
|
+
catch (e) {
|
|
1570
|
+
logger.debug(`${this.logPrefix()} recordInjectWatch(msg) failed: ${e}`);
|
|
1571
|
+
}
|
|
1572
|
+
// watch aid:事件流 + 统计(标 inject,便于过滤)
|
|
1573
|
+
try {
|
|
1574
|
+
const len = Buffer.byteLength(text, 'utf-8');
|
|
1575
|
+
if (dir === 'in') {
|
|
1576
|
+
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_in', aid: this.config.aid, from: ownerAid, msgId, kind: 'text', len, inject: true });
|
|
1577
|
+
this.aidStatsCollector?.recordInbound(this.config.aid, ownerAid, len, text, false, false, 'inject');
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: ownerAid, msgId, kind: 'text', len, inject: true });
|
|
1581
|
+
this.aidStatsCollector?.recordOutbound(this.config.aid, ownerAid, len, text, false, false, 'inject');
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
catch (e) {
|
|
1585
|
+
logger.debug(`${this.logPrefix()} recordInjectWatch(aid) failed: ${e}`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1438
1588
|
handleEcho(event) {
|
|
1439
1589
|
const ts = () => {
|
|
1440
1590
|
const d = new Date();
|
|
@@ -1910,8 +2060,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1910
2060
|
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 });
|
|
1911
2061
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1912
2062
|
this.appendOutboundJsonl(channelId, finalText, mid, encrypt, context, true, 'text', source);
|
|
1913
|
-
// Observer forward: outbound (group) —
|
|
1914
|
-
this.forwardOutbound(
|
|
2063
|
+
// Observer forward: outbound (group) — 原样转发 SDK SendResult(含 envelope + payload)
|
|
2064
|
+
this.forwardOutbound(result);
|
|
1915
2065
|
}
|
|
1916
2066
|
}
|
|
1917
2067
|
else {
|
|
@@ -1925,8 +2075,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1925
2075
|
appendAidEvent({ ts: Date.now(), iso: new Date().toISOString(), event: 'message_out', aid: this.config.aid, to: targetAid, msgId: result.message_id, kind: 'text', len: finalText.length });
|
|
1926
2076
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1927
2077
|
this.appendOutboundJsonl(targetAid, finalText, result.message_id, encrypt, context, false, 'text', source);
|
|
1928
|
-
// Observer forward: outbound (private) —
|
|
1929
|
-
this.forwardOutbound(
|
|
2078
|
+
// Observer forward: outbound (private) — 原样转发 SDK SendResult(含 envelope + payload)
|
|
2079
|
+
this.forwardOutbound(result);
|
|
1930
2080
|
}
|
|
1931
2081
|
}
|
|
1932
2082
|
return true;
|
|
@@ -1944,7 +2094,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1944
2094
|
if (!result || !result.message_id) {
|
|
1945
2095
|
logger.warn(`${this.logPrefix()} group.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1946
2096
|
}
|
|
1947
|
-
this.forwardOutbound(
|
|
2097
|
+
this.forwardOutbound(result);
|
|
1948
2098
|
}
|
|
1949
2099
|
else {
|
|
1950
2100
|
this.trace('OUT', 'message.send.fallback', params);
|
|
@@ -1953,7 +2103,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1953
2103
|
if (!result || !result.message_id) {
|
|
1954
2104
|
logger.warn(`${this.logPrefix()} message.send fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
1955
2105
|
}
|
|
1956
|
-
this.forwardOutbound(
|
|
2106
|
+
this.forwardOutbound(result);
|
|
1957
2107
|
}
|
|
1958
2108
|
return true;
|
|
1959
2109
|
}
|
|
@@ -2049,7 +2199,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2049
2199
|
const tid = putRes?.thought_id;
|
|
2050
2200
|
logger.info(`${this.logPrefix()} thought.put ok group=${targetId} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
2051
2201
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
2052
|
-
this.forwardOutbound(
|
|
2202
|
+
this.forwardOutbound(putRes);
|
|
2053
2203
|
if (thoughtText) {
|
|
2054
2204
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive');
|
|
2055
2205
|
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, true, 'thought', 'daemon');
|
|
@@ -2061,7 +2211,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2061
2211
|
const tid = putRes?.thought_id;
|
|
2062
2212
|
logger.info(`${this.logPrefix()} thought.put ok p2p=${this.peerLabel(targetId)} task=${taskId} stage=${stage} encrypt=${encrypt} tid=${tid ?? '?'}`);
|
|
2063
2213
|
this.eventBus?.publish?.({ type: 'message:thought-put', agentName: this.config.aid, channelId, taskId, text: thoughtText });
|
|
2064
|
-
this.forwardOutbound(
|
|
2214
|
+
this.forwardOutbound(putRes);
|
|
2065
2215
|
if (thoughtText) {
|
|
2066
2216
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetId, Buffer.byteLength(thoughtText, 'utf-8'), thoughtText, false, encrypt, context?.metadata?.chatmode ?? 'proactive');
|
|
2067
2217
|
this.appendOutboundJsonl(channelId, thoughtText, tid ?? `thought-${Date.now()}`, encrypt, context, false, 'thought', 'daemon');
|
|
@@ -2098,14 +2248,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2098
2248
|
const result = await this.callAndTrace('group.send', params);
|
|
2099
2249
|
const mid = result?.message?.message_id ?? result?.message_id ?? null;
|
|
2100
2250
|
logger.info(`${this.logPrefix()} group.send (${payload.type}) ok: group=${channelId} mid=${mid} encrypt=${encrypt}`);
|
|
2101
|
-
this.forwardOutbound(
|
|
2251
|
+
this.forwardOutbound(result);
|
|
2102
2252
|
return mid;
|
|
2103
2253
|
}
|
|
2104
2254
|
else {
|
|
2105
2255
|
params.to = targetAid;
|
|
2106
2256
|
const result = await this.callAndTrace('message.send', params);
|
|
2107
2257
|
logger.info(`${this.logPrefix()} message.send (${payload.type}) ok: to=${this.peerLabel(targetAid)} mid=${result?.message_id} encrypt=${encrypt}`);
|
|
2108
|
-
this.forwardOutbound(
|
|
2258
|
+
this.forwardOutbound(result);
|
|
2109
2259
|
return result?.message_id ?? null;
|
|
2110
2260
|
}
|
|
2111
2261
|
}
|
|
@@ -2222,11 +2372,13 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2222
2372
|
? !!(context.metadata.encrypted)
|
|
2223
2373
|
: this.shouldEncrypt(encryptTarget);
|
|
2224
2374
|
const params = { payload: filePayload, encrypt };
|
|
2375
|
+
let sendResult = null;
|
|
2225
2376
|
try {
|
|
2226
2377
|
if (isGroup) {
|
|
2227
2378
|
params.group_id = channelId;
|
|
2228
2379
|
this.trace('OUT', 'group.send.file', params);
|
|
2229
2380
|
const result = await this.client.call('group.send', params);
|
|
2381
|
+
sendResult = result;
|
|
2230
2382
|
const fileMid = result?.message?.message_id ?? result?.message_id;
|
|
2231
2383
|
this.trace('OUT', 'group.send.file.ok', { message_id: fileMid });
|
|
2232
2384
|
if (!fileMid) {
|
|
@@ -2237,6 +2389,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2237
2389
|
params.to = fileTargetAid;
|
|
2238
2390
|
this.trace('OUT', 'message.send.file', params);
|
|
2239
2391
|
const result = await this.client.call('message.send', params);
|
|
2392
|
+
sendResult = result;
|
|
2240
2393
|
this.trace('OUT', 'message.send.file.ok', { message_id: result?.message_id });
|
|
2241
2394
|
if (!result || !result.message_id) {
|
|
2242
2395
|
logger.warn(`${this.logPrefix()} message.send.file returned no message_id: ${JSON.stringify(result)}`);
|
|
@@ -2255,6 +2408,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2255
2408
|
if (isGroup) {
|
|
2256
2409
|
this.trace('OUT', 'group.send.file.fallback', params);
|
|
2257
2410
|
const result = await this.client.call('group.send', params);
|
|
2411
|
+
sendResult = result;
|
|
2258
2412
|
const fbMid = result?.message?.message_id ?? result?.message_id;
|
|
2259
2413
|
this.trace('OUT', 'group.send.file.fallback.ok', { message_id: fbMid });
|
|
2260
2414
|
if (!fbMid) {
|
|
@@ -2264,6 +2418,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2264
2418
|
else {
|
|
2265
2419
|
this.trace('OUT', 'message.send.file.fallback', params);
|
|
2266
2420
|
const result = await this.client.call('message.send', params);
|
|
2421
|
+
sendResult = result;
|
|
2267
2422
|
this.trace('OUT', 'message.send.file.fallback.ok', { message_id: result?.message_id });
|
|
2268
2423
|
if (!result || !result.message_id) {
|
|
2269
2424
|
logger.warn(`${this.logPrefix()} message.send.file fallback returned no message_id: ${JSON.stringify(result)}`);
|
|
@@ -2275,7 +2430,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2275
2430
|
}
|
|
2276
2431
|
}
|
|
2277
2432
|
logger.info(`${this.logPrefix()} File sent: ${filename} (${formatSize(stat.size)}) → ${channelId}`);
|
|
2278
|
-
|
|
2433
|
+
if (sendResult)
|
|
2434
|
+
this.forwardOutbound(sendResult);
|
|
2279
2435
|
return true;
|
|
2280
2436
|
}
|
|
2281
2437
|
catch (e) {
|
|
@@ -2365,7 +2521,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2365
2521
|
const c = this.client;
|
|
2366
2522
|
if (!c) {
|
|
2367
2523
|
logger.debug(`${this.logPrefix()} ${label} skipped: client gone`);
|
|
2368
|
-
return Promise.resolve();
|
|
2524
|
+
return Promise.resolve(null);
|
|
2369
2525
|
}
|
|
2370
2526
|
const encrypt = computeEncrypt();
|
|
2371
2527
|
const params = { payload, encrypt };
|
|
@@ -2374,24 +2530,28 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2374
2530
|
else
|
|
2375
2531
|
params.to = statusTargetAid;
|
|
2376
2532
|
this.trace('OUT', `${method}.task_${label}`, params);
|
|
2377
|
-
return c.call(method, params).catch(e => {
|
|
2533
|
+
return c.call(method, params).catch((e) => {
|
|
2378
2534
|
if (encrypt && e instanceof E2EEError) {
|
|
2379
2535
|
this.peerE2ee.set(encryptTarget, { ok: false, ts: Date.now() });
|
|
2380
2536
|
logger.warn(`${this.logPrefix()} E2EE task_${label} send failed to ${channelId}, retrying plaintext`);
|
|
2381
2537
|
const c2 = this.client;
|
|
2382
2538
|
if (!c2)
|
|
2383
|
-
return;
|
|
2539
|
+
return null;
|
|
2384
2540
|
const fallbackParams = { ...params, encrypt: false };
|
|
2385
|
-
return c2.call(method, fallbackParams).catch(e2 => {
|
|
2541
|
+
return c2.call(method, fallbackParams).catch((e2) => {
|
|
2386
2542
|
logger.debug(`${this.logPrefix()} task_${label} fallback failed: ${e2}`);
|
|
2543
|
+
return null;
|
|
2387
2544
|
});
|
|
2388
2545
|
}
|
|
2389
2546
|
logger.debug(`${this.logPrefix()} task_${label} failed: ${e}`);
|
|
2547
|
+
return null;
|
|
2390
2548
|
});
|
|
2391
2549
|
};
|
|
2392
2550
|
const method = isGroup ? 'group.send' : 'message.send';
|
|
2393
|
-
sendOne(method, statusPayload, 'status')
|
|
2394
|
-
|
|
2551
|
+
sendOne(method, statusPayload, 'status').then(result => {
|
|
2552
|
+
if (result)
|
|
2553
|
+
this.forwardOutbound(result);
|
|
2554
|
+
}).catch(() => { });
|
|
2395
2555
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, JSON.stringify(statusPayload).length, undefined, true);
|
|
2396
2556
|
// 群聊显示 group id 简称,P2P 显示 peer label;从 context.metadata 读取 chatmode
|
|
2397
2557
|
const targetLabel = this.isGroupId(channelId) ? channelId : this.peerLabel(channelId);
|
|
@@ -2619,247 +2779,200 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
2619
2779
|
// Plugin implementation
|
|
2620
2780
|
export class AUNChannelPlugin {
|
|
2621
2781
|
name = 'aun';
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2782
|
+
async createInstance(inst, ctx) {
|
|
2783
|
+
// AUN aid is the agent's own AID; loader injects it as inst.aid, ctx.agentName is the source of truth.
|
|
2784
|
+
const aid = inst.aid ?? ctx.agentName;
|
|
2785
|
+
if (inst.enabled === false || !aid)
|
|
2786
|
+
return null;
|
|
2787
|
+
const channel = new AUNChannel({
|
|
2788
|
+
aid,
|
|
2789
|
+
keystorePath: inst.keystorePath,
|
|
2790
|
+
gatewayUrl: inst.gatewayUrl,
|
|
2791
|
+
accessToken: inst.accessToken,
|
|
2792
|
+
flushDelay: inst.flushDelay,
|
|
2793
|
+
owner: inst.owner ?? inst.owners?.[0],
|
|
2794
|
+
agentName: ctx.agentName,
|
|
2795
|
+
channelName: inst.name,
|
|
2796
|
+
aunTrace: ctx.debug?.aunTrace,
|
|
2797
|
+
aunSdkLog: ctx.debug?.aunSdkLog,
|
|
2798
|
+
});
|
|
2799
|
+
const mode = resolveShowActivities(inst);
|
|
2800
|
+
const adapter = {
|
|
2801
|
+
channelName: inst.name,
|
|
2802
|
+
channelKey: inst.name, // channelName 实际上就是 channelKey
|
|
2803
|
+
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: true, status: true, thread: true },
|
|
2804
|
+
send: async (envelope, payload) => {
|
|
2805
|
+
const replyCtx = envelope.replyContext;
|
|
2806
|
+
const channelId = envelope.channelId;
|
|
2807
|
+
switch (payload.kind) {
|
|
2808
|
+
case 'result.text':
|
|
2809
|
+
case 'command.result':
|
|
2810
|
+
case 'command.error':
|
|
2811
|
+
case 'system.notice':
|
|
2812
|
+
case 'system.error':
|
|
2813
|
+
case 'result.error': {
|
|
2814
|
+
const sendCtx = { ...(replyCtx ?? {}) };
|
|
2815
|
+
if (payload.kind === 'result.text' && payload.isFinal)
|
|
2816
|
+
sendCtx.title = '✅ 最终回复:';
|
|
2817
|
+
await channel.sendMessage(channelId, payload.text, sendCtx);
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
case 'result.file':
|
|
2821
|
+
await channel.sendFile(channelId, payload.filePath, replyCtx);
|
|
2822
|
+
return;
|
|
2823
|
+
case 'result.image': {
|
|
2824
|
+
const buf = payload.data;
|
|
2825
|
+
const b64 = buf.toString('base64');
|
|
2826
|
+
await channel.sendStructured(channelId, {
|
|
2827
|
+
type: 'image', alt: payload.alt, data_base64: b64, mime_type: payload.mimeType,
|
|
2828
|
+
}, replyCtx);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
case 'activity.batch': {
|
|
2832
|
+
const aunPayload = {
|
|
2833
|
+
type: 'thought',
|
|
2834
|
+
items: payload.items,
|
|
2835
|
+
client_context: { task_id: envelope.taskId, chatmode: envelope.chatmode, agent_name: envelope.agentName },
|
|
2836
|
+
};
|
|
2837
|
+
if (replyCtx?.threadId)
|
|
2838
|
+
aunPayload.thread_id = replyCtx.threadId;
|
|
2839
|
+
if (envelope.chatmode === 'proactive') {
|
|
2840
|
+
await channel.sendThought(channelId, envelope.taskId, aunPayload, replyCtx);
|
|
2668
2841
|
}
|
|
2669
|
-
|
|
2670
|
-
await channel.
|
|
2671
|
-
return;
|
|
2672
|
-
case 'result.image': {
|
|
2673
|
-
// AUN 支持 image,走 sendStructured 发 type=image payload
|
|
2674
|
-
const buf = payload.data;
|
|
2675
|
-
const b64 = buf.toString('base64');
|
|
2676
|
-
await channel.sendStructured(channelId, {
|
|
2677
|
-
type: 'image',
|
|
2678
|
-
alt: payload.alt,
|
|
2679
|
-
data_base64: b64,
|
|
2680
|
-
mime_type: payload.mimeType,
|
|
2681
|
-
}, ctx);
|
|
2682
|
-
return;
|
|
2842
|
+
else {
|
|
2843
|
+
await channel.sendStructured(channelId, aunPayload, replyCtx);
|
|
2683
2844
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
case 'status.progress':
|
|
2848
|
+
channel.sendProcessingStatus(channelId, 'progress', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2849
|
+
return;
|
|
2850
|
+
case 'status.started':
|
|
2851
|
+
channel.sendProcessingStatus(channelId, 'start', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2852
|
+
return;
|
|
2853
|
+
case 'status.queued':
|
|
2854
|
+
channel.sendProcessingStatus(channelId, 'queued', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2855
|
+
return;
|
|
2856
|
+
case 'status.completed':
|
|
2857
|
+
channel.sendProcessingStatus(channelId, 'done', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2858
|
+
return;
|
|
2859
|
+
case 'status.interrupted':
|
|
2860
|
+
channel.sendProcessingStatus(channelId, 'interrupted', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2861
|
+
return;
|
|
2862
|
+
case 'status.error':
|
|
2863
|
+
channel.sendProcessingStatus(channelId, 'error', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2864
|
+
return;
|
|
2865
|
+
case 'status.timeout':
|
|
2866
|
+
channel.sendProcessingStatus(channelId, 'timeout', envelope.sessionId ?? envelope.taskId, envelope.taskId, replyCtx, payload.metadata);
|
|
2867
|
+
return;
|
|
2868
|
+
case 'interaction': {
|
|
2869
|
+
const req = payload.interaction;
|
|
2870
|
+
if (req.kind.kind === 'action') {
|
|
2871
|
+
const action = req.kind;
|
|
2872
|
+
const aunCard = {
|
|
2873
|
+
type: 'action_card',
|
|
2874
|
+
title: action.title,
|
|
2875
|
+
actions: action.buttons.map(btn => ({
|
|
2876
|
+
label: btn.label, value: btn.key, style: btn.style ?? 'default', behavior: 'reply',
|
|
2877
|
+
})),
|
|
2689
2878
|
};
|
|
2690
|
-
if (
|
|
2691
|
-
|
|
2692
|
-
if (
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2879
|
+
if (action.body)
|
|
2880
|
+
aunCard.description = action.body;
|
|
2881
|
+
if (req.initiatorId && channel.isGroupId(channelId))
|
|
2882
|
+
aunCard.initiator = req.initiatorId;
|
|
2883
|
+
if (replyCtx?.threadId)
|
|
2884
|
+
aunCard.thread_id = replyCtx.threadId;
|
|
2885
|
+
const msgId = await channel.sendStructured(channelId, aunCard, replyCtx);
|
|
2886
|
+
if (msgId) {
|
|
2887
|
+
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: false, initiatorAid: req.initiatorId });
|
|
2888
|
+
setTimeout(() => channel.cardMessageIdMap.delete(msgId), 20 * 60 * 1000);
|
|
2698
2889
|
}
|
|
2699
|
-
return;
|
|
2700
2890
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
return;
|
|
2719
|
-
case 'status.timeout':
|
|
2720
|
-
channel.sendProcessingStatus(channelId, 'timeout', envelope.sessionId ?? envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2721
|
-
return;
|
|
2722
|
-
case 'interaction': {
|
|
2723
|
-
const req = payload.interaction;
|
|
2724
|
-
if (req.kind.kind === 'action') {
|
|
2725
|
-
const action = req.kind;
|
|
2726
|
-
const aunCard = {
|
|
2727
|
-
type: 'action_card',
|
|
2728
|
-
title: action.title,
|
|
2729
|
-
actions: action.buttons.map(btn => ({
|
|
2730
|
-
label: btn.label,
|
|
2731
|
-
value: btn.key,
|
|
2732
|
-
style: btn.style ?? 'default',
|
|
2733
|
-
behavior: 'reply',
|
|
2734
|
-
})),
|
|
2735
|
-
};
|
|
2736
|
-
if (action.body)
|
|
2737
|
-
aunCard.description = action.body;
|
|
2738
|
-
if (ctx?.threadId)
|
|
2739
|
-
aunCard.thread_id = ctx.threadId;
|
|
2740
|
-
const msgId = await channel.sendStructured(channelId, aunCard, ctx);
|
|
2741
|
-
if (msgId) {
|
|
2742
|
-
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: false });
|
|
2743
|
-
setTimeout(() => channel.cardMessageIdMap.delete(msgId), 20 * 60 * 1000);
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
else if (req.kind.kind === 'command-card') {
|
|
2747
|
-
const card = req.kind;
|
|
2748
|
-
const aunCard = {
|
|
2749
|
-
type: 'action_card',
|
|
2750
|
-
title: card.title,
|
|
2751
|
-
actions: card.buttons.map(btn => ({
|
|
2752
|
-
label: btn.label,
|
|
2753
|
-
value: btn.command,
|
|
2754
|
-
style: btn.style ?? 'default',
|
|
2755
|
-
behavior: 'reply',
|
|
2756
|
-
})),
|
|
2757
|
-
};
|
|
2758
|
-
if (card.body)
|
|
2759
|
-
aunCard.description = card.body;
|
|
2760
|
-
if (ctx?.threadId)
|
|
2761
|
-
aunCard.thread_id = ctx.threadId;
|
|
2762
|
-
const msgId = await channel.sendStructured(channelId, aunCard, ctx);
|
|
2763
|
-
if (msgId) {
|
|
2764
|
-
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: true, initiatorAid: req.initiatorId });
|
|
2765
|
-
setTimeout(() => channel.cardMessageIdMap.delete(msgId), 20 * 60 * 1000);
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
else if (payload.fallbackText) {
|
|
2769
|
-
await channel.sendMessage(channelId, payload.fallbackText, ctx);
|
|
2891
|
+
else if (req.kind.kind === 'command-card') {
|
|
2892
|
+
const card = req.kind;
|
|
2893
|
+
const aunCard = {
|
|
2894
|
+
type: 'action_card',
|
|
2895
|
+
title: card.title,
|
|
2896
|
+
actions: card.buttons.map(btn => ({
|
|
2897
|
+
label: btn.label, value: btn.command, style: btn.style ?? 'default', behavior: 'reply',
|
|
2898
|
+
})),
|
|
2899
|
+
};
|
|
2900
|
+
if (card.body)
|
|
2901
|
+
aunCard.description = card.body;
|
|
2902
|
+
if (replyCtx?.threadId)
|
|
2903
|
+
aunCard.thread_id = replyCtx.threadId;
|
|
2904
|
+
const msgId = await channel.sendStructured(channelId, aunCard, replyCtx);
|
|
2905
|
+
if (msgId) {
|
|
2906
|
+
channel.cardMessageIdMap.set(msgId, { requestId: req.id, isCommandCard: true, initiatorAid: req.initiatorId });
|
|
2907
|
+
setTimeout(() => channel.cardMessageIdMap.delete(msgId), 20 * 60 * 1000);
|
|
2770
2908
|
}
|
|
2771
|
-
return;
|
|
2772
2909
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
channel.sendCustomPayload(channelId, text);
|
|
2776
|
-
return;
|
|
2910
|
+
else if (payload.fallbackText) {
|
|
2911
|
+
await channel.sendMessage(channelId, payload.fallbackText, replyCtx);
|
|
2777
2912
|
}
|
|
2778
|
-
|
|
2779
|
-
logger.warn(`[AUN] Unhandled payload kind: ${payload.kind}`);
|
|
2913
|
+
return;
|
|
2780
2914
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
};
|
|
2786
|
-
const policy = {
|
|
2787
|
-
canSwitchProject: (chatType, identity) => identity === 'owner' || identity === 'admin',
|
|
2788
|
-
canListProjects: (chatType, identity) => identity === 'owner' || identity === 'admin',
|
|
2789
|
-
canCreateSession: (chatType, identity) => true,
|
|
2790
|
-
canDeleteSession: (chatType, identity) => true,
|
|
2791
|
-
canImportCliSession: (chatType, identity) => identity === 'owner' || identity === 'admin',
|
|
2792
|
-
messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
|
|
2793
|
-
showMiddleResult: (chatType, identity) => {
|
|
2794
|
-
const mode = getChannelShowActivities(config, inst.name);
|
|
2795
|
-
if (mode === 'none')
|
|
2796
|
-
return false;
|
|
2797
|
-
if (mode === 'dm-only')
|
|
2798
|
-
return chatType === 'private';
|
|
2799
|
-
if (mode === 'owner-dm-only')
|
|
2800
|
-
return chatType === 'private' && identity === 'owner';
|
|
2801
|
-
return true;
|
|
2802
|
-
},
|
|
2803
|
-
showIdleMonitor: (chatType, identity) => {
|
|
2804
|
-
const mode = getChannelShowActivities(config, inst.name);
|
|
2805
|
-
if (mode === 'none')
|
|
2806
|
-
return false;
|
|
2807
|
-
if (mode === 'dm-only')
|
|
2808
|
-
return chatType === 'private';
|
|
2809
|
-
if (mode === 'owner-dm-only')
|
|
2810
|
-
return chatType === 'private' && identity === 'owner';
|
|
2811
|
-
return true;
|
|
2812
|
-
},
|
|
2813
|
-
accumulateErrors: (chatType, identity) => true,
|
|
2814
|
-
};
|
|
2815
|
-
const options = {
|
|
2816
|
-
flushDelay: inst.flushDelay ?? 3,
|
|
2817
|
-
fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
|
|
2818
|
-
};
|
|
2819
|
-
result.push({
|
|
2820
|
-
channelType: 'aun',
|
|
2821
|
-
adapter,
|
|
2822
|
-
channel,
|
|
2823
|
-
policy,
|
|
2824
|
-
options,
|
|
2825
|
-
connect: () => channel.connect(),
|
|
2826
|
-
disconnect: () => channel.disconnect(),
|
|
2827
|
-
onProjectPathRequest: (channelId) => Promise.resolve(config.projects?.defaultPath || process.cwd()),
|
|
2828
|
-
registerBridge(bridge, channelType) {
|
|
2829
|
-
bridge.register(adapter.channelName, (handler) => channel.onMessage(async (opts) => {
|
|
2830
|
-
handler(aunOptsToInbound(opts, adapter.channelName, channelType));
|
|
2831
|
-
}), (channelId, text, replyContext) => channel.sendMessage(channelId, text, replyContext), adapter, channelType);
|
|
2832
|
-
},
|
|
2833
|
-
registerHooks(ctx) {
|
|
2834
|
-
channel.setEventBus(ctx.eventBus);
|
|
2835
|
-
if (channel.setOnChannelDown) {
|
|
2836
|
-
channel.setOnChannelDown(() => {
|
|
2837
|
-
ctx.eventBus.publish({
|
|
2838
|
-
type: 'channel:error',
|
|
2839
|
-
channel: 'aun',
|
|
2840
|
-
channelName: adapter.channelName,
|
|
2841
|
-
status: 'auth_error',
|
|
2842
|
-
message: `⚠️ AUN 渠道 ${adapter.channelName} 断连,自动重试已用尽。\n使用 /check rty aun 手动重连`,
|
|
2843
|
-
timestamp: Date.now(),
|
|
2844
|
-
});
|
|
2845
|
-
});
|
|
2915
|
+
case 'custom': {
|
|
2916
|
+
const text = typeof payload.payload === 'string' ? payload.payload : JSON.stringify(payload.payload);
|
|
2917
|
+
channel.sendCustomPayload(channelId, text);
|
|
2918
|
+
return;
|
|
2846
2919
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2920
|
+
default:
|
|
2921
|
+
logger.warn(`[AUN] Unhandled payload kind: ${payload.kind}`);
|
|
2922
|
+
}
|
|
2923
|
+
},
|
|
2924
|
+
acknowledge: (messageId) => { channel.acknowledge(messageId); return Promise.resolve(); },
|
|
2925
|
+
onInteraction: (cb) => { channel.interactionCallback = cb; },
|
|
2926
|
+
uploadAgentMd: (content) => channel.uploadAgentMd(content),
|
|
2927
|
+
downloadAgentMd: (aid) => channel.downloadAgentMd(aid),
|
|
2928
|
+
getGroupName: (groupId) => channel.getGroupName(groupId),
|
|
2929
|
+
_selfAid: () => channel.getStatus().aid,
|
|
2930
|
+
_selfName: () => channel.getSelfName(),
|
|
2931
|
+
};
|
|
2932
|
+
const policy = {
|
|
2933
|
+
canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
|
|
2934
|
+
canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
|
|
2935
|
+
canCreateSession: () => true,
|
|
2936
|
+
canDeleteSession: () => true,
|
|
2937
|
+
canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
|
|
2938
|
+
messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
|
|
2939
|
+
showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
|
|
2940
|
+
showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
|
|
2941
|
+
accumulateErrors: () => true,
|
|
2942
|
+
};
|
|
2943
|
+
return {
|
|
2944
|
+
channelType: 'aun', adapter, channel,
|
|
2945
|
+
policy,
|
|
2946
|
+
options: { flushDelay: inst.flushDelay ?? 3, fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g },
|
|
2947
|
+
connect: () => channel.connect(),
|
|
2948
|
+
disconnect: () => channel.disconnect(),
|
|
2949
|
+
onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
|
|
2950
|
+
registerBridge(bridge, channelType) {
|
|
2951
|
+
bridge.register(adapter.channelName, (handler) => channel.onMessage(async (opts) => {
|
|
2952
|
+
handler(aunOptsToInbound(opts, adapter.channelName, channelType));
|
|
2953
|
+
}), (channelId, text, replyContext) => channel.sendMessage(channelId, text, replyContext), adapter, channelType);
|
|
2954
|
+
},
|
|
2955
|
+
registerHooks(hookCtx) {
|
|
2956
|
+
channel.setEventBus(hookCtx.eventBus);
|
|
2957
|
+
if (channel.setOnChannelDown) {
|
|
2958
|
+
channel.setOnChannelDown(() => {
|
|
2959
|
+
hookCtx.eventBus.publish({
|
|
2960
|
+
type: 'channel:error',
|
|
2961
|
+
channel: 'aun',
|
|
2962
|
+
channelName: adapter.channelName,
|
|
2963
|
+
status: 'auth_error',
|
|
2964
|
+
message: `⚠️ AUN 渠道 ${adapter.channelName} 断连,自动重试已用尽。\n使用 /check rty aun 手动重连`,
|
|
2965
|
+
timestamp: Date.now(),
|
|
2851
2966
|
});
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
}
|
|
2863
|
-
return instances[0];
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
if (typeof channel.setDispatchModeResolver === 'function') {
|
|
2970
|
+
channel.setDispatchModeResolver(async (channelId) => {
|
|
2971
|
+
const session = await hookCtx.sessionManager.getActiveSession(adapter.channelName, channelId);
|
|
2972
|
+
return session?.metadata?.dispatchMode;
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
},
|
|
2976
|
+
};
|
|
2864
2977
|
}
|
|
2865
2978
|
}
|