@searchfe/openclaw-baiduapp 0.1.6 → 0.1.7-beta.10
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 +55 -9
- package/dist/index.js +1378 -285
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +4 -1
- package/package.json +4 -2
- package/dist/index.d.ts +0 -407
package/dist/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path2 from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import crypto3 from 'crypto';
|
|
2
4
|
import { createRequire } from 'module';
|
|
5
|
+
import { lookup } from 'dns/promises';
|
|
6
|
+
import fs2 from 'fs/promises';
|
|
7
|
+
import net from 'net';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
3
10
|
|
|
4
11
|
var __defProp = Object.defineProperty;
|
|
5
12
|
var __export = (target, all) => {
|
|
@@ -7,7 +14,7 @@ var __export = (target, all) => {
|
|
|
7
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
15
|
};
|
|
9
16
|
|
|
10
|
-
//
|
|
17
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
11
18
|
var external_exports = {};
|
|
12
19
|
__export(external_exports, {
|
|
13
20
|
BRAND: () => BRAND,
|
|
@@ -119,7 +126,7 @@ __export(external_exports, {
|
|
|
119
126
|
void: () => voidType
|
|
120
127
|
});
|
|
121
128
|
|
|
122
|
-
//
|
|
129
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
123
130
|
var util;
|
|
124
131
|
(function(util2) {
|
|
125
132
|
util2.assertEqual = (_) => {
|
|
@@ -253,7 +260,7 @@ var getParsedType = (data) => {
|
|
|
253
260
|
}
|
|
254
261
|
};
|
|
255
262
|
|
|
256
|
-
//
|
|
263
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
257
264
|
var ZodIssueCode = util.arrayToEnum([
|
|
258
265
|
"invalid_type",
|
|
259
266
|
"invalid_literal",
|
|
@@ -371,7 +378,7 @@ ZodError.create = (issues) => {
|
|
|
371
378
|
return error;
|
|
372
379
|
};
|
|
373
380
|
|
|
374
|
-
//
|
|
381
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
375
382
|
var errorMap = (issue, _ctx) => {
|
|
376
383
|
let message;
|
|
377
384
|
switch (issue.code) {
|
|
@@ -474,7 +481,7 @@ var errorMap = (issue, _ctx) => {
|
|
|
474
481
|
};
|
|
475
482
|
var en_default = errorMap;
|
|
476
483
|
|
|
477
|
-
//
|
|
484
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
478
485
|
var overrideErrorMap = en_default;
|
|
479
486
|
function setErrorMap(map) {
|
|
480
487
|
overrideErrorMap = map;
|
|
@@ -483,10 +490,10 @@ function getErrorMap() {
|
|
|
483
490
|
return overrideErrorMap;
|
|
484
491
|
}
|
|
485
492
|
|
|
486
|
-
//
|
|
493
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
487
494
|
var makeIssue = (params) => {
|
|
488
|
-
const { data, path, errorMaps, issueData } = params;
|
|
489
|
-
const fullPath = [...
|
|
495
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
496
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
490
497
|
const fullIssue = {
|
|
491
498
|
...issueData,
|
|
492
499
|
path: fullPath
|
|
@@ -593,20 +600,20 @@ var isDirty = (x) => x.status === "dirty";
|
|
|
593
600
|
var isValid = (x) => x.status === "valid";
|
|
594
601
|
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
595
602
|
|
|
596
|
-
//
|
|
603
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
597
604
|
var errorUtil;
|
|
598
605
|
(function(errorUtil2) {
|
|
599
606
|
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
600
607
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
601
608
|
})(errorUtil || (errorUtil = {}));
|
|
602
609
|
|
|
603
|
-
//
|
|
610
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
604
611
|
var ParseInputLazyPath = class {
|
|
605
|
-
constructor(parent, value,
|
|
612
|
+
constructor(parent, value, path4, key) {
|
|
606
613
|
this._cachedPath = [];
|
|
607
614
|
this.parent = parent;
|
|
608
615
|
this.data = value;
|
|
609
|
-
this._path =
|
|
616
|
+
this._path = path4;
|
|
610
617
|
this._key = key;
|
|
611
618
|
}
|
|
612
619
|
get path() {
|
|
@@ -4053,6 +4060,7 @@ var DEFAULT_API_BASE = "https://claw.baidu.com";
|
|
|
4053
4060
|
var BaiduAppAccountSchema = external_exports.object({
|
|
4054
4061
|
name: external_exports.string().optional(),
|
|
4055
4062
|
enabled: external_exports.boolean().optional(),
|
|
4063
|
+
pollingEnabled: external_exports.boolean().optional(),
|
|
4056
4064
|
webhookPath: external_exports.string().optional(),
|
|
4057
4065
|
token: external_exports.string().optional(),
|
|
4058
4066
|
encodingAESKey: external_exports.string().optional(),
|
|
@@ -4072,6 +4080,7 @@ var BaiduAppConfigJsonSchema = {
|
|
|
4072
4080
|
properties: {
|
|
4073
4081
|
name: { type: "string" },
|
|
4074
4082
|
enabled: { type: "boolean" },
|
|
4083
|
+
pollingEnabled: { type: "boolean" },
|
|
4075
4084
|
webhookPath: { type: "string" },
|
|
4076
4085
|
token: { type: "string" },
|
|
4077
4086
|
encodingAESKey: { type: "string" },
|
|
@@ -4088,6 +4097,7 @@ var BaiduAppConfigJsonSchema = {
|
|
|
4088
4097
|
properties: {
|
|
4089
4098
|
name: { type: "string" },
|
|
4090
4099
|
enabled: { type: "boolean" },
|
|
4100
|
+
pollingEnabled: { type: "boolean" },
|
|
4091
4101
|
webhookPath: { type: "string" },
|
|
4092
4102
|
token: { type: "string" },
|
|
4093
4103
|
encodingAESKey: { type: "string" },
|
|
@@ -4117,6 +4127,9 @@ function listBaiduAppAccountIds(cfg) {
|
|
|
4117
4127
|
if (ids.length === 0) {
|
|
4118
4128
|
return [DEFAULT_ACCOUNT_ID];
|
|
4119
4129
|
}
|
|
4130
|
+
if (!ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
4131
|
+
ids.push(DEFAULT_ACCOUNT_ID);
|
|
4132
|
+
}
|
|
4120
4133
|
return ids.sort((a, b) => a.localeCompare(b));
|
|
4121
4134
|
}
|
|
4122
4135
|
function resolveDefaultBaiduAppAccountId(cfg) {
|
|
@@ -4154,7 +4167,7 @@ function resolveBaiduAppAccount(params) {
|
|
|
4154
4167
|
const appKey = merged.appKey?.trim() || (isDefaultAccount ? process.env.BAIDU_APP_KEY?.trim() : void 0) || void 0;
|
|
4155
4168
|
const appSecret = merged.appSecret?.trim() || (isDefaultAccount ? process.env.BAIDU_APP_SECRET?.trim() : void 0) || void 0;
|
|
4156
4169
|
const configured = Boolean(token && encodingAESKey);
|
|
4157
|
-
const canSendActive = Boolean(appKey && token && encodingAESKey);
|
|
4170
|
+
const canSendActive = Boolean(appKey && appSecret && token && encodingAESKey);
|
|
4158
4171
|
const rawApiBase = merged.apiBase?.trim() || (isDefaultAccount ? process.env.BAIDU_API_BASE?.trim() : void 0) || void 0;
|
|
4159
4172
|
const apiBase = (rawApiBase || DEFAULT_API_BASE).replace(/\/+$/, "");
|
|
4160
4173
|
return {
|
|
@@ -4217,7 +4230,7 @@ function pkcs7Unpad(buf, blockSize) {
|
|
|
4217
4230
|
return buf.subarray(0, buf.length - pad);
|
|
4218
4231
|
}
|
|
4219
4232
|
function sha1Hex(input) {
|
|
4220
|
-
return
|
|
4233
|
+
return crypto3.createHash("sha1").update(input).digest("hex");
|
|
4221
4234
|
}
|
|
4222
4235
|
function computeBaiduAppMsgSignature(params) {
|
|
4223
4236
|
const parts = [params.token, params.timestamp, params.nonce, params.encrypt].map((value) => String(value ?? "")).sort();
|
|
@@ -4235,7 +4248,7 @@ function verifyBaiduAppSignature(params) {
|
|
|
4235
4248
|
function decryptBaiduAppEncrypted(params) {
|
|
4236
4249
|
const aesKey = decodeEncodingAESKey(params.encodingAESKey);
|
|
4237
4250
|
const iv = aesKey.subarray(0, 16);
|
|
4238
|
-
const decipher =
|
|
4251
|
+
const decipher = crypto3.createDecipheriv("aes-256-cbc", aesKey, iv);
|
|
4239
4252
|
decipher.setAutoPadding(false);
|
|
4240
4253
|
const decryptedPadded = Buffer.concat([decipher.update(Buffer.from(params.encrypt, "base64")), decipher.final()]);
|
|
4241
4254
|
const decrypted = pkcs7Unpad(decryptedPadded, PKCS7_BLOCK_SIZE);
|
|
@@ -4253,13 +4266,13 @@ function decryptBaiduAppEncrypted(params) {
|
|
|
4253
4266
|
function encryptBaiduAppPlaintext(params) {
|
|
4254
4267
|
const aesKey = decodeEncodingAESKey(params.encodingAESKey);
|
|
4255
4268
|
const iv = aesKey.subarray(0, 16);
|
|
4256
|
-
const random16 =
|
|
4269
|
+
const random16 = crypto3.randomBytes(16);
|
|
4257
4270
|
const msg = Buffer.from(params.plaintext ?? "", "utf8");
|
|
4258
4271
|
const msgLen = Buffer.alloc(4);
|
|
4259
4272
|
msgLen.writeUInt32BE(msg.length, 0);
|
|
4260
4273
|
const raw = Buffer.concat([random16, msgLen, msg]);
|
|
4261
4274
|
const padded = pkcs7Pad(raw, PKCS7_BLOCK_SIZE);
|
|
4262
|
-
const cipher =
|
|
4275
|
+
const cipher = crypto3.createCipheriv("aes-256-cbc", aesKey, iv);
|
|
4263
4276
|
cipher.setAutoPadding(false);
|
|
4264
4277
|
const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]);
|
|
4265
4278
|
return encrypted.toString("base64");
|
|
@@ -4279,9 +4292,12 @@ async function sendBaiduAppMessage(account, message, options) {
|
|
|
4279
4292
|
errmsg: "Account not configured for active sending (missing appKey, token, or encodingAESKey)"
|
|
4280
4293
|
};
|
|
4281
4294
|
}
|
|
4282
|
-
const
|
|
4295
|
+
const normalizedPayload = typeof message === "string" ? {
|
|
4283
4296
|
msgtype: "text",
|
|
4284
|
-
text: { content: message }
|
|
4297
|
+
text: { content: message }
|
|
4298
|
+
} : message;
|
|
4299
|
+
const payload = {
|
|
4300
|
+
...normalizedPayload,
|
|
4285
4301
|
version: PLUGIN_VERSION,
|
|
4286
4302
|
...options?.msgid != null ? { msgid: options.msgid } : {},
|
|
4287
4303
|
...options?.streamId != null ? { streamId: options.streamId } : {},
|
|
@@ -4293,7 +4309,7 @@ async function sendBaiduAppMessage(account, message, options) {
|
|
|
4293
4309
|
plaintext
|
|
4294
4310
|
});
|
|
4295
4311
|
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
4296
|
-
const nonce =
|
|
4312
|
+
const nonce = crypto3.randomBytes(8).toString("hex");
|
|
4297
4313
|
const msgSignature = computeBaiduAppMsgSignature({
|
|
4298
4314
|
token: account.token ?? "",
|
|
4299
4315
|
timestamp,
|
|
@@ -4325,22 +4341,30 @@ async function sendBaiduAppMessage(account, message, options) {
|
|
|
4325
4341
|
return { ok: false, errcode: resp.status, errmsg };
|
|
4326
4342
|
}
|
|
4327
4343
|
const result = {
|
|
4328
|
-
ok:
|
|
4329
|
-
errcode: data.errcode,
|
|
4330
|
-
errmsg: data.errmsg,
|
|
4331
|
-
invaliduser: data.invaliduser,
|
|
4332
|
-
msgid: data.msgid
|
|
4344
|
+
ok: true
|
|
4333
4345
|
};
|
|
4334
|
-
|
|
4335
|
-
logger.info(`request succeeded: msgid=${result.msgid ?? "unknown"}`);
|
|
4336
|
-
} else {
|
|
4337
|
-
logger.error(`request failed: errcode=${result.errcode} errmsg=${result.errmsg ?? "unknown"}`);
|
|
4338
|
-
}
|
|
4346
|
+
logger.info(`request succeeded`);
|
|
4339
4347
|
return result;
|
|
4340
4348
|
}
|
|
4341
4349
|
|
|
4350
|
+
// src/media-payload.ts
|
|
4351
|
+
function buildMediaPayload(mediaList, opts) {
|
|
4352
|
+
const first = mediaList[0];
|
|
4353
|
+
const mediaPaths = mediaList.map((media) => media.path);
|
|
4354
|
+
const rawMediaTypes = mediaList.map((media) => media.contentType ?? "");
|
|
4355
|
+
const mediaTypes = opts?.preserveMediaTypeCardinality ? rawMediaTypes : rawMediaTypes.filter((value) => Boolean(value));
|
|
4356
|
+
return {
|
|
4357
|
+
MediaPath: first?.path,
|
|
4358
|
+
MediaType: first?.contentType,
|
|
4359
|
+
MediaUrl: first?.path,
|
|
4360
|
+
MediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0,
|
|
4361
|
+
MediaUrls: mediaPaths.length > 0 ? mediaPaths : void 0,
|
|
4362
|
+
MediaTypes: mediaTypes.length > 0 ? mediaTypes : void 0
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4342
4366
|
// src/bot.ts
|
|
4343
|
-
function
|
|
4367
|
+
function extractBaiduAppTextContent(msg) {
|
|
4344
4368
|
const msgtype = String(msg.msgtype ?? msg.MsgType ?? "").toLowerCase();
|
|
4345
4369
|
if (msgtype === "text") {
|
|
4346
4370
|
const content = msg.text?.content ?? msg.Content;
|
|
@@ -4354,13 +4378,71 @@ function extractBaiduAppContent(msg) {
|
|
|
4354
4378
|
}
|
|
4355
4379
|
return msgtype ? `[${msgtype}]` : "";
|
|
4356
4380
|
}
|
|
4381
|
+
function canDispatchBaiduAppInboundMessage(msg) {
|
|
4382
|
+
const msgtype = String(msg.msgtype ?? msg.MsgType ?? "").toLowerCase();
|
|
4383
|
+
if (msgtype === "text") {
|
|
4384
|
+
if (extractBaiduAppTextContent(msg)) {
|
|
4385
|
+
return true;
|
|
4386
|
+
}
|
|
4387
|
+
const files = msg.files;
|
|
4388
|
+
return Array.isArray(files) && files.length > 0;
|
|
4389
|
+
}
|
|
4390
|
+
return msgtype === "event" || Boolean(msgtype);
|
|
4391
|
+
}
|
|
4392
|
+
function buildFilesOnlySummary(fileCount) {
|
|
4393
|
+
return fileCount === 1 ? "[files] 1 attachment received" : `[files] ${fileCount} attachments received`;
|
|
4394
|
+
}
|
|
4395
|
+
function countInboundFiles(msg) {
|
|
4396
|
+
const files = msg.files;
|
|
4397
|
+
return Array.isArray(files) ? files.length : 0;
|
|
4398
|
+
}
|
|
4399
|
+
function appendLocalFilesBlock(base, localPaths) {
|
|
4400
|
+
if (localPaths.length === 0) {
|
|
4401
|
+
return base;
|
|
4402
|
+
}
|
|
4403
|
+
const fileList = localPaths.map((localPath) => `- ${localPath}`).join("\n");
|
|
4404
|
+
return `${base}
|
|
4405
|
+
|
|
4406
|
+
[local files]
|
|
4407
|
+
${fileList}`;
|
|
4408
|
+
}
|
|
4409
|
+
function appendNonEmptySections(sections) {
|
|
4410
|
+
return sections.map((section) => section?.trim()).filter(Boolean).join("\n\n");
|
|
4411
|
+
}
|
|
4412
|
+
function buildBaiduAppInboundDispatchContent(msg, localFiles = [], diagnostics = {}) {
|
|
4413
|
+
const baseText = extractBaiduAppTextContent(msg);
|
|
4414
|
+
const inboundFileCount = countInboundFiles(msg);
|
|
4415
|
+
const summaryLine = diagnostics.summaryLine ?? (inboundFileCount > 0 ? buildFilesOnlySummary(inboundFileCount) : "");
|
|
4416
|
+
const failureBlock = diagnostics.failurePlaceholders?.filter(Boolean).join("\n");
|
|
4417
|
+
const baseBody = appendNonEmptySections([baseText, summaryLine, failureBlock]);
|
|
4418
|
+
const localPaths = localFiles.map((file) => file.path);
|
|
4419
|
+
const rawBody = appendLocalFilesBlock(baseBody, localPaths);
|
|
4420
|
+
const mediaPayload = buildMediaPayload(
|
|
4421
|
+
localFiles.map((file) => ({ path: file.path, contentType: file.contentType })),
|
|
4422
|
+
{ preserveMediaTypeCardinality: true }
|
|
4423
|
+
);
|
|
4424
|
+
const contextPatch = {
|
|
4425
|
+
MediaPath: mediaPayload.MediaPath,
|
|
4426
|
+
MediaPaths: mediaPayload.MediaPaths,
|
|
4427
|
+
MediaType: mediaPayload.MediaType,
|
|
4428
|
+
MediaTypes: mediaPayload.MediaTypes
|
|
4429
|
+
};
|
|
4430
|
+
if (localFiles.length === 1) {
|
|
4431
|
+
contextPatch.FileName = localFiles[0]?.fileName;
|
|
4432
|
+
contextPatch.FileSize = localFiles[0]?.size;
|
|
4433
|
+
}
|
|
4434
|
+
return {
|
|
4435
|
+
rawBody,
|
|
4436
|
+
contextPatch
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4357
4439
|
async function dispatchBaiduAppMessage(params) {
|
|
4358
4440
|
const { cfg, account, msg, core, hooks } = params;
|
|
4359
4441
|
const safeCfg = cfg ?? {};
|
|
4360
|
-
const
|
|
4442
|
+
const logger3 = createLogger("openclaw-baiduapp", { log: params.log, error: params.error });
|
|
4361
4443
|
const channel = core.channel;
|
|
4362
4444
|
if (!channel?.routing?.resolveAgentRoute || !channel.reply?.dispatchReplyWithBufferedBlockDispatcher) {
|
|
4363
|
-
|
|
4445
|
+
logger3.warn("core routing or buffered dispatcher missing, skipping dispatch");
|
|
4364
4446
|
return;
|
|
4365
4447
|
}
|
|
4366
4448
|
const route = channel.routing.resolveAgentRoute({
|
|
@@ -4369,13 +4451,17 @@ async function dispatchBaiduAppMessage(params) {
|
|
|
4369
4451
|
accountId: account.accountId,
|
|
4370
4452
|
peer: { kind: "dm", id: "default" }
|
|
4371
4453
|
});
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
logger2.info(
|
|
4454
|
+
logger3.info(`SessionKey: ${route.sessionKey}`);
|
|
4455
|
+
logger3.info(
|
|
4375
4456
|
`route resolved: sessionKey=${route.sessionKey} agentId=${route.agentId ?? "default"} accountId=${route.accountId}`
|
|
4376
4457
|
);
|
|
4377
|
-
const
|
|
4378
|
-
|
|
4458
|
+
const inboundContent = buildBaiduAppInboundDispatchContent(
|
|
4459
|
+
msg,
|
|
4460
|
+
params.inboundMediaFiles ?? [],
|
|
4461
|
+
params.inboundFileDiagnostic
|
|
4462
|
+
);
|
|
4463
|
+
const rawBody = inboundContent.rawBody;
|
|
4464
|
+
logger3.debug(
|
|
4379
4465
|
`message content extracted: len=${rawBody.length} preview="${rawBody.slice(0, 80)}${rawBody.length > 80 ? "..." : ""}"`
|
|
4380
4466
|
);
|
|
4381
4467
|
const storePath = channel.session?.resolveStorePath?.(safeCfg.session?.store, {
|
|
@@ -4409,7 +4495,8 @@ async function dispatchBaiduAppMessage(params) {
|
|
|
4409
4495
|
MessageSid: msgid,
|
|
4410
4496
|
OriginatingChannel: "openclaw-baiduapp",
|
|
4411
4497
|
OriginatingTo: "user",
|
|
4412
|
-
CommandAuthorized: true
|
|
4498
|
+
CommandAuthorized: true,
|
|
4499
|
+
...inboundContent.contextPatch
|
|
4413
4500
|
}) : {
|
|
4414
4501
|
Body: body,
|
|
4415
4502
|
RawBody: rawBody,
|
|
@@ -4425,7 +4512,8 @@ async function dispatchBaiduAppMessage(params) {
|
|
|
4425
4512
|
MessageSid: msgid,
|
|
4426
4513
|
OriginatingChannel: "openclaw-baiduapp",
|
|
4427
4514
|
OriginatingTo: "user",
|
|
4428
|
-
CommandAuthorized: true
|
|
4515
|
+
CommandAuthorized: true,
|
|
4516
|
+
...inboundContent.contextPatch
|
|
4429
4517
|
};
|
|
4430
4518
|
if (channel.session?.recordInboundSession && storePath) {
|
|
4431
4519
|
await channel.session.recordInboundSession({
|
|
@@ -4433,7 +4521,7 @@ async function dispatchBaiduAppMessage(params) {
|
|
|
4433
4521
|
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
4434
4522
|
ctx: ctxPayload,
|
|
4435
4523
|
onRecordError: (err) => {
|
|
4436
|
-
|
|
4524
|
+
logger3.error(`openclaw-baiduapp: failed updating session meta: ${String(err)}`);
|
|
4437
4525
|
}
|
|
4438
4526
|
});
|
|
4439
4527
|
}
|
|
@@ -4442,7 +4530,7 @@ async function dispatchBaiduAppMessage(params) {
|
|
|
4442
4530
|
channel: "openclaw-baiduapp",
|
|
4443
4531
|
accountId: account.accountId
|
|
4444
4532
|
}) : void 0;
|
|
4445
|
-
|
|
4533
|
+
logger3.info(`dispatching to agent: sessionKey=${route.sessionKey}`);
|
|
4446
4534
|
await channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
4447
4535
|
ctx: ctxPayload,
|
|
4448
4536
|
cfg: safeCfg,
|
|
@@ -4450,20 +4538,575 @@ async function dispatchBaiduAppMessage(params) {
|
|
|
4450
4538
|
deliver: async (payload) => {
|
|
4451
4539
|
const rawText = payload.text ?? "";
|
|
4452
4540
|
if (!rawText.trim()) {
|
|
4453
|
-
|
|
4541
|
+
logger3.debug("deliver callback: empty text, skipping");
|
|
4454
4542
|
return;
|
|
4455
4543
|
}
|
|
4456
4544
|
const converted = channel.text?.convertMarkdownTables && tableMode ? channel.text.convertMarkdownTables(rawText, tableMode) : rawText;
|
|
4457
|
-
|
|
4545
|
+
logger3.debug(`deliver callback: textLen=${converted.length}`);
|
|
4458
4546
|
hooks.onChunk(converted);
|
|
4459
4547
|
},
|
|
4460
4548
|
onError: (err, info) => {
|
|
4461
4549
|
hooks.onError?.(err);
|
|
4462
|
-
|
|
4550
|
+
logger3.error(`${info.kind} reply failed: ${String(err)}`);
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
});
|
|
4554
|
+
logger3.info(`agent reply dispatch complete: sessionKey=${route.sessionKey}`);
|
|
4555
|
+
}
|
|
4556
|
+
var POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw";
|
|
4557
|
+
var TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK;
|
|
4558
|
+
function isNodeErrorWithCode(err, code) {
|
|
4559
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === code;
|
|
4560
|
+
}
|
|
4561
|
+
function resolvePreferredOpenClawTmpDir(options = {}) {
|
|
4562
|
+
const accessSync = options.accessSync ?? fs.accessSync;
|
|
4563
|
+
const chmodSync = options.chmodSync ?? fs.chmodSync;
|
|
4564
|
+
const lstatSync = options.lstatSync ?? fs.lstatSync;
|
|
4565
|
+
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
|
4566
|
+
const warn = options.warn ?? ((message) => console.warn(message));
|
|
4567
|
+
const getuid = options.getuid ?? (() => {
|
|
4568
|
+
try {
|
|
4569
|
+
return typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
4570
|
+
} catch {
|
|
4571
|
+
return void 0;
|
|
4572
|
+
}
|
|
4573
|
+
});
|
|
4574
|
+
const tmpdir$1 = typeof options.tmpdir === "function" ? options.tmpdir : tmpdir;
|
|
4575
|
+
const uid = getuid();
|
|
4576
|
+
const isSecureDirForUser = (st) => {
|
|
4577
|
+
if (uid === void 0) {
|
|
4578
|
+
return true;
|
|
4579
|
+
}
|
|
4580
|
+
if (typeof st.uid === "number" && st.uid !== uid) {
|
|
4581
|
+
return false;
|
|
4582
|
+
}
|
|
4583
|
+
if (typeof st.mode === "number" && (st.mode & 18) !== 0) {
|
|
4584
|
+
return false;
|
|
4585
|
+
}
|
|
4586
|
+
return true;
|
|
4587
|
+
};
|
|
4588
|
+
const fallback = () => {
|
|
4589
|
+
const base = tmpdir$1();
|
|
4590
|
+
const suffix = uid === void 0 ? "openclaw" : `openclaw-${uid}`;
|
|
4591
|
+
return path2.join(base, suffix);
|
|
4592
|
+
};
|
|
4593
|
+
const isTrustedTmpDir = (st) => {
|
|
4594
|
+
return st.isDirectory() && !st.isSymbolicLink() && isSecureDirForUser(st);
|
|
4595
|
+
};
|
|
4596
|
+
const resolveDirState = (candidatePath) => {
|
|
4597
|
+
try {
|
|
4598
|
+
const candidate = lstatSync(candidatePath);
|
|
4599
|
+
if (!isTrustedTmpDir(candidate)) {
|
|
4600
|
+
return "invalid";
|
|
4601
|
+
}
|
|
4602
|
+
accessSync(candidatePath, TMP_DIR_ACCESS_MODE);
|
|
4603
|
+
return "available";
|
|
4604
|
+
} catch (err) {
|
|
4605
|
+
if (isNodeErrorWithCode(err, "ENOENT")) {
|
|
4606
|
+
return "missing";
|
|
4607
|
+
}
|
|
4608
|
+
return "invalid";
|
|
4609
|
+
}
|
|
4610
|
+
};
|
|
4611
|
+
const tryRepairWritableBits = (candidatePath) => {
|
|
4612
|
+
try {
|
|
4613
|
+
const st = lstatSync(candidatePath);
|
|
4614
|
+
if (!st.isDirectory() || st.isSymbolicLink()) {
|
|
4615
|
+
return false;
|
|
4616
|
+
}
|
|
4617
|
+
if (uid !== void 0 && typeof st.uid === "number" && st.uid !== uid) {
|
|
4618
|
+
return false;
|
|
4619
|
+
}
|
|
4620
|
+
if (typeof st.mode !== "number" || (st.mode & 18) === 0) {
|
|
4621
|
+
return false;
|
|
4622
|
+
}
|
|
4623
|
+
chmodSync(candidatePath, 448);
|
|
4624
|
+
warn(`[openclaw] tightened permissions on temp dir: ${candidatePath}`);
|
|
4625
|
+
return resolveDirState(candidatePath) === "available";
|
|
4626
|
+
} catch {
|
|
4627
|
+
return false;
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
const ensureTrustedFallbackDir = () => {
|
|
4631
|
+
const fallbackPath = fallback();
|
|
4632
|
+
const state = resolveDirState(fallbackPath);
|
|
4633
|
+
if (state === "available") {
|
|
4634
|
+
return fallbackPath;
|
|
4635
|
+
}
|
|
4636
|
+
if (state === "invalid") {
|
|
4637
|
+
if (tryRepairWritableBits(fallbackPath)) {
|
|
4638
|
+
return fallbackPath;
|
|
4639
|
+
}
|
|
4640
|
+
throw new Error(`Unsafe fallback OpenClaw temp dir: ${fallbackPath}`);
|
|
4641
|
+
}
|
|
4642
|
+
try {
|
|
4643
|
+
mkdirSync(fallbackPath, { recursive: true, mode: 448 });
|
|
4644
|
+
chmodSync(fallbackPath, 448);
|
|
4645
|
+
} catch {
|
|
4646
|
+
throw new Error(`Unable to create fallback OpenClaw temp dir: ${fallbackPath}`);
|
|
4647
|
+
}
|
|
4648
|
+
if (resolveDirState(fallbackPath) !== "available" && !tryRepairWritableBits(fallbackPath)) {
|
|
4649
|
+
throw new Error(`Unsafe fallback OpenClaw temp dir: ${fallbackPath}`);
|
|
4650
|
+
}
|
|
4651
|
+
return fallbackPath;
|
|
4652
|
+
};
|
|
4653
|
+
const existingPreferredState = resolveDirState(POSIX_OPENCLAW_TMP_DIR);
|
|
4654
|
+
if (existingPreferredState === "available") {
|
|
4655
|
+
return POSIX_OPENCLAW_TMP_DIR;
|
|
4656
|
+
}
|
|
4657
|
+
if (existingPreferredState === "invalid") {
|
|
4658
|
+
if (tryRepairWritableBits(POSIX_OPENCLAW_TMP_DIR)) {
|
|
4659
|
+
return POSIX_OPENCLAW_TMP_DIR;
|
|
4660
|
+
}
|
|
4661
|
+
return ensureTrustedFallbackDir();
|
|
4662
|
+
}
|
|
4663
|
+
try {
|
|
4664
|
+
accessSync("/tmp", TMP_DIR_ACCESS_MODE);
|
|
4665
|
+
mkdirSync(POSIX_OPENCLAW_TMP_DIR, { recursive: true, mode: 448 });
|
|
4666
|
+
chmodSync(POSIX_OPENCLAW_TMP_DIR, 448);
|
|
4667
|
+
if (resolveDirState(POSIX_OPENCLAW_TMP_DIR) !== "available" && !tryRepairWritableBits(POSIX_OPENCLAW_TMP_DIR)) {
|
|
4668
|
+
return ensureTrustedFallbackDir();
|
|
4669
|
+
}
|
|
4670
|
+
return POSIX_OPENCLAW_TMP_DIR;
|
|
4671
|
+
} catch {
|
|
4672
|
+
return ensureTrustedFallbackDir();
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4676
|
+
// src/media.ts
|
|
4677
|
+
var MEDIA_TEMP_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4678
|
+
var DEFAULT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
4679
|
+
var DEFAULT_DOWNLOAD_MAX_BYTES = 20 * 1024 * 1024;
|
|
4680
|
+
path2.join("openclaw-baiduapp", "media");
|
|
4681
|
+
var require3 = createRequire(import.meta.url);
|
|
4682
|
+
var BaiduCloudSdk = require3("@baiducloud/sdk");
|
|
4683
|
+
var DefaultBosClient = BaiduCloudSdk.BosClient;
|
|
4684
|
+
var MAX_DOWNLOAD_REDIRECTS = 5;
|
|
4685
|
+
var EMPTY_QUERY_MD5 = crypto3.createHash("md5").update("").digest("hex");
|
|
4686
|
+
var SKS_BASE_TOKEN_POSITIONS = [12, 37, 5, 23, 48, 15, 62, 33];
|
|
4687
|
+
async function fetchSksCredentials(account, deps = {}) {
|
|
4688
|
+
if (!account.appKey?.trim()) {
|
|
4689
|
+
throw new Error("Cannot fetch SKS credentials without account.appKey");
|
|
4690
|
+
}
|
|
4691
|
+
if (!account.appSecret?.trim()) {
|
|
4692
|
+
throw new Error("Cannot fetch SKS credentials without account.appSecret");
|
|
4693
|
+
}
|
|
4694
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
4695
|
+
const apiBase = account.apiBase.replace(/\/+$/, "");
|
|
4696
|
+
const pageLid = deps.createPageLid?.() ?? generateSksPageLid();
|
|
4697
|
+
const timestamp = String((deps.now?.() ?? /* @__PURE__ */ new Date()).getTime());
|
|
4698
|
+
const token = createSksRequestToken({ pageLid, timestamp });
|
|
4699
|
+
const url = `${apiBase}/file/sts?ak=${encodeURIComponent(account.appKey)}&sk=${encodeURIComponent(account.appSecret)}&tk=${encodeURIComponent(token)}`;
|
|
4700
|
+
const response = await fetchImpl(url, { method: "POST" });
|
|
4701
|
+
if (!response.ok) {
|
|
4702
|
+
throw new Error(`SKS request failed with HTTP ${response.status}`);
|
|
4703
|
+
}
|
|
4704
|
+
let payload;
|
|
4705
|
+
try {
|
|
4706
|
+
payload = await response.json();
|
|
4707
|
+
} catch (error) {
|
|
4708
|
+
throw new Error(`SKS response is not valid JSON: ${formatError(error)}`, { cause: error });
|
|
4709
|
+
}
|
|
4710
|
+
return parseSksCredentialsFromPayload(payload);
|
|
4711
|
+
}
|
|
4712
|
+
function parseSksCredentialsFromPayload(payload) {
|
|
4713
|
+
const parsed = parseSksResponse(payload);
|
|
4714
|
+
if (parsed.status !== 0) {
|
|
4715
|
+
throw new Error(`SKS request failed with status ${parsed.status}`);
|
|
4716
|
+
}
|
|
4717
|
+
if (!parsed.data) {
|
|
4718
|
+
throw new Error("SKS response missing data");
|
|
4719
|
+
}
|
|
4720
|
+
return {
|
|
4721
|
+
ak: requireNonEmptyString(parsed.data.ak, "SKS data.ak"),
|
|
4722
|
+
sk: requireNonEmptyString(parsed.data.sk, "SKS data.sk"),
|
|
4723
|
+
sessionToken: requireNonEmptyString(parsed.data.token, "SKS data.token"),
|
|
4724
|
+
bucketName: requireNonEmptyString(parsed.data.bucketName, "SKS data.bucketName"),
|
|
4725
|
+
prefixPath: sanitizeObjectKeyPrefix(requireNonEmptyString(parsed.data.preFixPath, "SKS data.preFixPath")),
|
|
4726
|
+
endpoint: requireNonEmptyString(parsed.data.bceUrl, "SKS data.bceUrl")
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
function createSksRequestToken(params) {
|
|
4730
|
+
const pageLid = requireNonEmptyString(params.pageLid, "SKS pageLid");
|
|
4731
|
+
const timestamp = requireNonEmptyString(params.timestamp, "SKS timestamp");
|
|
4732
|
+
const baseToken = createSksBaseToken(pageLid);
|
|
4733
|
+
const payload = `${baseToken}|${EMPTY_QUERY_MD5}|${timestamp}|${pageLid}`;
|
|
4734
|
+
return `${Buffer.from(payload, "utf8").toString("base64")}-${pageLid}-3`;
|
|
4735
|
+
}
|
|
4736
|
+
function createBosClient(credentials, deps = {}) {
|
|
4737
|
+
const BosClientCtor = deps.bosClientCtor ?? DefaultBosClient;
|
|
4738
|
+
return new BosClientCtor({
|
|
4739
|
+
endpoint: credentials.endpoint,
|
|
4740
|
+
sessionToken: credentials.sessionToken,
|
|
4741
|
+
credentials: {
|
|
4742
|
+
ak: credentials.ak,
|
|
4743
|
+
sk: credentials.sk
|
|
4744
|
+
}
|
|
4745
|
+
});
|
|
4746
|
+
}
|
|
4747
|
+
async function uploadLocalFileToBos(options, deps = {}) {
|
|
4748
|
+
const localFile = await assertUploadableLocalFile(options.filePath);
|
|
4749
|
+
const credentials = await fetchSksCredentials(options.account, deps);
|
|
4750
|
+
const client = createBosClient(credentials, deps);
|
|
4751
|
+
const sourceName = options.fileName ?? path2.basename(localFile);
|
|
4752
|
+
const sanitizedFileName = sanitizeFileName(sourceName);
|
|
4753
|
+
const key = buildBosObjectKey({
|
|
4754
|
+
prefixPath: credentials.prefixPath,
|
|
4755
|
+
fileName: sanitizedFileName,
|
|
4756
|
+
now: deps.now
|
|
4757
|
+
});
|
|
4758
|
+
const uploadOptions = options.contentType?.trim() ? {
|
|
4759
|
+
"Content-Type": options.contentType.trim()
|
|
4760
|
+
} : void 0;
|
|
4761
|
+
const [, fileStat] = await Promise.all([
|
|
4762
|
+
client.putObjectFromFile(credentials.bucketName, key, localFile, uploadOptions),
|
|
4763
|
+
fs2.stat(localFile)
|
|
4764
|
+
]);
|
|
4765
|
+
return {
|
|
4766
|
+
bucketName: credentials.bucketName,
|
|
4767
|
+
key,
|
|
4768
|
+
url: client.generateUrl(credentials.bucketName, key),
|
|
4769
|
+
fileName: sanitizedFileName,
|
|
4770
|
+
fileSize: fileStat.size
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
async function downloadInboundFileToTemp(options, deps = {}) {
|
|
4774
|
+
const remoteUrl = parseRemoteHttpUrl(options.remoteUrl);
|
|
4775
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_DOWNLOAD_TIMEOUT_MS;
|
|
4776
|
+
const maxBytes = options.maxBytes ?? DEFAULT_DOWNLOAD_MAX_BYTES;
|
|
4777
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
4778
|
+
const tempDir = await ensurePluginTempDir(deps.resolveTmpDir);
|
|
4779
|
+
const lookupHost = deps.lookupHost ?? defaultLookupHost;
|
|
4780
|
+
const controller = new AbortController();
|
|
4781
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
4782
|
+
try {
|
|
4783
|
+
const response = await fetchInboundFileWithRedirectProtection({
|
|
4784
|
+
fetchImpl,
|
|
4785
|
+
lookupHost,
|
|
4786
|
+
remoteUrl,
|
|
4787
|
+
signal: controller.signal
|
|
4788
|
+
});
|
|
4789
|
+
if (!response.ok) {
|
|
4790
|
+
throw new Error(`Download failed with HTTP ${response.status}`);
|
|
4791
|
+
}
|
|
4792
|
+
const declaredLength = response.headers.get("content-length");
|
|
4793
|
+
if (declaredLength) {
|
|
4794
|
+
const parsedLength = Number.parseInt(declaredLength, 10);
|
|
4795
|
+
if (Number.isFinite(parsedLength) && parsedLength > maxBytes) {
|
|
4796
|
+
throw new Error(`Download exceeds max size of ${maxBytes} bytes`);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
const resolvedFileName = resolveDownloadFileName({
|
|
4800
|
+
explicitFileName: options.fileName,
|
|
4801
|
+
headerFileName: parseContentDispositionFileName(response.headers.get("content-disposition")),
|
|
4802
|
+
urlFileName: path2.posix.basename(remoteUrl.pathname)
|
|
4803
|
+
});
|
|
4804
|
+
const targetPath = buildTempFilePath({
|
|
4805
|
+
tempDir,
|
|
4806
|
+
fileName: resolvedFileName,
|
|
4807
|
+
now: deps.now
|
|
4808
|
+
});
|
|
4809
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
4810
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
4811
|
+
if (buffer.byteLength > maxBytes) {
|
|
4812
|
+
throw new Error(`Download exceeds max size of ${maxBytes} bytes`);
|
|
4813
|
+
}
|
|
4814
|
+
await fs2.writeFile(targetPath, buffer, { mode: 384, flag: "wx" });
|
|
4815
|
+
return {
|
|
4816
|
+
path: targetPath,
|
|
4817
|
+
fileName: path2.basename(targetPath),
|
|
4818
|
+
size: buffer.byteLength,
|
|
4819
|
+
contentType: response.headers.get("content-type") ?? void 0
|
|
4820
|
+
};
|
|
4821
|
+
} catch (error) {
|
|
4822
|
+
if (isAbortError(error)) {
|
|
4823
|
+
throw new Error(`Download timed out after ${timeoutMs}ms`, { cause: error });
|
|
4824
|
+
}
|
|
4825
|
+
throw error;
|
|
4826
|
+
} finally {
|
|
4827
|
+
clearTimeout(timeout);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
async function pruneExpiredTempFiles(deps = {}) {
|
|
4831
|
+
const tempDir = await ensurePluginTempDir(deps.resolveTmpDir);
|
|
4832
|
+
const now = deps.now?.() ?? /* @__PURE__ */ new Date();
|
|
4833
|
+
let deletedFiles = 0;
|
|
4834
|
+
const walk = async (currentDir) => {
|
|
4835
|
+
const entries = await fs2.readdir(currentDir, { withFileTypes: true });
|
|
4836
|
+
let hasRemainingEntries = false;
|
|
4837
|
+
for (const entry of entries) {
|
|
4838
|
+
const entryPath = path2.join(currentDir, entry.name);
|
|
4839
|
+
const stat = await fs2.lstat(entryPath);
|
|
4840
|
+
if (stat.isSymbolicLink()) {
|
|
4841
|
+
throw new Error(`Refusing to traverse symlink in media temp dir: ${entryPath}`);
|
|
4842
|
+
}
|
|
4843
|
+
if (stat.isDirectory()) {
|
|
4844
|
+
const childEmpty = await walk(entryPath);
|
|
4845
|
+
if (childEmpty) {
|
|
4846
|
+
await fs2.rmdir(entryPath);
|
|
4847
|
+
} else {
|
|
4848
|
+
hasRemainingEntries = true;
|
|
4849
|
+
}
|
|
4850
|
+
continue;
|
|
4851
|
+
}
|
|
4852
|
+
if (!stat.isFile()) {
|
|
4853
|
+
hasRemainingEntries = true;
|
|
4854
|
+
continue;
|
|
4855
|
+
}
|
|
4856
|
+
if (now.getTime() - stat.mtimeMs > MEDIA_TEMP_RETENTION_MS) {
|
|
4857
|
+
await fs2.rm(entryPath, { force: true });
|
|
4858
|
+
deletedFiles += 1;
|
|
4859
|
+
continue;
|
|
4463
4860
|
}
|
|
4861
|
+
hasRemainingEntries = true;
|
|
4464
4862
|
}
|
|
4863
|
+
return !hasRemainingEntries;
|
|
4864
|
+
};
|
|
4865
|
+
await walk(tempDir);
|
|
4866
|
+
return { deletedFiles };
|
|
4867
|
+
}
|
|
4868
|
+
function parseSksResponse(payload) {
|
|
4869
|
+
if (!payload || typeof payload !== "object") {
|
|
4870
|
+
throw new Error("SKS response must be an object");
|
|
4871
|
+
}
|
|
4872
|
+
const record = payload;
|
|
4873
|
+
const status = typeof record.code === "number" ? record.code : typeof record.status === "number" ? record.status : void 0;
|
|
4874
|
+
const data = record.data;
|
|
4875
|
+
return {
|
|
4876
|
+
status,
|
|
4877
|
+
data: data && typeof data === "object" ? {
|
|
4878
|
+
ak: data.ak,
|
|
4879
|
+
sk: data.sk,
|
|
4880
|
+
token: data.token,
|
|
4881
|
+
bucketName: data.bucketName,
|
|
4882
|
+
preFixPath: data.preFixPath,
|
|
4883
|
+
bceUrl: data.bceUrl
|
|
4884
|
+
} : void 0
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
function generateSksPageLid() {
|
|
4888
|
+
return crypto3.randomBytes(16).toString("hex");
|
|
4889
|
+
}
|
|
4890
|
+
function createSksBaseToken(pageLid) {
|
|
4891
|
+
const pageLidHash = crypto3.createHash("sha256").update(pageLid).digest("hex");
|
|
4892
|
+
const characters = SKS_BASE_TOKEN_POSITIONS.map((position) => {
|
|
4893
|
+
const character = pageLidHash[position];
|
|
4894
|
+
if (!character) {
|
|
4895
|
+
throw new Error(`SKS base token position is out of range: ${position}`);
|
|
4896
|
+
}
|
|
4897
|
+
return character;
|
|
4898
|
+
});
|
|
4899
|
+
return characters.join("");
|
|
4900
|
+
}
|
|
4901
|
+
function requireNonEmptyString(value, label) {
|
|
4902
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
4903
|
+
throw new Error(`${label} is required`);
|
|
4904
|
+
}
|
|
4905
|
+
return value.trim();
|
|
4906
|
+
}
|
|
4907
|
+
function sanitizeObjectKeyPrefix(prefixPath) {
|
|
4908
|
+
const normalized = prefixPath.replace(/\\/g, "/").trim();
|
|
4909
|
+
const segments = normalized.split("/").map((segment) => segment.trim()).filter(Boolean);
|
|
4910
|
+
if (segments.length === 0) {
|
|
4911
|
+
throw new Error("SKS data.preFixPath must contain at least one safe path segment");
|
|
4912
|
+
}
|
|
4913
|
+
const safeSegments = segments.map((segment) => {
|
|
4914
|
+
if (segment === "." || segment === "..") {
|
|
4915
|
+
throw new Error("SKS data.preFixPath contains path traversal");
|
|
4916
|
+
}
|
|
4917
|
+
return sanitizeObjectKeySegment(segment);
|
|
4465
4918
|
});
|
|
4466
|
-
|
|
4919
|
+
return safeSegments.join("/");
|
|
4920
|
+
}
|
|
4921
|
+
function sanitizeObjectKeySegment(segment) {
|
|
4922
|
+
const sanitized = segment.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4923
|
+
if (!sanitized) {
|
|
4924
|
+
throw new Error(`Object key segment is not safe: ${segment}`);
|
|
4925
|
+
}
|
|
4926
|
+
return sanitized.slice(0, 80);
|
|
4927
|
+
}
|
|
4928
|
+
function sanitizeFileName(fileName) {
|
|
4929
|
+
assertSafeUntrustedFileName(fileName);
|
|
4930
|
+
const parsed = path2.parse(fileName.trim());
|
|
4931
|
+
const safeBase = parsed.name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4932
|
+
const safeExt = parsed.ext.replace(/[^a-zA-Z0-9.]+/g, "").slice(0, 16);
|
|
4933
|
+
const baseName = safeBase || "file";
|
|
4934
|
+
return `${baseName.slice(0, 80)}${safeExt}`;
|
|
4935
|
+
}
|
|
4936
|
+
function assertSafeUntrustedFileName(fileName) {
|
|
4937
|
+
const trimmed = fileName.trim();
|
|
4938
|
+
if (!trimmed) {
|
|
4939
|
+
throw new Error("Filename cannot be empty");
|
|
4940
|
+
}
|
|
4941
|
+
if (trimmed.includes("/") || trimmed.includes("\\")) {
|
|
4942
|
+
throw new Error(`Filename contains path separators: ${fileName}`);
|
|
4943
|
+
}
|
|
4944
|
+
if (trimmed === "." || trimmed === "..") {
|
|
4945
|
+
throw new Error(`Filename is not safe: ${fileName}`);
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
function buildBosObjectKey(params) {
|
|
4949
|
+
const timestamp = formatUtcDate(params.now?.() ?? /* @__PURE__ */ new Date());
|
|
4950
|
+
const uniqueId = crypto3.randomUUID();
|
|
4951
|
+
return `${params.prefixPath}/${timestamp}/${uniqueId}-${params.fileName}`;
|
|
4952
|
+
}
|
|
4953
|
+
function formatUtcDate(date) {
|
|
4954
|
+
return date.toISOString().slice(0, 10);
|
|
4955
|
+
}
|
|
4956
|
+
async function assertUploadableLocalFile(filePath) {
|
|
4957
|
+
const resolvedPath = path2.resolve(filePath);
|
|
4958
|
+
const stat = await fs2.lstat(resolvedPath);
|
|
4959
|
+
if (stat.isSymbolicLink()) {
|
|
4960
|
+
throw new Error(`Refusing to upload symlink: ${resolvedPath}`);
|
|
4961
|
+
}
|
|
4962
|
+
if (!stat.isFile()) {
|
|
4963
|
+
throw new Error(`Upload source must be a regular file: ${resolvedPath}`);
|
|
4964
|
+
}
|
|
4965
|
+
return resolvedPath;
|
|
4966
|
+
}
|
|
4967
|
+
function parseRemoteHttpUrl(remoteUrl) {
|
|
4968
|
+
let url;
|
|
4969
|
+
try {
|
|
4970
|
+
url = new URL(remoteUrl);
|
|
4971
|
+
} catch {
|
|
4972
|
+
throw new Error(`Invalid remote URL: ${remoteUrl}`);
|
|
4973
|
+
}
|
|
4974
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
4975
|
+
throw new Error(`Unsupported remote URL protocol: ${url.protocol}`);
|
|
4976
|
+
}
|
|
4977
|
+
if (url.username || url.password) {
|
|
4978
|
+
throw new Error("Remote URL must not contain credentials");
|
|
4979
|
+
}
|
|
4980
|
+
return url;
|
|
4981
|
+
}
|
|
4982
|
+
async function fetchInboundFileWithRedirectProtection(params) {
|
|
4983
|
+
let currentUrl = params.remoteUrl;
|
|
4984
|
+
for (let redirectCount = 0; redirectCount <= MAX_DOWNLOAD_REDIRECTS; redirectCount += 1) {
|
|
4985
|
+
await assertSafeRemoteHttpUrl(currentUrl, params.lookupHost);
|
|
4986
|
+
const response = await params.fetchImpl(currentUrl.toString(), {
|
|
4987
|
+
signal: params.signal,
|
|
4988
|
+
redirect: "manual"
|
|
4989
|
+
});
|
|
4990
|
+
if (!isRedirectResponse(response.status)) {
|
|
4991
|
+
return response;
|
|
4992
|
+
}
|
|
4993
|
+
const location = response.headers.get("location");
|
|
4994
|
+
if (!location) {
|
|
4995
|
+
throw new Error(`Redirect response missing Location header: HTTP ${response.status}`);
|
|
4996
|
+
}
|
|
4997
|
+
if (redirectCount === MAX_DOWNLOAD_REDIRECTS) {
|
|
4998
|
+
throw new Error(`Download exceeded max redirects of ${MAX_DOWNLOAD_REDIRECTS}`);
|
|
4999
|
+
}
|
|
5000
|
+
currentUrl = parseRemoteHttpUrl(new URL(location, currentUrl).toString());
|
|
5001
|
+
}
|
|
5002
|
+
throw new Error(`Download exceeded max redirects of ${MAX_DOWNLOAD_REDIRECTS}`);
|
|
5003
|
+
}
|
|
5004
|
+
async function assertSafeRemoteHttpUrl(remoteUrl, lookupHost) {
|
|
5005
|
+
const hostname = remoteUrl.hostname.trim().replace(/\.$/, "").toLowerCase();
|
|
5006
|
+
if (!hostname) {
|
|
5007
|
+
throw new Error("Remote URL hostname is required");
|
|
5008
|
+
}
|
|
5009
|
+
if (isBlockedHostname(hostname)) {
|
|
5010
|
+
throw new Error(`Remote URL host is not allowed: ${hostname}`);
|
|
5011
|
+
}
|
|
5012
|
+
if (net.isIP(hostname)) {
|
|
5013
|
+
if (isBlockedIpAddress(hostname)) {
|
|
5014
|
+
throw new Error(`Remote URL host is not allowed: ${hostname}`);
|
|
5015
|
+
}
|
|
5016
|
+
return;
|
|
5017
|
+
}
|
|
5018
|
+
let resolvedAddresses;
|
|
5019
|
+
try {
|
|
5020
|
+
resolvedAddresses = await lookupHost(hostname);
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
throw new Error(`Remote URL host lookup failed: ${hostname} (${formatError(error)})`, { cause: error });
|
|
5023
|
+
}
|
|
5024
|
+
if (!resolvedAddresses.length) {
|
|
5025
|
+
throw new Error(`Remote URL host lookup returned no addresses: ${hostname}`);
|
|
5026
|
+
}
|
|
5027
|
+
const blockedAddress = resolvedAddresses.find((result) => isBlockedIpAddress(result.address));
|
|
5028
|
+
if (blockedAddress) {
|
|
5029
|
+
throw new Error(`Remote URL host resolved to a blocked address: ${hostname} -> ${blockedAddress.address}`);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
async function defaultLookupHost(hostname) {
|
|
5033
|
+
return await lookup(hostname, { all: true, verbatim: true });
|
|
5034
|
+
}
|
|
5035
|
+
function isRedirectResponse(status) {
|
|
5036
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
5037
|
+
}
|
|
5038
|
+
function isBlockedHostname(hostname) {
|
|
5039
|
+
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "metadata" || hostname.endsWith(".metadata") || hostname === "metadata.google.internal" || hostname.endsWith(".metadata.google.internal");
|
|
5040
|
+
}
|
|
5041
|
+
function isBlockedIpAddress(address) {
|
|
5042
|
+
const family = net.isIP(address);
|
|
5043
|
+
if (family === 4) {
|
|
5044
|
+
return isBlockedIpv4Address(address);
|
|
5045
|
+
}
|
|
5046
|
+
if (family === 6) {
|
|
5047
|
+
return isBlockedIpv6Address(address);
|
|
5048
|
+
}
|
|
5049
|
+
return false;
|
|
5050
|
+
}
|
|
5051
|
+
function isBlockedIpv4Address(address) {
|
|
5052
|
+
const octets = address.split(".").map((part) => Number.parseInt(part, 10));
|
|
5053
|
+
if (octets.length !== 4 || octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
|
|
5054
|
+
return false;
|
|
5055
|
+
}
|
|
5056
|
+
const [a, b] = octets;
|
|
5057
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a === 224 && b >= 0 || a >= 240;
|
|
5058
|
+
}
|
|
5059
|
+
function isBlockedIpv6Address(address) {
|
|
5060
|
+
const normalized = address.toLowerCase();
|
|
5061
|
+
return normalized === "::1" || normalized === "::" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80:") || normalized.startsWith("::ffff:127.") || normalized.startsWith("::ffff:10.") || normalized.startsWith("::ffff:169.254.") || normalized.startsWith("::ffff:172.16.") || normalized.startsWith("::ffff:172.17.") || normalized.startsWith("::ffff:172.18.") || normalized.startsWith("::ffff:172.19.") || normalized.startsWith("::ffff:172.2") || normalized.startsWith("::ffff:172.30.") || normalized.startsWith("::ffff:172.31.") || normalized.startsWith("::ffff:192.168.");
|
|
5062
|
+
}
|
|
5063
|
+
function parseContentDispositionFileName(contentDisposition) {
|
|
5064
|
+
if (!contentDisposition) {
|
|
5065
|
+
return void 0;
|
|
5066
|
+
}
|
|
5067
|
+
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
|
|
5068
|
+
if (utf8Match?.[1]) {
|
|
5069
|
+
return decodeURIComponent(utf8Match[1].replace(/^"|"$/g, "").trim());
|
|
5070
|
+
}
|
|
5071
|
+
const plainMatch = contentDisposition.match(/filename=([^;]+)/i);
|
|
5072
|
+
if (plainMatch?.[1]) {
|
|
5073
|
+
return plainMatch[1].replace(/^"|"$/g, "").trim();
|
|
5074
|
+
}
|
|
5075
|
+
return void 0;
|
|
5076
|
+
}
|
|
5077
|
+
function resolveDownloadFileName(params) {
|
|
5078
|
+
const candidate = params.explicitFileName ?? params.headerFileName ?? params.urlFileName ?? "file.bin";
|
|
5079
|
+
return sanitizeFileName(candidate);
|
|
5080
|
+
}
|
|
5081
|
+
function buildTempFilePath(params) {
|
|
5082
|
+
const timestamp = (params.now?.() ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5083
|
+
return path2.join(params.tempDir, `${timestamp}-${crypto3.randomUUID()}-${params.fileName}`);
|
|
5084
|
+
}
|
|
5085
|
+
async function ensurePluginTempDir(resolveTmpDir) {
|
|
5086
|
+
const baseTmpDir = resolveTmpDir?.() ?? resolvePreferredOpenClawTmpDir();
|
|
5087
|
+
const pluginRoot = await ensureSecureChildDirectory(baseTmpDir, "openclaw-baiduapp");
|
|
5088
|
+
return ensureSecureChildDirectory(pluginRoot, "media");
|
|
5089
|
+
}
|
|
5090
|
+
async function ensureSecureChildDirectory(parentDir, childName) {
|
|
5091
|
+
const targetDir = path2.join(parentDir, childName);
|
|
5092
|
+
const stat = await fs2.lstat(targetDir).catch(() => null);
|
|
5093
|
+
if (stat) {
|
|
5094
|
+
if (stat.isSymbolicLink()) {
|
|
5095
|
+
throw new Error(`Refusing to use symlink as temp directory: ${targetDir}`);
|
|
5096
|
+
}
|
|
5097
|
+
if (!stat.isDirectory()) {
|
|
5098
|
+
throw new Error(`Expected directory but found file: ${targetDir}`);
|
|
5099
|
+
}
|
|
5100
|
+
return targetDir;
|
|
5101
|
+
}
|
|
5102
|
+
await fs2.mkdir(targetDir, { mode: 448 });
|
|
5103
|
+
return targetDir;
|
|
5104
|
+
}
|
|
5105
|
+
function formatError(error) {
|
|
5106
|
+
return error instanceof Error ? error.message : String(error);
|
|
5107
|
+
}
|
|
5108
|
+
function isAbortError(error) {
|
|
5109
|
+
return error instanceof Error && error.name === "AbortError";
|
|
4467
5110
|
}
|
|
4468
5111
|
|
|
4469
5112
|
// src/runtime.ts
|
|
@@ -4488,6 +5131,7 @@ var msgidToStreamId = /* @__PURE__ */ new Map();
|
|
|
4488
5131
|
var STREAM_TTL_MS = 10 * 60 * 1e3;
|
|
4489
5132
|
var STREAM_MAX_BYTES = 512e3;
|
|
4490
5133
|
var MAX_MESSAGE_BYTES = 2048;
|
|
5134
|
+
var tempPruneInFlight = null;
|
|
4491
5135
|
function normalizeWebhookPath(raw) {
|
|
4492
5136
|
const trimmed = raw.trim();
|
|
4493
5137
|
if (!trimmed) {
|
|
@@ -4654,7 +5298,7 @@ function buildStreamReplyFromState(state) {
|
|
|
4654
5298
|
};
|
|
4655
5299
|
}
|
|
4656
5300
|
function createStreamId() {
|
|
4657
|
-
return
|
|
5301
|
+
return crypto3.randomBytes(16).toString("hex");
|
|
4658
5302
|
}
|
|
4659
5303
|
function parseBaiduAppPlainMessage(raw) {
|
|
4660
5304
|
const trimmed = raw.trim();
|
|
@@ -4725,230 +5369,96 @@ function buildLogger(target) {
|
|
|
4725
5369
|
error: target.runtime.error
|
|
4726
5370
|
});
|
|
4727
5371
|
}
|
|
4728
|
-
function
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
webhookTargets.set(key, next);
|
|
4734
|
-
return () => {
|
|
4735
|
-
const updated = (webhookTargets.get(key) ?? []).filter((entry) => entry !== normalizedTarget);
|
|
4736
|
-
if (updated.length > 0) {
|
|
4737
|
-
webhookTargets.set(key, updated);
|
|
4738
|
-
} else {
|
|
4739
|
-
webhookTargets.delete(key);
|
|
4740
|
-
}
|
|
4741
|
-
};
|
|
5372
|
+
function buildRuntimeLogger(runtime2) {
|
|
5373
|
+
return createLogger("openclaw-baiduapp", {
|
|
5374
|
+
log: runtime2.log,
|
|
5375
|
+
error: runtime2.error
|
|
5376
|
+
});
|
|
4742
5377
|
}
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
const targets = webhookTargets.get(path);
|
|
4747
|
-
if (!targets || targets.length === 0) {
|
|
4748
|
-
return false;
|
|
5378
|
+
function triggerBestEffortTempPrune(params) {
|
|
5379
|
+
if (tempPruneInFlight) {
|
|
5380
|
+
return;
|
|
4749
5381
|
}
|
|
4750
|
-
const
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
5382
|
+
const logger3 = buildRuntimeLogger(params.runtime);
|
|
5383
|
+
tempPruneInFlight = pruneExpiredTempFiles().then(() => void 0).catch((error) => {
|
|
5384
|
+
logger3.warn(`temp prune failed: reason=${params.reason} error=${String(error)}`);
|
|
5385
|
+
}).finally(() => {
|
|
5386
|
+
tempPruneInFlight = null;
|
|
5387
|
+
});
|
|
5388
|
+
}
|
|
5389
|
+
async function downloadInboundFiles(params) {
|
|
5390
|
+
const rawFiles = params.msg.files;
|
|
5391
|
+
if (!Array.isArray(rawFiles) || rawFiles.length === 0) {
|
|
5392
|
+
return { localFiles: [], diagnostic: {} };
|
|
5393
|
+
}
|
|
5394
|
+
const localFiles = [];
|
|
5395
|
+
const failurePlaceholders = [];
|
|
5396
|
+
try {
|
|
5397
|
+
for (const [index, rawFile] of rawFiles.entries()) {
|
|
5398
|
+
if (!rawFile || typeof rawFile !== "object") {
|
|
5399
|
+
params.logger.warn(`inbound file skipped: index=${index} reason=invalid-file-record`);
|
|
5400
|
+
continue;
|
|
5401
|
+
}
|
|
5402
|
+
const fileRecord = rawFile;
|
|
5403
|
+
const remoteUrl = typeof fileRecord.url === "string" ? fileRecord.url.trim() : "";
|
|
5404
|
+
if (!remoteUrl) {
|
|
5405
|
+
params.logger.warn(`inbound file skipped: index=${index} reason=missing-url`);
|
|
5406
|
+
continue;
|
|
5407
|
+
}
|
|
5408
|
+
try {
|
|
5409
|
+
const downloaded = await downloadInboundFileToTemp({ remoteUrl });
|
|
5410
|
+
localFiles.push({
|
|
5411
|
+
...downloaded,
|
|
5412
|
+
contentType: downloaded.contentType ?? (typeof fileRecord.fileType === "string" && fileRecord.fileType.trim() ? fileRecord.fileType : void 0)
|
|
5413
|
+
});
|
|
5414
|
+
params.logger.info(
|
|
5415
|
+
`inbound file downloaded: index=${index} path=${downloaded.path} size=${downloaded.size}`
|
|
5416
|
+
);
|
|
5417
|
+
} catch (err) {
|
|
5418
|
+
failurePlaceholders.push(`[file download failed] ${remoteUrl} (${err instanceof Error ? err.message : String(err)})`);
|
|
5419
|
+
params.logger.warn(`inbound file download failed: index=${index} error=${String(err)}`);
|
|
4765
5420
|
}
|
|
4766
|
-
return verifyBaiduAppSignature({
|
|
4767
|
-
token: candidate.account.token,
|
|
4768
|
-
timestamp,
|
|
4769
|
-
nonce,
|
|
4770
|
-
encrypt: echostr,
|
|
4771
|
-
signature
|
|
4772
|
-
});
|
|
4773
|
-
});
|
|
4774
|
-
if (signatureMatched2.length === 0) {
|
|
4775
|
-
jsonError(res, "unauthorized");
|
|
4776
|
-
return true;
|
|
4777
|
-
}
|
|
4778
|
-
const decryptable2 = signatureMatched2.filter((candidate) => Boolean(candidate.account.encodingAESKey));
|
|
4779
|
-
if (decryptable2.length === 0) {
|
|
4780
|
-
jsonError(res, "unauthorized");
|
|
4781
|
-
return true;
|
|
4782
|
-
}
|
|
4783
|
-
const decryptedCandidates2 = decryptBaiduAppCandidates({
|
|
4784
|
-
candidates: decryptable2,
|
|
4785
|
-
encrypt: echostr
|
|
4786
|
-
});
|
|
4787
|
-
if (decryptedCandidates2.length === 0) {
|
|
4788
|
-
jsonError(res, "decrypt failed");
|
|
4789
|
-
return true;
|
|
4790
5421
|
}
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
return true;
|
|
5422
|
+
} finally {
|
|
5423
|
+
triggerBestEffortTempPrune({ runtime: params.runtime, reason: "inbound-file" });
|
|
4794
5424
|
}
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
if (!timestamp || !nonce || !signature) {
|
|
4800
|
-
jsonError(res, "missing query params");
|
|
4801
|
-
return true;
|
|
4802
|
-
}
|
|
4803
|
-
const body = await readRawBody(req, 1024 * 1024);
|
|
4804
|
-
if (!body.ok || !body.raw) {
|
|
4805
|
-
jsonError(res, body.error ?? "invalid payload");
|
|
4806
|
-
return true;
|
|
4807
|
-
}
|
|
4808
|
-
const rawBody = body.raw;
|
|
4809
|
-
let encrypt = "";
|
|
4810
|
-
let msgSignature = signature;
|
|
4811
|
-
let msgTimestamp = timestamp;
|
|
4812
|
-
let msgNonce = nonce;
|
|
4813
|
-
if (isXmlFormat(rawBody)) {
|
|
4814
|
-
const xmlData = parseXmlBody(rawBody);
|
|
4815
|
-
encrypt = xmlData.Encrypt ?? "";
|
|
4816
|
-
msgSignature = xmlData.MsgSignature ?? signature;
|
|
4817
|
-
msgTimestamp = xmlData.TimeStamp ?? timestamp;
|
|
4818
|
-
msgNonce = xmlData.Nonce ?? nonce;
|
|
4819
|
-
logger2.info(`inbound xml parsed: hasEncrypt=${Boolean(encrypt)}, msg_signature=${msgSignature ? "yes" : "no"}`);
|
|
4820
|
-
} else {
|
|
4821
|
-
try {
|
|
4822
|
-
const record = JSON.parse(rawBody);
|
|
4823
|
-
encrypt = String(record.encrypt ?? record.Encrypt ?? "");
|
|
4824
|
-
logger2.info(`inbound json parsed: hasEncrypt=${Boolean(encrypt)}`);
|
|
4825
|
-
} catch {
|
|
4826
|
-
logger2.warn(`inbound payload parse failed: not valid xml or json`);
|
|
4827
|
-
jsonError(res, "invalid payload format");
|
|
4828
|
-
return true;
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
4831
|
-
if (!encrypt) {
|
|
4832
|
-
jsonError(res, "missing encrypt");
|
|
4833
|
-
return true;
|
|
4834
|
-
}
|
|
4835
|
-
const signatureMatched = targets.filter((candidate) => {
|
|
4836
|
-
if (!candidate.account.token) {
|
|
4837
|
-
return false;
|
|
5425
|
+
return {
|
|
5426
|
+
localFiles,
|
|
5427
|
+
diagnostic: {
|
|
5428
|
+
failurePlaceholders
|
|
4838
5429
|
}
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
signature: msgSignature
|
|
4845
|
-
});
|
|
4846
|
-
});
|
|
4847
|
-
if (signatureMatched.length === 0) {
|
|
4848
|
-
logger2.warn(`signature verification failed: checked ${targets.length} account(s), none matched`);
|
|
4849
|
-
jsonError(res, "unauthorized");
|
|
4850
|
-
return true;
|
|
4851
|
-
}
|
|
4852
|
-
logger2.debug(`signature verified: ${signatureMatched.length} account(s) matched`);
|
|
4853
|
-
const decryptable = signatureMatched.filter((candidate) => Boolean(candidate.account.encodingAESKey));
|
|
4854
|
-
if (decryptable.length === 0) {
|
|
4855
|
-
logger2.warn(`no account has encodingAESKey configured`);
|
|
4856
|
-
jsonError(res, "openclaw-baiduapp not configured");
|
|
4857
|
-
return true;
|
|
4858
|
-
}
|
|
4859
|
-
const decryptedCandidates = decryptBaiduAppCandidates({
|
|
4860
|
-
candidates: decryptable,
|
|
4861
|
-
encrypt
|
|
4862
|
-
});
|
|
4863
|
-
if (decryptedCandidates.length === 0) {
|
|
4864
|
-
logger2.warn(`decrypt failed for all ${decryptable.length} candidate account(s)`);
|
|
4865
|
-
jsonError(res, "decrypt failed");
|
|
4866
|
-
return true;
|
|
4867
|
-
}
|
|
4868
|
-
const selected = selectDecryptedTarget({ candidates: decryptedCandidates, logger: logger2 });
|
|
4869
|
-
const target = selected.target;
|
|
4870
|
-
if (!target.account.configured || !target.account.token || !target.account.encodingAESKey) {
|
|
4871
|
-
logger2.warn(`selected account ${target.account.accountId} not fully configured`);
|
|
4872
|
-
jsonError(res, "openclaw-baiduapp not configured");
|
|
4873
|
-
return true;
|
|
4874
|
-
}
|
|
4875
|
-
const msg = selected.msg;
|
|
5430
|
+
};
|
|
5431
|
+
}
|
|
5432
|
+
async function processBaiduAppInboundMessage(params) {
|
|
5433
|
+
const { target, msg } = params;
|
|
5434
|
+
const logger3 = buildLogger(target);
|
|
4876
5435
|
target.statusSink?.({ lastInboundAt: Date.now() });
|
|
4877
5436
|
const msgtype = String(msg.msgtype ?? msg.MsgType ?? "").toLowerCase();
|
|
4878
5437
|
const msgid = msg.msgid ?? msg.MsgId ? String(msg.msgid ?? msg.MsgId) : void 0;
|
|
4879
|
-
|
|
5438
|
+
logger3.info(`inbound: type=${msgtype || "unknown"} msgid=${msgid ?? "none"} account=${target.account.accountId}`);
|
|
4880
5439
|
if (msgtype === "stream") {
|
|
4881
5440
|
const streamId2 = String(msg.stream?.id ?? "").trim();
|
|
4882
5441
|
const state = streamId2 ? streams.get(streamId2) : void 0;
|
|
4883
|
-
|
|
5442
|
+
logger3.info(
|
|
4884
5443
|
`[REPLY-MODE:STREAM-POLL] stream poll request: streamId=${streamId2 || "none"} found=${Boolean(state)} finished=${state?.finished ?? "n/a"} contentLen=${state?.content.length ?? 0}`
|
|
4885
5444
|
);
|
|
4886
|
-
|
|
5445
|
+
return state ? buildStreamReplyFromState(state) : buildStreamReplyFromState({
|
|
4887
5446
|
streamId: streamId2 || "unknown",
|
|
4888
5447
|
finished: true,
|
|
4889
5448
|
content: ""
|
|
4890
5449
|
});
|
|
4891
|
-
jsonOk(
|
|
4892
|
-
res,
|
|
4893
|
-
buildEncryptedJsonReply({
|
|
4894
|
-
account: target.account,
|
|
4895
|
-
plaintextJson: reply,
|
|
4896
|
-
nonce: msgNonce,
|
|
4897
|
-
timestamp: msgTimestamp
|
|
4898
|
-
})
|
|
4899
|
-
);
|
|
4900
|
-
return true;
|
|
4901
5450
|
}
|
|
4902
5451
|
if (msgid && msgidToStreamId.has(msgid)) {
|
|
4903
5452
|
const streamId2 = msgidToStreamId.get(msgid) ?? "";
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
jsonOk(
|
|
4907
|
-
res,
|
|
4908
|
-
buildEncryptedJsonReply({
|
|
4909
|
-
account: target.account,
|
|
4910
|
-
plaintextJson: reply,
|
|
4911
|
-
nonce: msgNonce,
|
|
4912
|
-
timestamp: msgTimestamp
|
|
4913
|
-
})
|
|
4914
|
-
);
|
|
4915
|
-
return true;
|
|
5453
|
+
logger3.debug(`duplicate msgid detected: msgid=${msgid} streamId=${streamId2}, returning placeholder`);
|
|
5454
|
+
return buildStreamPlaceholderReply(streamId2);
|
|
4916
5455
|
}
|
|
4917
|
-
if (
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
).toLowerCase();
|
|
4921
|
-
logger2.info(`event received: type=${eventtype || "unknown"}`);
|
|
4922
|
-
if (eventtype === "enter_chat" || eventtype === "subscribe") {
|
|
4923
|
-
const welcome = target.account.config.welcomeText?.trim();
|
|
4924
|
-
if (welcome && target.account.canSendActive) {
|
|
4925
|
-
logger2.info("sending welcome message");
|
|
4926
|
-
sendBaiduAppMessage(target.account, welcome).catch((err) => {
|
|
4927
|
-
logger2.error(`failed to send welcome message: ${String(err)}`);
|
|
4928
|
-
});
|
|
4929
|
-
}
|
|
4930
|
-
jsonOk(
|
|
4931
|
-
res,
|
|
4932
|
-
buildEncryptedJsonReply({
|
|
4933
|
-
account: target.account,
|
|
4934
|
-
plaintextJson: {},
|
|
4935
|
-
nonce: msgNonce,
|
|
4936
|
-
timestamp: msgTimestamp
|
|
4937
|
-
})
|
|
4938
|
-
);
|
|
4939
|
-
return true;
|
|
4940
|
-
}
|
|
4941
|
-
jsonOk(
|
|
4942
|
-
res,
|
|
4943
|
-
buildEncryptedJsonReply({
|
|
4944
|
-
account: target.account,
|
|
4945
|
-
plaintextJson: {},
|
|
4946
|
-
nonce: msgNonce,
|
|
4947
|
-
timestamp: msgTimestamp
|
|
4948
|
-
})
|
|
4949
|
-
);
|
|
4950
|
-
return true;
|
|
5456
|
+
if (!canDispatchBaiduAppInboundMessage(msg)) {
|
|
5457
|
+
logger3.warn(`inbound message skipped: type=${msgtype || "unknown"} reason=no-dispatchable-content`);
|
|
5458
|
+
return {};
|
|
4951
5459
|
}
|
|
5460
|
+
const inboundFileResult = msgtype === "text" ? await downloadInboundFiles({ msg, logger: logger3, runtime: target.runtime }) : { localFiles: [], diagnostic: {} };
|
|
5461
|
+
const inboundMediaFiles = inboundFileResult.localFiles;
|
|
4952
5462
|
const streamId = createStreamId();
|
|
4953
5463
|
if (msgid) {
|
|
4954
5464
|
msgidToStreamId.set(msgid, streamId);
|
|
@@ -4962,14 +5472,14 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
4962
5472
|
finished: false,
|
|
4963
5473
|
content: ""
|
|
4964
5474
|
});
|
|
4965
|
-
|
|
5475
|
+
logger3.info(`stream created: streamId=${streamId} msgid=${msgid ?? "none"}`);
|
|
4966
5476
|
const core = tryGetBaiduAppRuntime();
|
|
4967
5477
|
if (core) {
|
|
4968
5478
|
const state = streams.get(streamId);
|
|
4969
5479
|
if (state) {
|
|
4970
5480
|
state.started = true;
|
|
4971
5481
|
}
|
|
4972
|
-
|
|
5482
|
+
logger3.info(`agent dispatch started: streamId=${streamId} canSendActive=${target.account.canSendActive}`);
|
|
4973
5483
|
const hooks = {
|
|
4974
5484
|
onChunk: (text) => {
|
|
4975
5485
|
const current = streams.get(streamId);
|
|
@@ -4977,7 +5487,7 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
4977
5487
|
return;
|
|
4978
5488
|
}
|
|
4979
5489
|
appendStreamContent(current, text);
|
|
4980
|
-
|
|
5490
|
+
logger3.debug(
|
|
4981
5491
|
`chunk received: streamId=${streamId} chunkLen=${text.length} totalLen=${current.content.length}`
|
|
4982
5492
|
);
|
|
4983
5493
|
target.statusSink?.({ lastOutboundAt: Date.now() });
|
|
@@ -4990,7 +5500,7 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
4990
5500
|
current.finished = true;
|
|
4991
5501
|
current.updatedAt = Date.now();
|
|
4992
5502
|
}
|
|
4993
|
-
|
|
5503
|
+
logger3.error(`openclaw-baiduapp agent failed: ${String(err)}`);
|
|
4994
5504
|
}
|
|
4995
5505
|
};
|
|
4996
5506
|
dispatchBaiduAppMessage({
|
|
@@ -4999,6 +5509,8 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
4999
5509
|
msg,
|
|
5000
5510
|
core,
|
|
5001
5511
|
hooks,
|
|
5512
|
+
inboundMediaFiles,
|
|
5513
|
+
inboundFileDiagnostic: inboundFileResult.diagnostic,
|
|
5002
5514
|
log: target.runtime.log,
|
|
5003
5515
|
error: target.runtime.error
|
|
5004
5516
|
}).then(async () => {
|
|
@@ -5007,20 +5519,20 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
5007
5519
|
current.finished = true;
|
|
5008
5520
|
current.updatedAt = Date.now();
|
|
5009
5521
|
const contentLen = current.content.trim().length;
|
|
5010
|
-
|
|
5522
|
+
logger3.info(
|
|
5011
5523
|
`agent dispatch done: streamId=${streamId} contentLen=${contentLen} canSendActive=${target.account.canSendActive}`
|
|
5012
5524
|
);
|
|
5013
5525
|
if (!target.account.canSendActive) {
|
|
5014
|
-
|
|
5526
|
+
logger3.warn(
|
|
5015
5527
|
`active send skipped: appKey/appSecret not configured for account ${target.account.accountId}`
|
|
5016
5528
|
);
|
|
5017
5529
|
} else if (!contentLen) {
|
|
5018
|
-
|
|
5530
|
+
logger3.warn(`active send skipped: agent produced no content`);
|
|
5019
5531
|
}
|
|
5020
5532
|
if (target.account.canSendActive && current.content.trim()) {
|
|
5021
5533
|
try {
|
|
5022
5534
|
const chunks = splitMessageByBytes(current.content, MAX_MESSAGE_BYTES);
|
|
5023
|
-
|
|
5535
|
+
logger3.info(
|
|
5024
5536
|
`[REPLY-MODE:ACTIVE-SEND] active send starting: streamId=${streamId} chunks=${chunks.length} contentLen=${contentLen}`
|
|
5025
5537
|
);
|
|
5026
5538
|
for (let i = 0; i < chunks.length; i++) {
|
|
@@ -5029,13 +5541,13 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
5029
5541
|
streamId,
|
|
5030
5542
|
chunkKey: i
|
|
5031
5543
|
});
|
|
5032
|
-
|
|
5544
|
+
logger3.debug(`active send chunk ${i + 1}/${chunks.length} sent: streamId=${streamId}`);
|
|
5033
5545
|
}
|
|
5034
|
-
|
|
5546
|
+
logger3.info(
|
|
5035
5547
|
`[REPLY-MODE:ACTIVE-SEND] active send complete: streamId=${streamId} chunks=${chunks.length}`
|
|
5036
5548
|
);
|
|
5037
5549
|
} catch (err) {
|
|
5038
|
-
|
|
5550
|
+
logger3.error(`active send failed: streamId=${streamId} error=${String(err)}`);
|
|
5039
5551
|
}
|
|
5040
5552
|
}
|
|
5041
5553
|
}
|
|
@@ -5047,10 +5559,10 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
5047
5559
|
current.finished = true;
|
|
5048
5560
|
current.updatedAt = Date.now();
|
|
5049
5561
|
}
|
|
5050
|
-
|
|
5562
|
+
logger3.error(`agent dispatch failed: streamId=${streamId} error=${String(err)}`);
|
|
5051
5563
|
});
|
|
5052
5564
|
} else {
|
|
5053
|
-
|
|
5565
|
+
logger3.warn(`runtime not available: streamId=${streamId} \u2014 agent dispatch skipped, no reply will be generated`);
|
|
5054
5566
|
const state = streams.get(streamId);
|
|
5055
5567
|
if (state) {
|
|
5056
5568
|
state.finished = true;
|
|
@@ -5058,12 +5570,165 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
5058
5570
|
}
|
|
5059
5571
|
}
|
|
5060
5572
|
const placeholderReply = buildStreamPlaceholderReply(streamId);
|
|
5061
|
-
|
|
5573
|
+
logger3.debug(`stream placeholder reply sent: streamId=${streamId}`);
|
|
5574
|
+
return placeholderReply;
|
|
5575
|
+
}
|
|
5576
|
+
function registerBaiduAppWebhookTarget(target) {
|
|
5577
|
+
const key = normalizeWebhookPath(target.path);
|
|
5578
|
+
const normalizedTarget = { ...target, path: key };
|
|
5579
|
+
const existing = webhookTargets.get(key) ?? [];
|
|
5580
|
+
const next = [...existing, normalizedTarget];
|
|
5581
|
+
webhookTargets.set(key, next);
|
|
5582
|
+
return () => {
|
|
5583
|
+
const updated = (webhookTargets.get(key) ?? []).filter((entry) => entry !== normalizedTarget);
|
|
5584
|
+
if (updated.length > 0) {
|
|
5585
|
+
webhookTargets.set(key, updated);
|
|
5586
|
+
} else {
|
|
5587
|
+
webhookTargets.delete(key);
|
|
5588
|
+
}
|
|
5589
|
+
};
|
|
5590
|
+
}
|
|
5591
|
+
async function handleBaiduAppWebhookRequest(req, res) {
|
|
5592
|
+
pruneStreams();
|
|
5593
|
+
const path4 = resolvePath(req);
|
|
5594
|
+
const targets = webhookTargets.get(path4);
|
|
5595
|
+
if (!targets || targets.length === 0) {
|
|
5596
|
+
return false;
|
|
5597
|
+
}
|
|
5598
|
+
const query = resolveQueryParams(req);
|
|
5599
|
+
const timestamp = query.get("timestamp") ?? "";
|
|
5600
|
+
const nonce = query.get("nonce") ?? "";
|
|
5601
|
+
const signature = resolveSignatureParam(query);
|
|
5602
|
+
const primary = targets[0];
|
|
5603
|
+
const logger3 = buildLogger(primary);
|
|
5604
|
+
if (req.method === "GET") {
|
|
5605
|
+
const echostr = query.get("echostr") ?? "";
|
|
5606
|
+
if (!timestamp || !nonce || !signature || !echostr) {
|
|
5607
|
+
jsonError(res, "missing query params", 400);
|
|
5608
|
+
return true;
|
|
5609
|
+
}
|
|
5610
|
+
const signatureMatched2 = targets.filter((candidate) => {
|
|
5611
|
+
if (!candidate.account.token) {
|
|
5612
|
+
return false;
|
|
5613
|
+
}
|
|
5614
|
+
return verifyBaiduAppSignature({
|
|
5615
|
+
token: candidate.account.token,
|
|
5616
|
+
timestamp,
|
|
5617
|
+
nonce,
|
|
5618
|
+
encrypt: echostr,
|
|
5619
|
+
signature
|
|
5620
|
+
});
|
|
5621
|
+
});
|
|
5622
|
+
if (signatureMatched2.length === 0) {
|
|
5623
|
+
jsonError(res, "unauthorized");
|
|
5624
|
+
return true;
|
|
5625
|
+
}
|
|
5626
|
+
const decryptable2 = signatureMatched2.filter((candidate) => Boolean(candidate.account.encodingAESKey));
|
|
5627
|
+
if (decryptable2.length === 0) {
|
|
5628
|
+
jsonError(res, "unauthorized");
|
|
5629
|
+
return true;
|
|
5630
|
+
}
|
|
5631
|
+
const decryptedCandidates2 = decryptBaiduAppCandidates({
|
|
5632
|
+
candidates: decryptable2,
|
|
5633
|
+
encrypt: echostr
|
|
5634
|
+
});
|
|
5635
|
+
if (decryptedCandidates2.length === 0) {
|
|
5636
|
+
jsonError(res, "decrypt failed");
|
|
5637
|
+
return true;
|
|
5638
|
+
}
|
|
5639
|
+
const selected2 = selectDecryptedTarget({ candidates: decryptedCandidates2, logger: logger3 });
|
|
5640
|
+
jsonOk(res, selected2.plaintext);
|
|
5641
|
+
return true;
|
|
5642
|
+
}
|
|
5643
|
+
if (req.method !== "POST") {
|
|
5644
|
+
jsonError(res, "Method Not Allowed", 405);
|
|
5645
|
+
return true;
|
|
5646
|
+
}
|
|
5647
|
+
if (!timestamp || !nonce || !signature) {
|
|
5648
|
+
jsonError(res, "missing query params");
|
|
5649
|
+
return true;
|
|
5650
|
+
}
|
|
5651
|
+
const body = await readRawBody(req, 1024 * 1024);
|
|
5652
|
+
if (!body.ok || !body.raw) {
|
|
5653
|
+
jsonError(res, body.error ?? "invalid payload");
|
|
5654
|
+
return true;
|
|
5655
|
+
}
|
|
5656
|
+
const rawBody = body.raw;
|
|
5657
|
+
let encrypt = "";
|
|
5658
|
+
let msgSignature = signature;
|
|
5659
|
+
let msgTimestamp = timestamp;
|
|
5660
|
+
let msgNonce = nonce;
|
|
5661
|
+
if (isXmlFormat(rawBody)) {
|
|
5662
|
+
const xmlData = parseXmlBody(rawBody);
|
|
5663
|
+
encrypt = xmlData.Encrypt ?? "";
|
|
5664
|
+
msgSignature = xmlData.MsgSignature ?? signature;
|
|
5665
|
+
msgTimestamp = xmlData.TimeStamp ?? timestamp;
|
|
5666
|
+
msgNonce = xmlData.Nonce ?? nonce;
|
|
5667
|
+
logger3.info(`inbound xml parsed: hasEncrypt=${Boolean(encrypt)}, msg_signature=${msgSignature ? "yes" : "no"}`);
|
|
5668
|
+
} else {
|
|
5669
|
+
try {
|
|
5670
|
+
const record = JSON.parse(rawBody);
|
|
5671
|
+
encrypt = String(record.encrypt ?? record.Encrypt ?? "");
|
|
5672
|
+
logger3.info(`inbound json parsed: hasEncrypt=${Boolean(encrypt)}`);
|
|
5673
|
+
} catch {
|
|
5674
|
+
logger3.warn(`inbound payload parse failed: not valid xml or json`);
|
|
5675
|
+
jsonError(res, "invalid payload format");
|
|
5676
|
+
return true;
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
if (!encrypt) {
|
|
5680
|
+
jsonError(res, "missing encrypt");
|
|
5681
|
+
return true;
|
|
5682
|
+
}
|
|
5683
|
+
const signatureMatched = targets.filter((candidate) => {
|
|
5684
|
+
if (!candidate.account.token) {
|
|
5685
|
+
return false;
|
|
5686
|
+
}
|
|
5687
|
+
return verifyBaiduAppSignature({
|
|
5688
|
+
token: candidate.account.token,
|
|
5689
|
+
timestamp: msgTimestamp,
|
|
5690
|
+
nonce: msgNonce,
|
|
5691
|
+
encrypt,
|
|
5692
|
+
signature: msgSignature
|
|
5693
|
+
});
|
|
5694
|
+
});
|
|
5695
|
+
if (signatureMatched.length === 0) {
|
|
5696
|
+
logger3.warn(`signature verification failed: checked ${targets.length} account(s), none matched`);
|
|
5697
|
+
jsonError(res, "unauthorized");
|
|
5698
|
+
return true;
|
|
5699
|
+
}
|
|
5700
|
+
logger3.debug(`signature verified: ${signatureMatched.length} account(s) matched`);
|
|
5701
|
+
const decryptable = signatureMatched.filter((candidate) => Boolean(candidate.account.encodingAESKey));
|
|
5702
|
+
if (decryptable.length === 0) {
|
|
5703
|
+
logger3.warn(`no account has encodingAESKey configured`);
|
|
5704
|
+
jsonError(res, "openclaw-baiduapp not configured");
|
|
5705
|
+
return true;
|
|
5706
|
+
}
|
|
5707
|
+
const decryptedCandidates = decryptBaiduAppCandidates({
|
|
5708
|
+
candidates: decryptable,
|
|
5709
|
+
encrypt
|
|
5710
|
+
});
|
|
5711
|
+
if (decryptedCandidates.length === 0) {
|
|
5712
|
+
logger3.warn(`decrypt failed for all ${decryptable.length} candidate account(s)`);
|
|
5713
|
+
jsonError(res, "decrypt failed");
|
|
5714
|
+
return true;
|
|
5715
|
+
}
|
|
5716
|
+
const selected = selectDecryptedTarget({ candidates: decryptedCandidates, logger: logger3 });
|
|
5717
|
+
const target = selected.target;
|
|
5718
|
+
if (!target.account.configured || !target.account.token || !target.account.encodingAESKey) {
|
|
5719
|
+
logger3.warn(`selected account ${target.account.accountId} not fully configured`);
|
|
5720
|
+
jsonError(res, "openclaw-baiduapp not configured");
|
|
5721
|
+
return true;
|
|
5722
|
+
}
|
|
5723
|
+
const reply = await processBaiduAppInboundMessage({
|
|
5724
|
+
target,
|
|
5725
|
+
msg: selected.msg
|
|
5726
|
+
});
|
|
5062
5727
|
jsonOk(
|
|
5063
5728
|
res,
|
|
5064
5729
|
buildEncryptedJsonReply({
|
|
5065
5730
|
account: target.account,
|
|
5066
|
-
plaintextJson:
|
|
5731
|
+
plaintextJson: reply,
|
|
5067
5732
|
nonce: msgNonce,
|
|
5068
5733
|
timestamp: msgTimestamp
|
|
5069
5734
|
})
|
|
@@ -5071,7 +5736,243 @@ async function handleBaiduAppWebhookRequest(req, res) {
|
|
|
5071
5736
|
return true;
|
|
5072
5737
|
}
|
|
5073
5738
|
|
|
5739
|
+
// src/poller.ts
|
|
5740
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
5741
|
+
var DEFAULT_POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
5742
|
+
var accountPollers = /* @__PURE__ */ new Map();
|
|
5743
|
+
function buildPollingTextInboundMessage(content) {
|
|
5744
|
+
return {
|
|
5745
|
+
msgtype: "text",
|
|
5746
|
+
text: {
|
|
5747
|
+
content
|
|
5748
|
+
}
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
async function dispatchPendingPollingMessages(data, target) {
|
|
5752
|
+
if (!target || data.length === 0) {
|
|
5753
|
+
return;
|
|
5754
|
+
}
|
|
5755
|
+
for (const item of data) {
|
|
5756
|
+
if (String(item.msgtype).toLowerCase() !== "text") {
|
|
5757
|
+
continue;
|
|
5758
|
+
}
|
|
5759
|
+
const content = item.data?.content;
|
|
5760
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
5761
|
+
continue;
|
|
5762
|
+
}
|
|
5763
|
+
await processBaiduAppInboundMessage({
|
|
5764
|
+
target,
|
|
5765
|
+
msg: buildPollingTextInboundMessage(content)
|
|
5766
|
+
});
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
function isAbortError2(error) {
|
|
5770
|
+
return error instanceof Error && error.name === "AbortError";
|
|
5771
|
+
}
|
|
5772
|
+
function buildPollResult(params) {
|
|
5773
|
+
return {
|
|
5774
|
+
ok: params.ok,
|
|
5775
|
+
data: params.data ?? [],
|
|
5776
|
+
error: params.error
|
|
5777
|
+
};
|
|
5778
|
+
}
|
|
5779
|
+
function createAbortSignalController(params) {
|
|
5780
|
+
if (params.signal == null && params.timeoutMs == null) {
|
|
5781
|
+
return {
|
|
5782
|
+
signal: void 0,
|
|
5783
|
+
cleanup: () => void 0,
|
|
5784
|
+
didTimeout: () => false
|
|
5785
|
+
};
|
|
5786
|
+
}
|
|
5787
|
+
const controller = new AbortController();
|
|
5788
|
+
let timeoutId;
|
|
5789
|
+
let timedOut = false;
|
|
5790
|
+
const abortFromParent = () => {
|
|
5791
|
+
controller.abort();
|
|
5792
|
+
};
|
|
5793
|
+
if (params.signal) {
|
|
5794
|
+
if (params.signal.aborted) {
|
|
5795
|
+
controller.abort();
|
|
5796
|
+
} else {
|
|
5797
|
+
params.signal.addEventListener("abort", abortFromParent, { once: true });
|
|
5798
|
+
}
|
|
5799
|
+
}
|
|
5800
|
+
if (params.timeoutMs != null) {
|
|
5801
|
+
timeoutId = setTimeout(() => {
|
|
5802
|
+
timedOut = true;
|
|
5803
|
+
controller.abort();
|
|
5804
|
+
}, params.timeoutMs);
|
|
5805
|
+
}
|
|
5806
|
+
return {
|
|
5807
|
+
signal: controller.signal,
|
|
5808
|
+
cleanup: () => {
|
|
5809
|
+
if (timeoutId) {
|
|
5810
|
+
clearTimeout(timeoutId);
|
|
5811
|
+
}
|
|
5812
|
+
if (params.signal) {
|
|
5813
|
+
params.signal.removeEventListener("abort", abortFromParent);
|
|
5814
|
+
}
|
|
5815
|
+
},
|
|
5816
|
+
didTimeout: () => timedOut
|
|
5817
|
+
};
|
|
5818
|
+
}
|
|
5819
|
+
async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions) {
|
|
5820
|
+
const endpoint = `${account.apiBase}/chat/demo/chatlist`;
|
|
5821
|
+
const query = new URLSearchParams({
|
|
5822
|
+
ak: account.appKey ?? "",
|
|
5823
|
+
sk: account.appSecret ?? ""
|
|
5824
|
+
});
|
|
5825
|
+
const url = `${endpoint}?${query.toString()}`;
|
|
5826
|
+
const requestSignal = createAbortSignalController({
|
|
5827
|
+
signal: requestOptions?.signal,
|
|
5828
|
+
timeoutMs: requestOptions?.timeoutMs
|
|
5829
|
+
});
|
|
5830
|
+
let response;
|
|
5831
|
+
try {
|
|
5832
|
+
response = await (requestOptions?.fetchImpl ?? fetch)(url, {
|
|
5833
|
+
method: "GET",
|
|
5834
|
+
signal: requestSignal.signal
|
|
5835
|
+
});
|
|
5836
|
+
} catch (error) {
|
|
5837
|
+
requestSignal.cleanup();
|
|
5838
|
+
if (requestSignal.didTimeout()) {
|
|
5839
|
+
return buildPollResult({
|
|
5840
|
+
ok: false,
|
|
5841
|
+
error: {
|
|
5842
|
+
kind: "timeout",
|
|
5843
|
+
message: `chatlist request timed out after ${requestOptions?.timeoutMs}ms`
|
|
5844
|
+
}
|
|
5845
|
+
});
|
|
5846
|
+
}
|
|
5847
|
+
if (isAbortError2(error)) {
|
|
5848
|
+
return buildPollResult({
|
|
5849
|
+
ok: false,
|
|
5850
|
+
error: {
|
|
5851
|
+
kind: "aborted",
|
|
5852
|
+
message: "chatlist request aborted"
|
|
5853
|
+
}
|
|
5854
|
+
});
|
|
5855
|
+
}
|
|
5856
|
+
return buildPollResult({
|
|
5857
|
+
ok: false,
|
|
5858
|
+
error: {
|
|
5859
|
+
kind: "request-failed",
|
|
5860
|
+
message: error instanceof Error ? error.message : String(error)
|
|
5861
|
+
}
|
|
5862
|
+
});
|
|
5863
|
+
}
|
|
5864
|
+
const responseText = await response.text();
|
|
5865
|
+
requestSignal.cleanup();
|
|
5866
|
+
if (!responseText) {
|
|
5867
|
+
return buildPollResult({
|
|
5868
|
+
ok: false,
|
|
5869
|
+
error: {
|
|
5870
|
+
kind: "empty-response",
|
|
5871
|
+
message: `empty response from chatlist API (status=${response.status})`
|
|
5872
|
+
}
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5875
|
+
let parsed;
|
|
5876
|
+
try {
|
|
5877
|
+
parsed = JSON.parse(responseText);
|
|
5878
|
+
} catch {
|
|
5879
|
+
return buildPollResult({
|
|
5880
|
+
ok: false,
|
|
5881
|
+
error: {
|
|
5882
|
+
kind: "invalid-json",
|
|
5883
|
+
message: `invalid JSON response from chatlist API (status=${response.status})`
|
|
5884
|
+
}
|
|
5885
|
+
});
|
|
5886
|
+
}
|
|
5887
|
+
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
5888
|
+
return buildPollResult({
|
|
5889
|
+
ok: true,
|
|
5890
|
+
data
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
function scheduleNextPoll(account, state) {
|
|
5894
|
+
if (state.stopped) {
|
|
5895
|
+
return;
|
|
5896
|
+
}
|
|
5897
|
+
state.timer = setTimeout(() => {
|
|
5898
|
+
void runPollCycle(account, state);
|
|
5899
|
+
}, state.intervalMs);
|
|
5900
|
+
}
|
|
5901
|
+
async function runPollCycle(account, state) {
|
|
5902
|
+
if (state.stopped || state.pending) {
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
const controller = new AbortController();
|
|
5906
|
+
state.activeController = controller;
|
|
5907
|
+
const pending = (async () => {
|
|
5908
|
+
try {
|
|
5909
|
+
const result = await pollBaiduAppChatlistOnce(account, void 0, {
|
|
5910
|
+
signal: controller.signal,
|
|
5911
|
+
fetchImpl: state.fetchImpl,
|
|
5912
|
+
timeoutMs: state.requestTimeoutMs
|
|
5913
|
+
});
|
|
5914
|
+
await dispatchPendingPollingMessages(result.data, state.dispatchTarget);
|
|
5915
|
+
} catch (error) {
|
|
5916
|
+
if (!(state.stopped && isAbortError2(error))) {
|
|
5917
|
+
state.onError?.(error);
|
|
5918
|
+
}
|
|
5919
|
+
} finally {
|
|
5920
|
+
if (state.activeController === controller) {
|
|
5921
|
+
state.activeController = null;
|
|
5922
|
+
}
|
|
5923
|
+
state.pending = null;
|
|
5924
|
+
if (!state.stopped) {
|
|
5925
|
+
scheduleNextPoll(account, state);
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
})();
|
|
5929
|
+
state.pending = pending;
|
|
5930
|
+
await pending;
|
|
5931
|
+
}
|
|
5932
|
+
function startAccountPolling(params) {
|
|
5933
|
+
if (accountPollers.has(params.account.accountId)) {
|
|
5934
|
+
return;
|
|
5935
|
+
}
|
|
5936
|
+
const state = {
|
|
5937
|
+
stopped: false,
|
|
5938
|
+
timer: null,
|
|
5939
|
+
activeController: null,
|
|
5940
|
+
pending: null,
|
|
5941
|
+
intervalMs: params.intervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
5942
|
+
requestTimeoutMs: params.requestTimeoutMs ?? DEFAULT_POLL_REQUEST_TIMEOUT_MS,
|
|
5943
|
+
fetchImpl: params.fetchImpl,
|
|
5944
|
+
onError: params.onError,
|
|
5945
|
+
dispatchTarget: params.dispatchTarget
|
|
5946
|
+
};
|
|
5947
|
+
accountPollers.set(params.account.accountId, state);
|
|
5948
|
+
void runPollCycle(params.account, state);
|
|
5949
|
+
}
|
|
5950
|
+
function stopAccountPolling(accountId) {
|
|
5951
|
+
const state = accountPollers.get(accountId);
|
|
5952
|
+
if (!state) {
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5955
|
+
state.stopped = true;
|
|
5956
|
+
if (state.timer) {
|
|
5957
|
+
clearTimeout(state.timer);
|
|
5958
|
+
state.timer = null;
|
|
5959
|
+
}
|
|
5960
|
+
state.activeController?.abort();
|
|
5961
|
+
state.activeController = null;
|
|
5962
|
+
accountPollers.delete(accountId);
|
|
5963
|
+
}
|
|
5964
|
+
|
|
5074
5965
|
// src/channel.ts
|
|
5966
|
+
function resolveOutboundMediaUrls(payload) {
|
|
5967
|
+
if (payload.mediaUrls?.length) {
|
|
5968
|
+
return payload.mediaUrls;
|
|
5969
|
+
}
|
|
5970
|
+
if (payload.mediaUrl) {
|
|
5971
|
+
return [payload.mediaUrl];
|
|
5972
|
+
}
|
|
5973
|
+
return [];
|
|
5974
|
+
}
|
|
5975
|
+
var logger2 = createLogger("openclaw-baiduapp");
|
|
5075
5976
|
var meta = {
|
|
5076
5977
|
id: "openclaw-baiduapp",
|
|
5077
5978
|
label: "Baidu App",
|
|
@@ -5083,6 +5984,87 @@ var meta = {
|
|
|
5083
5984
|
order: 85
|
|
5084
5985
|
};
|
|
5085
5986
|
var unregisterHooks = /* @__PURE__ */ new Map();
|
|
5987
|
+
function resolveOutboundLocalMediaPath(mediaUrl) {
|
|
5988
|
+
const trimmed = mediaUrl?.trim();
|
|
5989
|
+
if (!trimmed) {
|
|
5990
|
+
return void 0;
|
|
5991
|
+
}
|
|
5992
|
+
if (trimmed.startsWith("file://")) {
|
|
5993
|
+
return fileURLToPath(trimmed);
|
|
5994
|
+
}
|
|
5995
|
+
if (/^[a-zA-Z]:[\\/]/.test(trimmed)) {
|
|
5996
|
+
return trimmed;
|
|
5997
|
+
}
|
|
5998
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
5999
|
+
return trimmed;
|
|
6000
|
+
}
|
|
6001
|
+
if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(trimmed)) {
|
|
6002
|
+
return void 0;
|
|
6003
|
+
}
|
|
6004
|
+
return trimmed;
|
|
6005
|
+
}
|
|
6006
|
+
function inferBaiduOutboundFileType(params) {
|
|
6007
|
+
const ext = path2.extname(params.mediaPath).toLowerCase();
|
|
6008
|
+
return ext.startsWith(".") ? ext.slice(1) : ext || "bin";
|
|
6009
|
+
}
|
|
6010
|
+
function buildOutboundMediaPayload(params) {
|
|
6011
|
+
const caption = params.caption?.trim();
|
|
6012
|
+
return {
|
|
6013
|
+
msgtype: "text",
|
|
6014
|
+
...caption ? { text: { content: caption } } : {},
|
|
6015
|
+
files: [{ url: params.uploadedUrl, fileType: params.fileType }]
|
|
6016
|
+
};
|
|
6017
|
+
}
|
|
6018
|
+
function resolveDirectOutboundFiles(files) {
|
|
6019
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
6020
|
+
return [];
|
|
6021
|
+
}
|
|
6022
|
+
return files.map((file) => ({
|
|
6023
|
+
mediaUrl: file.url?.trim() ?? "",
|
|
6024
|
+
mimeType: file.fileType?.trim() || void 0
|
|
6025
|
+
})).filter((file) => Boolean(file.mediaUrl));
|
|
6026
|
+
}
|
|
6027
|
+
async function sendBaiduAppPayload(params) {
|
|
6028
|
+
const text = params.payload.text ?? params.payload.content ?? "";
|
|
6029
|
+
const mediaInputs = [
|
|
6030
|
+
...resolveOutboundMediaUrls(params.payload).map((mediaUrl) => ({ mediaUrl, mimeType: void 0 })),
|
|
6031
|
+
...resolveDirectOutboundFiles(params.payload.files)
|
|
6032
|
+
];
|
|
6033
|
+
if (!text && mediaInputs.length === 0) {
|
|
6034
|
+
return {
|
|
6035
|
+
channel: "openclaw-baiduapp",
|
|
6036
|
+
ok: false,
|
|
6037
|
+
messageId: ""
|
|
6038
|
+
};
|
|
6039
|
+
}
|
|
6040
|
+
if (mediaInputs.length === 0) {
|
|
6041
|
+
return baiduAppPlugin.outbound.sendText({
|
|
6042
|
+
cfg: params.cfg,
|
|
6043
|
+
accountId: params.accountId ?? void 0,
|
|
6044
|
+
to: params.to,
|
|
6045
|
+
text
|
|
6046
|
+
});
|
|
6047
|
+
}
|
|
6048
|
+
let lastResult = {
|
|
6049
|
+
channel: "openclaw-baiduapp",
|
|
6050
|
+
ok: false,
|
|
6051
|
+
messageId: ""
|
|
6052
|
+
};
|
|
6053
|
+
for (const [index, mediaInput] of mediaInputs.entries()) {
|
|
6054
|
+
lastResult = await baiduAppPlugin.outbound.sendMedia({
|
|
6055
|
+
cfg: params.cfg,
|
|
6056
|
+
accountId: params.accountId,
|
|
6057
|
+
to: params.to,
|
|
6058
|
+
text: index === 0 ? text : "",
|
|
6059
|
+
mediaUrl: mediaInput.mediaUrl,
|
|
6060
|
+
mimeType: mediaInput.mimeType
|
|
6061
|
+
});
|
|
6062
|
+
if (!lastResult.ok) {
|
|
6063
|
+
return lastResult;
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
return lastResult;
|
|
6067
|
+
}
|
|
5086
6068
|
var baiduAppPlugin = {
|
|
5087
6069
|
id: "openclaw-baiduapp",
|
|
5088
6070
|
meta: {
|
|
@@ -5090,7 +6072,7 @@ var baiduAppPlugin = {
|
|
|
5090
6072
|
},
|
|
5091
6073
|
capabilities: {
|
|
5092
6074
|
chatTypes: ["direct"],
|
|
5093
|
-
media:
|
|
6075
|
+
media: true,
|
|
5094
6076
|
reactions: false,
|
|
5095
6077
|
threads: false,
|
|
5096
6078
|
edit: false,
|
|
@@ -5098,6 +6080,19 @@ var baiduAppPlugin = {
|
|
|
5098
6080
|
polls: false,
|
|
5099
6081
|
activeSend: true
|
|
5100
6082
|
},
|
|
6083
|
+
messaging: {
|
|
6084
|
+
normalizeTarget: (raw) => {
|
|
6085
|
+
const trimmed = raw.trim();
|
|
6086
|
+
if (!trimmed) {
|
|
6087
|
+
return void 0;
|
|
6088
|
+
}
|
|
6089
|
+
return trimmed.replace(/^openclaw-baiduapp:/i, "").trim() || void 0;
|
|
6090
|
+
},
|
|
6091
|
+
targetResolver: {
|
|
6092
|
+
looksLikeId: () => true,
|
|
6093
|
+
hint: "<user|openclaw-baiduapp:userId>"
|
|
6094
|
+
}
|
|
6095
|
+
},
|
|
5101
6096
|
configSchema: BaiduAppConfigJsonSchema,
|
|
5102
6097
|
reload: { configPrefixes: ["channels.openclaw-baiduapp"] },
|
|
5103
6098
|
config: {
|
|
@@ -5113,7 +6108,7 @@ var baiduAppPlugin = {
|
|
|
5113
6108
|
channels: {
|
|
5114
6109
|
...params.cfg.channels,
|
|
5115
6110
|
"openclaw-baiduapp": {
|
|
5116
|
-
...params.cfg.channels?.["openclaw-baiduapp"]
|
|
6111
|
+
...params.cfg.channels?.["openclaw-baiduapp"],
|
|
5117
6112
|
enabled: params.enabled
|
|
5118
6113
|
}
|
|
5119
6114
|
}
|
|
@@ -5124,11 +6119,11 @@ var baiduAppPlugin = {
|
|
|
5124
6119
|
channels: {
|
|
5125
6120
|
...params.cfg.channels,
|
|
5126
6121
|
"openclaw-baiduapp": {
|
|
5127
|
-
...params.cfg.channels?.["openclaw-baiduapp"]
|
|
6122
|
+
...params.cfg.channels?.["openclaw-baiduapp"],
|
|
5128
6123
|
accounts: {
|
|
5129
|
-
...params.cfg.channels?.["openclaw-baiduapp"]?.accounts
|
|
6124
|
+
...params.cfg.channels?.["openclaw-baiduapp"]?.accounts,
|
|
5130
6125
|
[accountId]: {
|
|
5131
|
-
...params.cfg.channels?.["openclaw-baiduapp"]?.accounts?.[accountId]
|
|
6126
|
+
...params.cfg.channels?.["openclaw-baiduapp"]?.accounts?.[accountId],
|
|
5132
6127
|
enabled: params.enabled
|
|
5133
6128
|
}
|
|
5134
6129
|
}
|
|
@@ -5151,7 +6146,7 @@ var baiduAppPlugin = {
|
|
|
5151
6146
|
};
|
|
5152
6147
|
return next;
|
|
5153
6148
|
}
|
|
5154
|
-
const accounts = { ...current.accounts
|
|
6149
|
+
const accounts = { ...current.accounts };
|
|
5155
6150
|
delete accounts[accountId];
|
|
5156
6151
|
next.channels = {
|
|
5157
6152
|
...next.channels,
|
|
@@ -5239,6 +6234,10 @@ var baiduAppPlugin = {
|
|
|
5239
6234
|
},
|
|
5240
6235
|
outbound: {
|
|
5241
6236
|
deliveryMode: "direct",
|
|
6237
|
+
resolveTarget: (params) => {
|
|
6238
|
+
return { ok: true, to: params.to?.trim() || "default" };
|
|
6239
|
+
},
|
|
6240
|
+
sendPayload: async (params) => sendBaiduAppPayload(params),
|
|
5242
6241
|
sendText: async (params) => {
|
|
5243
6242
|
const account = resolveBaiduAppAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
5244
6243
|
if (!account.canSendActive) {
|
|
@@ -5267,6 +6266,7 @@ var baiduAppPlugin = {
|
|
|
5267
6266
|
}
|
|
5268
6267
|
},
|
|
5269
6268
|
sendMedia: async (params) => {
|
|
6269
|
+
logger2.info(`sendMedia start ${params.mediaUrl}`);
|
|
5270
6270
|
const account = resolveBaiduAppAccount({ cfg: params.cfg, accountId: params.accountId ?? void 0 });
|
|
5271
6271
|
if (!account.canSendActive) {
|
|
5272
6272
|
return {
|
|
@@ -5276,29 +6276,84 @@ var baiduAppPlugin = {
|
|
|
5276
6276
|
error: new Error("Account not configured for active sending (missing appKey or appSecret)")
|
|
5277
6277
|
};
|
|
5278
6278
|
}
|
|
5279
|
-
const
|
|
5280
|
-
if (!
|
|
6279
|
+
const localMediaPath = resolveOutboundLocalMediaPath(params.mediaUrl);
|
|
6280
|
+
if (!localMediaPath) {
|
|
6281
|
+
const remoteUrl = params.mediaUrl?.trim() ?? "";
|
|
6282
|
+
if (!remoteUrl) {
|
|
6283
|
+
return {
|
|
6284
|
+
channel: "openclaw-baiduapp",
|
|
6285
|
+
ok: false,
|
|
6286
|
+
messageId: "",
|
|
6287
|
+
error: new Error("Outbound media requires a URL or local file path")
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
try {
|
|
6291
|
+
const result = await sendBaiduAppMessage(
|
|
6292
|
+
account,
|
|
6293
|
+
buildOutboundMediaPayload({
|
|
6294
|
+
caption: params.text,
|
|
6295
|
+
uploadedUrl: remoteUrl,
|
|
6296
|
+
fileType: inferBaiduOutboundFileType({ mediaPath: remoteUrl })
|
|
6297
|
+
})
|
|
6298
|
+
);
|
|
6299
|
+
return {
|
|
6300
|
+
channel: "openclaw-baiduapp",
|
|
6301
|
+
ok: true,
|
|
6302
|
+
messageId: result.msgid ?? ""
|
|
6303
|
+
};
|
|
6304
|
+
} catch (err) {
|
|
6305
|
+
return {
|
|
6306
|
+
channel: "openclaw-baiduapp",
|
|
6307
|
+
ok: false,
|
|
6308
|
+
messageId: "",
|
|
6309
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
6310
|
+
};
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
let uploaded;
|
|
6314
|
+
try {
|
|
6315
|
+
uploaded = await uploadLocalFileToBos({
|
|
6316
|
+
account,
|
|
6317
|
+
filePath: localMediaPath,
|
|
6318
|
+
contentType: params.mimeType
|
|
6319
|
+
});
|
|
6320
|
+
} catch (err) {
|
|
5281
6321
|
return {
|
|
5282
6322
|
channel: "openclaw-baiduapp",
|
|
5283
6323
|
ok: false,
|
|
5284
6324
|
messageId: "",
|
|
5285
|
-
error: new Error(
|
|
6325
|
+
error: new Error(
|
|
6326
|
+
`Failed to upload outbound media: ${err instanceof Error ? err.message : String(err)}`
|
|
6327
|
+
)
|
|
5286
6328
|
};
|
|
5287
6329
|
}
|
|
5288
6330
|
try {
|
|
5289
|
-
const result = await sendBaiduAppMessage(
|
|
6331
|
+
const result = await sendBaiduAppMessage(
|
|
6332
|
+
account,
|
|
6333
|
+
buildOutboundMediaPayload({
|
|
6334
|
+
caption: params.text,
|
|
6335
|
+
uploadedUrl: uploaded.url,
|
|
6336
|
+
fileType: inferBaiduOutboundFileType({
|
|
6337
|
+
mediaPath: uploaded.fileName || localMediaPath
|
|
6338
|
+
})
|
|
6339
|
+
})
|
|
6340
|
+
);
|
|
5290
6341
|
return {
|
|
5291
6342
|
channel: "openclaw-baiduapp",
|
|
5292
6343
|
ok: result.ok,
|
|
5293
6344
|
messageId: result.msgid ?? "",
|
|
5294
|
-
error: result.ok ? void 0 : new Error(result.errmsg ?? "send failed")
|
|
6345
|
+
error: result.ok ? void 0 : new Error(result.errmsg ?? "send failed"),
|
|
6346
|
+
fileName: uploaded.fileName,
|
|
6347
|
+
fileSize: uploaded.fileSize
|
|
5295
6348
|
};
|
|
5296
6349
|
} catch (err) {
|
|
5297
6350
|
return {
|
|
5298
6351
|
channel: "openclaw-baiduapp",
|
|
5299
6352
|
ok: false,
|
|
5300
6353
|
messageId: "",
|
|
5301
|
-
error:
|
|
6354
|
+
error: new Error(
|
|
6355
|
+
`Failed to send media message: ${err instanceof Error ? err.message : String(err)}`
|
|
6356
|
+
)
|
|
5302
6357
|
};
|
|
5303
6358
|
}
|
|
5304
6359
|
}
|
|
@@ -5318,35 +6373,66 @@ var baiduAppPlugin = {
|
|
|
5318
6373
|
ctx.setStatus?.({ accountId: ctx.accountId, running: false, configured: false });
|
|
5319
6374
|
return;
|
|
5320
6375
|
}
|
|
5321
|
-
const
|
|
6376
|
+
const path4 = (account.config.webhookPath ?? "/openclaw-baiduapp").trim();
|
|
6377
|
+
const runtime2 = {
|
|
6378
|
+
log: ctx.log?.info ?? console.log,
|
|
6379
|
+
error: ctx.log?.error ?? console.error
|
|
6380
|
+
};
|
|
5322
6381
|
const unregister = registerBaiduAppWebhookTarget({
|
|
5323
6382
|
account,
|
|
5324
6383
|
config: ctx.cfg ?? {},
|
|
5325
|
-
runtime:
|
|
5326
|
-
|
|
5327
|
-
error: ctx.log?.error ?? console.error
|
|
5328
|
-
},
|
|
5329
|
-
path,
|
|
6384
|
+
runtime: runtime2,
|
|
6385
|
+
path: path4,
|
|
5330
6386
|
statusSink: (patch) => ctx.setStatus?.({ accountId: ctx.accountId, ...patch })
|
|
5331
6387
|
});
|
|
6388
|
+
try {
|
|
6389
|
+
triggerBestEffortTempPrune({ runtime: runtime2, reason: "startup" });
|
|
6390
|
+
} catch (error) {
|
|
6391
|
+
runtime2.error(`[openclaw-baiduapp] temp prune failed during startup: ${String(error)}`);
|
|
6392
|
+
}
|
|
5332
6393
|
const existing = unregisterHooks.get(ctx.accountId);
|
|
5333
6394
|
if (existing) {
|
|
5334
6395
|
existing();
|
|
5335
6396
|
}
|
|
5336
6397
|
unregisterHooks.set(ctx.accountId, unregister);
|
|
6398
|
+
if (account.config.pollingEnabled === true) {
|
|
6399
|
+
startAccountPolling({
|
|
6400
|
+
account,
|
|
6401
|
+
dispatchTarget: {
|
|
6402
|
+
account,
|
|
6403
|
+
config: ctx.cfg ?? {},
|
|
6404
|
+
runtime: runtime2,
|
|
6405
|
+
statusSink: (patch) => ctx.setStatus?.({ accountId: ctx.accountId, ...patch })
|
|
6406
|
+
},
|
|
6407
|
+
onError: (error) => {
|
|
6408
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6409
|
+
ctx.log?.error(`[openclaw-baiduapp] polling failed for account ${ctx.accountId}: ${message}`);
|
|
6410
|
+
}
|
|
6411
|
+
});
|
|
6412
|
+
}
|
|
5337
6413
|
ctx.log?.info(
|
|
5338
|
-
`[openclaw-baiduapp] webhook registered at ${
|
|
6414
|
+
`[openclaw-baiduapp] webhook registered at ${path4} for account ${ctx.accountId} (canSendActive=${account.canSendActive})`
|
|
5339
6415
|
);
|
|
5340
6416
|
ctx.setStatus?.({
|
|
5341
6417
|
accountId: ctx.accountId,
|
|
5342
6418
|
running: true,
|
|
5343
6419
|
configured: true,
|
|
5344
6420
|
canSendActive: account.canSendActive,
|
|
5345
|
-
webhookPath:
|
|
6421
|
+
webhookPath: path4,
|
|
5346
6422
|
lastStartAt: Date.now()
|
|
5347
6423
|
});
|
|
6424
|
+
if (ctx.abortSignal) {
|
|
6425
|
+
await new Promise((resolve) => {
|
|
6426
|
+
if (ctx.abortSignal?.aborted) {
|
|
6427
|
+
resolve();
|
|
6428
|
+
return;
|
|
6429
|
+
}
|
|
6430
|
+
ctx.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
|
|
6431
|
+
});
|
|
6432
|
+
}
|
|
5348
6433
|
},
|
|
5349
6434
|
stopAccount: async (ctx) => {
|
|
6435
|
+
stopAccountPolling(ctx.accountId);
|
|
5350
6436
|
const unregister = unregisterHooks.get(ctx.accountId);
|
|
5351
6437
|
if (unregister) {
|
|
5352
6438
|
unregister();
|
|
@@ -5413,7 +6499,14 @@ var plugin = {
|
|
|
5413
6499
|
setBaiduAppRuntime(api.runtime);
|
|
5414
6500
|
}
|
|
5415
6501
|
api.registerChannel({ plugin: baiduAppPlugin });
|
|
5416
|
-
if (api.
|
|
6502
|
+
if (api.registerHttpRoute) {
|
|
6503
|
+
api.registerHttpRoute({
|
|
6504
|
+
path: "/openclaw-baiduapp",
|
|
6505
|
+
auth: "plugin",
|
|
6506
|
+
match: "prefix",
|
|
6507
|
+
handler: handleBaiduAppWebhookRequest
|
|
6508
|
+
});
|
|
6509
|
+
} else if (api.registerHttpHandler) {
|
|
5417
6510
|
api.registerHttpHandler(handleBaiduAppWebhookRequest);
|
|
5418
6511
|
}
|
|
5419
6512
|
}
|