evolclaw 3.1.5 → 3.1.7
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 +68 -3
- package/dist/agents/claude-runner.js +69 -24
- package/dist/agents/kit-renderer.js +78 -321
- package/dist/agents/manifest-engine.js +243 -0
- package/dist/agents/message-renderer.js +112 -0
- package/dist/aun/aid/agentmd.js +10 -3
- package/dist/aun/msg/group.js +2 -2
- package/dist/channels/aun.js +154 -18
- package/dist/channels/dingtalk.js +1 -1
- package/dist/channels/feishu.js +31 -9
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/channels/wecom.js +1 -1
- package/dist/cli/agent.js +10 -11
- package/dist/cli/bench.js +1 -5
- package/dist/cli/help.js +8 -0
- package/dist/cli/index.js +91 -128
- package/dist/cli/init.js +37 -21
- package/dist/cli/link-rules.js +1 -7
- package/dist/cli/model.js +231 -6
- package/dist/config-store.js +1 -22
- package/dist/core/command-handler.js +181 -48
- package/dist/core/evolagent.js +0 -18
- package/dist/core/message/im-renderer.js +9 -20
- package/dist/core/message/message-bridge.js +9 -10
- package/dist/core/message/message-processor.js +188 -39
- package/dist/core/message/message-queue.js +15 -1
- package/dist/core/relation/peer-identity.js +23 -11
- package/dist/core/trigger/parser.js +4 -4
- package/dist/core/trigger/scheduler.js +43 -13
- package/dist/index.js +102 -52
- package/dist/ipc.js +1 -1
- package/dist/utils/error-utils.js +6 -0
- package/dist/utils/process-introspect.js +7 -5
- package/kits/docs/INDEX.md +4 -8
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolclaw/INDEX.md +43 -0
- package/kits/docs/evolclaw/group.md +13 -6
- package/kits/docs/evolclaw/model.md +51 -0
- package/kits/docs/evolclaw/msg.md +5 -0
- package/kits/docs/venues/group.md +13 -1
- package/kits/eck_manifest.json +9 -0
- package/kits/eck_message_manifest.json +14 -0
- package/kits/rules/06-channel.md +5 -1
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +7 -5
- package/kits/templates/system-fragments/commands.md +19 -0
- package/kits/templates/system-fragments/session.md +12 -0
- package/kits/templates/system-fragments/venue.md +15 -0
- package/package.json +3 -3
package/dist/channels/aun.js
CHANGED
|
@@ -576,7 +576,7 @@ export class AUNChannel {
|
|
|
576
576
|
const store = await getAidStore({
|
|
577
577
|
slotId: SLOT.daemon,
|
|
578
578
|
aunPath,
|
|
579
|
-
debug: this.config.aunSdkLog ??
|
|
579
|
+
debug: this.config.aunSdkLog ?? false,
|
|
580
580
|
});
|
|
581
581
|
this.store = store;
|
|
582
582
|
const client = await loadClient(store, aidName);
|
|
@@ -790,18 +790,16 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
790
790
|
|
|
791
791
|
📋 **日常使用方法**:
|
|
792
792
|
|
|
793
|
-
1.
|
|
794
|
-
2.
|
|
795
|
-
3.
|
|
796
|
-
4. **查看状态**:发送 \`/status\` 查看当前会话状态
|
|
797
|
-
5. **会话管理**:发送 \`/session\` 查看和切换会话
|
|
793
|
+
1. **查看帮助**:发送 \`/help\` 查看所有可用命令
|
|
794
|
+
2. **查看状态**:发送 \`/status\` 查看当前会话状态
|
|
795
|
+
3. **会话管理**:发送 \`/session\` 查看和切换会话
|
|
798
796
|
|
|
799
797
|
💡 **提示**:
|
|
800
798
|
- 直接发送消息即可与 Claude/Codex 对话
|
|
801
|
-
-
|
|
799
|
+
- 支持多会话管理,每个会话独立上下文
|
|
802
800
|
- 所有命令以 \`/\` 开头
|
|
803
801
|
|
|
804
|
-
|
|
802
|
+
现在就可以开始工作了!`;
|
|
805
803
|
// First contact with Owner races against Owner's async cert fetch from
|
|
806
804
|
// gateway PKI; a 3s pause lets the cert propagate. persist_required asks
|
|
807
805
|
// the gateway to durably store the message so Owner can recover it via
|
|
@@ -818,6 +816,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
818
816
|
persist_required: true,
|
|
819
817
|
});
|
|
820
818
|
logger.info(`${this.logPrefix()} Welcome message sent to owner: ${owner}`);
|
|
819
|
+
// Send binding credential for Evol App to persist locally
|
|
820
|
+
await this.sendBindingCredential(owner, agentDisplayName, agentConfig.active_baseagent || 'claude').catch(e => logger.warn(`${this.logPrefix()} Binding credential failed: ${e}`));
|
|
821
821
|
// Mark agent as initialized in config.json (replaces old agent.md frontmatter flag)
|
|
822
822
|
try {
|
|
823
823
|
const fresh = loadAgent(aidName);
|
|
@@ -835,7 +835,57 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
835
835
|
logger.warn(`${this.logPrefix()} Failed to send welcome message: ${e}`);
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
|
+
async sendBindingCredential(owner, name, baseagent) {
|
|
839
|
+
if (!this.client)
|
|
840
|
+
return;
|
|
841
|
+
await this.callAndTrace('message.send', {
|
|
842
|
+
to: owner,
|
|
843
|
+
payload: { type: 'binding', aid: this.config.aid, name, owner, baseagent },
|
|
844
|
+
encrypt: true,
|
|
845
|
+
persist_required: true,
|
|
846
|
+
});
|
|
847
|
+
logger.info(`${this.logPrefix()} Binding credential sent to owner: ${owner}`);
|
|
848
|
+
}
|
|
838
849
|
// ── Event handlers ──────────────────────────────────────────
|
|
850
|
+
/**
|
|
851
|
+
* 判断附件是否为图片,返回 MIME 类型(非图片返回空)。
|
|
852
|
+
* 多重检测:附件元数据字段 → 文件名后缀 → 文件 magic bytes。
|
|
853
|
+
*/
|
|
854
|
+
detectImageMime(att, filePath) {
|
|
855
|
+
const extToMime = {
|
|
856
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
857
|
+
'.gif': 'image/gif', '.webp': 'image/webp',
|
|
858
|
+
};
|
|
859
|
+
// 1. 附件元数据字段(content_type / mime_type / mimeType)
|
|
860
|
+
const metaCt = (att?.content_type || att?.mime_type || att?.mimeType || '');
|
|
861
|
+
if (typeof metaCt === 'string' && metaCt.startsWith('image/'))
|
|
862
|
+
return metaCt;
|
|
863
|
+
// 2. 文件名后缀
|
|
864
|
+
const name = (att?.filename || att?.object_key || filePath || '').toLowerCase();
|
|
865
|
+
for (const [ext, mime] of Object.entries(extToMime)) {
|
|
866
|
+
if (name.endsWith(ext))
|
|
867
|
+
return mime;
|
|
868
|
+
}
|
|
869
|
+
// 3. magic bytes
|
|
870
|
+
try {
|
|
871
|
+
const { openSync, readSync, closeSync } = require('node:fs');
|
|
872
|
+
const fd = openSync(filePath, 'r');
|
|
873
|
+
const head = Buffer.alloc(12);
|
|
874
|
+
readSync(fd, head, 0, 12, 0);
|
|
875
|
+
closeSync(fd);
|
|
876
|
+
if (head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4e && head[3] === 0x47)
|
|
877
|
+
return 'image/png';
|
|
878
|
+
if (head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff)
|
|
879
|
+
return 'image/jpeg';
|
|
880
|
+
if (head[0] === 0x47 && head[1] === 0x49 && head[2] === 0x46)
|
|
881
|
+
return 'image/gif';
|
|
882
|
+
if (head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46 &&
|
|
883
|
+
head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50)
|
|
884
|
+
return 'image/webp';
|
|
885
|
+
}
|
|
886
|
+
catch { /* not readable, skip */ }
|
|
887
|
+
return '';
|
|
888
|
+
}
|
|
839
889
|
async downloadAttachment(att, channelId) {
|
|
840
890
|
const ownerAid = att.owner_aid || this._aid || '';
|
|
841
891
|
const objectKey = att.object_key;
|
|
@@ -844,7 +894,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
844
894
|
return null;
|
|
845
895
|
}
|
|
846
896
|
const filename = att.filename || objectKey.split('/').pop() || 'unknown';
|
|
847
|
-
|
|
897
|
+
// 安全:始终通过受信任的 ticket 路径获取下载 URL。
|
|
898
|
+
// 不信任 att.url(来自对端消息 payload,可被构造为内网/元数据地址,SSRF)。
|
|
899
|
+
let downloadUrl = '';
|
|
848
900
|
try {
|
|
849
901
|
const ticket = await this.callAndTrace('storage.create_download_ticket', {
|
|
850
902
|
owner_aid: ownerAid,
|
|
@@ -924,12 +976,21 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
924
976
|
// Process attachments (顶层 + 嵌套在 merge.items / quote.quote 中的)
|
|
925
977
|
const rawAttachments = this.collectAllAttachments(payload);
|
|
926
978
|
let finalText = text;
|
|
979
|
+
const inboundImages = [];
|
|
927
980
|
if (rawAttachments.length > 0 && this.client) {
|
|
928
981
|
const fileParts = [];
|
|
929
982
|
for (const att of rawAttachments) {
|
|
930
983
|
const filePath = await this.downloadAttachment(att, fromAid);
|
|
931
984
|
if (filePath) {
|
|
932
985
|
const name = sanitizeFileName(att.filename || att.object_key?.split('/').pop() || 'file');
|
|
986
|
+
const mime = this.detectImageMime(att, filePath);
|
|
987
|
+
if (mime) {
|
|
988
|
+
try {
|
|
989
|
+
const { readFileSync } = await import('node:fs');
|
|
990
|
+
inboundImages.push({ data: readFileSync(filePath).toString('base64'), mimeType: mime });
|
|
991
|
+
}
|
|
992
|
+
catch { /* fallback to file path */ }
|
|
993
|
+
}
|
|
933
994
|
fileParts.push(`[文件: ${name} → ${filePath}]`);
|
|
934
995
|
}
|
|
935
996
|
}
|
|
@@ -938,9 +999,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
938
999
|
if (text)
|
|
939
1000
|
parts.push(text);
|
|
940
1001
|
parts.push(...fileParts);
|
|
941
|
-
|
|
1002
|
+
if (inboundImages.length === 0)
|
|
1003
|
+
parts.push('请使用 Read 工具读取文件内容。');
|
|
942
1004
|
finalText = parts.join('\n\n');
|
|
943
1005
|
}
|
|
1006
|
+
logger.info(`${this.logPrefix()} [img-debug] private attachments=${rawAttachments.length} images=${inboundImages.length}`);
|
|
944
1007
|
}
|
|
945
1008
|
// 私聊 channelId = 对端 AID(不再读 payload.chat_id 含 device 三段式)
|
|
946
1009
|
// device_id 仅 SDK 内部多实例去重用,evolclaw session 层面跨端共享会话
|
|
@@ -993,7 +1056,11 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
993
1056
|
mentions,
|
|
994
1057
|
peerName: displayName || undefined,
|
|
995
1058
|
peerType: peerIdentity.type,
|
|
1059
|
+
sameDevice: msg.same_device === true || undefined,
|
|
1060
|
+
sameNetwork: msg.same_network === true || undefined,
|
|
1061
|
+
sameEgressIp: msg.same_egress_ip === true || undefined,
|
|
996
1062
|
replyContext,
|
|
1063
|
+
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
997
1064
|
});
|
|
998
1065
|
}
|
|
999
1066
|
async handleIncomingGroupMessage(data) {
|
|
@@ -1144,12 +1211,21 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1144
1211
|
: mentionedSelf && this._aid ? [this._aid] : [];
|
|
1145
1212
|
// Process attachments
|
|
1146
1213
|
let finalText = strippedText;
|
|
1214
|
+
const inboundImages = [];
|
|
1147
1215
|
if (hasAttachments && this.client) {
|
|
1148
1216
|
const fileParts = [];
|
|
1149
1217
|
for (const att of rawAttachments) {
|
|
1150
1218
|
const filePath = await this.downloadAttachment(att, groupId);
|
|
1151
1219
|
if (filePath) {
|
|
1152
1220
|
const name = sanitizeFileName(att.filename || att.object_key?.split('/').pop() || 'file');
|
|
1221
|
+
const mime = this.detectImageMime(att, filePath);
|
|
1222
|
+
if (mime) {
|
|
1223
|
+
try {
|
|
1224
|
+
const { readFileSync } = await import('node:fs');
|
|
1225
|
+
inboundImages.push({ data: readFileSync(filePath).toString('base64'), mimeType: mime });
|
|
1226
|
+
}
|
|
1227
|
+
catch { /* fallback to file path */ }
|
|
1228
|
+
}
|
|
1153
1229
|
fileParts.push(`[文件: ${name} → ${filePath}]`);
|
|
1154
1230
|
}
|
|
1155
1231
|
}
|
|
@@ -1158,7 +1234,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1158
1234
|
if (strippedText)
|
|
1159
1235
|
parts.push(strippedText);
|
|
1160
1236
|
parts.push(...fileParts);
|
|
1161
|
-
|
|
1237
|
+
if (inboundImages.length === 0)
|
|
1238
|
+
parts.push('请使用 Read 工具读取文件内容。');
|
|
1162
1239
|
finalText = parts.join('\n\n');
|
|
1163
1240
|
}
|
|
1164
1241
|
}
|
|
@@ -1191,6 +1268,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1191
1268
|
userId: senderAid,
|
|
1192
1269
|
peerName: displayName || undefined,
|
|
1193
1270
|
peerType: peerIdentity.type,
|
|
1271
|
+
sameDevice: msg.same_device === true || undefined,
|
|
1272
|
+
sameNetwork: msg.same_network === true || undefined,
|
|
1273
|
+
sameEgressIp: msg.same_egress_ip === true || undefined,
|
|
1194
1274
|
text: finalText,
|
|
1195
1275
|
chatType: 'group',
|
|
1196
1276
|
messageId,
|
|
@@ -1198,6 +1278,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1198
1278
|
threadId,
|
|
1199
1279
|
mentions,
|
|
1200
1280
|
replyContext: this.buildGroupReplyContext(threadId, senderAid, msgEncrypted, messageId, msgChatmode),
|
|
1281
|
+
images: inboundImages.length > 0 ? inboundImages : undefined,
|
|
1201
1282
|
});
|
|
1202
1283
|
}
|
|
1203
1284
|
dispatchMessage(event) {
|
|
@@ -1260,13 +1341,55 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1260
1341
|
peerId: event.userId || event.channelId || '',
|
|
1261
1342
|
peerName: event.peerName,
|
|
1262
1343
|
peerType: event.peerType,
|
|
1344
|
+
sameDevice: event.sameDevice,
|
|
1345
|
+
sameNetwork: event.sameNetwork,
|
|
1346
|
+
sameEgressIp: event.sameEgressIp,
|
|
1263
1347
|
messageId: event.messageId,
|
|
1264
1348
|
threadId: event.threadId,
|
|
1265
1349
|
mentions: mentionObjects,
|
|
1266
1350
|
replyContext,
|
|
1351
|
+
images: event.images,
|
|
1267
1352
|
}).catch(err => {
|
|
1268
1353
|
logger.error(`${this.logPrefix()} Message handler error:`, err);
|
|
1269
1354
|
});
|
|
1355
|
+
// Observer forward: inbound
|
|
1356
|
+
this.forwardToOwners('inbound', {
|
|
1357
|
+
from: event.userId || event.channelId || '',
|
|
1358
|
+
to: this.config.aid,
|
|
1359
|
+
seq: event.seq,
|
|
1360
|
+
payload: { type: 'text', text: event.text },
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* 观察者模式转发:将消息副本以 observer.forward 格式发给所有 owners。
|
|
1365
|
+
* 仅在 AgentConfig.observable === true 时执行;owners 为空或无法加载配置时静默跳过。
|
|
1366
|
+
*/
|
|
1367
|
+
forwardToOwners(direction, original) {
|
|
1368
|
+
if (!this.connected || !this.client)
|
|
1369
|
+
return;
|
|
1370
|
+
const agentConfig = loadAgent(this.config.aid);
|
|
1371
|
+
if (!agentConfig?.observable)
|
|
1372
|
+
return;
|
|
1373
|
+
const owners = agentConfig.owners ?? [];
|
|
1374
|
+
if (owners.length === 0)
|
|
1375
|
+
return;
|
|
1376
|
+
const forwardPayload = {
|
|
1377
|
+
type: 'observer.forward',
|
|
1378
|
+
direction,
|
|
1379
|
+
agent_aid: this.config.aid,
|
|
1380
|
+
original: {
|
|
1381
|
+
from: original.from,
|
|
1382
|
+
to: original.to,
|
|
1383
|
+
...(original.seq != null ? { seq: original.seq } : {}),
|
|
1384
|
+
timestamp: Date.now(),
|
|
1385
|
+
payload: original.payload,
|
|
1386
|
+
},
|
|
1387
|
+
};
|
|
1388
|
+
for (const ownerAid of owners) {
|
|
1389
|
+
const encrypt = this.shouldEncrypt(ownerAid);
|
|
1390
|
+
this.callAndTrace('message.send', { to: ownerAid, payload: forwardPayload, encrypt })
|
|
1391
|
+
.catch(e => logger.debug(`${this.logPrefix()} observer.forward to ${ownerAid} failed: ${e}`));
|
|
1392
|
+
}
|
|
1270
1393
|
}
|
|
1271
1394
|
handleEcho(event) {
|
|
1272
1395
|
const ts = () => {
|
|
@@ -1743,6 +1866,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1743
1866
|
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 });
|
|
1744
1867
|
this.aidStatsCollector?.recordOutbound(this.config.aid, channelId, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1745
1868
|
this.appendOutboundJsonl(channelId, finalText, mid, encrypt, context, true, 'text', source);
|
|
1869
|
+
// Observer forward: outbound (group)
|
|
1870
|
+
this.forwardToOwners('outbound', {
|
|
1871
|
+
from: this.config.aid,
|
|
1872
|
+
to: channelId,
|
|
1873
|
+
payload: { type: 'text', text: finalText },
|
|
1874
|
+
});
|
|
1746
1875
|
}
|
|
1747
1876
|
}
|
|
1748
1877
|
else {
|
|
@@ -1756,6 +1885,12 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
|
|
|
1756
1885
|
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 });
|
|
1757
1886
|
this.aidStatsCollector?.recordOutbound(this.config.aid, targetAid, Buffer.byteLength(finalText, 'utf-8'), finalText, false, encrypt, context?.metadata?.chatmode);
|
|
1758
1887
|
this.appendOutboundJsonl(targetAid, finalText, result.message_id, encrypt, context, false, 'text', source);
|
|
1888
|
+
// Observer forward: outbound (private)
|
|
1889
|
+
this.forwardToOwners('outbound', {
|
|
1890
|
+
from: this.config.aid,
|
|
1891
|
+
to: targetAid,
|
|
1892
|
+
payload: { type: 'text', text: finalText },
|
|
1893
|
+
});
|
|
1759
1894
|
}
|
|
1760
1895
|
}
|
|
1761
1896
|
return true;
|
|
@@ -2444,7 +2579,7 @@ export class AUNChannelPlugin {
|
|
|
2444
2579
|
const adapter = {
|
|
2445
2580
|
channelName: inst.name,
|
|
2446
2581
|
channelKey: inst.name, // channelName 实际上就是 channelKey
|
|
2447
|
-
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: true, status: true },
|
|
2582
|
+
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: true, status: true, thread: true },
|
|
2448
2583
|
send: async (envelope, payload) => {
|
|
2449
2584
|
const ctx = envelope.replyContext;
|
|
2450
2585
|
const channelId = envelope.channelId;
|
|
@@ -2497,22 +2632,22 @@ export class AUNChannelPlugin {
|
|
|
2497
2632
|
channel.sendProcessingStatus(channelId, 'progress', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2498
2633
|
return;
|
|
2499
2634
|
case 'status.started':
|
|
2500
|
-
channel.sendProcessingStatus(channelId, 'start', envelope.taskId, envelope.taskId, ctx);
|
|
2635
|
+
channel.sendProcessingStatus(channelId, 'start', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2501
2636
|
return;
|
|
2502
2637
|
case 'status.queued':
|
|
2503
|
-
channel.sendProcessingStatus(channelId, 'queued', envelope.taskId, envelope.taskId, ctx);
|
|
2638
|
+
channel.sendProcessingStatus(channelId, 'queued', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2504
2639
|
return;
|
|
2505
2640
|
case 'status.completed':
|
|
2506
|
-
channel.sendProcessingStatus(channelId, 'done', envelope.taskId, envelope.taskId, ctx);
|
|
2641
|
+
channel.sendProcessingStatus(channelId, 'done', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2507
2642
|
return;
|
|
2508
2643
|
case 'status.interrupted':
|
|
2509
|
-
channel.sendProcessingStatus(channelId, 'interrupted', envelope.taskId, envelope.taskId, ctx);
|
|
2644
|
+
channel.sendProcessingStatus(channelId, 'interrupted', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2510
2645
|
return;
|
|
2511
2646
|
case 'status.error':
|
|
2512
|
-
channel.sendProcessingStatus(channelId, 'error', envelope.taskId, envelope.taskId, ctx);
|
|
2647
|
+
channel.sendProcessingStatus(channelId, 'error', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2513
2648
|
return;
|
|
2514
2649
|
case 'status.timeout':
|
|
2515
|
-
channel.sendProcessingStatus(channelId, 'timeout', envelope.taskId, envelope.taskId, ctx);
|
|
2650
|
+
channel.sendProcessingStatus(channelId, 'timeout', envelope.taskId, envelope.taskId, ctx, payload.metadata);
|
|
2516
2651
|
return;
|
|
2517
2652
|
case 'interaction': {
|
|
2518
2653
|
const req = payload.interaction;
|
|
@@ -2637,6 +2772,7 @@ export class AUNChannelPlugin {
|
|
|
2637
2772
|
threadId: opts.threadId,
|
|
2638
2773
|
replyContext: opts.replyContext,
|
|
2639
2774
|
source: opts.source,
|
|
2775
|
+
images: opts.images,
|
|
2640
2776
|
});
|
|
2641
2777
|
}), (channelId, text, replyContext) => channel.sendMessage(channelId, text, replyContext), adapter, channelType);
|
|
2642
2778
|
},
|
|
@@ -448,7 +448,7 @@ export class DingtalkChannelPlugin {
|
|
|
448
448
|
const adapter = {
|
|
449
449
|
channelName: inst.name,
|
|
450
450
|
channelKey: inst.name,
|
|
451
|
-
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
451
|
+
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
|
|
452
452
|
send: async (envelope, payload) => {
|
|
453
453
|
const ctx = envelope.replyContext;
|
|
454
454
|
const channelId = envelope.channelId;
|
package/dist/channels/feishu.js
CHANGED
|
@@ -283,7 +283,8 @@ export class FeishuChannel {
|
|
|
283
283
|
else if (msg.message_type === 'merge_forward') {
|
|
284
284
|
const { text: mergedText, images: mergedImages } = await this.extractMergeForwardContent(msg.message_id, msg.chat_id);
|
|
285
285
|
if (mergedText) {
|
|
286
|
-
|
|
286
|
+
// 直接发送合并转发时,parent_id 指向自己,引用解析会把相同内容填入 quotedText 导致重复,丢弃
|
|
287
|
+
const finalContent = mergedText;
|
|
287
288
|
const allImages = [...quotedImages, ...mergedImages];
|
|
288
289
|
await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: allImages.length > 0 ? allImages : undefined, peerId, peerName, messageId: msg.message_id, threadId, rootId, chatType });
|
|
289
290
|
}
|
|
@@ -513,10 +514,15 @@ export class FeishuChannel {
|
|
|
513
514
|
if (options.replyInThread) {
|
|
514
515
|
replyData.reply_in_thread = true;
|
|
515
516
|
}
|
|
516
|
-
await this.client.im.message.reply({
|
|
517
|
+
const replyRes = await this.client.im.message.reply({
|
|
517
518
|
path: { message_id: options.replyToMessageId },
|
|
518
519
|
data: replyData
|
|
519
520
|
});
|
|
521
|
+
if (options.replyInThread && options.onThreadCreated) {
|
|
522
|
+
const newThreadId = replyRes?.data?.thread_id;
|
|
523
|
+
if (newThreadId)
|
|
524
|
+
options.onThreadCreated(newThreadId);
|
|
525
|
+
}
|
|
520
526
|
}
|
|
521
527
|
else {
|
|
522
528
|
await this.client.im.message.create({
|
|
@@ -710,13 +716,18 @@ export class FeishuChannel {
|
|
|
710
716
|
// seenThreads 无时间戳,仅限容量(话题持久存在,不按时间清理)
|
|
711
717
|
if (this.seenThreads.size > 1000)
|
|
712
718
|
this.seenThreads.clear();
|
|
713
|
-
//
|
|
714
|
-
if (
|
|
719
|
+
// 重写文件,去掉过期条目(仅在有记录被清理时才写)
|
|
720
|
+
if (cleaned > 0 && this.config.seenMsgFile) {
|
|
715
721
|
try {
|
|
716
|
-
|
|
717
|
-
.
|
|
718
|
-
|
|
719
|
-
|
|
722
|
+
if (this.seenMessages.size === 0) {
|
|
723
|
+
fs.unlinkSync(this.config.seenMsgFile);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
const lines = [...this.seenMessages.entries()]
|
|
727
|
+
.map(([id, ts]) => JSON.stringify({ id, ts }))
|
|
728
|
+
.join('\n') + '\n';
|
|
729
|
+
fs.writeFileSync(this.config.seenMsgFile, lines);
|
|
730
|
+
}
|
|
720
731
|
}
|
|
721
732
|
catch { }
|
|
722
733
|
}
|
|
@@ -1196,6 +1207,15 @@ export function buildResolvedV2(interaction, response) {
|
|
|
1196
1207
|
});
|
|
1197
1208
|
bodyElements.push({ tag: 'markdown', content: lines.join('\n') });
|
|
1198
1209
|
}
|
|
1210
|
+
// CommandCard: 显示原有按钮列表(保留上下文)
|
|
1211
|
+
if (kind.kind === 'command-card' && kind.buttons?.length) {
|
|
1212
|
+
const lines = kind.buttons.map(btn => {
|
|
1213
|
+
const prefix = btn.command === action ? '✓' : '•';
|
|
1214
|
+
const cleanLabel = btn.label.replace(/^✓\s*/, '');
|
|
1215
|
+
return `${prefix} ${cleanLabel}`;
|
|
1216
|
+
});
|
|
1217
|
+
bodyElements.push({ tag: 'markdown', content: lines.join('\n') });
|
|
1218
|
+
}
|
|
1199
1219
|
return {
|
|
1200
1220
|
toast: { type: 'success', content: statusText },
|
|
1201
1221
|
card: {
|
|
@@ -1403,7 +1423,7 @@ export class FeishuChannelPlugin {
|
|
|
1403
1423
|
const adapter = {
|
|
1404
1424
|
channelName: inst.name,
|
|
1405
1425
|
channelKey: inst.name,
|
|
1406
|
-
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true },
|
|
1426
|
+
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true, thread: true },
|
|
1407
1427
|
send: async (envelope, payload) => {
|
|
1408
1428
|
const ctx = envelope.replyContext;
|
|
1409
1429
|
const channelId = envelope.channelId;
|
|
@@ -1417,6 +1437,8 @@ export class FeishuChannelPlugin {
|
|
|
1417
1437
|
const sendCtx = { ...(ctx ?? {}) };
|
|
1418
1438
|
if (payload.kind === 'result.text' && payload.isFinal)
|
|
1419
1439
|
sendCtx.title = '✅ 最终回复:';
|
|
1440
|
+
if (ctx?.metadata?.onThreadCreated)
|
|
1441
|
+
sendCtx.onThreadCreated = ctx.metadata.onThreadCreated;
|
|
1420
1442
|
await channel.sendMessage(channelId, payload.text, sendCtx);
|
|
1421
1443
|
return;
|
|
1422
1444
|
}
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -335,7 +335,7 @@ export class QQBotChannelPlugin {
|
|
|
335
335
|
const adapter = {
|
|
336
336
|
channelName: inst.name,
|
|
337
337
|
channelKey: inst.name,
|
|
338
|
-
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
338
|
+
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
|
|
339
339
|
send: async (envelope, payload) => {
|
|
340
340
|
const ctx = envelope.replyContext;
|
|
341
341
|
const channelId = envelope.channelId;
|
package/dist/channels/wechat.js
CHANGED
|
@@ -731,7 +731,7 @@ export class WechatChannelPlugin {
|
|
|
731
731
|
const adapter = {
|
|
732
732
|
channelName: inst.name,
|
|
733
733
|
channelKey: inst.name,
|
|
734
|
-
capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true },
|
|
734
|
+
capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true, thread: false },
|
|
735
735
|
send: async (envelope, payload) => {
|
|
736
736
|
const channelId = envelope.channelId;
|
|
737
737
|
switch (payload.kind) {
|
package/dist/channels/wecom.js
CHANGED
|
@@ -491,7 +491,7 @@ export class WecomChannelPlugin {
|
|
|
491
491
|
const adapter = {
|
|
492
492
|
channelName: inst.name,
|
|
493
493
|
channelKey: inst.name,
|
|
494
|
-
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
494
|
+
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
|
|
495
495
|
send: async (envelope, payload) => {
|
|
496
496
|
const ctx = envelope.replyContext;
|
|
497
497
|
const channelId = envelope.channelId;
|
package/dist/cli/agent.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import readline from 'readline';
|
|
5
4
|
import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
|
|
6
5
|
import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
|
|
@@ -11,11 +10,6 @@ import { commandExists } from '../utils/cross-platform.js';
|
|
|
11
10
|
import { isCodexSdkAvailable } from '../agents/codex-runner.js';
|
|
12
11
|
// ==================== Helpers ====================
|
|
13
12
|
const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
|
|
14
|
-
const BASEAGENT_ENV_KEY = {
|
|
15
|
-
claude: 'ANTHROPIC_API_KEY',
|
|
16
|
-
codex: 'OPENAI_API_KEY',
|
|
17
|
-
gemini: 'GEMINI_API_KEY',
|
|
18
|
-
};
|
|
19
13
|
function isBaseagentAvailable(baseagent) {
|
|
20
14
|
if (baseagent === 'codex')
|
|
21
15
|
return isCodexSdkAvailable();
|
|
@@ -30,8 +24,7 @@ function pickDefaultBaseagent(available) {
|
|
|
30
24
|
return available.includes('claude') ? 'claude' : available[0];
|
|
31
25
|
}
|
|
32
26
|
function buildBaseagentsBlock(chosen) {
|
|
33
|
-
|
|
34
|
-
return { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} };
|
|
27
|
+
return { [chosen]: {} };
|
|
35
28
|
}
|
|
36
29
|
const DEFAULT_CHATMODE = { private: 'interactive', group: 'proactive', nothuman: 'proactive' };
|
|
37
30
|
const DEFAULT_DISPATCH = 'mention';
|
|
@@ -296,11 +289,11 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
296
289
|
const defaults = loadDefaults();
|
|
297
290
|
const rootPath = defaults?.projects?.rootPath
|
|
298
291
|
|| (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
|
|
299
|
-
||
|
|
292
|
+
|| resolvePaths().root + '/projects';
|
|
300
293
|
suggestedProjectPath = deriveAgentProjectPath(rootPath, aid);
|
|
301
294
|
}
|
|
302
295
|
catch {
|
|
303
|
-
suggestedProjectPath = deriveAgentProjectPath(
|
|
296
|
+
suggestedProjectPath = deriveAgentProjectPath(resolvePaths().root + '/projects', aid);
|
|
304
297
|
}
|
|
305
298
|
const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
|
|
306
299
|
const projectPath = projectInput || suggestedProjectPath;
|
|
@@ -633,7 +626,7 @@ export async function agentSyncAids() {
|
|
|
633
626
|
const defaults = loadDefaults();
|
|
634
627
|
const rootPath = defaults?.projects?.rootPath
|
|
635
628
|
|| (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
|
|
636
|
-
||
|
|
629
|
+
|| resolvePaths().root + '/projects';
|
|
637
630
|
const created = [];
|
|
638
631
|
for (const aid of localAids) {
|
|
639
632
|
if (existingAids.has(aid))
|
|
@@ -761,6 +754,12 @@ export async function agentSet(aid, key, rawValue) {
|
|
|
761
754
|
return { ok: false, error: `Failed to read config: ${e?.message || e}` };
|
|
762
755
|
}
|
|
763
756
|
const value = parseJsonValue(rawValue);
|
|
757
|
+
// active_baseagent 白名单校验:只允许已知 baseagent,挡住把模型名(如 deepseek)误设为后端
|
|
758
|
+
if (key === 'active_baseagent') {
|
|
759
|
+
if (typeof value !== 'string' || !BASEAGENT_CANDIDATES.includes(value)) {
|
|
760
|
+
return { ok: false, error: `无效 active_baseagent: ${JSON.stringify(value)}(可选: ${BASEAGENT_CANDIDATES.join(' / ')})` };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
764
763
|
setNestedValue(config, key, value);
|
|
765
764
|
try {
|
|
766
765
|
saveAgent(config);
|
package/dist/cli/bench.js
CHANGED
|
@@ -8,7 +8,7 @@ import { aidList, aidCreate } from '../aun/aid/identity.js';
|
|
|
8
8
|
import { msgSend, msgPull } from '../aun/msg/index.js';
|
|
9
9
|
import { getPackageRoot, aunPath as defaultAunPath } from '../paths.js';
|
|
10
10
|
import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
|
|
11
|
-
import { isHelpFlag } from './help.js';
|
|
11
|
+
import { isHelpFlag, getArgValue } from './help.js';
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
// ==================== ANSI ====================
|
|
14
14
|
const GREEN = '\x1b[32m';
|
|
@@ -132,10 +132,6 @@ function percentile(sorted, p) {
|
|
|
132
132
|
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
133
133
|
return sorted[Math.max(0, idx)];
|
|
134
134
|
}
|
|
135
|
-
function getArgValue(args, flag) {
|
|
136
|
-
const idx = args.indexOf(flag);
|
|
137
|
-
return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
138
|
-
}
|
|
139
135
|
// ==================== Promise Pool ====================
|
|
140
136
|
function withTimeout(promise, ms, label) {
|
|
141
137
|
return new Promise((resolve, reject) => {
|
package/dist/cli/help.js
CHANGED
|
@@ -21,3 +21,11 @@ export function wantsHelp(args) {
|
|
|
21
21
|
return true;
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* 取出 `--flag <value>` 形式的参数值。
|
|
26
|
+
* flag 不存在或其后无值时返回 undefined。
|
|
27
|
+
*/
|
|
28
|
+
export function getArgValue(args, flag) {
|
|
29
|
+
const idx = args.indexOf(flag);
|
|
30
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
31
|
+
}
|