evolclaw 2.2.0 → 2.4.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/README.md +49 -27
- package/data/evolclaw.sample.json +6 -3
- package/dist/agents/claude-runner.js +125 -52
- package/dist/agents/codex-runner.js +10 -5
- package/dist/agents/gemini-runner.js +425 -0
- package/dist/channels/aun.js +283 -95
- package/dist/channels/feishu.js +556 -96
- package/dist/channels/wechat.js +98 -74
- package/dist/cli.js +232 -57
- package/dist/config.js +185 -31
- package/dist/core/channel-loader.js +11 -4
- package/dist/core/command-handler.js +803 -247
- package/dist/core/interaction-router.js +68 -0
- package/dist/core/message/message-bridge.js +217 -0
- package/dist/core/{message-processor.js → message/message-processor.js} +411 -124
- package/dist/core/{message-queue.js → message/message-queue.js} +1 -1
- package/dist/{utils → core/message}/stream-debouncer.js +1 -1
- package/dist/{utils → core/message}/stream-flusher.js +73 -13
- package/dist/core/permission.js +212 -11
- package/dist/core/{adapters → session/adapters}/claude-session-file-adapter.js +2 -2
- package/dist/core/{adapters → session/adapters}/codex-session-file-adapter.js +117 -52
- package/dist/core/session/adapters/gemini-session-file-adapter.js +177 -0
- package/dist/{utils → core/session}/session-file-health.js +1 -1
- package/dist/core/{session-manager.js → session/session-manager.js} +61 -11
- package/dist/index.js +140 -57
- package/dist/{core/ipc-server.js → ipc.js} +36 -1
- package/dist/types.js +3 -0
- package/dist/utils/cross-platform.js +38 -1
- package/dist/utils/error-utils.js +130 -5
- package/dist/utils/init-channel.js +649 -0
- package/dist/utils/init.js +55 -150
- package/dist/utils/logger.js +8 -3
- package/dist/utils/media-cache.js +207 -0
- package/dist/{core → utils}/stats-collector.js +16 -0
- package/package.json +3 -3
- package/dist/core/message-bridge.js +0 -187
- package/dist/utils/init-feishu.js +0 -263
- package/dist/utils/init-wechat.js +0 -172
- package/dist/utils/ipc-client.js +0 -36
- package/dist/utils/permission-utils.js +0 -71
- /package/dist/{utils → core/message}/message-cache.js +0 -0
- /package/dist/{utils → core/message}/stream-idle-monitor.js +0 -0
- /package/dist/core/{session-file-adapter.js → session/session-file-adapter.js} +0 -0
package/dist/channels/aun.js
CHANGED
|
@@ -1,10 +1,73 @@
|
|
|
1
|
-
import { AUNClient } from '@aun
|
|
2
|
-
import
|
|
1
|
+
import { AUNClient } from '@eleans/aun-core-node';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { logger, localTimestamp } from '../utils/logger.js';
|
|
5
|
+
import { normalizeChannelInstances } from '../config.js';
|
|
6
|
+
import { resolvePaths } from '../paths.js';
|
|
3
7
|
export class AUNChannel {
|
|
4
8
|
config;
|
|
5
9
|
client = null;
|
|
6
10
|
messageHandler;
|
|
7
11
|
connected = false;
|
|
12
|
+
traceStream = null;
|
|
13
|
+
trace(dir, event, data) {
|
|
14
|
+
if (!this.traceStream)
|
|
15
|
+
return;
|
|
16
|
+
const line = JSON.stringify({ ts: localTimestamp(), dir, event, data });
|
|
17
|
+
this.traceStream.write(line + '\n');
|
|
18
|
+
}
|
|
19
|
+
/** 判断 channelId 是否为群组 ID(g-xxx.agentid.pub 或 grp_ 前缀) */
|
|
20
|
+
isGroupId(id) {
|
|
21
|
+
return id.startsWith('grp_') || (id.startsWith('g-') && id.includes('.'));
|
|
22
|
+
}
|
|
23
|
+
getShortAid(aid) {
|
|
24
|
+
if (!aid)
|
|
25
|
+
return undefined;
|
|
26
|
+
const trimmed = aid.trim();
|
|
27
|
+
if (!trimmed)
|
|
28
|
+
return undefined;
|
|
29
|
+
return trimmed.split('.')[0] || trimmed;
|
|
30
|
+
}
|
|
31
|
+
extractTextPayload(payload) {
|
|
32
|
+
if (typeof payload === 'string')
|
|
33
|
+
return payload;
|
|
34
|
+
if (payload && typeof payload === 'object') {
|
|
35
|
+
const text = payload.text;
|
|
36
|
+
if (typeof text === 'string')
|
|
37
|
+
return text;
|
|
38
|
+
return JSON.stringify(payload);
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
hasExplicitMention(text, target) {
|
|
43
|
+
const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
44
|
+
return new RegExp(`(^|\\s)@${escaped}(?=$|\\s|[.,!?;:,。!?;:]|[\\u4e00-\\u9fff])`).test(text);
|
|
45
|
+
}
|
|
46
|
+
stripTriggerMentions(text, selfAid) {
|
|
47
|
+
let result = text;
|
|
48
|
+
if (selfAid) {
|
|
49
|
+
const escapedAid = selfAid.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
50
|
+
result = result.replace(new RegExp(`(^|\\s)@${escapedAid}(?=$|\\s|[.,!?;:,。!?;:]|[\\u4e00-\\u9fff])`, 'g'), '$1');
|
|
51
|
+
}
|
|
52
|
+
result = result.replace(/(^|\s)@all(?=$|\s|[.,!?;:,。!?;:]|[\u4e00-\u9fff])/gi, '$1');
|
|
53
|
+
return result.replace(/[ \t]+/g, ' ').trim();
|
|
54
|
+
}
|
|
55
|
+
buildGroupReplyContext(taskId, senderAid) {
|
|
56
|
+
const replyContext = {};
|
|
57
|
+
if (taskId)
|
|
58
|
+
replyContext.threadId = taskId;
|
|
59
|
+
replyContext.peerId = senderAid;
|
|
60
|
+
return replyContext;
|
|
61
|
+
}
|
|
62
|
+
acknowledgeImmediately(messageId, seq) {
|
|
63
|
+
if (seq != null && this.client) {
|
|
64
|
+
this.client.call('message.ack', { seq }).catch(e => {
|
|
65
|
+
logger.debug(`[AUN] Immediate ack failed: ${e}`);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (messageId)
|
|
69
|
+
this.messageSeqMap.delete(messageId);
|
|
70
|
+
}
|
|
8
71
|
_aid;
|
|
9
72
|
seenMessages = new Map();
|
|
10
73
|
messageSeqMap = new Map(); // messageId → seq (for ack)
|
|
@@ -17,6 +80,11 @@ export class AUNChannel {
|
|
|
17
80
|
onChannelDown;
|
|
18
81
|
constructor(config) {
|
|
19
82
|
this.config = config;
|
|
83
|
+
if (config.aunTrace) {
|
|
84
|
+
const logPath = path.join(resolvePaths().logs, 'aun-trace.log');
|
|
85
|
+
this.traceStream = fs.createWriteStream(logPath, { flags: 'a' });
|
|
86
|
+
logger.info(`[AUN] Trace logging enabled: ${logPath}`);
|
|
87
|
+
}
|
|
20
88
|
}
|
|
21
89
|
async connect() {
|
|
22
90
|
this.intentionalDisconnect = false;
|
|
@@ -53,21 +121,51 @@ export class AUNChannel {
|
|
|
53
121
|
logger.info(`[AUN] Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
|
|
54
122
|
// Create client with FileSecretStore (AES-256-GCM)
|
|
55
123
|
// 不传 encryption_seed 时,SDK 自动从 {aun_path}/.seed 文件派生密钥(与 aun_cli.py 对齐)
|
|
124
|
+
const rootCaPath = `${aunPath}/CA/root/root.crt`;
|
|
56
125
|
this.client = new AUNClient({
|
|
57
126
|
aun_path: aunPath,
|
|
127
|
+
root_ca_path: rootCaPath,
|
|
58
128
|
...(encryptionSeed && { encryption_seed: encryptionSeed }),
|
|
59
129
|
});
|
|
60
130
|
// Set gateway URL (internal property, same as Python SDK)
|
|
61
131
|
this.client._gatewayUrl = gateway;
|
|
62
132
|
// Register event handlers before connecting
|
|
63
|
-
this.client.on('message.received', (data) =>
|
|
64
|
-
|
|
65
|
-
|
|
133
|
+
this.client.on('message.received', (data) => {
|
|
134
|
+
this.trace('IN', 'message.received', data);
|
|
135
|
+
const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
|
|
136
|
+
const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
|
|
137
|
+
logger.info(`[AUN][DIAG] message.received: kind=${kind} keys=${keys}`);
|
|
138
|
+
this.handleIncomingPrivateMessage(data);
|
|
139
|
+
});
|
|
140
|
+
this.client.on('group.message_created', (data) => {
|
|
141
|
+
this.trace('IN', 'group.message_created', data);
|
|
142
|
+
const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
|
|
143
|
+
const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
|
|
144
|
+
logger.info(`[AUN][DIAG] group.message_created: group_id=${gid} sender=${sender}`);
|
|
145
|
+
this.handleIncomingGroupMessage(data);
|
|
146
|
+
});
|
|
147
|
+
this.client.on('connection.state', (data) => {
|
|
148
|
+
this.trace('IN', 'connection.state', data);
|
|
149
|
+
this.handleConnectionState(data);
|
|
150
|
+
});
|
|
66
151
|
// Authenticate
|
|
152
|
+
// Workaround: SDK 0.3.x _loadIdentityOrRaise doesn't set identity.aid from requested aid,
|
|
153
|
+
// causing gateway "missing aid" error. Patch to backfill aid on loaded identity.
|
|
154
|
+
const authFlow = this.client._auth;
|
|
155
|
+
if (authFlow && typeof authFlow._loadIdentityOrRaise === 'function') {
|
|
156
|
+
const origLoad = authFlow._loadIdentityOrRaise.bind(authFlow);
|
|
157
|
+
authFlow._loadIdentityOrRaise = (aid) => {
|
|
158
|
+
const identity = origLoad(aid);
|
|
159
|
+
if (identity && !identity.aid)
|
|
160
|
+
identity.aid = aid ?? authFlow._aid;
|
|
161
|
+
return identity;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
67
164
|
let accessToken;
|
|
68
165
|
try {
|
|
69
166
|
logger.info(`[AUN] Authenticating as ${aidName}...`);
|
|
70
167
|
const auth = await this.client.auth.authenticate(aidName ? { aid: aidName } : undefined);
|
|
168
|
+
this.trace('IN', 'auth.result', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
|
|
71
169
|
accessToken = auth.access_token;
|
|
72
170
|
const resolvedGateway = auth.gateway || gateway;
|
|
73
171
|
this.client._gatewayUrl = resolvedGateway;
|
|
@@ -82,7 +180,8 @@ export class AUNChannel {
|
|
|
82
180
|
// Fallback: try direct token from env/config (legacy)
|
|
83
181
|
accessToken = this.config.accessToken || process.env.AUN_ACCESS_TOKEN || '';
|
|
84
182
|
if (!accessToken) {
|
|
85
|
-
logger.error(`[AUN] No accessToken fallback available,
|
|
183
|
+
logger.error(`[AUN] No accessToken fallback available, scheduling retry`);
|
|
184
|
+
this.scheduleReconnect();
|
|
86
185
|
return;
|
|
87
186
|
}
|
|
88
187
|
logger.warn(`[AUN] Using accessToken fallback`);
|
|
@@ -93,10 +192,22 @@ export class AUNChannel {
|
|
|
93
192
|
this._aid = this.client.aid ?? undefined;
|
|
94
193
|
this.connected = true;
|
|
95
194
|
this.reconnectAttempt = 0;
|
|
195
|
+
// Workaround: SDK e2ee uses _identity.cert for sender_cert_fingerprint;
|
|
196
|
+
// if cert is missing, it falls back to public key SPKI fingerprint which
|
|
197
|
+
// causes peer cert lookup failures. Backfill from keystore if needed.
|
|
198
|
+
const clientAny = this.client;
|
|
199
|
+
if (clientAny._identity && !clientAny._identity.cert) {
|
|
200
|
+
const cert = clientAny._keystore?.loadCert?.(aidName);
|
|
201
|
+
if (cert) {
|
|
202
|
+
clientAny._identity.cert = cert;
|
|
203
|
+
logger.info('[AUN] Backfilled identity.cert from keystore for e2ee fingerprint');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
96
206
|
logger.info(`[AUN] Connected as ${this._aid}`);
|
|
97
207
|
}
|
|
98
208
|
catch (e) {
|
|
99
209
|
logger.error(`[AUN] Connection failed: ${e}`);
|
|
210
|
+
this.scheduleReconnect();
|
|
100
211
|
return;
|
|
101
212
|
}
|
|
102
213
|
}
|
|
@@ -107,7 +218,7 @@ export class AUNChannel {
|
|
|
107
218
|
const msg = data;
|
|
108
219
|
const fromAid = msg.from ?? '';
|
|
109
220
|
const payload = msg.payload ?? '';
|
|
110
|
-
const text =
|
|
221
|
+
const text = this.extractTextPayload(payload);
|
|
111
222
|
const taskId = msg.task_id;
|
|
112
223
|
const messageId = msg.message_id ?? '';
|
|
113
224
|
const seq = msg.seq;
|
|
@@ -134,24 +245,48 @@ export class AUNChannel {
|
|
|
134
245
|
const groupId = msg.group_id ?? '';
|
|
135
246
|
const senderAid = msg.sender_aid ?? msg.from ?? '';
|
|
136
247
|
const payload = msg.payload ?? '';
|
|
137
|
-
const text =
|
|
248
|
+
const text = this.extractTextPayload(payload);
|
|
138
249
|
const taskId = msg.task_id;
|
|
139
250
|
const messageId = msg.message_id ?? '';
|
|
140
251
|
const seq = msg.seq;
|
|
141
|
-
//
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
252
|
+
// Extract structured mentions from payload (e.g. payload.mentions: ["evolai.agentid.pub"])
|
|
253
|
+
const payloadMentions = Array.isArray(payload?.mentions)
|
|
254
|
+
? payload.mentions.filter((m) => typeof m === 'string')
|
|
255
|
+
: [];
|
|
256
|
+
logger.info(`[AUN][DIAG-GRP] full_msg=${JSON.stringify(msg).substring(0, 500)}`);
|
|
257
|
+
if (!groupId || !senderAid) {
|
|
258
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (this._aid && senderAid === this._aid) {
|
|
262
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const mentionedSelf = this._aid
|
|
266
|
+
? (this.hasExplicitMention(text, this._aid) || payloadMentions.includes(this._aid))
|
|
267
|
+
: false;
|
|
268
|
+
const mentionedAll = this.hasExplicitMention(text, 'all') || payloadMentions.includes('all');
|
|
269
|
+
if (!mentionedSelf && !mentionedAll) {
|
|
270
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
271
|
+
return;
|
|
145
272
|
}
|
|
273
|
+
const strippedText = this.stripTriggerMentions(text, this._aid);
|
|
274
|
+
if (!strippedText) {
|
|
275
|
+
this.acknowledgeImmediately(messageId, seq);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const mentions = mentionedAll ? ['all'] : (this._aid ? [this._aid] : []);
|
|
146
279
|
this.dispatchMessage({
|
|
147
280
|
channelId: groupId,
|
|
148
281
|
userId: senderAid,
|
|
149
|
-
|
|
282
|
+
peerName: this.getShortAid(senderAid),
|
|
283
|
+
text: strippedText,
|
|
150
284
|
chatType: 'group',
|
|
151
285
|
messageId,
|
|
152
286
|
seq,
|
|
153
287
|
taskId,
|
|
154
288
|
mentions,
|
|
289
|
+
replyContext: this.buildGroupReplyContext(taskId, senderAid),
|
|
155
290
|
});
|
|
156
291
|
}
|
|
157
292
|
dispatchMessage(event) {
|
|
@@ -169,8 +304,10 @@ export class AUNChannel {
|
|
|
169
304
|
if (!this.messageHandler)
|
|
170
305
|
return;
|
|
171
306
|
const mentionObjects = event.mentions?.map(aid => ({ userId: aid }));
|
|
172
|
-
|
|
173
|
-
|
|
307
|
+
// Use caller-supplied replyContext (group path builds mentionUserIds);
|
|
308
|
+
// fall back to simple threadId-only context for private messages
|
|
309
|
+
let replyContext = event.replyContext;
|
|
310
|
+
if (!replyContext && event.taskId) {
|
|
174
311
|
replyContext = { threadId: event.taskId };
|
|
175
312
|
}
|
|
176
313
|
this.messageHandler({
|
|
@@ -178,6 +315,7 @@ export class AUNChannel {
|
|
|
178
315
|
content: event.text || '',
|
|
179
316
|
chatType: event.chatType,
|
|
180
317
|
peerId: event.userId || event.channelId || '',
|
|
318
|
+
peerName: event.peerName,
|
|
181
319
|
messageId: event.messageId,
|
|
182
320
|
threadId: event.taskId,
|
|
183
321
|
mentions: mentionObjects,
|
|
@@ -230,60 +368,88 @@ export class AUNChannel {
|
|
|
230
368
|
finalText = '最终回复\n' + text;
|
|
231
369
|
}
|
|
232
370
|
this.sentCount.set(channelId, (this.sentCount.get(channelId) || 0) + 1);
|
|
233
|
-
|
|
371
|
+
// 群聊 @ 兜底:提示词已告知 agent 要 @,但如果 agent 没写,系统自动补上
|
|
372
|
+
if (this.isGroupId(channelId) && context?.peerId) {
|
|
373
|
+
if (!finalText.includes(`@${context.peerId}`)) {
|
|
374
|
+
finalText = `@${context.peerId} ` + finalText;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const params = { payload: { text: finalText }, encrypt: true };
|
|
234
378
|
if (context?.threadId)
|
|
235
379
|
params.task_id = context.threadId;
|
|
236
380
|
try {
|
|
237
|
-
if (
|
|
381
|
+
if (this.isGroupId(channelId)) {
|
|
238
382
|
params.group_id = channelId;
|
|
383
|
+
this.trace('OUT', 'group.send', params);
|
|
239
384
|
await this.client.call('group.send', params);
|
|
240
385
|
}
|
|
241
386
|
else {
|
|
242
387
|
params.to = channelId;
|
|
388
|
+
this.trace('OUT', 'message.send', params);
|
|
243
389
|
await this.client.call('message.send', params);
|
|
244
390
|
}
|
|
245
391
|
}
|
|
246
392
|
catch (e) {
|
|
393
|
+
this.trace('OUT', 'send.error', { channelId, error: String(e) });
|
|
247
394
|
logger.error(`[AUN] Send failed to ${channelId}: ${e}`);
|
|
248
395
|
}
|
|
249
396
|
}
|
|
250
397
|
acknowledge(messageId) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
logger.debug(`[AUN] Ack failed: ${e}`);
|
|
255
|
-
});
|
|
256
|
-
this.messageSeqMap.delete(messageId);
|
|
257
|
-
}
|
|
398
|
+
// Gateway auto-delivery-ack is sufficient; skip explicit message.ack RPC
|
|
399
|
+
// to avoid duplicate "已送达" at the sender CLI
|
|
400
|
+
this.messageSeqMap.delete(messageId);
|
|
258
401
|
}
|
|
259
402
|
sendProcessingStatus(channelId, status, sessionId, context) {
|
|
260
403
|
if (status === 'start')
|
|
261
404
|
this.sentCount.delete(channelId); // 新任务开始,重置计数
|
|
262
405
|
if (!this.client || !this.connected)
|
|
263
406
|
return;
|
|
264
|
-
const payload =
|
|
407
|
+
const payload = {
|
|
265
408
|
type: 'processing',
|
|
266
409
|
status,
|
|
267
410
|
sessionId,
|
|
268
411
|
timestamp: Math.floor(Date.now() / 1000),
|
|
269
|
-
}
|
|
412
|
+
};
|
|
270
413
|
const params = {
|
|
271
|
-
|
|
272
|
-
encrypt: true,
|
|
414
|
+
payload,
|
|
415
|
+
encrypt: true,
|
|
273
416
|
};
|
|
274
417
|
if (context?.threadId)
|
|
275
418
|
params.task_id = context.threadId;
|
|
276
|
-
this.
|
|
277
|
-
|
|
278
|
-
|
|
419
|
+
if (this.isGroupId(channelId)) {
|
|
420
|
+
params.group_id = channelId;
|
|
421
|
+
this.trace('OUT', 'group.send.status', params);
|
|
422
|
+
this.client.call('group.send', params).catch(e => {
|
|
423
|
+
logger.debug(`[AUN] Processing status failed: ${e}`);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
params.to = channelId;
|
|
428
|
+
this.trace('OUT', 'message.send.status', params);
|
|
429
|
+
this.client.call('message.send', params).catch(e => {
|
|
430
|
+
logger.debug(`[AUN] Processing status failed: ${e}`);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
279
433
|
}
|
|
280
434
|
sendCustomPayload(channelId, payload) {
|
|
281
435
|
if (!this.client || !this.connected)
|
|
282
436
|
return;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
437
|
+
// SDK 0.3.0 E2EE requires payload to be an object
|
|
438
|
+
let payloadObj;
|
|
439
|
+
try {
|
|
440
|
+
const parsed = JSON.parse(payload);
|
|
441
|
+
payloadObj = (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
442
|
+
? parsed : { text: payload };
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
payloadObj = { text: payload };
|
|
446
|
+
}
|
|
447
|
+
const sendParams = {
|
|
448
|
+
to: channelId, payload: payloadObj,
|
|
449
|
+
encrypt: true,
|
|
450
|
+
};
|
|
451
|
+
this.trace('OUT', 'message.send.custom', sendParams);
|
|
452
|
+
this.client.call('message.send', sendParams).catch(e => {
|
|
287
453
|
logger.debug(`[AUN] Custom payload failed: ${e}`);
|
|
288
454
|
});
|
|
289
455
|
}
|
|
@@ -301,6 +467,10 @@ export class AUNChannel {
|
|
|
301
467
|
this.client = null;
|
|
302
468
|
}
|
|
303
469
|
this.connected = false;
|
|
470
|
+
if (this.traceStream) {
|
|
471
|
+
this.traceStream.end();
|
|
472
|
+
this.traceStream = null;
|
|
473
|
+
}
|
|
304
474
|
logger.info('[AUN] Disconnected');
|
|
305
475
|
}
|
|
306
476
|
// ── TS-layer reconnect (fallback when SDK auto_reconnect exhausted) ──
|
|
@@ -367,69 +537,87 @@ export class AUNChannel {
|
|
|
367
537
|
export class AUNChannelPlugin {
|
|
368
538
|
name = 'aun';
|
|
369
539
|
isEnabled(config) {
|
|
370
|
-
|
|
540
|
+
const raw = config.channels?.aun;
|
|
541
|
+
if (!raw)
|
|
542
|
+
return false;
|
|
543
|
+
if (Array.isArray(raw)) {
|
|
544
|
+
return raw.some(inst => inst.enabled !== false && !!inst.aid);
|
|
545
|
+
}
|
|
546
|
+
return raw.enabled !== false && !!raw.aid;
|
|
547
|
+
}
|
|
548
|
+
async createChannels(config) {
|
|
549
|
+
const instances = normalizeChannelInstances(config.channels?.aun, 'aun');
|
|
550
|
+
const result = [];
|
|
551
|
+
for (const inst of instances) {
|
|
552
|
+
if (inst.enabled === false || !inst.aid)
|
|
553
|
+
continue;
|
|
554
|
+
const channel = new AUNChannel({
|
|
555
|
+
aid: inst.aid,
|
|
556
|
+
keystorePath: inst.keystorePath,
|
|
557
|
+
gatewayPort: inst.gatewayPort,
|
|
558
|
+
gatewayUrl: inst.gatewayUrl,
|
|
559
|
+
accessToken: inst.accessToken,
|
|
560
|
+
flushDelay: inst.flushDelay,
|
|
561
|
+
encryptionSeed: inst.encryptionSeed,
|
|
562
|
+
aunTrace: config.debug?.aunTrace,
|
|
563
|
+
});
|
|
564
|
+
const adapter = {
|
|
565
|
+
channelName: inst.name,
|
|
566
|
+
sendText: (id, text, context) => channel.sendMessage(id, text, context),
|
|
567
|
+
acknowledge: (messageId) => { channel.acknowledge(messageId); return Promise.resolve(); },
|
|
568
|
+
sendProcessingStatus: (id, status, sessionId, context) => channel.sendProcessingStatus(id, status, sessionId, context),
|
|
569
|
+
sendCustomPayload: (id, payload) => channel.sendCustomPayload(id, payload),
|
|
570
|
+
};
|
|
571
|
+
const policy = {
|
|
572
|
+
canSwitchProject: (chatType, identity) => identity === 'owner',
|
|
573
|
+
canListProjects: (chatType, identity) => identity === 'owner',
|
|
574
|
+
canCreateSession: (chatType, identity) => true,
|
|
575
|
+
canDeleteSession: (chatType, identity) => true,
|
|
576
|
+
canImportCliSession: (chatType, identity) => identity === 'owner',
|
|
577
|
+
messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
|
|
578
|
+
showMiddleResult: (chatType, identity) => {
|
|
579
|
+
const mode = inst.showActivities ?? config.showActivities ?? 'all';
|
|
580
|
+
if (mode === 'none')
|
|
581
|
+
return false;
|
|
582
|
+
if (mode === 'dm-only')
|
|
583
|
+
return chatType === 'private';
|
|
584
|
+
if (mode === 'owner-dm-only')
|
|
585
|
+
return chatType === 'private' && identity === 'owner';
|
|
586
|
+
return true;
|
|
587
|
+
},
|
|
588
|
+
showIdleMonitor: (chatType, identity) => {
|
|
589
|
+
const mode = inst.showActivities ?? config.showActivities ?? 'all';
|
|
590
|
+
if (mode === 'none')
|
|
591
|
+
return false;
|
|
592
|
+
if (mode === 'dm-only')
|
|
593
|
+
return chatType === 'private';
|
|
594
|
+
if (mode === 'owner-dm-only')
|
|
595
|
+
return chatType === 'private' && identity === 'owner';
|
|
596
|
+
return true;
|
|
597
|
+
},
|
|
598
|
+
accumulateErrors: (chatType, identity) => true,
|
|
599
|
+
};
|
|
600
|
+
const options = {
|
|
601
|
+
flushDelay: inst.flushDelay ?? 3,
|
|
602
|
+
fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
|
|
603
|
+
};
|
|
604
|
+
result.push({
|
|
605
|
+
channelType: 'aun',
|
|
606
|
+
adapter,
|
|
607
|
+
channel,
|
|
608
|
+
policy,
|
|
609
|
+
options,
|
|
610
|
+
connect: () => channel.connect(),
|
|
611
|
+
disconnect: () => channel.disconnect(),
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
371
615
|
}
|
|
372
616
|
async createChannel(config) {
|
|
373
|
-
const
|
|
374
|
-
if (
|
|
617
|
+
const instances = await this.createChannels(config);
|
|
618
|
+
if (instances.length === 0) {
|
|
375
619
|
throw new Error('AUN config missing (aid required, e.g. "mybot.agentid.pub")');
|
|
376
620
|
}
|
|
377
|
-
|
|
378
|
-
aid: aunConfig.aid,
|
|
379
|
-
keystorePath: aunConfig.keystorePath,
|
|
380
|
-
gatewayPort: aunConfig.gatewayPort,
|
|
381
|
-
gatewayUrl: aunConfig.gatewayUrl,
|
|
382
|
-
accessToken: aunConfig.accessToken,
|
|
383
|
-
flushDelay: aunConfig.flushDelay,
|
|
384
|
-
encryptionSeed: aunConfig.encryptionSeed,
|
|
385
|
-
});
|
|
386
|
-
const adapter = {
|
|
387
|
-
name: 'aun',
|
|
388
|
-
sendText: (id, text, context) => channel.sendMessage(id, text, context),
|
|
389
|
-
acknowledge: (messageId) => { channel.acknowledge(messageId); return Promise.resolve(); },
|
|
390
|
-
sendProcessingStatus: (id, status, sessionId, context) => channel.sendProcessingStatus(id, status, sessionId, context),
|
|
391
|
-
sendCustomPayload: (id, payload) => channel.sendCustomPayload(id, payload),
|
|
392
|
-
};
|
|
393
|
-
const policy = {
|
|
394
|
-
canSwitchProject: (chatType, identity) => identity === 'owner',
|
|
395
|
-
canListProjects: (chatType, identity) => identity === 'owner',
|
|
396
|
-
canCreateSession: (chatType, identity) => true,
|
|
397
|
-
canDeleteSession: (chatType, identity) => true,
|
|
398
|
-
canImportCliSession: (chatType, identity) => identity === 'owner',
|
|
399
|
-
messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
|
|
400
|
-
showMiddleResult: (chatType, identity) => {
|
|
401
|
-
const mode = aunConfig.showActivities ?? config.showActivities ?? 'all';
|
|
402
|
-
if (mode === 'none')
|
|
403
|
-
return false;
|
|
404
|
-
if (mode === 'dm-only')
|
|
405
|
-
return chatType === 'private';
|
|
406
|
-
if (mode === 'owner-dm-only')
|
|
407
|
-
return chatType === 'private' && identity === 'owner';
|
|
408
|
-
return true;
|
|
409
|
-
},
|
|
410
|
-
showIdleMonitor: (chatType, identity) => {
|
|
411
|
-
const mode = aunConfig.showActivities ?? config.showActivities ?? 'all';
|
|
412
|
-
if (mode === 'none')
|
|
413
|
-
return false;
|
|
414
|
-
if (mode === 'dm-only')
|
|
415
|
-
return chatType === 'private';
|
|
416
|
-
if (mode === 'owner-dm-only')
|
|
417
|
-
return chatType === 'private' && identity === 'owner';
|
|
418
|
-
return true;
|
|
419
|
-
},
|
|
420
|
-
accumulateErrors: (chatType, identity) => true,
|
|
421
|
-
};
|
|
422
|
-
const options = {
|
|
423
|
-
flushDelay: aunConfig.flushDelay ?? 3,
|
|
424
|
-
fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
|
|
425
|
-
};
|
|
426
|
-
return {
|
|
427
|
-
adapter,
|
|
428
|
-
channel,
|
|
429
|
-
policy,
|
|
430
|
-
options,
|
|
431
|
-
connect: () => channel.connect(),
|
|
432
|
-
disconnect: () => channel.disconnect(),
|
|
433
|
-
};
|
|
621
|
+
return instances[0];
|
|
434
622
|
}
|
|
435
623
|
}
|