csdn-im 0.1.4 → 0.1.6
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/dist/index.cjs +174 -72
- package/dist/index.js +174 -72
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4078,10 +4078,12 @@ var NEVER = INVALID;
|
|
|
4078
4078
|
|
|
4079
4079
|
// src/config.ts
|
|
4080
4080
|
var CSDN_PLUGIN_ID = "csdn-im";
|
|
4081
|
+
var CSDN_LOG_TAG = "[csdn-im]";
|
|
4081
4082
|
var FIXED_LONG_POLLING_TIMEOUT_SEC = 30;
|
|
4082
4083
|
var FIXED_TAKE = 1;
|
|
4083
4084
|
var FIXED_RETRY_INTERVAL_MS = 3e3;
|
|
4084
4085
|
var FIXED_RETRY_BACKOFF_MAX_MS = 12e4;
|
|
4086
|
+
var MAX_POLL_INTERVAL_MS = 6e5;
|
|
4085
4087
|
function getCsdnChannelObjectFromRoot(cfg) {
|
|
4086
4088
|
return cfg?.channels?.[CSDN_PLUGIN_ID];
|
|
4087
4089
|
}
|
|
@@ -4101,7 +4103,12 @@ var CsdnConfigSchema = external_exports.object({
|
|
|
4101
4103
|
/**
|
|
4102
4104
|
* 为 true 时打印完整消息体、HTTP 拉取/发送细节、路由与分发路径等调试日志。
|
|
4103
4105
|
*/
|
|
4104
|
-
verbose: external_exports.boolean().default(false)
|
|
4106
|
+
verbose: external_exports.boolean().default(false),
|
|
4107
|
+
/**
|
|
4108
|
+
* 两次成功拉取(poll end → 下一次 poll begin)之间的额外间隔(毫秒)。
|
|
4109
|
+
* `0` 表示仅让出事件循环后立即下一轮(历史默认行为);设为 `1000` 等可减轻对服务端的请求频率。
|
|
4110
|
+
*/
|
|
4111
|
+
pollIntervalMs: external_exports.number().min(0).max(MAX_POLL_INTERVAL_MS).default(0)
|
|
4105
4112
|
});
|
|
4106
4113
|
function clampLongPollingSeconds(n) {
|
|
4107
4114
|
if (!Number.isFinite(n) || n < 1) return 30;
|
|
@@ -4114,6 +4121,10 @@ function clampTake(n) {
|
|
|
4114
4121
|
function normalizeApiBaseUrl(url) {
|
|
4115
4122
|
return url.replace(/\/+$/, "");
|
|
4116
4123
|
}
|
|
4124
|
+
function clampPollIntervalMs(n) {
|
|
4125
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
4126
|
+
return Math.min(MAX_POLL_INTERVAL_MS, Math.floor(n));
|
|
4127
|
+
}
|
|
4117
4128
|
function isConfigured(config) {
|
|
4118
4129
|
if (!config) return false;
|
|
4119
4130
|
const parsed = CsdnConfigSchema.safeParse(config);
|
|
@@ -4204,6 +4215,9 @@ function joinBaseAndPath(base, path) {
|
|
|
4204
4215
|
function isAbortError(e) {
|
|
4205
4216
|
return e instanceof Error && e.name === "AbortError" || typeof DOMException !== "undefined" && e instanceof DOMException && e.name === "AbortError";
|
|
4206
4217
|
}
|
|
4218
|
+
function pullErrorLog(baseUrl, detail) {
|
|
4219
|
+
console.error(`${CSDN_LOG_TAG} pull GET ${PATH_UNREAD} base=${baseUrl} \u2014 ${detail}`);
|
|
4220
|
+
}
|
|
4207
4221
|
var CsdnClient = class {
|
|
4208
4222
|
constructor(config) {
|
|
4209
4223
|
this.config = config;
|
|
@@ -4233,7 +4247,7 @@ var CsdnClient = class {
|
|
|
4233
4247
|
try {
|
|
4234
4248
|
if (this.config.verbose) {
|
|
4235
4249
|
console.info(
|
|
4236
|
-
|
|
4250
|
+
`${CSDN_LOG_TAG} pull GET ${PATH_UNREAD} base=${this.baseUrl} timeout=${sec}s take=${take}${cursor ? ` afterMessageId=${cursor}` : ""}`
|
|
4237
4251
|
);
|
|
4238
4252
|
}
|
|
4239
4253
|
const response = await fetch(unreadUrl.href, {
|
|
@@ -4251,31 +4265,42 @@ var CsdnClient = class {
|
|
|
4251
4265
|
if (fromResult !== null) {
|
|
4252
4266
|
if (this.config.verbose) {
|
|
4253
4267
|
console.info(
|
|
4254
|
-
|
|
4268
|
+
`${CSDN_LOG_TAG} pull response ok code=0 messages=${fromResult.length} httpStatus=${response.status}`
|
|
4255
4269
|
);
|
|
4256
4270
|
}
|
|
4257
4271
|
return fromResult;
|
|
4258
4272
|
}
|
|
4273
|
+
if (response.status === 408 || response.status === 504) {
|
|
4274
|
+
console.warn(
|
|
4275
|
+
`${CSDN_LOG_TAG} pull GET ${PATH_UNREAD} base=${this.baseUrl} httpStatus=${response.status} (timeout/\u7F51\u5173\u8D85\u65F6\uFF0C\u672C\u8F6E\u6309\u7A7A\u7ED3\u679C\u5904\u7406)`
|
|
4276
|
+
);
|
|
4277
|
+
return [];
|
|
4278
|
+
}
|
|
4259
4279
|
const fail = getBotApiFailureInfo(body);
|
|
4260
4280
|
if (fail) {
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4281
|
+
pullErrorLog(
|
|
4282
|
+
this.baseUrl,
|
|
4283
|
+
`business failure httpStatus=${response.status} code=${fail.code} message=${fail.message || "(empty)"}`
|
|
4284
|
+
);
|
|
4285
|
+
if (response.ok) {
|
|
4286
|
+
return [];
|
|
4265
4287
|
}
|
|
4266
4288
|
}
|
|
4267
4289
|
const legacy = tryLegacyUnreadMessages(body);
|
|
4268
4290
|
if (legacy) {
|
|
4269
4291
|
console.warn(
|
|
4270
|
-
|
|
4292
|
+
`${CSDN_LOG_TAG} using legacy unread response format; prefer ClawBot Result with code "0" messages=${legacy.length}`
|
|
4271
4293
|
);
|
|
4272
4294
|
return legacy;
|
|
4273
4295
|
}
|
|
4274
4296
|
if (body !== void 0 && body !== null) {
|
|
4275
|
-
|
|
4297
|
+
pullErrorLog(this.baseUrl, `unexpected response format httpStatus=${response.status} body=${JSON.stringify(body).substring(0, 200)}`);
|
|
4276
4298
|
}
|
|
4277
|
-
if (response.
|
|
4278
|
-
|
|
4299
|
+
if (!response.ok) {
|
|
4300
|
+
const hint = body !== void 0 && body !== null ? (typeof body === "string" ? body : JSON.stringify(body)).slice(0, 200) : "";
|
|
4301
|
+
const detail = `HTTP ${response.status}${hint ? ` body=${hint}` : ""}`;
|
|
4302
|
+
pullErrorLog(this.baseUrl, detail);
|
|
4303
|
+
throw new Error(`${CSDN_LOG_TAG} GET ${PATH_UNREAD} failed: ${detail}`);
|
|
4279
4304
|
}
|
|
4280
4305
|
return [];
|
|
4281
4306
|
} catch (error) {
|
|
@@ -4286,7 +4311,9 @@ var CsdnClient = class {
|
|
|
4286
4311
|
return [];
|
|
4287
4312
|
}
|
|
4288
4313
|
const msg = error instanceof Error ? error.message : String(error);
|
|
4289
|
-
|
|
4314
|
+
if (!(error instanceof Error && msg.startsWith(`${CSDN_LOG_TAG} GET`))) {
|
|
4315
|
+
pullErrorLog(this.baseUrl, `request error: ${msg}`);
|
|
4316
|
+
}
|
|
4290
4317
|
throw error instanceof Error ? error : new Error(msg);
|
|
4291
4318
|
}
|
|
4292
4319
|
}
|
|
@@ -4299,7 +4326,7 @@ var CsdnClient = class {
|
|
|
4299
4326
|
try {
|
|
4300
4327
|
if (this.config.verbose) {
|
|
4301
4328
|
console.info(
|
|
4302
|
-
|
|
4329
|
+
`${CSDN_LOG_TAG} send POST ${PATH_SEND} to=${to} contentLength=${content.length}${opts?.type ? ` type=${opts.type}` : ""}`
|
|
4303
4330
|
);
|
|
4304
4331
|
}
|
|
4305
4332
|
const response = await fetch(sendUrl, {
|
|
@@ -4318,19 +4345,22 @@ var CsdnClient = class {
|
|
|
4318
4345
|
if (ok) {
|
|
4319
4346
|
if (this.config.verbose) {
|
|
4320
4347
|
console.info(
|
|
4321
|
-
|
|
4348
|
+
`${CSDN_LOG_TAG} send response ok code=0 id=${ok.id} httpStatus=${response.status}${ok.timestamp != null ? ` timestamp=${ok.timestamp}` : ""}`
|
|
4322
4349
|
);
|
|
4323
4350
|
}
|
|
4324
4351
|
return ok;
|
|
4325
4352
|
}
|
|
4326
4353
|
const fail = getBotApiFailureInfo(body);
|
|
4327
4354
|
const hint = fail ? `${fail.message} (code=${fail.code})` : `HTTP ${response.status}`;
|
|
4328
|
-
|
|
4355
|
+
console.error(
|
|
4356
|
+
`${CSDN_LOG_TAG} send POST ${PATH_SEND} base=${this.baseUrl} httpStatus=${response.status} ${hint}`
|
|
4357
|
+
);
|
|
4358
|
+
throw new Error(`${CSDN_LOG_TAG} sendMessage failed: ${hint}`);
|
|
4329
4359
|
} catch (error) {
|
|
4330
|
-
if (error instanceof Error && error.message.startsWith(
|
|
4360
|
+
if (error instanceof Error && error.message.startsWith(`${CSDN_LOG_TAG} sendMessage failed:`)) {
|
|
4331
4361
|
throw error;
|
|
4332
4362
|
}
|
|
4333
|
-
console.error(
|
|
4363
|
+
console.error(`${CSDN_LOG_TAG} send POST ${PATH_SEND} base=${this.baseUrl} request error:`, error);
|
|
4334
4364
|
throw error;
|
|
4335
4365
|
}
|
|
4336
4366
|
}
|
|
@@ -4472,31 +4502,62 @@ function findRequireAnchorPackageJson() {
|
|
|
4472
4502
|
dir = parent;
|
|
4473
4503
|
}
|
|
4474
4504
|
throw new Error(
|
|
4475
|
-
|
|
4505
|
+
`${CSDN_LOG_TAG} loadOpenclawPluginSdk: no package.json found walking up from process.cwd(); cannot createRequire for openclaw fallback`
|
|
4476
4506
|
);
|
|
4477
4507
|
}
|
|
4508
|
+
var OPENCLAW_SEARCH_ROOTS = [
|
|
4509
|
+
process.cwd(),
|
|
4510
|
+
(0, import_node_path.join)(process.cwd(), ".."),
|
|
4511
|
+
(0, import_node_path.join)(process.cwd(), "..", ".."),
|
|
4512
|
+
(0, import_node_path.join)(process.cwd(), "packages", "csdn"),
|
|
4513
|
+
(0, import_node_path.join)(process.cwd(), "..", "packages", "csdn")
|
|
4514
|
+
];
|
|
4515
|
+
async function importOpenclawPluginSdkFromDisk() {
|
|
4516
|
+
const require2 = (0, import_node_module.createRequire)(findRequireAnchorPackageJson());
|
|
4517
|
+
for (const root of OPENCLAW_SEARCH_ROOTS) {
|
|
4518
|
+
try {
|
|
4519
|
+
const mainEntry = require2.resolve("openclaw", { paths: [root] });
|
|
4520
|
+
const pkgRoot = (0, import_node_path.join)((0, import_node_path.dirname)(mainEntry), "..");
|
|
4521
|
+
const sdkEntry = (0, import_node_path.join)(pkgRoot, "dist", "plugin-sdk", "index.js");
|
|
4522
|
+
return await import((0, import_node_url.pathToFileURL)(sdkEntry).href);
|
|
4523
|
+
} catch {
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
throw new Error(`${CSDN_LOG_TAG} could not resolve openclaw from disk`);
|
|
4527
|
+
}
|
|
4528
|
+
async function importOpenclawReplyRuntimeFromDisk() {
|
|
4529
|
+
const require2 = (0, import_node_module.createRequire)(findRequireAnchorPackageJson());
|
|
4530
|
+
for (const root of OPENCLAW_SEARCH_ROOTS) {
|
|
4531
|
+
try {
|
|
4532
|
+
const mainEntry = require2.resolve("openclaw", { paths: [root] });
|
|
4533
|
+
const pkgRoot = (0, import_node_path.join)((0, import_node_path.dirname)(mainEntry), "..");
|
|
4534
|
+
const entry = (0, import_node_path.join)(pkgRoot, "dist", "plugin-sdk", "reply-runtime.js");
|
|
4535
|
+
return await import((0, import_node_url.pathToFileURL)(entry).href);
|
|
4536
|
+
} catch {
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
throw new Error(`${CSDN_LOG_TAG} could not resolve openclaw reply-runtime from disk`);
|
|
4540
|
+
}
|
|
4541
|
+
async function loadOpenclawReplyRuntime() {
|
|
4542
|
+
try {
|
|
4543
|
+
return await import("openclaw/plugin-sdk/reply-runtime");
|
|
4544
|
+
} catch (first) {
|
|
4545
|
+
try {
|
|
4546
|
+
return await importOpenclawReplyRuntimeFromDisk();
|
|
4547
|
+
} catch {
|
|
4548
|
+
throw first;
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4478
4552
|
async function loadOpenclawPluginSdk() {
|
|
4479
4553
|
try {
|
|
4480
4554
|
return await import("openclaw/plugin-sdk");
|
|
4481
4555
|
} catch (first) {
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
(0, import_node_path.join)(process.cwd(), "..", ".."),
|
|
4487
|
-
(0, import_node_path.join)(process.cwd(), "packages", "csdn"),
|
|
4488
|
-
(0, import_node_path.join)(process.cwd(), "..", "packages", "csdn")
|
|
4489
|
-
];
|
|
4490
|
-
for (const root of searchRoots) {
|
|
4491
|
-
try {
|
|
4492
|
-
const mainEntry = require2.resolve("openclaw", { paths: [root] });
|
|
4493
|
-
const pkgRoot = (0, import_node_path.join)((0, import_node_path.dirname)(mainEntry), "..");
|
|
4494
|
-
const sdkEntry = (0, import_node_path.join)(pkgRoot, "dist", "plugin-sdk", "index.js");
|
|
4495
|
-
return await import((0, import_node_url.pathToFileURL)(sdkEntry).href);
|
|
4496
|
-
} catch {
|
|
4497
|
-
}
|
|
4556
|
+
try {
|
|
4557
|
+
return await importOpenclawPluginSdkFromDisk();
|
|
4558
|
+
} catch {
|
|
4559
|
+
throw first;
|
|
4498
4560
|
}
|
|
4499
|
-
throw first;
|
|
4500
4561
|
}
|
|
4501
4562
|
}
|
|
4502
4563
|
|
|
@@ -4507,12 +4568,12 @@ var csdnOutbound = {
|
|
|
4507
4568
|
const csdnCfgRaw = getCsdnChannelObjectFromRoot(cfg);
|
|
4508
4569
|
const parsed = CsdnConfigSchema.safeParse(csdnCfgRaw);
|
|
4509
4570
|
if (!parsed.success) {
|
|
4510
|
-
throw new Error(
|
|
4571
|
+
throw new Error(`${CSDN_LOG_TAG} invalid config for outbound`);
|
|
4511
4572
|
}
|
|
4512
4573
|
const client = new CsdnClient(parsed.data);
|
|
4513
4574
|
const result = await client.sendMessage(to, text);
|
|
4514
4575
|
if (parsed.data.verbose) {
|
|
4515
|
-
console.info(
|
|
4576
|
+
console.info(`${CSDN_LOG_TAG} outbound sent messageId=${result.id} to=${to} textLength=${text.length}`);
|
|
4516
4577
|
}
|
|
4517
4578
|
return {
|
|
4518
4579
|
channel: CSDN_PLUGIN_ID,
|
|
@@ -4576,14 +4637,14 @@ function logRoutingFallbackOnce(log) {
|
|
|
4576
4637
|
if (routingFallbackLogged) return;
|
|
4577
4638
|
routingFallbackLogged = true;
|
|
4578
4639
|
log(
|
|
4579
|
-
"
|
|
4640
|
+
"host runtime.channel.routing.resolveAgentRoute unavailable \u2192 embedded session key (normal for some gateways; once per process)"
|
|
4580
4641
|
);
|
|
4581
4642
|
}
|
|
4582
4643
|
function logDispatchFallbackOnce(log) {
|
|
4583
4644
|
if (dispatchFallbackLogged) return;
|
|
4584
4645
|
dispatchFallbackLogged = true;
|
|
4585
4646
|
log(
|
|
4586
|
-
"
|
|
4647
|
+
"host runtime.channel.reply.dispatchReplyFromConfig unavailable \u2192 openclaw/plugin-sdk (normal for some gateways; once per process)"
|
|
4587
4648
|
);
|
|
4588
4649
|
}
|
|
4589
4650
|
|
|
@@ -4626,7 +4687,7 @@ async function handleCsdnMessage(params) {
|
|
|
4626
4687
|
}
|
|
4627
4688
|
if (usedEmbeddedRoute && verbose) {
|
|
4628
4689
|
try {
|
|
4629
|
-
logger.info(`
|
|
4690
|
+
logger.info(`route.fallback sessionKey=${route.sessionKey} accountId=${route.accountId ?? accountId}`);
|
|
4630
4691
|
} catch {
|
|
4631
4692
|
}
|
|
4632
4693
|
}
|
|
@@ -4651,7 +4712,7 @@ async function handleCsdnMessage(params) {
|
|
|
4651
4712
|
};
|
|
4652
4713
|
if (verbose) {
|
|
4653
4714
|
try {
|
|
4654
|
-
logger.info(`
|
|
4715
|
+
logger.info(`inbound.content ${inboundCtx.Body}`);
|
|
4655
4716
|
} catch {
|
|
4656
4717
|
}
|
|
4657
4718
|
}
|
|
@@ -4663,10 +4724,10 @@ async function handleCsdnMessage(params) {
|
|
|
4663
4724
|
if (!text) return;
|
|
4664
4725
|
try {
|
|
4665
4726
|
if (verbose) {
|
|
4666
|
-
logger.info(`
|
|
4667
|
-
logger.info(`
|
|
4727
|
+
logger.info(`outbound text to=${msg.from} len=${text.length}`);
|
|
4728
|
+
logger.info(`outbound.content ${text}`);
|
|
4668
4729
|
} else {
|
|
4669
|
-
logger.info(`
|
|
4730
|
+
logger.info(`reply to=${msg.from} len=${text.length}`);
|
|
4670
4731
|
}
|
|
4671
4732
|
await csdnOutbound.sendText({
|
|
4672
4733
|
cfg: config,
|
|
@@ -4674,21 +4735,21 @@ async function handleCsdnMessage(params) {
|
|
|
4674
4735
|
text
|
|
4675
4736
|
});
|
|
4676
4737
|
if (verbose) {
|
|
4677
|
-
logger.info(`
|
|
4738
|
+
logger.info(`outbound done to=${msg.from}`);
|
|
4678
4739
|
}
|
|
4679
4740
|
} catch (err) {
|
|
4680
|
-
logger.error(`
|
|
4741
|
+
logger.error(`failed to send reply: ${String(err)}`);
|
|
4681
4742
|
}
|
|
4682
4743
|
},
|
|
4683
4744
|
onError: (err, info) => {
|
|
4684
|
-
logger.error(`
|
|
4745
|
+
logger.error(`reply dispatch kind=${info.kind} error: ${String(err)}`);
|
|
4685
4746
|
}
|
|
4686
4747
|
});
|
|
4687
4748
|
const dispatchFromRuntime = runtime?.channel?.reply?.dispatchReplyFromConfig;
|
|
4688
4749
|
const runDispatch = async () => {
|
|
4689
4750
|
if (typeof dispatchFromRuntime === "function") {
|
|
4690
4751
|
if (verbose) {
|
|
4691
|
-
logger.info(`
|
|
4752
|
+
logger.info(`dispatching via runtime.channel.reply session=${route.sessionKey}`);
|
|
4692
4753
|
}
|
|
4693
4754
|
await dispatchFromRuntime({
|
|
4694
4755
|
ctx: inboundCtx,
|
|
@@ -4699,12 +4760,35 @@ async function handleCsdnMessage(params) {
|
|
|
4699
4760
|
return;
|
|
4700
4761
|
}
|
|
4701
4762
|
logDispatchFallbackOnce((m) => logger.info(m));
|
|
4763
|
+
let replyRuntime;
|
|
4764
|
+
try {
|
|
4765
|
+
replyRuntime = await loadOpenclawReplyRuntime();
|
|
4766
|
+
} catch {
|
|
4767
|
+
replyRuntime = void 0;
|
|
4768
|
+
}
|
|
4769
|
+
if (replyRuntime && typeof replyRuntime.dispatchInboundMessage === "function") {
|
|
4770
|
+
try {
|
|
4771
|
+
if (verbose) {
|
|
4772
|
+
logger.info(`dispatching via plugin-sdk/reply-runtime session=${route.sessionKey}`);
|
|
4773
|
+
}
|
|
4774
|
+
await replyRuntime.dispatchInboundMessage({
|
|
4775
|
+
ctx: inboundCtx,
|
|
4776
|
+
cfg: config,
|
|
4777
|
+
dispatcher,
|
|
4778
|
+
replyOptions: {}
|
|
4779
|
+
});
|
|
4780
|
+
return;
|
|
4781
|
+
} catch (err) {
|
|
4782
|
+
logger.error(`plugin-sdk/reply-runtime dispatch failed: ${String(err)}`);
|
|
4783
|
+
throw err;
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4702
4786
|
try {
|
|
4703
4787
|
const sdk = await loadOpenclawPluginSdk();
|
|
4704
4788
|
const withSettled = sdk.dispatchReplyFromConfigWithSettledDispatcher;
|
|
4705
4789
|
const plain = sdk.dispatchReplyFromConfig;
|
|
4706
4790
|
if (verbose) {
|
|
4707
|
-
logger.info(`
|
|
4791
|
+
logger.info(`dispatching via plugin-sdk (legacy barrel) session=${route.sessionKey}`);
|
|
4708
4792
|
}
|
|
4709
4793
|
if (typeof withSettled === "function") {
|
|
4710
4794
|
await withSettled({
|
|
@@ -4724,28 +4808,28 @@ async function handleCsdnMessage(params) {
|
|
|
4724
4808
|
});
|
|
4725
4809
|
} else {
|
|
4726
4810
|
throw new Error(
|
|
4727
|
-
"openclaw
|
|
4811
|
+
"openclaw: no channel dispatch API (need plugin-sdk/reply-runtime.dispatchInboundMessage or legacy dispatchReplyFromConfig on plugin-sdk barrel)"
|
|
4728
4812
|
);
|
|
4729
4813
|
}
|
|
4730
4814
|
} catch (err) {
|
|
4731
|
-
logger.error(`
|
|
4815
|
+
logger.error(`plugin-sdk dispatch unavailable: ${String(err)}`);
|
|
4732
4816
|
throw err;
|
|
4733
4817
|
}
|
|
4734
4818
|
};
|
|
4735
4819
|
try {
|
|
4736
4820
|
await runDispatch();
|
|
4737
4821
|
if (verbose) {
|
|
4738
|
-
logger.info(`
|
|
4822
|
+
logger.info(`dispatch complete session=${route.sessionKey}`);
|
|
4739
4823
|
}
|
|
4740
4824
|
} catch (err) {
|
|
4741
|
-
logger.error(`
|
|
4825
|
+
logger.error(`dispatch error: ${String(err)}`);
|
|
4742
4826
|
try {
|
|
4743
4827
|
const echo = `\u5DF2\u6536\u5230\uFF1A${inboundCtx.Body}`;
|
|
4744
4828
|
if (verbose) {
|
|
4745
|
-
logger.info(`
|
|
4746
|
-
logger.info(`
|
|
4829
|
+
logger.info(`outbound text(to=${msg.from}) via fallback len=${echo.length}`);
|
|
4830
|
+
logger.info(`outbound.content ${echo}`);
|
|
4747
4831
|
} else {
|
|
4748
|
-
logger.info(`
|
|
4832
|
+
logger.info(`echo fallback to=${msg.from} len=${echo.length}`);
|
|
4749
4833
|
}
|
|
4750
4834
|
await csdnOutbound.sendText({
|
|
4751
4835
|
cfg: config,
|
|
@@ -4753,10 +4837,10 @@ async function handleCsdnMessage(params) {
|
|
|
4753
4837
|
text: echo
|
|
4754
4838
|
});
|
|
4755
4839
|
if (verbose) {
|
|
4756
|
-
logger.info(`
|
|
4840
|
+
logger.info(`outbound done to=${msg.from} (fallback)`);
|
|
4757
4841
|
}
|
|
4758
4842
|
} catch (sendErr) {
|
|
4759
|
-
logger.error(`
|
|
4843
|
+
logger.error(`fallback reply failed: ${String(sendErr)}`);
|
|
4760
4844
|
}
|
|
4761
4845
|
}
|
|
4762
4846
|
}
|
|
@@ -4769,6 +4853,26 @@ function maxMessageId(messages) {
|
|
|
4769
4853
|
}
|
|
4770
4854
|
return max;
|
|
4771
4855
|
}
|
|
4856
|
+
function sleepBetweenPolls(ms, signal) {
|
|
4857
|
+
if (ms <= 0) {
|
|
4858
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
4859
|
+
}
|
|
4860
|
+
return new Promise((resolve) => {
|
|
4861
|
+
if (signal?.aborted) {
|
|
4862
|
+
resolve();
|
|
4863
|
+
return;
|
|
4864
|
+
}
|
|
4865
|
+
const t = setTimeout(resolve, ms);
|
|
4866
|
+
signal?.addEventListener(
|
|
4867
|
+
"abort",
|
|
4868
|
+
() => {
|
|
4869
|
+
clearTimeout(t);
|
|
4870
|
+
resolve();
|
|
4871
|
+
},
|
|
4872
|
+
{ once: true }
|
|
4873
|
+
);
|
|
4874
|
+
});
|
|
4875
|
+
}
|
|
4772
4876
|
async function monitorCsdnProvider(opts) {
|
|
4773
4877
|
const { config, runtime, accountId = "default", abortSignal } = opts;
|
|
4774
4878
|
const baseLogger = runtime && (runtime.logger || runtime.log) || console;
|
|
@@ -4780,18 +4884,19 @@ async function monitorCsdnProvider(opts) {
|
|
|
4780
4884
|
const csdnCfgRaw = getCsdnChannelObjectFromRoot(config);
|
|
4781
4885
|
const parsed = CsdnConfigSchema.safeParse(csdnCfgRaw);
|
|
4782
4886
|
if (!parsed.success) {
|
|
4783
|
-
logger.error(`
|
|
4887
|
+
logger.error(`invalid config for account ${accountId}: ${parsed.error}`);
|
|
4784
4888
|
return;
|
|
4785
4889
|
}
|
|
4786
4890
|
const csdnCfg = parsed.data;
|
|
4787
4891
|
if (!csdnCfg.token) {
|
|
4788
|
-
logger.warn(`
|
|
4892
|
+
logger.warn(`token missing; skip start for account ${accountId}`);
|
|
4789
4893
|
return;
|
|
4790
4894
|
}
|
|
4791
4895
|
const client = new CsdnClient(csdnCfg);
|
|
4896
|
+
const pollIntervalMs = clampPollIntervalMs(csdnCfg.pollIntervalMs);
|
|
4792
4897
|
const v = csdnCfg.verbose;
|
|
4793
4898
|
logger.info(
|
|
4794
|
-
`
|
|
4899
|
+
`starting long-polling account=${accountId} apiUrl=${csdnCfg.apiUrl} timeout=${FIXED_LONG_POLLING_TIMEOUT_SEC}s take=${FIXED_TAKE} pollIntervalMs=${pollIntervalMs}`
|
|
4795
4900
|
);
|
|
4796
4901
|
let backoffMs = FIXED_RETRY_INTERVAL_MS;
|
|
4797
4902
|
const backoffCap = Math.max(FIXED_RETRY_INTERVAL_MS, FIXED_RETRY_BACKOFF_MAX_MS);
|
|
@@ -4801,9 +4906,6 @@ async function monitorCsdnProvider(opts) {
|
|
|
4801
4906
|
try {
|
|
4802
4907
|
const t0 = Date.now();
|
|
4803
4908
|
if (v) {
|
|
4804
|
-
logger.info(
|
|
4805
|
-
`[csdn] poll begin account=${accountId} timeout=${FIXED_LONG_POLLING_TIMEOUT_SEC}s take=${FIXED_TAKE}${afterMessageId ? ` afterMessageId=${afterMessageId}` : ""}`
|
|
4806
|
-
);
|
|
4807
4909
|
}
|
|
4808
4910
|
const messages = await client.getUnreadMessages(FIXED_LONG_POLLING_TIMEOUT_SEC, {
|
|
4809
4911
|
afterMessageId,
|
|
@@ -4811,12 +4913,11 @@ async function monitorCsdnProvider(opts) {
|
|
|
4811
4913
|
});
|
|
4812
4914
|
backoffMs = FIXED_RETRY_INTERVAL_MS;
|
|
4813
4915
|
const dur = Date.now() - t0;
|
|
4814
|
-
logger.info(`[csdn] poll end account=${accountId} duration=${dur}ms count=${messages.length}`);
|
|
4815
4916
|
for (const msg of messages) {
|
|
4816
|
-
logger.info(`
|
|
4917
|
+
logger.info(`inbound id=${msg.id} from=${msg.from} type=${msg.type} size=${msg.content?.length ?? 0}`);
|
|
4817
4918
|
if (v) {
|
|
4818
4919
|
try {
|
|
4819
|
-
logger.info(`
|
|
4920
|
+
logger.info(`inbound.full ${JSON.stringify(msg)}`);
|
|
4820
4921
|
} catch {
|
|
4821
4922
|
}
|
|
4822
4923
|
}
|
|
@@ -4830,17 +4931,17 @@ async function monitorCsdnProvider(opts) {
|
|
|
4830
4931
|
if (messages.length > 0) {
|
|
4831
4932
|
afterMessageId = maxMessageId(messages);
|
|
4832
4933
|
}
|
|
4833
|
-
await
|
|
4934
|
+
await sleepBetweenPolls(pollIntervalMs, abortSignal);
|
|
4834
4935
|
} catch (err) {
|
|
4835
|
-
logger.error(`
|
|
4936
|
+
logger.error(`polling error: ${err}`);
|
|
4836
4937
|
logger.info(
|
|
4837
|
-
`
|
|
4938
|
+
`waiting ${backoffMs}ms before retry (exponential backoff, cap=${backoffCap}ms)...`
|
|
4838
4939
|
);
|
|
4839
4940
|
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
4840
4941
|
backoffMs = Math.min(backoffCap, Math.max(FIXED_RETRY_INTERVAL_MS, backoffMs * 2));
|
|
4841
4942
|
}
|
|
4842
4943
|
}
|
|
4843
|
-
logger.info(`
|
|
4944
|
+
logger.info(`polling stopped for account ${accountId}`);
|
|
4844
4945
|
};
|
|
4845
4946
|
return pollLoop();
|
|
4846
4947
|
}
|
|
@@ -4903,7 +5004,8 @@ var csdnPlugin = {
|
|
|
4903
5004
|
token: { type: "string" },
|
|
4904
5005
|
apiUrl: { type: "string" },
|
|
4905
5006
|
fallbackAgentId: { type: "string" },
|
|
4906
|
-
verbose: { type: "boolean" }
|
|
5007
|
+
verbose: { type: "boolean" },
|
|
5008
|
+
pollIntervalMs: { type: "number", minimum: 0, maximum: 6e5 }
|
|
4907
5009
|
}
|
|
4908
5010
|
}
|
|
4909
5011
|
},
|
package/dist/index.js
CHANGED
|
@@ -4047,10 +4047,12 @@ var NEVER = INVALID;
|
|
|
4047
4047
|
|
|
4048
4048
|
// src/config.ts
|
|
4049
4049
|
var CSDN_PLUGIN_ID = "csdn-im";
|
|
4050
|
+
var CSDN_LOG_TAG = "[csdn-im]";
|
|
4050
4051
|
var FIXED_LONG_POLLING_TIMEOUT_SEC = 30;
|
|
4051
4052
|
var FIXED_TAKE = 1;
|
|
4052
4053
|
var FIXED_RETRY_INTERVAL_MS = 3e3;
|
|
4053
4054
|
var FIXED_RETRY_BACKOFF_MAX_MS = 12e4;
|
|
4055
|
+
var MAX_POLL_INTERVAL_MS = 6e5;
|
|
4054
4056
|
function getCsdnChannelObjectFromRoot(cfg) {
|
|
4055
4057
|
return cfg?.channels?.[CSDN_PLUGIN_ID];
|
|
4056
4058
|
}
|
|
@@ -4070,7 +4072,12 @@ var CsdnConfigSchema = external_exports.object({
|
|
|
4070
4072
|
/**
|
|
4071
4073
|
* 为 true 时打印完整消息体、HTTP 拉取/发送细节、路由与分发路径等调试日志。
|
|
4072
4074
|
*/
|
|
4073
|
-
verbose: external_exports.boolean().default(false)
|
|
4075
|
+
verbose: external_exports.boolean().default(false),
|
|
4076
|
+
/**
|
|
4077
|
+
* 两次成功拉取(poll end → 下一次 poll begin)之间的额外间隔(毫秒)。
|
|
4078
|
+
* `0` 表示仅让出事件循环后立即下一轮(历史默认行为);设为 `1000` 等可减轻对服务端的请求频率。
|
|
4079
|
+
*/
|
|
4080
|
+
pollIntervalMs: external_exports.number().min(0).max(MAX_POLL_INTERVAL_MS).default(0)
|
|
4074
4081
|
});
|
|
4075
4082
|
function clampLongPollingSeconds(n) {
|
|
4076
4083
|
if (!Number.isFinite(n) || n < 1) return 30;
|
|
@@ -4083,6 +4090,10 @@ function clampTake(n) {
|
|
|
4083
4090
|
function normalizeApiBaseUrl(url) {
|
|
4084
4091
|
return url.replace(/\/+$/, "");
|
|
4085
4092
|
}
|
|
4093
|
+
function clampPollIntervalMs(n) {
|
|
4094
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
4095
|
+
return Math.min(MAX_POLL_INTERVAL_MS, Math.floor(n));
|
|
4096
|
+
}
|
|
4086
4097
|
function isConfigured(config) {
|
|
4087
4098
|
if (!config) return false;
|
|
4088
4099
|
const parsed = CsdnConfigSchema.safeParse(config);
|
|
@@ -4173,6 +4184,9 @@ function joinBaseAndPath(base, path) {
|
|
|
4173
4184
|
function isAbortError(e) {
|
|
4174
4185
|
return e instanceof Error && e.name === "AbortError" || typeof DOMException !== "undefined" && e instanceof DOMException && e.name === "AbortError";
|
|
4175
4186
|
}
|
|
4187
|
+
function pullErrorLog(baseUrl, detail) {
|
|
4188
|
+
console.error(`${CSDN_LOG_TAG} pull GET ${PATH_UNREAD} base=${baseUrl} \u2014 ${detail}`);
|
|
4189
|
+
}
|
|
4176
4190
|
var CsdnClient = class {
|
|
4177
4191
|
constructor(config) {
|
|
4178
4192
|
this.config = config;
|
|
@@ -4202,7 +4216,7 @@ var CsdnClient = class {
|
|
|
4202
4216
|
try {
|
|
4203
4217
|
if (this.config.verbose) {
|
|
4204
4218
|
console.info(
|
|
4205
|
-
|
|
4219
|
+
`${CSDN_LOG_TAG} pull GET ${PATH_UNREAD} base=${this.baseUrl} timeout=${sec}s take=${take}${cursor ? ` afterMessageId=${cursor}` : ""}`
|
|
4206
4220
|
);
|
|
4207
4221
|
}
|
|
4208
4222
|
const response = await fetch(unreadUrl.href, {
|
|
@@ -4220,31 +4234,42 @@ var CsdnClient = class {
|
|
|
4220
4234
|
if (fromResult !== null) {
|
|
4221
4235
|
if (this.config.verbose) {
|
|
4222
4236
|
console.info(
|
|
4223
|
-
|
|
4237
|
+
`${CSDN_LOG_TAG} pull response ok code=0 messages=${fromResult.length} httpStatus=${response.status}`
|
|
4224
4238
|
);
|
|
4225
4239
|
}
|
|
4226
4240
|
return fromResult;
|
|
4227
4241
|
}
|
|
4242
|
+
if (response.status === 408 || response.status === 504) {
|
|
4243
|
+
console.warn(
|
|
4244
|
+
`${CSDN_LOG_TAG} pull GET ${PATH_UNREAD} base=${this.baseUrl} httpStatus=${response.status} (timeout/\u7F51\u5173\u8D85\u65F6\uFF0C\u672C\u8F6E\u6309\u7A7A\u7ED3\u679C\u5904\u7406)`
|
|
4245
|
+
);
|
|
4246
|
+
return [];
|
|
4247
|
+
}
|
|
4228
4248
|
const fail = getBotApiFailureInfo(body);
|
|
4229
4249
|
if (fail) {
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4250
|
+
pullErrorLog(
|
|
4251
|
+
this.baseUrl,
|
|
4252
|
+
`business failure httpStatus=${response.status} code=${fail.code} message=${fail.message || "(empty)"}`
|
|
4253
|
+
);
|
|
4254
|
+
if (response.ok) {
|
|
4255
|
+
return [];
|
|
4234
4256
|
}
|
|
4235
4257
|
}
|
|
4236
4258
|
const legacy = tryLegacyUnreadMessages(body);
|
|
4237
4259
|
if (legacy) {
|
|
4238
4260
|
console.warn(
|
|
4239
|
-
|
|
4261
|
+
`${CSDN_LOG_TAG} using legacy unread response format; prefer ClawBot Result with code "0" messages=${legacy.length}`
|
|
4240
4262
|
);
|
|
4241
4263
|
return legacy;
|
|
4242
4264
|
}
|
|
4243
4265
|
if (body !== void 0 && body !== null) {
|
|
4244
|
-
|
|
4266
|
+
pullErrorLog(this.baseUrl, `unexpected response format httpStatus=${response.status} body=${JSON.stringify(body).substring(0, 200)}`);
|
|
4245
4267
|
}
|
|
4246
|
-
if (response.
|
|
4247
|
-
|
|
4268
|
+
if (!response.ok) {
|
|
4269
|
+
const hint = body !== void 0 && body !== null ? (typeof body === "string" ? body : JSON.stringify(body)).slice(0, 200) : "";
|
|
4270
|
+
const detail = `HTTP ${response.status}${hint ? ` body=${hint}` : ""}`;
|
|
4271
|
+
pullErrorLog(this.baseUrl, detail);
|
|
4272
|
+
throw new Error(`${CSDN_LOG_TAG} GET ${PATH_UNREAD} failed: ${detail}`);
|
|
4248
4273
|
}
|
|
4249
4274
|
return [];
|
|
4250
4275
|
} catch (error) {
|
|
@@ -4255,7 +4280,9 @@ var CsdnClient = class {
|
|
|
4255
4280
|
return [];
|
|
4256
4281
|
}
|
|
4257
4282
|
const msg = error instanceof Error ? error.message : String(error);
|
|
4258
|
-
|
|
4283
|
+
if (!(error instanceof Error && msg.startsWith(`${CSDN_LOG_TAG} GET`))) {
|
|
4284
|
+
pullErrorLog(this.baseUrl, `request error: ${msg}`);
|
|
4285
|
+
}
|
|
4259
4286
|
throw error instanceof Error ? error : new Error(msg);
|
|
4260
4287
|
}
|
|
4261
4288
|
}
|
|
@@ -4268,7 +4295,7 @@ var CsdnClient = class {
|
|
|
4268
4295
|
try {
|
|
4269
4296
|
if (this.config.verbose) {
|
|
4270
4297
|
console.info(
|
|
4271
|
-
|
|
4298
|
+
`${CSDN_LOG_TAG} send POST ${PATH_SEND} to=${to} contentLength=${content.length}${opts?.type ? ` type=${opts.type}` : ""}`
|
|
4272
4299
|
);
|
|
4273
4300
|
}
|
|
4274
4301
|
const response = await fetch(sendUrl, {
|
|
@@ -4287,19 +4314,22 @@ var CsdnClient = class {
|
|
|
4287
4314
|
if (ok) {
|
|
4288
4315
|
if (this.config.verbose) {
|
|
4289
4316
|
console.info(
|
|
4290
|
-
|
|
4317
|
+
`${CSDN_LOG_TAG} send response ok code=0 id=${ok.id} httpStatus=${response.status}${ok.timestamp != null ? ` timestamp=${ok.timestamp}` : ""}`
|
|
4291
4318
|
);
|
|
4292
4319
|
}
|
|
4293
4320
|
return ok;
|
|
4294
4321
|
}
|
|
4295
4322
|
const fail = getBotApiFailureInfo(body);
|
|
4296
4323
|
const hint = fail ? `${fail.message} (code=${fail.code})` : `HTTP ${response.status}`;
|
|
4297
|
-
|
|
4324
|
+
console.error(
|
|
4325
|
+
`${CSDN_LOG_TAG} send POST ${PATH_SEND} base=${this.baseUrl} httpStatus=${response.status} ${hint}`
|
|
4326
|
+
);
|
|
4327
|
+
throw new Error(`${CSDN_LOG_TAG} sendMessage failed: ${hint}`);
|
|
4298
4328
|
} catch (error) {
|
|
4299
|
-
if (error instanceof Error && error.message.startsWith(
|
|
4329
|
+
if (error instanceof Error && error.message.startsWith(`${CSDN_LOG_TAG} sendMessage failed:`)) {
|
|
4300
4330
|
throw error;
|
|
4301
4331
|
}
|
|
4302
|
-
console.error(
|
|
4332
|
+
console.error(`${CSDN_LOG_TAG} send POST ${PATH_SEND} base=${this.baseUrl} request error:`, error);
|
|
4303
4333
|
throw error;
|
|
4304
4334
|
}
|
|
4305
4335
|
}
|
|
@@ -4441,31 +4471,62 @@ function findRequireAnchorPackageJson() {
|
|
|
4441
4471
|
dir = parent;
|
|
4442
4472
|
}
|
|
4443
4473
|
throw new Error(
|
|
4444
|
-
|
|
4474
|
+
`${CSDN_LOG_TAG} loadOpenclawPluginSdk: no package.json found walking up from process.cwd(); cannot createRequire for openclaw fallback`
|
|
4445
4475
|
);
|
|
4446
4476
|
}
|
|
4477
|
+
var OPENCLAW_SEARCH_ROOTS = [
|
|
4478
|
+
process.cwd(),
|
|
4479
|
+
join(process.cwd(), ".."),
|
|
4480
|
+
join(process.cwd(), "..", ".."),
|
|
4481
|
+
join(process.cwd(), "packages", "csdn"),
|
|
4482
|
+
join(process.cwd(), "..", "packages", "csdn")
|
|
4483
|
+
];
|
|
4484
|
+
async function importOpenclawPluginSdkFromDisk() {
|
|
4485
|
+
const require2 = createRequire(findRequireAnchorPackageJson());
|
|
4486
|
+
for (const root of OPENCLAW_SEARCH_ROOTS) {
|
|
4487
|
+
try {
|
|
4488
|
+
const mainEntry = require2.resolve("openclaw", { paths: [root] });
|
|
4489
|
+
const pkgRoot = join(dirname(mainEntry), "..");
|
|
4490
|
+
const sdkEntry = join(pkgRoot, "dist", "plugin-sdk", "index.js");
|
|
4491
|
+
return await import(pathToFileURL(sdkEntry).href);
|
|
4492
|
+
} catch {
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
throw new Error(`${CSDN_LOG_TAG} could not resolve openclaw from disk`);
|
|
4496
|
+
}
|
|
4497
|
+
async function importOpenclawReplyRuntimeFromDisk() {
|
|
4498
|
+
const require2 = createRequire(findRequireAnchorPackageJson());
|
|
4499
|
+
for (const root of OPENCLAW_SEARCH_ROOTS) {
|
|
4500
|
+
try {
|
|
4501
|
+
const mainEntry = require2.resolve("openclaw", { paths: [root] });
|
|
4502
|
+
const pkgRoot = join(dirname(mainEntry), "..");
|
|
4503
|
+
const entry = join(pkgRoot, "dist", "plugin-sdk", "reply-runtime.js");
|
|
4504
|
+
return await import(pathToFileURL(entry).href);
|
|
4505
|
+
} catch {
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
throw new Error(`${CSDN_LOG_TAG} could not resolve openclaw reply-runtime from disk`);
|
|
4509
|
+
}
|
|
4510
|
+
async function loadOpenclawReplyRuntime() {
|
|
4511
|
+
try {
|
|
4512
|
+
return await import("openclaw/plugin-sdk/reply-runtime");
|
|
4513
|
+
} catch (first) {
|
|
4514
|
+
try {
|
|
4515
|
+
return await importOpenclawReplyRuntimeFromDisk();
|
|
4516
|
+
} catch {
|
|
4517
|
+
throw first;
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4447
4521
|
async function loadOpenclawPluginSdk() {
|
|
4448
4522
|
try {
|
|
4449
4523
|
return await import("openclaw/plugin-sdk");
|
|
4450
4524
|
} catch (first) {
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
join(process.cwd(), "..", ".."),
|
|
4456
|
-
join(process.cwd(), "packages", "csdn"),
|
|
4457
|
-
join(process.cwd(), "..", "packages", "csdn")
|
|
4458
|
-
];
|
|
4459
|
-
for (const root of searchRoots) {
|
|
4460
|
-
try {
|
|
4461
|
-
const mainEntry = require2.resolve("openclaw", { paths: [root] });
|
|
4462
|
-
const pkgRoot = join(dirname(mainEntry), "..");
|
|
4463
|
-
const sdkEntry = join(pkgRoot, "dist", "plugin-sdk", "index.js");
|
|
4464
|
-
return await import(pathToFileURL(sdkEntry).href);
|
|
4465
|
-
} catch {
|
|
4466
|
-
}
|
|
4525
|
+
try {
|
|
4526
|
+
return await importOpenclawPluginSdkFromDisk();
|
|
4527
|
+
} catch {
|
|
4528
|
+
throw first;
|
|
4467
4529
|
}
|
|
4468
|
-
throw first;
|
|
4469
4530
|
}
|
|
4470
4531
|
}
|
|
4471
4532
|
|
|
@@ -4476,12 +4537,12 @@ var csdnOutbound = {
|
|
|
4476
4537
|
const csdnCfgRaw = getCsdnChannelObjectFromRoot(cfg);
|
|
4477
4538
|
const parsed = CsdnConfigSchema.safeParse(csdnCfgRaw);
|
|
4478
4539
|
if (!parsed.success) {
|
|
4479
|
-
throw new Error(
|
|
4540
|
+
throw new Error(`${CSDN_LOG_TAG} invalid config for outbound`);
|
|
4480
4541
|
}
|
|
4481
4542
|
const client = new CsdnClient(parsed.data);
|
|
4482
4543
|
const result = await client.sendMessage(to, text);
|
|
4483
4544
|
if (parsed.data.verbose) {
|
|
4484
|
-
console.info(
|
|
4545
|
+
console.info(`${CSDN_LOG_TAG} outbound sent messageId=${result.id} to=${to} textLength=${text.length}`);
|
|
4485
4546
|
}
|
|
4486
4547
|
return {
|
|
4487
4548
|
channel: CSDN_PLUGIN_ID,
|
|
@@ -4545,14 +4606,14 @@ function logRoutingFallbackOnce(log) {
|
|
|
4545
4606
|
if (routingFallbackLogged) return;
|
|
4546
4607
|
routingFallbackLogged = true;
|
|
4547
4608
|
log(
|
|
4548
|
-
"
|
|
4609
|
+
"host runtime.channel.routing.resolveAgentRoute unavailable \u2192 embedded session key (normal for some gateways; once per process)"
|
|
4549
4610
|
);
|
|
4550
4611
|
}
|
|
4551
4612
|
function logDispatchFallbackOnce(log) {
|
|
4552
4613
|
if (dispatchFallbackLogged) return;
|
|
4553
4614
|
dispatchFallbackLogged = true;
|
|
4554
4615
|
log(
|
|
4555
|
-
"
|
|
4616
|
+
"host runtime.channel.reply.dispatchReplyFromConfig unavailable \u2192 openclaw/plugin-sdk (normal for some gateways; once per process)"
|
|
4556
4617
|
);
|
|
4557
4618
|
}
|
|
4558
4619
|
|
|
@@ -4595,7 +4656,7 @@ async function handleCsdnMessage(params) {
|
|
|
4595
4656
|
}
|
|
4596
4657
|
if (usedEmbeddedRoute && verbose) {
|
|
4597
4658
|
try {
|
|
4598
|
-
logger.info(`
|
|
4659
|
+
logger.info(`route.fallback sessionKey=${route.sessionKey} accountId=${route.accountId ?? accountId}`);
|
|
4599
4660
|
} catch {
|
|
4600
4661
|
}
|
|
4601
4662
|
}
|
|
@@ -4620,7 +4681,7 @@ async function handleCsdnMessage(params) {
|
|
|
4620
4681
|
};
|
|
4621
4682
|
if (verbose) {
|
|
4622
4683
|
try {
|
|
4623
|
-
logger.info(`
|
|
4684
|
+
logger.info(`inbound.content ${inboundCtx.Body}`);
|
|
4624
4685
|
} catch {
|
|
4625
4686
|
}
|
|
4626
4687
|
}
|
|
@@ -4632,10 +4693,10 @@ async function handleCsdnMessage(params) {
|
|
|
4632
4693
|
if (!text) return;
|
|
4633
4694
|
try {
|
|
4634
4695
|
if (verbose) {
|
|
4635
|
-
logger.info(`
|
|
4636
|
-
logger.info(`
|
|
4696
|
+
logger.info(`outbound text to=${msg.from} len=${text.length}`);
|
|
4697
|
+
logger.info(`outbound.content ${text}`);
|
|
4637
4698
|
} else {
|
|
4638
|
-
logger.info(`
|
|
4699
|
+
logger.info(`reply to=${msg.from} len=${text.length}`);
|
|
4639
4700
|
}
|
|
4640
4701
|
await csdnOutbound.sendText({
|
|
4641
4702
|
cfg: config,
|
|
@@ -4643,21 +4704,21 @@ async function handleCsdnMessage(params) {
|
|
|
4643
4704
|
text
|
|
4644
4705
|
});
|
|
4645
4706
|
if (verbose) {
|
|
4646
|
-
logger.info(`
|
|
4707
|
+
logger.info(`outbound done to=${msg.from}`);
|
|
4647
4708
|
}
|
|
4648
4709
|
} catch (err) {
|
|
4649
|
-
logger.error(`
|
|
4710
|
+
logger.error(`failed to send reply: ${String(err)}`);
|
|
4650
4711
|
}
|
|
4651
4712
|
},
|
|
4652
4713
|
onError: (err, info) => {
|
|
4653
|
-
logger.error(`
|
|
4714
|
+
logger.error(`reply dispatch kind=${info.kind} error: ${String(err)}`);
|
|
4654
4715
|
}
|
|
4655
4716
|
});
|
|
4656
4717
|
const dispatchFromRuntime = runtime?.channel?.reply?.dispatchReplyFromConfig;
|
|
4657
4718
|
const runDispatch = async () => {
|
|
4658
4719
|
if (typeof dispatchFromRuntime === "function") {
|
|
4659
4720
|
if (verbose) {
|
|
4660
|
-
logger.info(`
|
|
4721
|
+
logger.info(`dispatching via runtime.channel.reply session=${route.sessionKey}`);
|
|
4661
4722
|
}
|
|
4662
4723
|
await dispatchFromRuntime({
|
|
4663
4724
|
ctx: inboundCtx,
|
|
@@ -4668,12 +4729,35 @@ async function handleCsdnMessage(params) {
|
|
|
4668
4729
|
return;
|
|
4669
4730
|
}
|
|
4670
4731
|
logDispatchFallbackOnce((m) => logger.info(m));
|
|
4732
|
+
let replyRuntime;
|
|
4733
|
+
try {
|
|
4734
|
+
replyRuntime = await loadOpenclawReplyRuntime();
|
|
4735
|
+
} catch {
|
|
4736
|
+
replyRuntime = void 0;
|
|
4737
|
+
}
|
|
4738
|
+
if (replyRuntime && typeof replyRuntime.dispatchInboundMessage === "function") {
|
|
4739
|
+
try {
|
|
4740
|
+
if (verbose) {
|
|
4741
|
+
logger.info(`dispatching via plugin-sdk/reply-runtime session=${route.sessionKey}`);
|
|
4742
|
+
}
|
|
4743
|
+
await replyRuntime.dispatchInboundMessage({
|
|
4744
|
+
ctx: inboundCtx,
|
|
4745
|
+
cfg: config,
|
|
4746
|
+
dispatcher,
|
|
4747
|
+
replyOptions: {}
|
|
4748
|
+
});
|
|
4749
|
+
return;
|
|
4750
|
+
} catch (err) {
|
|
4751
|
+
logger.error(`plugin-sdk/reply-runtime dispatch failed: ${String(err)}`);
|
|
4752
|
+
throw err;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4671
4755
|
try {
|
|
4672
4756
|
const sdk = await loadOpenclawPluginSdk();
|
|
4673
4757
|
const withSettled = sdk.dispatchReplyFromConfigWithSettledDispatcher;
|
|
4674
4758
|
const plain = sdk.dispatchReplyFromConfig;
|
|
4675
4759
|
if (verbose) {
|
|
4676
|
-
logger.info(`
|
|
4760
|
+
logger.info(`dispatching via plugin-sdk (legacy barrel) session=${route.sessionKey}`);
|
|
4677
4761
|
}
|
|
4678
4762
|
if (typeof withSettled === "function") {
|
|
4679
4763
|
await withSettled({
|
|
@@ -4693,28 +4777,28 @@ async function handleCsdnMessage(params) {
|
|
|
4693
4777
|
});
|
|
4694
4778
|
} else {
|
|
4695
4779
|
throw new Error(
|
|
4696
|
-
"openclaw
|
|
4780
|
+
"openclaw: no channel dispatch API (need plugin-sdk/reply-runtime.dispatchInboundMessage or legacy dispatchReplyFromConfig on plugin-sdk barrel)"
|
|
4697
4781
|
);
|
|
4698
4782
|
}
|
|
4699
4783
|
} catch (err) {
|
|
4700
|
-
logger.error(`
|
|
4784
|
+
logger.error(`plugin-sdk dispatch unavailable: ${String(err)}`);
|
|
4701
4785
|
throw err;
|
|
4702
4786
|
}
|
|
4703
4787
|
};
|
|
4704
4788
|
try {
|
|
4705
4789
|
await runDispatch();
|
|
4706
4790
|
if (verbose) {
|
|
4707
|
-
logger.info(`
|
|
4791
|
+
logger.info(`dispatch complete session=${route.sessionKey}`);
|
|
4708
4792
|
}
|
|
4709
4793
|
} catch (err) {
|
|
4710
|
-
logger.error(`
|
|
4794
|
+
logger.error(`dispatch error: ${String(err)}`);
|
|
4711
4795
|
try {
|
|
4712
4796
|
const echo = `\u5DF2\u6536\u5230\uFF1A${inboundCtx.Body}`;
|
|
4713
4797
|
if (verbose) {
|
|
4714
|
-
logger.info(`
|
|
4715
|
-
logger.info(`
|
|
4798
|
+
logger.info(`outbound text(to=${msg.from}) via fallback len=${echo.length}`);
|
|
4799
|
+
logger.info(`outbound.content ${echo}`);
|
|
4716
4800
|
} else {
|
|
4717
|
-
logger.info(`
|
|
4801
|
+
logger.info(`echo fallback to=${msg.from} len=${echo.length}`);
|
|
4718
4802
|
}
|
|
4719
4803
|
await csdnOutbound.sendText({
|
|
4720
4804
|
cfg: config,
|
|
@@ -4722,10 +4806,10 @@ async function handleCsdnMessage(params) {
|
|
|
4722
4806
|
text: echo
|
|
4723
4807
|
});
|
|
4724
4808
|
if (verbose) {
|
|
4725
|
-
logger.info(`
|
|
4809
|
+
logger.info(`outbound done to=${msg.from} (fallback)`);
|
|
4726
4810
|
}
|
|
4727
4811
|
} catch (sendErr) {
|
|
4728
|
-
logger.error(`
|
|
4812
|
+
logger.error(`fallback reply failed: ${String(sendErr)}`);
|
|
4729
4813
|
}
|
|
4730
4814
|
}
|
|
4731
4815
|
}
|
|
@@ -4738,6 +4822,26 @@ function maxMessageId(messages) {
|
|
|
4738
4822
|
}
|
|
4739
4823
|
return max;
|
|
4740
4824
|
}
|
|
4825
|
+
function sleepBetweenPolls(ms, signal) {
|
|
4826
|
+
if (ms <= 0) {
|
|
4827
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
4828
|
+
}
|
|
4829
|
+
return new Promise((resolve) => {
|
|
4830
|
+
if (signal?.aborted) {
|
|
4831
|
+
resolve();
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
const t = setTimeout(resolve, ms);
|
|
4835
|
+
signal?.addEventListener(
|
|
4836
|
+
"abort",
|
|
4837
|
+
() => {
|
|
4838
|
+
clearTimeout(t);
|
|
4839
|
+
resolve();
|
|
4840
|
+
},
|
|
4841
|
+
{ once: true }
|
|
4842
|
+
);
|
|
4843
|
+
});
|
|
4844
|
+
}
|
|
4741
4845
|
async function monitorCsdnProvider(opts) {
|
|
4742
4846
|
const { config, runtime, accountId = "default", abortSignal } = opts;
|
|
4743
4847
|
const baseLogger = runtime && (runtime.logger || runtime.log) || console;
|
|
@@ -4749,18 +4853,19 @@ async function monitorCsdnProvider(opts) {
|
|
|
4749
4853
|
const csdnCfgRaw = getCsdnChannelObjectFromRoot(config);
|
|
4750
4854
|
const parsed = CsdnConfigSchema.safeParse(csdnCfgRaw);
|
|
4751
4855
|
if (!parsed.success) {
|
|
4752
|
-
logger.error(`
|
|
4856
|
+
logger.error(`invalid config for account ${accountId}: ${parsed.error}`);
|
|
4753
4857
|
return;
|
|
4754
4858
|
}
|
|
4755
4859
|
const csdnCfg = parsed.data;
|
|
4756
4860
|
if (!csdnCfg.token) {
|
|
4757
|
-
logger.warn(`
|
|
4861
|
+
logger.warn(`token missing; skip start for account ${accountId}`);
|
|
4758
4862
|
return;
|
|
4759
4863
|
}
|
|
4760
4864
|
const client = new CsdnClient(csdnCfg);
|
|
4865
|
+
const pollIntervalMs = clampPollIntervalMs(csdnCfg.pollIntervalMs);
|
|
4761
4866
|
const v = csdnCfg.verbose;
|
|
4762
4867
|
logger.info(
|
|
4763
|
-
`
|
|
4868
|
+
`starting long-polling account=${accountId} apiUrl=${csdnCfg.apiUrl} timeout=${FIXED_LONG_POLLING_TIMEOUT_SEC}s take=${FIXED_TAKE} pollIntervalMs=${pollIntervalMs}`
|
|
4764
4869
|
);
|
|
4765
4870
|
let backoffMs = FIXED_RETRY_INTERVAL_MS;
|
|
4766
4871
|
const backoffCap = Math.max(FIXED_RETRY_INTERVAL_MS, FIXED_RETRY_BACKOFF_MAX_MS);
|
|
@@ -4770,9 +4875,6 @@ async function monitorCsdnProvider(opts) {
|
|
|
4770
4875
|
try {
|
|
4771
4876
|
const t0 = Date.now();
|
|
4772
4877
|
if (v) {
|
|
4773
|
-
logger.info(
|
|
4774
|
-
`[csdn] poll begin account=${accountId} timeout=${FIXED_LONG_POLLING_TIMEOUT_SEC}s take=${FIXED_TAKE}${afterMessageId ? ` afterMessageId=${afterMessageId}` : ""}`
|
|
4775
|
-
);
|
|
4776
4878
|
}
|
|
4777
4879
|
const messages = await client.getUnreadMessages(FIXED_LONG_POLLING_TIMEOUT_SEC, {
|
|
4778
4880
|
afterMessageId,
|
|
@@ -4780,12 +4882,11 @@ async function monitorCsdnProvider(opts) {
|
|
|
4780
4882
|
});
|
|
4781
4883
|
backoffMs = FIXED_RETRY_INTERVAL_MS;
|
|
4782
4884
|
const dur = Date.now() - t0;
|
|
4783
|
-
logger.info(`[csdn] poll end account=${accountId} duration=${dur}ms count=${messages.length}`);
|
|
4784
4885
|
for (const msg of messages) {
|
|
4785
|
-
logger.info(`
|
|
4886
|
+
logger.info(`inbound id=${msg.id} from=${msg.from} type=${msg.type} size=${msg.content?.length ?? 0}`);
|
|
4786
4887
|
if (v) {
|
|
4787
4888
|
try {
|
|
4788
|
-
logger.info(`
|
|
4889
|
+
logger.info(`inbound.full ${JSON.stringify(msg)}`);
|
|
4789
4890
|
} catch {
|
|
4790
4891
|
}
|
|
4791
4892
|
}
|
|
@@ -4799,17 +4900,17 @@ async function monitorCsdnProvider(opts) {
|
|
|
4799
4900
|
if (messages.length > 0) {
|
|
4800
4901
|
afterMessageId = maxMessageId(messages);
|
|
4801
4902
|
}
|
|
4802
|
-
await
|
|
4903
|
+
await sleepBetweenPolls(pollIntervalMs, abortSignal);
|
|
4803
4904
|
} catch (err) {
|
|
4804
|
-
logger.error(`
|
|
4905
|
+
logger.error(`polling error: ${err}`);
|
|
4805
4906
|
logger.info(
|
|
4806
|
-
`
|
|
4907
|
+
`waiting ${backoffMs}ms before retry (exponential backoff, cap=${backoffCap}ms)...`
|
|
4807
4908
|
);
|
|
4808
4909
|
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
4809
4910
|
backoffMs = Math.min(backoffCap, Math.max(FIXED_RETRY_INTERVAL_MS, backoffMs * 2));
|
|
4810
4911
|
}
|
|
4811
4912
|
}
|
|
4812
|
-
logger.info(`
|
|
4913
|
+
logger.info(`polling stopped for account ${accountId}`);
|
|
4813
4914
|
};
|
|
4814
4915
|
return pollLoop();
|
|
4815
4916
|
}
|
|
@@ -4872,7 +4973,8 @@ var csdnPlugin = {
|
|
|
4872
4973
|
token: { type: "string" },
|
|
4873
4974
|
apiUrl: { type: "string" },
|
|
4874
4975
|
fallbackAgentId: { type: "string" },
|
|
4875
|
-
verbose: { type: "boolean" }
|
|
4976
|
+
verbose: { type: "boolean" },
|
|
4977
|
+
pollIntervalMs: { type: "number", minimum: 0, maximum: 6e5 }
|
|
4876
4978
|
}
|
|
4877
4979
|
}
|
|
4878
4980
|
},
|