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.
Files changed (3) hide show
  1. package/dist/index.cjs +174 -72
  2. package/dist/index.js +174 -72
  3. 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
- `[csdn] pull GET ${PATH_UNREAD} base=${this.baseUrl} timeout=${sec}s take=${take}${cursor ? ` afterMessageId=${cursor}` : ""}`
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
- `[csdn] pull response ok code=0 messages=${fromResult.length} httpStatus=${response.status}`
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
- if (fail.code === "401") {
4262
- console.warn(`[csdn] getUnreadMessages: ${fail.message || "Token\u65E0\u6548\u6216\u5DF2\u8FC7\u671F"} (code=${fail.code})`);
4263
- } else {
4264
- console.warn(`[csdn] getUnreadMessages: ${fail.message} (code=${fail.code})`);
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
- `[csdn] using legacy unread response format; prefer ClawBot Result with code "0" messages=${legacy.length}`
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
- console.warn("[csdn] unexpected response format:", JSON.stringify(body).substring(0, 200));
4297
+ pullErrorLog(this.baseUrl, `unexpected response format httpStatus=${response.status} body=${JSON.stringify(body).substring(0, 200)}`);
4276
4298
  }
4277
- if (response.status === 408 || response.status === 504) {
4278
- return [];
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
- console.error(`[csdn] getUnreadMessages failed: ${msg}`);
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
- `[csdn] send POST ${PATH_SEND} to=${to} contentLength=${content.length}${opts?.type ? ` type=${opts.type}` : ""}`
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
- `[csdn] send response ok code=0 id=${ok.id} httpStatus=${response.status}${ok.timestamp != null ? ` timestamp=${ok.timestamp}` : ""}`
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
- throw new Error(`[csdn] sendMessage failed: ${hint}`);
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("[csdn] sendMessage failed:")) {
4360
+ if (error instanceof Error && error.message.startsWith(`${CSDN_LOG_TAG} sendMessage failed:`)) {
4331
4361
  throw error;
4332
4362
  }
4333
- console.error("[csdn] sendMessage failed:", 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
- "[csdn] loadOpenclawPluginSdk: no package.json found walking up from process.cwd(); cannot createRequire for openclaw fallback"
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
- const require2 = (0, import_node_module.createRequire)(findRequireAnchorPackageJson());
4483
- const searchRoots = [
4484
- process.cwd(),
4485
- (0, import_node_path.join)(process.cwd(), ".."),
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("[csdn] invalid config for outbound");
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(`[csdn] outbound sent messageId=${result.id} to=${to} textLength=${text.length}`);
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
- "[csdn] host runtime.channel.routing.resolveAgentRoute unavailable \u2192 embedded session key (normal for some gateways; once per process)"
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
- "[csdn] host runtime.channel.reply.dispatchReplyFromConfig unavailable \u2192 openclaw/plugin-sdk (normal for some gateways; once per process)"
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(`[csdn] route.fallback sessionKey=${route.sessionKey} accountId=${route.accountId ?? accountId}`);
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(`[csdn] inbound.content ${inboundCtx.Body}`);
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(`[csdn] outbound text to=${msg.from} len=${text.length}`);
4667
- logger.info(`[csdn] outbound.content ${text}`);
4727
+ logger.info(`outbound text to=${msg.from} len=${text.length}`);
4728
+ logger.info(`outbound.content ${text}`);
4668
4729
  } else {
4669
- logger.info(`[csdn] reply to=${msg.from} len=${text.length}`);
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(`[csdn] outbound done to=${msg.from}`);
4738
+ logger.info(`outbound done to=${msg.from}`);
4678
4739
  }
4679
4740
  } catch (err) {
4680
- logger.error(`[csdn] failed to send reply: ${String(err)}`);
4741
+ logger.error(`failed to send reply: ${String(err)}`);
4681
4742
  }
4682
4743
  },
4683
4744
  onError: (err, info) => {
4684
- logger.error(`[csdn] reply dispatch kind=${info.kind} error: ${String(err)}`);
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(`[csdn] dispatching via runtime.channel.reply session=${route.sessionKey}`);
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(`[csdn] dispatching via plugin-sdk session=${route.sessionKey}`);
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/plugin-sdk: neither dispatchReplyFromConfigWithSettledDispatcher nor dispatchReplyFromConfig is exported"
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(`[csdn] plugin-sdk dispatch unavailable: ${String(err)}`);
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(`[csdn] dispatch complete session=${route.sessionKey}`);
4822
+ logger.info(`dispatch complete session=${route.sessionKey}`);
4739
4823
  }
4740
4824
  } catch (err) {
4741
- logger.error(`[csdn] dispatch error: ${String(err)}`);
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(`[csdn] outbound text(to=${msg.from}) via fallback len=${echo.length}`);
4746
- logger.info(`[csdn] outbound.content ${echo}`);
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(`[csdn] echo fallback to=${msg.from} len=${echo.length}`);
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(`[csdn] outbound done to=${msg.from} (fallback)`);
4840
+ logger.info(`outbound done to=${msg.from} (fallback)`);
4757
4841
  }
4758
4842
  } catch (sendErr) {
4759
- logger.error(`[csdn] fallback reply failed: ${String(sendErr)}`);
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(`[csdn] invalid config for account ${accountId}: ${parsed.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(`[csdn] token missing; skip start for account ${accountId}`);
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
- `[csdn] starting long-polling account=${accountId} apiUrl=${csdnCfg.apiUrl} timeout=${FIXED_LONG_POLLING_TIMEOUT_SEC}s take=${FIXED_TAKE}`
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(`[csdn] inbound id=${msg.id} from=${msg.from} type=${msg.type} size=${msg.content?.length ?? 0}`);
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(`[csdn] inbound.full ${JSON.stringify(msg)}`);
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 new Promise((resolve) => setImmediate(resolve));
4934
+ await sleepBetweenPolls(pollIntervalMs, abortSignal);
4834
4935
  } catch (err) {
4835
- logger.error(`[csdn] polling error: ${err}`);
4936
+ logger.error(`polling error: ${err}`);
4836
4937
  logger.info(
4837
- `[csdn] waiting ${backoffMs}ms before retry (exponential backoff, cap=${backoffCap}ms)...`
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(`[csdn] polling stopped for account ${accountId}`);
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
- `[csdn] pull GET ${PATH_UNREAD} base=${this.baseUrl} timeout=${sec}s take=${take}${cursor ? ` afterMessageId=${cursor}` : ""}`
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
- `[csdn] pull response ok code=0 messages=${fromResult.length} httpStatus=${response.status}`
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
- if (fail.code === "401") {
4231
- console.warn(`[csdn] getUnreadMessages: ${fail.message || "Token\u65E0\u6548\u6216\u5DF2\u8FC7\u671F"} (code=${fail.code})`);
4232
- } else {
4233
- console.warn(`[csdn] getUnreadMessages: ${fail.message} (code=${fail.code})`);
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
- `[csdn] using legacy unread response format; prefer ClawBot Result with code "0" messages=${legacy.length}`
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
- console.warn("[csdn] unexpected response format:", JSON.stringify(body).substring(0, 200));
4266
+ pullErrorLog(this.baseUrl, `unexpected response format httpStatus=${response.status} body=${JSON.stringify(body).substring(0, 200)}`);
4245
4267
  }
4246
- if (response.status === 408 || response.status === 504) {
4247
- return [];
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
- console.error(`[csdn] getUnreadMessages failed: ${msg}`);
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
- `[csdn] send POST ${PATH_SEND} to=${to} contentLength=${content.length}${opts?.type ? ` type=${opts.type}` : ""}`
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
- `[csdn] send response ok code=0 id=${ok.id} httpStatus=${response.status}${ok.timestamp != null ? ` timestamp=${ok.timestamp}` : ""}`
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
- throw new Error(`[csdn] sendMessage failed: ${hint}`);
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("[csdn] sendMessage failed:")) {
4329
+ if (error instanceof Error && error.message.startsWith(`${CSDN_LOG_TAG} sendMessage failed:`)) {
4300
4330
  throw error;
4301
4331
  }
4302
- console.error("[csdn] sendMessage failed:", 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
- "[csdn] loadOpenclawPluginSdk: no package.json found walking up from process.cwd(); cannot createRequire for openclaw fallback"
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
- const require2 = createRequire(findRequireAnchorPackageJson());
4452
- const searchRoots = [
4453
- process.cwd(),
4454
- join(process.cwd(), ".."),
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("[csdn] invalid config for outbound");
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(`[csdn] outbound sent messageId=${result.id} to=${to} textLength=${text.length}`);
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
- "[csdn] host runtime.channel.routing.resolveAgentRoute unavailable \u2192 embedded session key (normal for some gateways; once per process)"
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
- "[csdn] host runtime.channel.reply.dispatchReplyFromConfig unavailable \u2192 openclaw/plugin-sdk (normal for some gateways; once per process)"
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(`[csdn] route.fallback sessionKey=${route.sessionKey} accountId=${route.accountId ?? accountId}`);
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(`[csdn] inbound.content ${inboundCtx.Body}`);
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(`[csdn] outbound text to=${msg.from} len=${text.length}`);
4636
- logger.info(`[csdn] outbound.content ${text}`);
4696
+ logger.info(`outbound text to=${msg.from} len=${text.length}`);
4697
+ logger.info(`outbound.content ${text}`);
4637
4698
  } else {
4638
- logger.info(`[csdn] reply to=${msg.from} len=${text.length}`);
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(`[csdn] outbound done to=${msg.from}`);
4707
+ logger.info(`outbound done to=${msg.from}`);
4647
4708
  }
4648
4709
  } catch (err) {
4649
- logger.error(`[csdn] failed to send reply: ${String(err)}`);
4710
+ logger.error(`failed to send reply: ${String(err)}`);
4650
4711
  }
4651
4712
  },
4652
4713
  onError: (err, info) => {
4653
- logger.error(`[csdn] reply dispatch kind=${info.kind} error: ${String(err)}`);
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(`[csdn] dispatching via runtime.channel.reply session=${route.sessionKey}`);
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(`[csdn] dispatching via plugin-sdk session=${route.sessionKey}`);
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/plugin-sdk: neither dispatchReplyFromConfigWithSettledDispatcher nor dispatchReplyFromConfig is exported"
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(`[csdn] plugin-sdk dispatch unavailable: ${String(err)}`);
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(`[csdn] dispatch complete session=${route.sessionKey}`);
4791
+ logger.info(`dispatch complete session=${route.sessionKey}`);
4708
4792
  }
4709
4793
  } catch (err) {
4710
- logger.error(`[csdn] dispatch error: ${String(err)}`);
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(`[csdn] outbound text(to=${msg.from}) via fallback len=${echo.length}`);
4715
- logger.info(`[csdn] outbound.content ${echo}`);
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(`[csdn] echo fallback to=${msg.from} len=${echo.length}`);
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(`[csdn] outbound done to=${msg.from} (fallback)`);
4809
+ logger.info(`outbound done to=${msg.from} (fallback)`);
4726
4810
  }
4727
4811
  } catch (sendErr) {
4728
- logger.error(`[csdn] fallback reply failed: ${String(sendErr)}`);
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(`[csdn] invalid config for account ${accountId}: ${parsed.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(`[csdn] token missing; skip start for account ${accountId}`);
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
- `[csdn] starting long-polling account=${accountId} apiUrl=${csdnCfg.apiUrl} timeout=${FIXED_LONG_POLLING_TIMEOUT_SEC}s take=${FIXED_TAKE}`
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(`[csdn] inbound id=${msg.id} from=${msg.from} type=${msg.type} size=${msg.content?.length ?? 0}`);
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(`[csdn] inbound.full ${JSON.stringify(msg)}`);
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 new Promise((resolve) => setImmediate(resolve));
4903
+ await sleepBetweenPolls(pollIntervalMs, abortSignal);
4803
4904
  } catch (err) {
4804
- logger.error(`[csdn] polling error: ${err}`);
4905
+ logger.error(`polling error: ${err}`);
4805
4906
  logger.info(
4806
- `[csdn] waiting ${backoffMs}ms before retry (exponential backoff, cap=${backoffCap}ms)...`
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(`[csdn] polling stopped for account ${accountId}`);
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "csdn-im",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for CSDN IM",
6
6
  "license": "MIT",