@kafca/agentdock 0.1.22 → 0.1.24
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 +4 -0
- package/dist/renderer/assets/{Badge-B_rgzxYO.js → Badge-CXcUMqb-.js} +1 -1
- package/dist/renderer/assets/{Chat-CYtp6m7T.js → Chat-BRxdPzi-.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-DOBMwa9d.js → ChatMarkdown-sWGBIfIb.js} +1 -1
- package/dist/renderer/assets/{Config-DppMSPHF.js → Config-DqxuxIGg.js} +1 -1
- package/dist/renderer/assets/{CronList-BDtB1SuA.js → CronList-OgHxbti5.js} +1 -1
- package/dist/renderer/assets/{Dashboard-iFlRnd7Y.js → Dashboard-DmklXiH1.js} +2 -2
- package/dist/renderer/assets/{EmptyState-bKDt_T88.js → EmptyState-BvDmi8dq.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-CKPhKPIg.js → KnowledgeDetail-Bj59Hdpo.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-CFeJre6i.js → KnowledgeHome-CigQ-nSo.js} +3 -3
- package/dist/renderer/assets/{Logs-CyxSVOFK.js → Logs-Bfc7gBpY.js} +1 -1
- package/dist/renderer/assets/{Modal-CxPnk1Ey.js → Modal-CfmiFknz.js} +1 -1
- package/dist/renderer/assets/{Page-kU9vubzj.js → Page-BxtLEw_b.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-D9FY9NWZ.js → ProjectDetail-CcUphxGm.js} +1 -1
- package/dist/renderer/assets/{ProjectList-DIwr2TXg.js → ProjectList-CVkvDIfy.js} +1 -1
- package/dist/renderer/assets/{Select-BaWoz5Dn.js → Select-DgQMZ0MD.js} +1 -1
- package/dist/renderer/assets/{SessionChat-Cm9CW8Zx.js → SessionChat-CRmGKfBx.js} +1 -1
- package/dist/renderer/assets/{SessionList-UPnWlXXO.js → SessionList-D0E8XbYw.js} +1 -1
- package/dist/renderer/assets/ThreadChat-DUgqZSBB.js +23 -0
- package/dist/renderer/assets/Workspace-Q4g4rlRT.js +14 -0
- package/dist/renderer/assets/{arrow-left-BmZAVGV7.js → arrow-left-BySN_zhF.js} +1 -1
- package/dist/renderer/assets/{book-open-DZ3GXFgI.js → book-open-BGlrnrTc.js} +1 -1
- package/dist/renderer/assets/{check-CeVSvDCv.js → check-BiJogEa5.js} +1 -1
- package/dist/renderer/assets/{chevron-down-yryuMoQ-.js → chevron-down-D2fHRJv6.js} +1 -1
- package/dist/renderer/assets/{circle-eASsFISH.js → circle-CIcjixmI.js} +1 -1
- package/dist/renderer/assets/desktop-DAXY9Bxv.js +4 -0
- package/dist/renderer/assets/desktop-DyA_NYew.js +1 -0
- package/dist/renderer/assets/{funnel-pwAe2Ixp.js → funnel-BUzwb-id.js} +1 -1
- package/dist/renderer/assets/{heart-B2Sp-bCm.js → heart-DZEQ1z_V.js} +1 -1
- package/dist/renderer/assets/{index-BC3MWbS1.js → index-CB_KyLOr.js} +39 -39
- package/dist/renderer/assets/{index-Cse2Br64.js → index-DgsIo4vJ.js} +3 -3
- package/dist/renderer/assets/{pencil-BfEunUF3.js → pencil-DGL4agcl.js} +1 -1
- package/dist/renderer/assets/{play-Dcs9Lj_Z.js → play-DzasCLM4.js} +1 -1
- package/dist/renderer/assets/{plug-B_YschQe.js → plug-dubNMe_m.js} +1 -1
- package/dist/renderer/assets/{plus-Pqo-cXHQ.js → plus-DN4kML4k.js} +1 -1
- package/dist/renderer/assets/projects-migPtfRe.js +1 -0
- package/dist/renderer/assets/{save-BKM5yi1A.js → save-CNIc9tjl.js} +1 -1
- package/dist/renderer/assets/{search-BU42h10M.js → search-C0EGbF3I.js} +1 -1
- package/dist/renderer/assets/{server-DA5t3Rtr.js → server-BMxQ4zmc.js} +1 -1
- package/dist/renderer/assets/{session-utils-Cr9VhNSq.js → session-utils-Bf5O-iMi.js} +1 -1
- package/dist/renderer/assets/{sessions-bvV8ZMfX.js → sessions-BMSs6rEI.js} +1 -1
- package/dist/renderer/assets/{shield-check-BeOJKur_.js → shield-check-i3Tf1TUz.js} +1 -1
- package/dist/renderer/assets/{trash-2-CQ7HKO3q.js → trash-2-SKrcp18f.js} +1 -1
- package/dist/renderer/assets/{user-CtFjY9SV.js → user-cNUZddjZ.js} +1 -1
- package/dist/renderer/assets/{wifi-off-Ci5m0ysf.js → wifi-off-Dyb84ANR.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/electron/channel-content.test.js +29 -0
- package/dist-electron/electron/lac-cli.test.js +49 -4
- package/dist-electron/electron/local-core-acp-content.test.js +39 -0
- package/dist-electron/electron/local-core-contracts.test.js +67 -0
- package/dist-electron/electron/local-core-refactor.test.js +628 -0
- package/dist-electron/electron/plugin-kernel.test.js +74 -14
- package/dist-electron/electron/workspace-task-store.test.js +124 -0
- package/dist-electron/packages/contracts/src/local-core.js +110 -0
- package/dist-electron/packages/core-sdk/src/index.js +40 -20
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-content.js +25 -4
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +3 -2
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +44 -28
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +5 -1
- package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +15 -6
- package/dist-electron/services/local-ai-core/src/channel/lark/cards.js +210 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/config.js +54 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +280 -496
- package/dist-electron/services/local-ai-core/src/channel/lark/registration.js +50 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +191 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/types.js +2 -0
- package/dist-electron/services/local-ai-core/src/channel/shared/content.js +23 -0
- package/dist-electron/services/local-ai-core/src/channel/shared/plugin.js +31 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/config.js +125 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +164 -352
- package/dist-electron/services/local-ai-core/src/channel/weixin/transport.js +169 -0
- package/dist-electron/services/local-ai-core/src/channel/weixin/types.js +2 -0
- package/dist-electron/services/local-ai-core/src/cli/lac.js +19 -16
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +5 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +18 -39
- package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +18 -39
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +55 -44
- package/dist-electron/services/local-ai-core/src/runtime/server.js +10 -8
- package/dist-electron/services/local-ai-core/src/scheduler/job-id.js +15 -0
- package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +30 -5
- package/dist-electron/shared/desktop.js +37 -5
- package/dist-electron/src/pages/Threads/thread-chat-model.js +57 -0
- package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +70 -0
- package/package.json +1 -1
- package/dist/renderer/assets/ThreadChat-YvVsACW3.js +0 -23
- package/dist/renderer/assets/Workspace-ST7V8u6T.js +0 -14
- package/dist/renderer/assets/desktop-Che1cbpE.js +0 -4
- package/dist/renderer/assets/desktop-DkrP_s-b.js +0 -1
- package/dist/renderer/assets/projects-BCj0k_Gy.js +0 -1
package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js
CHANGED
|
@@ -4,19 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.LocalCoreWeixinGateway = void 0;
|
|
7
|
+
exports.createWeixinAttachmentContentPart = createWeixinAttachmentContentPart;
|
|
7
8
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
11
|
const node_events_1 = require("node:events");
|
|
11
12
|
const node_crypto_2 = require("node:crypto");
|
|
12
13
|
const desktop_js_1 = require("../../../../../shared/desktop.js");
|
|
14
|
+
const content_js_1 = require("../shared/content.js");
|
|
13
15
|
const file_utils_js_1 = require("../shared/file-utils.js");
|
|
16
|
+
const config_js_1 = require("./config.js");
|
|
17
|
+
const transport_js_1 = require("./transport.js");
|
|
14
18
|
// ==================== Constants ====================
|
|
15
19
|
const PAIRING_EXPIRY_MS = 10 * 60 * 1000;
|
|
16
|
-
const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
|
|
17
|
-
const DEFAULT_CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';
|
|
18
|
-
const LONG_POLL_TIMEOUT_MS = 35_000;
|
|
19
|
-
const API_TIMEOUT_MS = 15_000;
|
|
20
20
|
const RETRY_DELAY_MS = 2_000;
|
|
21
21
|
const BACKOFF_DELAY_MS = 30_000;
|
|
22
22
|
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
@@ -26,15 +26,7 @@ const WEIXIN_CONTEXT_REPLY_MAX_BYTES = 3500;
|
|
|
26
26
|
const WEIXIN_CONTEXT_SEND_LIMIT = 10;
|
|
27
27
|
const WEIXIN_RESERVED_TERMINAL_SENDS = 1;
|
|
28
28
|
const WEIXIN_PROGRESS_SEND_BUDGET = WEIXIN_CONTEXT_SEND_LIMIT - WEIXIN_RESERVED_TERMINAL_SENDS;
|
|
29
|
-
const WEIXIN_CHANNEL_VERSION = '2.1.7';
|
|
30
|
-
const WEIXIN_ILINK_APP_ID = 'bot';
|
|
31
|
-
const WEIXIN_ILINK_APP_CLIENT_VERSION = '131335';
|
|
32
29
|
const RESPONSE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
33
|
-
const TEXT_ITEM_TYPE = 1;
|
|
34
|
-
const IMAGE_ITEM_TYPE = 2;
|
|
35
|
-
const VOICE_ITEM_TYPE = 3;
|
|
36
|
-
const FILE_ITEM_TYPE = 4;
|
|
37
|
-
const UPLOAD_MEDIA_TYPE_FILE = 3;
|
|
38
30
|
const WEIXIN_MAX_UPLOAD_FILE_SIZE = 30 * 1024 * 1024;
|
|
39
31
|
// ==================== Utilities ====================
|
|
40
32
|
function stripHtml(html) {
|
|
@@ -60,18 +52,6 @@ function sleep(ms, signal) {
|
|
|
60
52
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
61
53
|
});
|
|
62
54
|
}
|
|
63
|
-
function safeFilePart(value) {
|
|
64
|
-
return String(value || 'default').replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 80) || 'default';
|
|
65
|
-
}
|
|
66
|
-
function createWechatUin() {
|
|
67
|
-
return Buffer.from(String(node_crypto_1.default.randomInt(0, 0xffffffff))).toString('base64');
|
|
68
|
-
}
|
|
69
|
-
function createIlinkHeaders() {
|
|
70
|
-
return {
|
|
71
|
-
'iLink-App-Id': WEIXIN_ILINK_APP_ID,
|
|
72
|
-
'iLink-App-ClientVersion': WEIXIN_ILINK_APP_CLIENT_VERSION,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
55
|
function utf8ByteLength(value) {
|
|
76
56
|
return Buffer.byteLength(value, 'utf-8');
|
|
77
57
|
}
|
|
@@ -119,6 +99,23 @@ function splitTextByUtf8Bytes(text, maxBytes) {
|
|
|
119
99
|
pushCurrent();
|
|
120
100
|
return chunks;
|
|
121
101
|
}
|
|
102
|
+
function createWeixinAttachmentContentPart(att) {
|
|
103
|
+
if (att.kind === 'image') {
|
|
104
|
+
if (!att.data)
|
|
105
|
+
return null;
|
|
106
|
+
return {
|
|
107
|
+
type: 'image',
|
|
108
|
+
data: att.data,
|
|
109
|
+
mimeType: att.mimeType,
|
|
110
|
+
fileName: att.name,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
type: 'file',
|
|
115
|
+
path: att.path,
|
|
116
|
+
fileName: att.name,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
122
119
|
function truncateTextByUtf8Bytes(text, maxBytes) {
|
|
123
120
|
const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
124
121
|
if (utf8ByteLength(normalized) <= maxBytes)
|
|
@@ -163,21 +160,25 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
163
160
|
async refreshBindings() {
|
|
164
161
|
const config = await this.options.readConfig();
|
|
165
162
|
const bindings = this.collectBindings(config);
|
|
166
|
-
const
|
|
167
|
-
for (const
|
|
168
|
-
if (!
|
|
169
|
-
await this.
|
|
163
|
+
const nextKeys = new Set(bindings.map((b) => (0, config_js_1.runtimeKey)(b.workspaceId, b.instanceId)));
|
|
164
|
+
for (const key of [...this.runtime.keys()]) {
|
|
165
|
+
if (!nextKeys.has(key)) {
|
|
166
|
+
await this.stopWorkspaceKey(key);
|
|
170
167
|
}
|
|
171
168
|
}
|
|
172
169
|
for (const binding of bindings) {
|
|
173
|
-
const
|
|
170
|
+
const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
|
|
171
|
+
const current = this.runtime.get(key);
|
|
174
172
|
if (!binding.enabled) {
|
|
175
173
|
if (current) {
|
|
176
|
-
await this.
|
|
174
|
+
await this.stopWorkspaceKey(key);
|
|
177
175
|
}
|
|
178
176
|
else {
|
|
179
|
-
this.runtime.set(
|
|
177
|
+
this.runtime.set(key, {
|
|
180
178
|
workspaceId: binding.workspaceId,
|
|
179
|
+
instanceId: binding.instanceId,
|
|
180
|
+
displayName: binding.displayName,
|
|
181
|
+
platformKey: binding.platformKey,
|
|
181
182
|
enabled: false,
|
|
182
183
|
status: 'disabled',
|
|
183
184
|
connected: false,
|
|
@@ -193,35 +194,39 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
193
194
|
}
|
|
194
195
|
this.notifyRuntimeStateChanged();
|
|
195
196
|
}
|
|
196
|
-
async testConnection(workspaceId) {
|
|
197
|
-
const binding = await this.getBinding(workspaceId);
|
|
197
|
+
async testConnection(workspaceId, instanceId) {
|
|
198
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
198
199
|
try {
|
|
199
|
-
const bufPath =
|
|
200
|
+
const bufPath = (0, config_js_1.getWeixinBufPath)(binding);
|
|
200
201
|
node_fs_1.default.accessSync(bufPath);
|
|
201
|
-
return { success: true, platform: 'weixin', workspaceId, appId: binding.accountId };
|
|
202
|
+
return { success: true, platform: 'weixin', workspaceId, instanceId: binding.instanceId, appId: binding.accountId };
|
|
202
203
|
}
|
|
203
204
|
catch {
|
|
204
|
-
return { success: false, platform: 'weixin', workspaceId, error: 'No sync buf found for account. Ensure the plugin has been connected at least once.' };
|
|
205
|
+
return { success: false, platform: 'weixin', workspaceId, instanceId: binding.instanceId, error: 'No sync buf found for account. Ensure the plugin has been connected at least once.' };
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
|
-
async enable(workspaceId) {
|
|
208
|
-
const binding = await this.getBinding(workspaceId);
|
|
208
|
+
async enable(workspaceId, instanceId) {
|
|
209
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
209
210
|
await this.startWorkspace(binding);
|
|
210
|
-
return this.getStatus(workspaceId);
|
|
211
|
+
return this.getStatus(workspaceId, binding.instanceId);
|
|
211
212
|
}
|
|
212
|
-
async disable(workspaceId) {
|
|
213
|
-
await this.stopWorkspace(workspaceId);
|
|
214
|
-
return this.getStatus(workspaceId);
|
|
213
|
+
async disable(workspaceId, instanceId) {
|
|
214
|
+
await this.stopWorkspace(workspaceId, instanceId);
|
|
215
|
+
return this.getStatus(workspaceId, instanceId);
|
|
215
216
|
}
|
|
216
|
-
getStatus(workspaceId) {
|
|
217
|
+
getStatus(workspaceId, instanceId) {
|
|
217
218
|
this.options.store.expirePendingPairings();
|
|
218
|
-
const
|
|
219
|
+
const resolved = this.resolveRuntimeState(workspaceId, instanceId);
|
|
220
|
+
const binding = resolved.state;
|
|
221
|
+
const platformKey = binding?.platformKey || (0, config_js_1.channelPlatformKey)('weixin', resolved.instanceId);
|
|
219
222
|
const pairings = this.options.store.listPendingPairings(workspaceId)
|
|
220
|
-
.filter((row) => row.platform ===
|
|
221
|
-
const users = this.options.store.listAuthorizedUsers(workspaceId,
|
|
223
|
+
.filter((row) => row.platform === platformKey && row.expires_at >= new Date().toISOString());
|
|
224
|
+
const users = this.options.store.listAuthorizedUsers(workspaceId, platformKey);
|
|
222
225
|
return {
|
|
223
226
|
workspaceId,
|
|
224
227
|
platform: 'weixin',
|
|
228
|
+
instanceId: resolved.instanceId,
|
|
229
|
+
displayName: binding?.displayName,
|
|
225
230
|
enabled: Boolean(binding?.enabled),
|
|
226
231
|
connected: Boolean(binding?.connected),
|
|
227
232
|
status: binding?.status || 'disabled',
|
|
@@ -233,16 +238,20 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
233
238
|
};
|
|
234
239
|
}
|
|
235
240
|
listStatuses() {
|
|
236
|
-
return [...this.runtime.
|
|
241
|
+
return [...this.runtime.values()]
|
|
242
|
+
.sort((a, b) => `${a.workspaceId}:${a.instanceId}`.localeCompare(`${b.workspaceId}:${b.instanceId}`))
|
|
243
|
+
.map((state) => this.getStatus(state.workspaceId, state.instanceId));
|
|
237
244
|
}
|
|
238
245
|
listPendingPairings(workspaceId) {
|
|
239
246
|
this.options.store.expirePendingPairings();
|
|
240
247
|
return this.options.store
|
|
241
|
-
.listPairingRequests(workspaceId
|
|
248
|
+
.listPairingRequests(workspaceId)
|
|
249
|
+
.filter((item) => item.platform === 'weixin' || item.platform.startsWith('weixin:'))
|
|
242
250
|
.filter((item) => item.status === 'pending' && item.expiresAt >= new Date().toISOString());
|
|
243
251
|
}
|
|
244
252
|
listAuthorizedUsers(workspaceId) {
|
|
245
|
-
return this.options.store.listAuthorizedUsers(workspaceId
|
|
253
|
+
return this.options.store.listAuthorizedUsers(workspaceId)
|
|
254
|
+
.filter((item) => item.platform === 'weixin' || item.platform.startsWith('weixin:'));
|
|
246
255
|
}
|
|
247
256
|
async start() {
|
|
248
257
|
await this.refreshBindings();
|
|
@@ -251,12 +260,12 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
251
260
|
this.close();
|
|
252
261
|
}
|
|
253
262
|
close() {
|
|
254
|
-
return Promise.all([...this.runtime.keys()].map((id) => this.
|
|
263
|
+
return Promise.all([...this.runtime.keys()].map((id) => this.stopWorkspaceKey(id))).then(() => undefined);
|
|
255
264
|
}
|
|
256
265
|
// ==================== QR Code Login ====================
|
|
257
|
-
async getQrCode(workspaceId) {
|
|
258
|
-
const binding = await this.getBinding(workspaceId);
|
|
259
|
-
const data = await
|
|
266
|
+
async getQrCode(workspaceId, instanceId) {
|
|
267
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
268
|
+
const data = await (0, transport_js_1.weixinApiGet)(binding, `ilink/bot/get_bot_qrcode?bot_type=3`);
|
|
260
269
|
if (data.errcode && data.errcode !== 0) {
|
|
261
270
|
throw new Error(`Failed to get QR code: ${data.errmsg || data.errcode}`);
|
|
262
271
|
}
|
|
@@ -264,11 +273,11 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
264
273
|
throw new Error('No QR code returned from iLink');
|
|
265
274
|
}
|
|
266
275
|
const qrCodeUrl = data.qrcode_img_content || `${binding.baseUrl.replace(/\/$/, '')}/ilink/bot/qr_code/${data.qrcode}`;
|
|
267
|
-
return { ticket: data.qrcode, expiresIn: data.expired || 180, qrCodeUrl };
|
|
276
|
+
return { ticket: data.qrcode, expiresIn: data.expired || 180, qrCodeUrl, instanceId: binding.instanceId, displayName: binding.displayName };
|
|
268
277
|
}
|
|
269
|
-
async checkQrCodeStatus(workspaceId, ticket) {
|
|
270
|
-
const binding = await this.getBinding(workspaceId);
|
|
271
|
-
const data = await
|
|
278
|
+
async checkQrCodeStatus(workspaceId, ticket, instanceId) {
|
|
279
|
+
const binding = await this.getBinding(workspaceId, instanceId);
|
|
280
|
+
const data = await (0, transport_js_1.weixinApiGet)(binding, `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(ticket)}`);
|
|
272
281
|
if (data.errcode && data.errcode !== 0) {
|
|
273
282
|
throw new Error(`Failed to check QR code status: ${data.errmsg || data.errcode}`);
|
|
274
283
|
}
|
|
@@ -277,14 +286,14 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
277
286
|
if (!data.bot_token) {
|
|
278
287
|
throw new Error('WeChat QR code confirmed but no bot token was returned.');
|
|
279
288
|
}
|
|
280
|
-
|
|
289
|
+
(0, config_js_1.saveWeixinCredentials)(binding, {
|
|
281
290
|
token: data.bot_token,
|
|
282
291
|
baseUrl: data.baseurl || binding.baseUrl,
|
|
283
292
|
botId: data.ilink_bot_id,
|
|
284
293
|
userId: data.ilink_user_id || data.user_id,
|
|
285
294
|
savedAt: new Date().toISOString(),
|
|
286
295
|
});
|
|
287
|
-
await this.startWorkspace(await this.getBinding(workspaceId));
|
|
296
|
+
await this.startWorkspace(await this.getBinding(workspaceId, binding.instanceId));
|
|
288
297
|
}
|
|
289
298
|
if (!['wait', 'signed', 'confirmed', 'expired'].includes(status)) {
|
|
290
299
|
return { status: 'wait' };
|
|
@@ -301,7 +310,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
301
310
|
const pairing = this.options.store.getPairingRequest(code);
|
|
302
311
|
if (!pairing)
|
|
303
312
|
throw new Error(`Pairing code not found: ${code}`);
|
|
304
|
-
if (pairing.platform !== 'weixin')
|
|
313
|
+
if (pairing.platform !== 'weixin' && !pairing.platform.startsWith('weixin:'))
|
|
305
314
|
throw new Error(`Pairing code ${code} is not a WeChat pairing`);
|
|
306
315
|
if (pairing.status !== 'pending')
|
|
307
316
|
throw new Error(`Pairing code ${code} is already ${pairing.status}`);
|
|
@@ -309,13 +318,13 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
309
318
|
this.options.store.updatePairingStatus(code, 'expired');
|
|
310
319
|
throw new Error(`Pairing code ${code} has expired`);
|
|
311
320
|
}
|
|
312
|
-
const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id,
|
|
321
|
+
const existing = this.options.store.getAuthorizedUser(pairing.workspace_id, pairing.platform_user_id, pairing.platform);
|
|
313
322
|
const userId = existing?.id || `wx-user-${(0, node_crypto_2.randomUUID)()}`;
|
|
314
323
|
const authorizedAt = new Date().toISOString();
|
|
315
324
|
this.options.store.createAuthorizedUser({
|
|
316
325
|
id: userId,
|
|
317
326
|
workspace_id: pairing.workspace_id,
|
|
318
|
-
platform:
|
|
327
|
+
platform: pairing.platform,
|
|
319
328
|
platform_user_id: pairing.platform_user_id,
|
|
320
329
|
chat_id: pairing.chat_id,
|
|
321
330
|
display_name: pairing.display_name,
|
|
@@ -324,7 +333,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
324
333
|
});
|
|
325
334
|
this.options.store.updatePairingStatus(code, 'approved');
|
|
326
335
|
this.notifyRuntimeStateChanged();
|
|
327
|
-
const user = this.options.store.listAuthorizedUsers(pairing.workspace_id,
|
|
336
|
+
const user = this.options.store.listAuthorizedUsers(pairing.workspace_id, pairing.platform).find((e) => e.id === userId);
|
|
328
337
|
if (!user)
|
|
329
338
|
throw new Error('Authorized user lookup failed after approval');
|
|
330
339
|
return user;
|
|
@@ -333,7 +342,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
333
342
|
const pairing = this.options.store.getPairingRequest(code);
|
|
334
343
|
if (!pairing)
|
|
335
344
|
throw new Error(`Pairing code not found: ${code}`);
|
|
336
|
-
if (pairing.platform !== 'weixin')
|
|
345
|
+
if (pairing.platform !== 'weixin' && !pairing.platform.startsWith('weixin:'))
|
|
337
346
|
throw new Error(`Pairing code ${code} is not a WeChat pairing`);
|
|
338
347
|
this.options.store.updatePairingStatus(code, 'rejected');
|
|
339
348
|
this.notifyRuntimeStateChanged();
|
|
@@ -363,12 +372,14 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
363
372
|
this.options.log?.(`localcore-weixin bridge route miss for sessionKey=${sessionKey} type=${event.type}`);
|
|
364
373
|
return;
|
|
365
374
|
}
|
|
366
|
-
const
|
|
375
|
+
const routeInstanceId = route.instanceId || 'default';
|
|
376
|
+
const routePlatformKey = route.platformKey || (0, config_js_1.channelPlatformKey)('weixin', routeInstanceId);
|
|
377
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(route.workspaceId, routeInstanceId)) || this.runtime.get(route.workspaceId);
|
|
367
378
|
if (!state?.connected) {
|
|
368
379
|
this.options.log?.(`localcore-weixin bridge event ignored because workspace is not connected: ${route.workspaceId}`);
|
|
369
380
|
return;
|
|
370
381
|
}
|
|
371
|
-
const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId,
|
|
382
|
+
const initialBinding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
|
|
372
383
|
if (!initialBinding) {
|
|
373
384
|
this.options.log?.(`localcore-weixin bridge binding miss for workspace=${route.workspaceId}`);
|
|
374
385
|
return;
|
|
@@ -386,7 +397,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
386
397
|
const current = previous
|
|
387
398
|
.catch(() => undefined)
|
|
388
399
|
.then(async () => {
|
|
389
|
-
const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId,
|
|
400
|
+
const binding = this.options.store.getPlatformThreadBinding(route.workspaceId, route.chatId, route.platformUserId, routePlatformKey);
|
|
390
401
|
if (!binding)
|
|
391
402
|
return;
|
|
392
403
|
if (this.mutedThreadBridgeCounts.has(binding.thread_id))
|
|
@@ -441,7 +452,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
441
452
|
return this.sendScheduledMessage(workspaceId, { type: 'channel.chat', channelId: chatId }, text);
|
|
442
453
|
}
|
|
443
454
|
async sendScheduledMessage(workspaceId, route, text) {
|
|
444
|
-
const state = this.
|
|
455
|
+
const state = this.resolveRuntimeState(workspaceId, route.instanceId).state;
|
|
445
456
|
if (!state?.connected) {
|
|
446
457
|
this.options.log?.(`localcore-weixin scheduled message skipped: workspace not connected: ${workspaceId}`);
|
|
447
458
|
return '';
|
|
@@ -457,7 +468,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
457
468
|
}
|
|
458
469
|
}
|
|
459
470
|
async sendOutboundMessage(workspaceId, input) {
|
|
460
|
-
const state = this.
|
|
471
|
+
const state = this.resolveRuntimeState(workspaceId, input.route?.instanceId).state;
|
|
461
472
|
if (!state?.connected) {
|
|
462
473
|
throw new Error(`WeChat workspace is not connected: ${workspaceId}`);
|
|
463
474
|
}
|
|
@@ -465,8 +476,9 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
465
476
|
if (!channelId) {
|
|
466
477
|
throw new Error('Missing WeChat target channel id');
|
|
467
478
|
}
|
|
468
|
-
const binding = await this.getBinding(workspaceId);
|
|
469
|
-
const
|
|
479
|
+
const binding = await this.getBinding(workspaceId, input.route?.instanceId);
|
|
480
|
+
const platformKey = state.platformKey;
|
|
481
|
+
const contextToken = this.resolveContextTokenForFileSend(workspaceId, channelId, input.route.participantId, platformKey);
|
|
470
482
|
const messageIds = [];
|
|
471
483
|
const attachments = [];
|
|
472
484
|
for (const part of input.parts || []) {
|
|
@@ -528,6 +540,8 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
528
540
|
}
|
|
529
541
|
// ==================== Inbound Message Handling ====================
|
|
530
542
|
async handleInboundMessage(input) {
|
|
543
|
+
const instanceId = input.instanceId || 'default';
|
|
544
|
+
const platformKey = input.platformKey || (0, config_js_1.channelPlatformKey)('weixin', instanceId);
|
|
531
545
|
if (this.isDuplicateInboundMessage(input)) {
|
|
532
546
|
this.options.log?.(`localcore-weixin skipped duplicate inbound message workspace=${input.workspaceId} chat=${input.chatId} id=${input.contextToken || input.messageId}`);
|
|
533
547
|
return { paired: true, duplicate: true };
|
|
@@ -545,28 +559,28 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
545
559
|
},
|
|
546
560
|
});
|
|
547
561
|
this.options.store.expirePendingPairings();
|
|
548
|
-
const binding = await this.getBinding(input.workspaceId);
|
|
549
|
-
let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId,
|
|
562
|
+
const binding = await this.getBinding(input.workspaceId, instanceId);
|
|
563
|
+
let authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
|
|
550
564
|
if (!authorized) {
|
|
551
565
|
if (binding.allowFrom === '*') {
|
|
552
566
|
const authorizedAt = new Date().toISOString();
|
|
553
567
|
this.options.store.createAuthorizedUser({
|
|
554
568
|
id: `wx-user-${(0, node_crypto_2.randomUUID)()}`,
|
|
555
569
|
workspace_id: input.workspaceId,
|
|
556
|
-
platform:
|
|
570
|
+
platform: platformKey,
|
|
557
571
|
platform_user_id: input.platformUserId,
|
|
558
572
|
chat_id: input.chatId,
|
|
559
573
|
display_name: input.displayName,
|
|
560
574
|
thread_id: null,
|
|
561
575
|
authorized_at: authorizedAt,
|
|
562
576
|
});
|
|
563
|
-
authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId,
|
|
577
|
+
authorized = this.options.store.getAuthorizedUser(input.workspaceId, input.platformUserId, platformKey);
|
|
564
578
|
this.options.log?.(`localcore-weixin auto-approved user for ${input.workspaceId}: ${input.platformUserId}`);
|
|
565
579
|
this.notifyRuntimeStateChanged();
|
|
566
580
|
}
|
|
567
581
|
}
|
|
568
582
|
if (!authorized) {
|
|
569
|
-
const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform ===
|
|
583
|
+
const existingPending = this.options.store.listPendingPairings(input.workspaceId).find((item) => item.platform === platformKey && item.platform_user_id === input.platformUserId && item.chat_id === input.chatId && item.status === 'pending');
|
|
570
584
|
let pairingCode = existingPending?.code || '';
|
|
571
585
|
if (!existingPending) {
|
|
572
586
|
const now = new Date();
|
|
@@ -574,7 +588,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
574
588
|
this.options.store.createPairingRequest({
|
|
575
589
|
code: pairingCode,
|
|
576
590
|
workspace_id: input.workspaceId,
|
|
577
|
-
platform:
|
|
591
|
+
platform: platformKey,
|
|
578
592
|
platform_user_id: input.platformUserId,
|
|
579
593
|
chat_id: input.chatId,
|
|
580
594
|
display_name: input.displayName,
|
|
@@ -584,23 +598,23 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
584
598
|
});
|
|
585
599
|
this.notifyRuntimeStateChanged();
|
|
586
600
|
}
|
|
587
|
-
const state = this.runtime.get(input.workspaceId);
|
|
601
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(input.workspaceId, instanceId));
|
|
588
602
|
if (state?.connected) {
|
|
589
603
|
await this.sendTextMessage(state, input.chatId, `**已收到消息**\n\n当前账号还未授权接入这个工作区。\n请在桌面端完成审批后再次发送消息。\n\n配对码:\`${pairingCode}\``, input.contextToken);
|
|
590
604
|
}
|
|
591
605
|
return { paired: false };
|
|
592
606
|
}
|
|
593
607
|
const router = this.options.getWorkspaceRouter();
|
|
594
|
-
const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId,
|
|
608
|
+
const threadBinding = this.options.store.getPlatformThreadBinding(input.workspaceId, input.chatId, input.platformUserId, platformKey);
|
|
595
609
|
let threadId = threadBinding?.thread_id || authorized.thread_id || '';
|
|
596
610
|
if (!threadId) {
|
|
597
611
|
const thread = await router.createThread(input.workspaceId, input.displayName || `WeChat ${input.chatId}`);
|
|
598
612
|
threadId = thread.id;
|
|
599
|
-
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId,
|
|
613
|
+
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, threadId, platformKey);
|
|
600
614
|
const now = new Date().toISOString();
|
|
601
615
|
this.options.store.upsertPlatformThreadBinding({
|
|
602
616
|
workspace_id: input.workspaceId,
|
|
603
|
-
platform:
|
|
617
|
+
platform: platformKey,
|
|
604
618
|
chat_id: input.chatId,
|
|
605
619
|
platform_user_id: input.platformUserId,
|
|
606
620
|
thread_id: threadId,
|
|
@@ -611,13 +625,15 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
611
625
|
}
|
|
612
626
|
const sessionKey = router.getThreadSessionKey(threadId);
|
|
613
627
|
const normalizedText = String(input.text || '').trim().toLowerCase();
|
|
614
|
-
const permissionThreadId = (normalizedText === 'allow' || normalizedText === 'allow all' || normalizedText === 'deny') ? this.findAwaitingPermissionThreadId(input.workspaceId, input.chatId, input.platformUserId) : '';
|
|
628
|
+
const permissionThreadId = (normalizedText === 'allow' || normalizedText === 'allow all' || normalizedText === 'deny') ? this.findAwaitingPermissionThreadId(input.workspaceId, input.chatId, input.platformUserId, input.platformKey) : '';
|
|
615
629
|
if (permissionThreadId && permissionThreadId !== threadId) {
|
|
616
630
|
threadId = permissionThreadId;
|
|
617
631
|
}
|
|
618
632
|
const effectiveSessionKey = router.getThreadSessionKey(threadId);
|
|
619
633
|
this.threadRouting.set(effectiveSessionKey, {
|
|
620
634
|
workspaceId: input.workspaceId,
|
|
635
|
+
instanceId,
|
|
636
|
+
platformKey,
|
|
621
637
|
platformUserId: input.platformUserId,
|
|
622
638
|
chatId: input.chatId,
|
|
623
639
|
threadId,
|
|
@@ -628,10 +644,10 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
628
644
|
const title = slashCommand.args.join(' ').trim() || `${input.displayName || 'WeChat'} ${new Date().toLocaleTimeString()}`;
|
|
629
645
|
const nextThread = await router.createThread(input.workspaceId, title);
|
|
630
646
|
const now = new Date().toISOString();
|
|
631
|
-
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id,
|
|
647
|
+
this.options.store.updateAuthorizedUserThread(input.workspaceId, input.platformUserId, nextThread.id, platformKey);
|
|
632
648
|
this.options.store.upsertPlatformThreadBinding({
|
|
633
649
|
workspace_id: input.workspaceId,
|
|
634
|
-
platform:
|
|
650
|
+
platform: platformKey,
|
|
635
651
|
chat_id: input.chatId,
|
|
636
652
|
platform_user_id: input.platformUserId,
|
|
637
653
|
thread_id: nextThread.id,
|
|
@@ -641,11 +657,13 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
641
657
|
});
|
|
642
658
|
this.threadRouting.set(router.getThreadSessionKey(nextThread.id), {
|
|
643
659
|
workspaceId: input.workspaceId,
|
|
660
|
+
instanceId,
|
|
661
|
+
platformKey,
|
|
644
662
|
platformUserId: input.platformUserId,
|
|
645
663
|
chatId: input.chatId,
|
|
646
664
|
threadId: nextThread.id,
|
|
647
665
|
});
|
|
648
|
-
const st = this.runtime.get(input.workspaceId);
|
|
666
|
+
const st = this.runtime.get((0, config_js_1.runtimeKey)(input.workspaceId, instanceId));
|
|
649
667
|
if (st?.connected) {
|
|
650
668
|
await this.sendTextMessage(st, input.chatId, '**已开始新会话**', input.contextToken);
|
|
651
669
|
}
|
|
@@ -657,70 +675,49 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
657
675
|
await router.sendThreadAction(threadId, input.text);
|
|
658
676
|
return { paired: true, threadId };
|
|
659
677
|
}
|
|
660
|
-
this.options.store.updatePlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId, input.contextToken || input.messageId,
|
|
678
|
+
this.options.store.updatePlatformThreadMessageId(input.workspaceId, input.chatId, input.platformUserId, input.contextToken || input.messageId, platformKey);
|
|
661
679
|
const wrappedText = (0, desktop_js_1.wrapUserMessageWithSchedulerProtocol)(input.text);
|
|
662
|
-
await router.sendThreadMessage(threadId,
|
|
680
|
+
await router.sendThreadMessage(threadId, (0, content_js_1.createChannelThreadMessageInput)(wrappedText, input.contentParts));
|
|
663
681
|
return { paired: true, threadId };
|
|
664
682
|
}
|
|
665
683
|
// ==================== Private: Bindings ====================
|
|
666
|
-
|
|
684
|
+
resolveRuntimeState(workspaceId, instanceId) {
|
|
685
|
+
if (instanceId) {
|
|
686
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(workspaceId, instanceId)) || (instanceId === 'default' ? this.runtime.get(workspaceId) : undefined);
|
|
687
|
+
return { instanceId, state };
|
|
688
|
+
}
|
|
689
|
+
const states = [...this.runtime.values()].filter((entry) => entry.workspaceId === workspaceId);
|
|
690
|
+
const state = states.find((entry) => entry.instanceId === 'default') || states[0];
|
|
691
|
+
return { instanceId: state?.instanceId || 'default', state: state || this.runtime.get(workspaceId) };
|
|
692
|
+
}
|
|
693
|
+
async getBinding(workspaceId, instanceId) {
|
|
667
694
|
const config = await this.options.readConfig();
|
|
668
|
-
const
|
|
695
|
+
const bindings = this.collectBindings(config).filter((e) => e.workspaceId === workspaceId);
|
|
696
|
+
const binding = instanceId
|
|
697
|
+
? bindings.find((entry) => entry.instanceId === instanceId)
|
|
698
|
+
: bindings.find((entry) => entry.instanceId === 'default') || bindings[0];
|
|
669
699
|
if (!binding)
|
|
670
|
-
throw new Error(`No WeChat binding configured for workspace "${workspaceId}"`);
|
|
700
|
+
throw new Error(`No WeChat binding configured for workspace "${workspaceId}"${instanceId ? ` instance "${instanceId}"` : ''}`);
|
|
671
701
|
return binding;
|
|
672
702
|
}
|
|
673
703
|
collectBindings(config) {
|
|
674
|
-
|
|
675
|
-
return projects.flatMap((project) => {
|
|
676
|
-
const platforms = Array.isArray(project.platforms) ? project.platforms : [];
|
|
677
|
-
return platforms
|
|
678
|
-
.map((platform) => ({
|
|
679
|
-
platformType: (0, desktop_js_1.normalizeDesktopPlatformType)(platform?.type),
|
|
680
|
-
options: platform?.options && typeof platform.options === 'object'
|
|
681
|
-
? platform.options
|
|
682
|
-
: {},
|
|
683
|
-
}))
|
|
684
|
-
.filter((p) => p.platformType === 'weixin')
|
|
685
|
-
.map((p) => {
|
|
686
|
-
const stateDir = String(p.options.state_dir || this.getDefaultStateDir()).trim();
|
|
687
|
-
const credentials = this.loadCredentials(project.name, stateDir);
|
|
688
|
-
const configuredToken = String(p.options.token || '').trim();
|
|
689
|
-
const configuredBaseUrl = String(p.options.base_url || '').trim();
|
|
690
|
-
const accountId = String(p.options.account_id || credentials?.botId || 'qr-login').trim();
|
|
691
|
-
return {
|
|
692
|
-
workspaceId: project.name,
|
|
693
|
-
token: configuredToken || credentials?.token || '',
|
|
694
|
-
accountId,
|
|
695
|
-
baseUrl: configuredBaseUrl || credentials?.baseUrl || DEFAULT_BASE_URL,
|
|
696
|
-
cdnBaseUrl: String(p.options.cdn_base_url || DEFAULT_CDN_BASE_URL).trim(),
|
|
697
|
-
allowFrom: String(p.options.allow_from || '*').trim(),
|
|
698
|
-
routeTag: String(p.options.route_tag || '').trim(),
|
|
699
|
-
longPollTimeoutMs: Number(p.options.long_poll_timeout_ms || LONG_POLL_TIMEOUT_MS) || LONG_POLL_TIMEOUT_MS,
|
|
700
|
-
stateDir,
|
|
701
|
-
proxy: String(p.options.proxy || '').trim(),
|
|
702
|
-
proxyUsername: String(p.options.proxy_username || '').trim(),
|
|
703
|
-
proxyPassword: String(p.options.proxy_password || '').trim(),
|
|
704
|
-
enabled: true,
|
|
705
|
-
project,
|
|
706
|
-
};
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
getDefaultStateDir() {
|
|
711
|
-
return node_path_1.default.join(process.cwd(), 'weixin-monitor');
|
|
704
|
+
return (0, config_js_1.collectWeixinWorkspaceBindings)(config);
|
|
712
705
|
}
|
|
713
706
|
// ==================== Private: Workspace Lifecycle ====================
|
|
714
707
|
async startWorkspace(binding) {
|
|
715
|
-
|
|
708
|
+
const key = (0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId);
|
|
709
|
+
await this.stopWorkspaceKey(key);
|
|
716
710
|
const status = {
|
|
717
711
|
workspaceId: binding.workspaceId,
|
|
712
|
+
instanceId: binding.instanceId,
|
|
713
|
+
displayName: binding.displayName,
|
|
714
|
+
platformKey: binding.platformKey,
|
|
718
715
|
enabled: true,
|
|
719
716
|
status: 'starting',
|
|
720
717
|
connected: false,
|
|
721
718
|
accountId: binding.accountId,
|
|
722
719
|
};
|
|
723
|
-
this.runtime.set(
|
|
720
|
+
this.runtime.set(key, status);
|
|
724
721
|
this.notifyRuntimeStateChanged();
|
|
725
722
|
try {
|
|
726
723
|
const abortController = new AbortController();
|
|
@@ -755,16 +752,23 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
755
752
|
}
|
|
756
753
|
this.notifyRuntimeStateChanged();
|
|
757
754
|
}
|
|
758
|
-
async stopWorkspace(workspaceId) {
|
|
759
|
-
const
|
|
755
|
+
async stopWorkspace(workspaceId, instanceId) {
|
|
756
|
+
const resolved = this.resolveRuntimeState(workspaceId, instanceId);
|
|
757
|
+
await this.stopWorkspaceKey((0, config_js_1.runtimeKey)(workspaceId, resolved.instanceId));
|
|
758
|
+
}
|
|
759
|
+
async stopWorkspaceKey(key) {
|
|
760
|
+
const state = this.runtime.get(key);
|
|
760
761
|
if (!state)
|
|
761
762
|
return;
|
|
762
763
|
try {
|
|
763
764
|
state.abortController?.abort();
|
|
764
765
|
}
|
|
765
766
|
catch { }
|
|
766
|
-
this.runtime.set(
|
|
767
|
-
workspaceId,
|
|
767
|
+
this.runtime.set(key, {
|
|
768
|
+
workspaceId: state.workspaceId,
|
|
769
|
+
instanceId: state.instanceId,
|
|
770
|
+
displayName: state.displayName,
|
|
771
|
+
platformKey: state.platformKey,
|
|
768
772
|
enabled: false,
|
|
769
773
|
status: 'stopped',
|
|
770
774
|
connected: false,
|
|
@@ -773,35 +777,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
773
777
|
this.notifyRuntimeStateChanged();
|
|
774
778
|
}
|
|
775
779
|
// ==================== Private: Long-poll Monitor ====================
|
|
776
|
-
getBufPath(binding) {
|
|
777
|
-
return node_path_1.default.join(binding.stateDir, `${binding.accountId}.buf`);
|
|
778
|
-
}
|
|
779
|
-
getCredentialsPath(workspaceId, stateDir) {
|
|
780
|
-
return node_path_1.default.join(stateDir, `${safeFilePart(workspaceId)}.credentials.json`);
|
|
781
|
-
}
|
|
782
|
-
loadCredentials(workspaceId, stateDir) {
|
|
783
|
-
try {
|
|
784
|
-
const raw = node_fs_1.default.readFileSync(this.getCredentialsPath(workspaceId, stateDir), 'utf-8');
|
|
785
|
-
const parsed = JSON.parse(raw);
|
|
786
|
-
const token = String(parsed.token || '').trim();
|
|
787
|
-
if (!token)
|
|
788
|
-
return null;
|
|
789
|
-
return {
|
|
790
|
-
token,
|
|
791
|
-
baseUrl: parsed.baseUrl ? String(parsed.baseUrl) : undefined,
|
|
792
|
-
botId: parsed.botId ? String(parsed.botId) : undefined,
|
|
793
|
-
userId: parsed.userId ? String(parsed.userId) : undefined,
|
|
794
|
-
savedAt: parsed.savedAt ? String(parsed.savedAt) : new Date().toISOString(),
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
catch {
|
|
798
|
-
return null;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
saveCredentials(binding, credentials) {
|
|
802
|
-
node_fs_1.default.mkdirSync(binding.stateDir, { recursive: true });
|
|
803
|
-
node_fs_1.default.writeFileSync(this.getCredentialsPath(binding.workspaceId, binding.stateDir), JSON.stringify(credentials, null, 2), 'utf-8');
|
|
804
|
-
}
|
|
805
780
|
normalizeQrStatus(status) {
|
|
806
781
|
const normalized = String(status || 'wait').trim().toLowerCase();
|
|
807
782
|
if (normalized === 'scaned' || normalized === 'scanned' || normalized === 'signed')
|
|
@@ -812,32 +787,19 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
812
787
|
return 'expired';
|
|
813
788
|
return 'wait';
|
|
814
789
|
}
|
|
815
|
-
loadBuf(binding) {
|
|
816
|
-
try {
|
|
817
|
-
return node_fs_1.default.readFileSync(this.getBufPath(binding), 'utf-8');
|
|
818
|
-
}
|
|
819
|
-
catch {
|
|
820
|
-
return '';
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
saveBuf(binding, buf) {
|
|
824
|
-
const dir = binding.stateDir;
|
|
825
|
-
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
826
|
-
node_fs_1.default.writeFileSync(this.getBufPath(binding), buf, 'utf-8');
|
|
827
|
-
}
|
|
828
790
|
async runMonitorLoop(binding, signal) {
|
|
829
|
-
let buf =
|
|
791
|
+
let buf = (0, config_js_1.loadWeixinBuf)(binding);
|
|
830
792
|
let consecutiveFailures = 0;
|
|
831
793
|
while (!signal.aborted) {
|
|
832
794
|
try {
|
|
833
|
-
const resp = await
|
|
795
|
+
const resp = await (0, transport_js_1.getWeixinUpdates)(binding, buf, signal);
|
|
834
796
|
const isApiError = (resp.ret !== undefined && resp.ret !== 0) || (resp.errcode !== undefined && resp.errcode !== 0);
|
|
835
797
|
if (isApiError) {
|
|
836
798
|
consecutiveFailures++;
|
|
837
799
|
const errorText = resp.errmsg ? ` errmsg=${resp.errmsg}` : '';
|
|
838
800
|
this.options.log?.(`localcore-weixin getUpdates failed for ${binding.workspaceId}: ret=${resp.ret} errcode=${resp.errcode}${errorText} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
|
|
839
801
|
if (resp.errcode === -14 || resp.ret === -14) {
|
|
840
|
-
const state = this.runtime.get(binding.workspaceId);
|
|
802
|
+
const state = this.runtime.get((0, config_js_1.runtimeKey)(binding.workspaceId, binding.instanceId));
|
|
841
803
|
if (state) {
|
|
842
804
|
state.status = 'error';
|
|
843
805
|
state.connected = false;
|
|
@@ -857,13 +819,13 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
857
819
|
consecutiveFailures = 0;
|
|
858
820
|
if (resp.get_updates_buf) {
|
|
859
821
|
buf = resp.get_updates_buf;
|
|
860
|
-
|
|
822
|
+
(0, config_js_1.saveWeixinBuf)(binding, buf);
|
|
861
823
|
}
|
|
862
824
|
for (const msg of resp.msgs ?? []) {
|
|
863
825
|
const items = msg.item_list ?? [];
|
|
864
|
-
const textItem = items.find((i) => i.type === TEXT_ITEM_TYPE);
|
|
865
|
-
const voiceTextItems = items.filter((i) => i.type === VOICE_ITEM_TYPE && i.voice_item?.text);
|
|
866
|
-
const mediaItems = items.filter((i) => i.type === IMAGE_ITEM_TYPE || i.type === FILE_ITEM_TYPE);
|
|
826
|
+
const textItem = items.find((i) => i.type === transport_js_1.TEXT_ITEM_TYPE);
|
|
827
|
+
const voiceTextItems = items.filter((i) => i.type === transport_js_1.VOICE_ITEM_TYPE && i.voice_item?.text);
|
|
828
|
+
const mediaItems = items.filter((i) => i.type === transport_js_1.IMAGE_ITEM_TYPE || i.type === transport_js_1.FILE_ITEM_TYPE);
|
|
867
829
|
if (!textItem && voiceTextItems.length === 0 && mediaItems.length === 0)
|
|
868
830
|
continue;
|
|
869
831
|
const conversationId = msg.from_user_id ?? '';
|
|
@@ -882,14 +844,9 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
882
844
|
if (att) {
|
|
883
845
|
attachmentText += attachmentText ? '\n' : '';
|
|
884
846
|
attachmentText += att.kind === 'image' ? `[Image: ${att.path}]` : `[File "${att.name}": ${att.path}]`;
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
data: att.data,
|
|
889
|
-
mimeType: att.mimeType,
|
|
890
|
-
fileName: att.name,
|
|
891
|
-
});
|
|
892
|
-
}
|
|
847
|
+
const part = createWeixinAttachmentContentPart(att);
|
|
848
|
+
if (part)
|
|
849
|
+
attachmentParts.push(part);
|
|
893
850
|
}
|
|
894
851
|
}
|
|
895
852
|
catch (dlErr) {
|
|
@@ -902,6 +859,8 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
902
859
|
continue;
|
|
903
860
|
await this.handleInboundMessage({
|
|
904
861
|
workspaceId: binding.workspaceId,
|
|
862
|
+
instanceId: binding.instanceId,
|
|
863
|
+
platformKey: binding.platformKey,
|
|
905
864
|
platformUserId: conversationId,
|
|
906
865
|
chatId: conversationId,
|
|
907
866
|
displayName: conversationId.slice(-6),
|
|
@@ -931,65 +890,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
931
890
|
}
|
|
932
891
|
}
|
|
933
892
|
// ==================== Private: HTTP API ====================
|
|
934
|
-
async apiPost(binding, endpoint, bodyObj, timeoutMs, signal) {
|
|
935
|
-
const url = `${binding.baseUrl.replace(/\/$/, '')}/${endpoint}`;
|
|
936
|
-
const body = JSON.stringify(bodyObj);
|
|
937
|
-
const controller = new AbortController();
|
|
938
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
939
|
-
const onAbort = () => controller.abort();
|
|
940
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
941
|
-
try {
|
|
942
|
-
const headers = {
|
|
943
|
-
'Content-Type': 'application/json',
|
|
944
|
-
'Content-Length': String(Buffer.byteLength(body, 'utf-8')),
|
|
945
|
-
'X-WECHAT-UIN': createWechatUin(),
|
|
946
|
-
...createIlinkHeaders(),
|
|
947
|
-
};
|
|
948
|
-
if (binding.token) {
|
|
949
|
-
headers.AuthorizationType = 'ilink_bot_token';
|
|
950
|
-
headers.Authorization = `Bearer ${binding.token}`;
|
|
951
|
-
}
|
|
952
|
-
const res = await fetch(url, {
|
|
953
|
-
method: 'POST',
|
|
954
|
-
headers,
|
|
955
|
-
body,
|
|
956
|
-
signal: controller.signal,
|
|
957
|
-
});
|
|
958
|
-
if (!res.ok)
|
|
959
|
-
throw new Error(`HTTP ${res.status}`);
|
|
960
|
-
return (await res.json());
|
|
961
|
-
}
|
|
962
|
-
finally {
|
|
963
|
-
clearTimeout(timer);
|
|
964
|
-
signal?.removeEventListener('abort', onAbort);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
async apiGet(binding, endpoint, timeoutMs = API_TIMEOUT_MS) {
|
|
968
|
-
const url = `${binding.baseUrl.replace(/\/$/, '')}/${endpoint}`;
|
|
969
|
-
const controller = new AbortController();
|
|
970
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
971
|
-
try {
|
|
972
|
-
const headers = createIlinkHeaders();
|
|
973
|
-
if (binding.token) {
|
|
974
|
-
headers.AuthorizationType = 'ilink_bot_token';
|
|
975
|
-
headers.Authorization = `Bearer ${binding.token}`;
|
|
976
|
-
}
|
|
977
|
-
const res = await fetch(url, {
|
|
978
|
-
method: 'GET',
|
|
979
|
-
headers,
|
|
980
|
-
signal: controller.signal,
|
|
981
|
-
});
|
|
982
|
-
if (!res.ok)
|
|
983
|
-
throw new Error(`HTTP ${res.status}`);
|
|
984
|
-
return (await res.json());
|
|
985
|
-
}
|
|
986
|
-
finally {
|
|
987
|
-
clearTimeout(timer);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
async getUpdates(binding, buf, signal) {
|
|
991
|
-
return this.apiPost(binding, 'ilink/bot/getupdates', { get_updates_buf: buf, base_info: { channel_version: WEIXIN_CHANNEL_VERSION } }, binding.longPollTimeoutMs, signal);
|
|
992
|
-
}
|
|
993
893
|
async sendTextMessage(state, toUserId, text, contextToken, options = {}) {
|
|
994
894
|
const binding = await this.getBinding(state.workspaceId);
|
|
995
895
|
const stripped = stripHtml(text);
|
|
@@ -998,56 +898,20 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
998
898
|
: splitTextByUtf8Bytes(stripped, WEIXIN_TEXT_MESSAGE_MAX_BYTES);
|
|
999
899
|
for (const [index, chunk] of chunks.entries()) {
|
|
1000
900
|
const finalChunk = options.final && index === chunks.length - 1;
|
|
1001
|
-
const resp = await
|
|
901
|
+
const resp = await (0, transport_js_1.sendWeixinTextMessageChunk)(binding, toUserId, chunk, contextToken, {
|
|
1002
902
|
clientId: options.clientId,
|
|
1003
903
|
final: finalChunk,
|
|
1004
904
|
});
|
|
1005
|
-
if (
|
|
905
|
+
if ((0, transport_js_1.isWeixinApiError)(resp)) {
|
|
1006
906
|
throw new Error(`WeChat sendmessage failed: ret=${resp.ret} errcode=${resp.errcode}${resp.errmsg ? ` errmsg=${resp.errmsg}` : ''} chunk=${index + 1}/${chunks.length} bytes=${utf8ByteLength(chunk)} context=${contextToken ? 'yes' : 'no'} message_state=${finalChunk ? 2 : 1}`);
|
|
1007
907
|
}
|
|
1008
908
|
}
|
|
1009
909
|
this.options.log?.(`localcore-weixin sent message to ${toUserId} for workspace ${state.workspaceId}${chunks.length > 1 ? ` chunks=${chunks.length}` : ''}`);
|
|
1010
910
|
}
|
|
1011
|
-
async sendTextMessageChunk(binding, toUserId, text, contextToken, options = {}) {
|
|
1012
|
-
return this.apiPost(binding, 'ilink/bot/sendmessage', {
|
|
1013
|
-
msg: {
|
|
1014
|
-
from_user_id: '',
|
|
1015
|
-
to_user_id: toUserId,
|
|
1016
|
-
client_id: options.clientId || `openclaw-weixin-${node_crypto_1.default.randomUUID()}`,
|
|
1017
|
-
message_type: 2,
|
|
1018
|
-
message_state: options.final === false ? 1 : 2,
|
|
1019
|
-
item_list: [{ type: TEXT_ITEM_TYPE, text_item: { text } }],
|
|
1020
|
-
...(contextToken ? { context_token: contextToken } : {}),
|
|
1021
|
-
},
|
|
1022
|
-
base_info: { channel_version: WEIXIN_CHANNEL_VERSION },
|
|
1023
|
-
}, API_TIMEOUT_MS);
|
|
1024
|
-
}
|
|
1025
911
|
async sendFileMessage(binding, toUserId, fileName, uploaded, contextToken) {
|
|
1026
912
|
const clientId = `openclaw-weixin-${node_crypto_1.default.randomUUID()}`;
|
|
1027
|
-
const resp = await
|
|
1028
|
-
|
|
1029
|
-
from_user_id: '',
|
|
1030
|
-
to_user_id: toUserId,
|
|
1031
|
-
client_id: clientId,
|
|
1032
|
-
message_type: 2,
|
|
1033
|
-
message_state: 2,
|
|
1034
|
-
item_list: [{
|
|
1035
|
-
type: FILE_ITEM_TYPE,
|
|
1036
|
-
file_item: {
|
|
1037
|
-
media: {
|
|
1038
|
-
encrypt_query_param: uploaded.encryptedQueryParam,
|
|
1039
|
-
aes_key: Buffer.from(uploaded.aesKeyHex).toString('base64'),
|
|
1040
|
-
encrypt_type: 1,
|
|
1041
|
-
},
|
|
1042
|
-
file_name: fileName,
|
|
1043
|
-
len: String(uploaded.fileSize),
|
|
1044
|
-
},
|
|
1045
|
-
}],
|
|
1046
|
-
...(contextToken ? { context_token: contextToken } : {}),
|
|
1047
|
-
},
|
|
1048
|
-
base_info: { channel_version: WEIXIN_CHANNEL_VERSION },
|
|
1049
|
-
}, API_TIMEOUT_MS);
|
|
1050
|
-
if (this.isSendMessageError(resp)) {
|
|
913
|
+
const resp = await (0, transport_js_1.sendWeixinFileMessage)(binding, toUserId, fileName, uploaded, contextToken, { clientId });
|
|
914
|
+
if ((0, transport_js_1.isWeixinApiError)(resp)) {
|
|
1051
915
|
throw new Error(`WeChat send file failed: ret=${resp.ret} errcode=${resp.errcode}${resp.errmsg ? ` errmsg=${resp.errmsg}` : ''}`);
|
|
1052
916
|
}
|
|
1053
917
|
return clientId;
|
|
@@ -1068,9 +932,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
1068
932
|
file,
|
|
1069
933
|
};
|
|
1070
934
|
}
|
|
1071
|
-
isSendMessageError(resp) {
|
|
1072
|
-
return (resp.ret !== undefined && resp.ret !== 0) || (resp.errcode !== undefined && resp.errcode !== 0);
|
|
1073
|
-
}
|
|
1074
935
|
async uploadFileToCdn(binding, toUserId, filePath) {
|
|
1075
936
|
const plaintext = await node_fs_1.default.promises.readFile(filePath);
|
|
1076
937
|
const fileKey = node_crypto_1.default.randomBytes(16).toString('hex');
|
|
@@ -1078,21 +939,11 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
1078
939
|
const aesKeyHex = aesKey.toString('hex');
|
|
1079
940
|
const rawMd5 = node_crypto_1.default.createHash('md5').update(plaintext).digest('hex');
|
|
1080
941
|
const cipherSize = this.getAesEcbPaddedSize(plaintext.length);
|
|
1081
|
-
const uploadUrlResp = await
|
|
1082
|
-
|
|
1083
|
-
media_type: UPLOAD_MEDIA_TYPE_FILE,
|
|
1084
|
-
to_user_id: toUserId,
|
|
1085
|
-
rawsize: plaintext.length,
|
|
1086
|
-
rawfilemd5: rawMd5,
|
|
1087
|
-
filesize: cipherSize,
|
|
1088
|
-
no_need_thumb: true,
|
|
1089
|
-
aeskey: aesKeyHex,
|
|
1090
|
-
base_info: { channel_version: WEIXIN_CHANNEL_VERSION },
|
|
1091
|
-
}, API_TIMEOUT_MS);
|
|
1092
|
-
if (this.isSendMessageError(uploadUrlResp) || !uploadUrlResp.upload_param) {
|
|
942
|
+
const uploadUrlResp = await (0, transport_js_1.getWeixinUploadUrl)(binding, fileKey, toUserId, plaintext.length, rawMd5, cipherSize, aesKeyHex);
|
|
943
|
+
if ((0, transport_js_1.isWeixinApiError)(uploadUrlResp) || !uploadUrlResp.upload_param) {
|
|
1093
944
|
throw new Error(`WeChat getuploadurl failed: ret=${uploadUrlResp.ret} errcode=${uploadUrlResp.errcode}${uploadUrlResp.errmsg ? ` errmsg=${uploadUrlResp.errmsg}` : ''}`);
|
|
1094
945
|
}
|
|
1095
|
-
const encryptedQueryParam = await
|
|
946
|
+
const encryptedQueryParam = await (0, transport_js_1.uploadEncryptedBufferToWeixinCdn)(binding, plaintext, uploadUrlResp.upload_param, fileKey, aesKey);
|
|
1096
947
|
return {
|
|
1097
948
|
fileKey,
|
|
1098
949
|
encryptedQueryParam,
|
|
@@ -1101,43 +952,23 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
1101
952
|
cipherSize,
|
|
1102
953
|
};
|
|
1103
954
|
}
|
|
1104
|
-
async uploadEncryptedBufferToCdn(binding, plaintext, uploadParam, fileKey, aesKey) {
|
|
1105
|
-
const cipher = node_crypto_1.default.createCipheriv('aes-128-ecb', aesKey, null);
|
|
1106
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
1107
|
-
const url = `${binding.cdnBaseUrl.replace(/\/$/, '')}/upload?encrypted_query_param=${encodeURIComponent(uploadParam)}&filekey=${encodeURIComponent(fileKey)}`;
|
|
1108
|
-
const res = await fetch(url, {
|
|
1109
|
-
method: 'POST',
|
|
1110
|
-
headers: {
|
|
1111
|
-
'Content-Type': 'application/octet-stream',
|
|
1112
|
-
},
|
|
1113
|
-
body: new Uint8Array(ciphertext),
|
|
1114
|
-
});
|
|
1115
|
-
if (!res.ok) {
|
|
1116
|
-
throw new Error(`WeChat CDN upload failed: HTTP ${res.status}`);
|
|
1117
|
-
}
|
|
1118
|
-
const encryptedQueryParam = res.headers.get('x-encrypted-param') || '';
|
|
1119
|
-
if (!encryptedQueryParam) {
|
|
1120
|
-
throw new Error('WeChat CDN upload response missing x-encrypted-param');
|
|
1121
|
-
}
|
|
1122
|
-
return encryptedQueryParam;
|
|
1123
|
-
}
|
|
1124
955
|
getAesEcbPaddedSize(size) {
|
|
1125
956
|
return Math.ceil((size + 1) / 16) * 16;
|
|
1126
957
|
}
|
|
1127
|
-
resolveContextTokenForFileSend(workspaceId, channelId, participantId) {
|
|
958
|
+
resolveContextTokenForFileSend(workspaceId, channelId, participantId, platformKey = 'weixin') {
|
|
1128
959
|
const preferredParticipantId = String(participantId || '').trim();
|
|
1129
960
|
const direct = preferredParticipantId
|
|
1130
|
-
? this.options.store.getPlatformThreadBinding(workspaceId, channelId, preferredParticipantId,
|
|
961
|
+
? this.options.store.getPlatformThreadBinding(workspaceId, channelId, preferredParticipantId, platformKey)
|
|
1131
962
|
: undefined;
|
|
1132
963
|
if (direct?.last_platform_message_id) {
|
|
1133
964
|
return direct.last_platform_message_id;
|
|
1134
965
|
}
|
|
1135
|
-
const users = this.options.store.listAuthorizedUsers(workspaceId,
|
|
966
|
+
const users = this.options.store.listAuthorizedUsers(workspaceId, platformKey);
|
|
1136
967
|
const user = users.find((entry) => entry.chatId === channelId || entry.platformUserId === channelId);
|
|
1137
968
|
if (!user) {
|
|
1138
969
|
return '';
|
|
1139
970
|
}
|
|
1140
|
-
return this.options.store.getPlatformThreadBinding(workspaceId, user.chatId, user.platformUserId,
|
|
971
|
+
return this.options.store.getPlatformThreadBinding(workspaceId, user.chatId, user.platformUserId, platformKey)?.last_platform_message_id || '';
|
|
1141
972
|
}
|
|
1142
973
|
// ==================== Private: Attachment Download ====================
|
|
1143
974
|
sniffExtAndKind(buf) {
|
|
@@ -1185,7 +1016,7 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
1185
1016
|
resultBuf = rawBuf;
|
|
1186
1017
|
}
|
|
1187
1018
|
const { ext, kind } = this.sniffExtAndKind(resultBuf);
|
|
1188
|
-
const declaredName = String(itemData?.file_name ?? (item.type === IMAGE_ITEM_TYPE ? 'image' : 'file'));
|
|
1019
|
+
const declaredName = String(itemData?.file_name ?? (item.type === transport_js_1.IMAGE_ITEM_TYPE ? 'image' : 'file'));
|
|
1189
1020
|
const safeName = declaredName.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 64);
|
|
1190
1021
|
const safeMsgId = msgId.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 48);
|
|
1191
1022
|
const fileName = `${safeMsgId}-${idx}-${safeName}${ext}`;
|
|
@@ -1200,25 +1031,6 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
1200
1031
|
mimeType: kind === 'image' ? this.mimeTypeForImageExt(ext) : undefined,
|
|
1201
1032
|
};
|
|
1202
1033
|
}
|
|
1203
|
-
createWrappedContentParts(wrappedText, parts) {
|
|
1204
|
-
const nonTextParts = Array.isArray(parts)
|
|
1205
|
-
? parts.filter((part) => part.type !== 'text')
|
|
1206
|
-
: [];
|
|
1207
|
-
return [
|
|
1208
|
-
{ type: 'text', text: wrappedText },
|
|
1209
|
-
...nonTextParts,
|
|
1210
|
-
];
|
|
1211
|
-
}
|
|
1212
|
-
createThreadMessageInput(wrappedText, parts) {
|
|
1213
|
-
const hasNonTextPart = Array.isArray(parts) && parts.some((part) => part.type !== 'text');
|
|
1214
|
-
if (!hasNonTextPart) {
|
|
1215
|
-
return wrappedText;
|
|
1216
|
-
}
|
|
1217
|
-
return {
|
|
1218
|
-
displayText: wrappedText,
|
|
1219
|
-
contentParts: this.createWrappedContentParts(wrappedText, parts),
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
1034
|
mimeTypeForImageExt(ext) {
|
|
1223
1035
|
if (ext === '.jpg')
|
|
1224
1036
|
return 'image/jpeg';
|
|
@@ -1361,9 +1173,9 @@ class LocalCoreWeixinGateway extends node_events_1.EventEmitter {
|
|
|
1361
1173
|
this.processedInboundMessages.set(key, now + PROCESSED_MESSAGE_TTL_MS);
|
|
1362
1174
|
return false;
|
|
1363
1175
|
}
|
|
1364
|
-
findAwaitingPermissionThreadId(workspaceId, chatId, platformUserId) {
|
|
1176
|
+
findAwaitingPermissionThreadId(workspaceId, chatId, platformUserId, platformKey = 'weixin') {
|
|
1365
1177
|
for (const [sessionKey, route] of this.threadRouting.entries()) {
|
|
1366
|
-
if (route.workspaceId !== workspaceId || route.chatId !== chatId || route.platformUserId !== platformUserId)
|
|
1178
|
+
if (route.workspaceId !== workspaceId || route.platformKey !== platformKey || route.chatId !== chatId || route.platformUserId !== platformUserId)
|
|
1367
1179
|
continue;
|
|
1368
1180
|
const turn = this.outboundTurns.get(sessionKey);
|
|
1369
1181
|
if (turn?.awaitingPermission && route.threadId)
|