@searchfe/openclaw-baiduapp 0.1.11 → 0.1.12-beta.2

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/cli.js ADDED
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {execSync, spawnSync} from 'node:child_process';
4
+ import {createRequire} from 'node:module';
5
+ import readline from 'node:readline';
6
+ import {Writable} from 'node:stream';
7
+
8
+ import {printQRCode} from './src/cli-qrcode.mjs';
9
+
10
+ const _require = createRequire(import.meta.url);
11
+ const CLI_VERSION = _require('./package.json').version;
12
+
13
+ const PLUGIN_SPEC = '@searchfe/openclaw-baiduapp';
14
+ const CHANNEL_ID = 'openclaw-baiduapp';
15
+ const QR_URL = 'https://www.baidu.com';
16
+
17
+ function parseArgs(argv) {
18
+ const args = argv.slice(2);
19
+ let command;
20
+ let ak;
21
+ let sk;
22
+ let agent;
23
+ let yes = false;
24
+
25
+ for (let index = 0; index < args.length; index++) {
26
+ const token = args[index];
27
+
28
+ if (token === '--help' || token === '-h') {
29
+ if (!command) {
30
+ command = token;
31
+ }
32
+ continue;
33
+ }
34
+
35
+ if (token === '--yes' || token === '-y') {
36
+ yes = true;
37
+ continue;
38
+ }
39
+
40
+ if (token === '--ak') {
41
+ ak = args[index + 1];
42
+ index += 1;
43
+ continue;
44
+ }
45
+
46
+ if (token.startsWith('--ak=')) {
47
+ ak = token.slice('--ak='.length);
48
+ continue;
49
+ }
50
+
51
+ if (token === '--sk') {
52
+ sk = args[index + 1];
53
+ index += 1;
54
+ continue;
55
+ }
56
+
57
+ if (token.startsWith('--sk=')) {
58
+ sk = token.slice('--sk='.length);
59
+ continue;
60
+ }
61
+
62
+ if (token === '--agent') {
63
+ agent = args[index + 1];
64
+ index += 1;
65
+ continue;
66
+ }
67
+
68
+ if (token.startsWith('--agent=')) {
69
+ agent = token.slice('--agent='.length);
70
+ continue;
71
+ }
72
+
73
+ if (!token.startsWith('--') && !command) {
74
+ command = token;
75
+ }
76
+ }
77
+
78
+ return {command, ak, sk, agent, yes};
79
+ }
80
+
81
+ function prompt(question, {hidden = false} = {}) {
82
+ if (!process.stdin.isTTY) {
83
+ throw new Error('非交互环境:请通过 --ak/--sk 参数传入凭证');
84
+ }
85
+
86
+ const askOnce = () => new Promise(resolve => {
87
+ if (hidden) {
88
+ process.stdout.write(question);
89
+ const rl = readline.createInterface({
90
+ input: process.stdin,
91
+ output: new Writable({write(_chunk, _enc, cb) { cb(); }}),
92
+ terminal: true,
93
+ });
94
+ rl.on('SIGINT', () => { rl.close(); process.stdout.write('\n'); process.exit(1); });
95
+ rl.question('', answer => {
96
+ rl.close();
97
+ process.stdout.write('\n');
98
+ resolve(String(answer || '').trim());
99
+ });
100
+ return;
101
+ }
102
+
103
+ const rl = readline.createInterface({
104
+ input: process.stdin,
105
+ output: process.stdout,
106
+ terminal: true,
107
+ });
108
+ rl.on('SIGINT', () => { rl.close(); process.exit(1); });
109
+ rl.question(question, answer => {
110
+ rl.close();
111
+ resolve(String(answer || '').trim());
112
+ });
113
+ });
114
+
115
+ return (async () => {
116
+ for (let attempt = 0; attempt < 3; attempt++) {
117
+ const answer = await askOnce();
118
+ if (answer) {
119
+ return answer;
120
+ }
121
+
122
+ if (attempt < 2) {
123
+ error('输入不能为空,请重试');
124
+ }
125
+ }
126
+
127
+ throw new Error('输入不能为空,已超过最大重试次数');
128
+ })();
129
+ }
130
+
131
+ function promptOptional(question) {
132
+ if (!process.stdin.isTTY) {
133
+ return Promise.resolve('');
134
+ }
135
+ return new Promise(resolve => {
136
+ const rl = readline.createInterface({
137
+ input: process.stdin,
138
+ output: process.stdout,
139
+ terminal: true,
140
+ });
141
+ rl.on('SIGINT', () => {
142
+ rl.close();
143
+ process.exit(1);
144
+ });
145
+ rl.question(question, answer => {
146
+ rl.close();
147
+ resolve(String(answer || '').trim());
148
+ });
149
+ });
150
+ }
151
+
152
+ async function checkAndCreateAgent(agentId, {yes = false} = {}) {
153
+ let agentsList;
154
+ try {
155
+ const agentsJson = run('openclaw', ['agents', 'list', '--json']);
156
+ agentsList = JSON.parse(agentsJson);
157
+ } catch {
158
+ // 解析失败时假设 agent 不存在,继续后续流程
159
+ agentsList = [];
160
+ }
161
+
162
+ const exists = Array.isArray(agentsList) && agentsList.some(a => a.id === agentId);
163
+ if (exists) {
164
+ return;
165
+ }
166
+
167
+ log(`Agent "${agentId}" 不存在`);
168
+ console.log(` 你可以手动执行以下命令新建:`);
169
+ console.log(` openclaw agents add ${agentId}`);
170
+ console.log();
171
+
172
+ let confirm;
173
+ if (yes) {
174
+ confirm = 'y';
175
+ } else {
176
+ try {
177
+ confirm = await prompt(`是否立即自动新建 agent "${agentId}"?[y/N]: `);
178
+ } catch {
179
+ confirm = '';
180
+ }
181
+ }
182
+
183
+ if (confirm.toLowerCase() !== 'y') {
184
+ error('已取消,请手动新建 agent 后重试');
185
+ process.exit(1);
186
+ }
187
+
188
+ log(`正在新建 agent "${agentId}"...`);
189
+ try {
190
+ run('openclaw', ['agents', 'add', agentId], {silent: false});
191
+ } catch {
192
+ error('新建 agent 失败,请手动执行:');
193
+ console.log(` openclaw agents add ${agentId}`);
194
+ process.exit(1);
195
+ }
196
+ }
197
+
198
+ function run(cmd, args = [], {silent = true} = {}) {
199
+ const stdio = silent ? ['pipe', 'pipe', 'pipe'] : 'inherit';
200
+ const result = spawnSync(cmd, args, {encoding: 'utf-8', stdio});
201
+
202
+ if (result.error) {
203
+ const err = new Error(`Command failed: ${cmd}`);
204
+ err.stderr = silent ? String(result.stderr || result.error.message || '') : '';
205
+ throw err;
206
+ }
207
+
208
+ if (result.status !== 0) {
209
+ const err = new Error(`Command failed with exit code ${result.status}: ${cmd}`);
210
+ err.stderr = silent ? String(result.stderr || '') : '';
211
+ throw err;
212
+ }
213
+
214
+ return silent ? String(result.stdout || '').trim() : '';
215
+ }
216
+
217
+ function which(bin) {
218
+ const cmd = process.platform === 'win32' ? `where ${bin}` : `which ${bin}`;
219
+
220
+ try {
221
+ return execSync(cmd, {
222
+ encoding: 'utf-8',
223
+ shell: true,
224
+ stdio: ['pipe', 'pipe', 'pipe'],
225
+ }).trim();
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+
231
+ function log(msg) {
232
+ console.log(`\x1b[36m[openclaw-baiduapp]\x1b[0m ${msg}`);
233
+ }
234
+
235
+ function error(msg) {
236
+ console.error(`\x1b[31m[openclaw-baiduapp]\x1b[0m ${msg}`);
237
+ }
238
+
239
+ async function install() {
240
+ let {ak, sk, agent, yes} = parseArgs(process.argv);
241
+
242
+ if ((!ak || !sk) && !process.stdin.isTTY) {
243
+ error('非交互环境:请通过 --ak/--sk 参数传入凭证');
244
+ process.exit(1);
245
+ }
246
+
247
+ if (agent !== undefined && !agent.trim()) {
248
+ error('--agent 参数不能为空');
249
+ process.exit(1);
250
+ }
251
+ let agentId = agent?.trim();
252
+
253
+ if (!which('openclaw')) {
254
+ error('未找到 openclaw,请先安装:');
255
+ console.log(' npm install -g openclaw');
256
+ console.log(' 详见 https://docs.openclaw.ai/install');
257
+ process.exit(1);
258
+ }
259
+ log('已找到本地安装的 openclaw');
260
+
261
+ log('正在检查插件版本...');
262
+ let installedVersion = null;
263
+ try {
264
+ const infoJson = run('openclaw', ['config', 'get', `plugins.installs.${CHANNEL_ID}`, '--json']);
265
+ const info = JSON.parse(infoJson);
266
+ installedVersion = info?.version ?? null;
267
+ } catch {
268
+ // 未安装或无法读取,后续走安装流程
269
+ }
270
+
271
+ if (installedVersion === null) {
272
+ log(`插件未安装,正在安装 ${PLUGIN_SPEC}@${CLI_VERSION}...`);
273
+ try {
274
+ run('openclaw', ['plugins', 'install', `${PLUGIN_SPEC}@${CLI_VERSION}`], {silent: false});
275
+ } catch (installErr) {
276
+ error('插件安装失败,请手动执行:');
277
+ console.log(` openclaw plugins install "${PLUGIN_SPEC}@${CLI_VERSION}"`);
278
+ process.exit(1);
279
+ }
280
+ } else if (installedVersion !== CLI_VERSION) {
281
+ log(`插件版本不一致(已安装: ${installedVersion},当前 CLI: ${CLI_VERSION}),正在更新...`);
282
+ try {
283
+ run('openclaw', ['config', 'set', `plugins.installs.${CHANNEL_ID}.spec`, `${PLUGIN_SPEC}@${CLI_VERSION}`]);
284
+ run('openclaw', ['config', 'unset', `plugins.installs.${CHANNEL_ID}.integrity`]);
285
+ run('openclaw', ['config', 'unset', `plugins.installs.${CHANNEL_ID}.shasum`]);
286
+ run('openclaw', ['plugins', 'update', CHANNEL_ID], {silent: false});
287
+ run('openclaw', ['gateway', 'restart'], {silent: false});
288
+ } catch (updateErr) {
289
+ error('插件更新失败,请手动执行:');
290
+ console.log(` openclaw config set plugins.installs.${CHANNEL_ID}.spec ${PLUGIN_SPEC}@${CLI_VERSION}`);
291
+ console.log(` openclaw config unset plugins.installs.${CHANNEL_ID}.integrity`);
292
+ console.log(` openclaw config unset plugins.installs.${CHANNEL_ID}.shasum`);
293
+ console.log(` openclaw plugins update ${CHANNEL_ID}`);
294
+ console.log(` openclaw gateway restart`);
295
+ process.exit(1);
296
+ }
297
+ } else {
298
+ log(`插件版本已是最新(${CLI_VERSION}),跳过更新`);
299
+ }
300
+
301
+ try {
302
+ if (!ak) {
303
+ ak = await prompt('请输入 App Key (appKey): ');
304
+ }
305
+ if (!sk) {
306
+ sk = await prompt('请输入 App Secret (appSecret): ', {hidden: true});
307
+ }
308
+ } catch (promptErr) {
309
+ error(promptErr instanceof Error ? promptErr.message : String(promptErr));
310
+ process.exit(1);
311
+ }
312
+
313
+ if (!ak.trim() || !sk.trim()) {
314
+ error('appKey 和 appSecret 不能为空');
315
+ process.exit(1);
316
+ }
317
+
318
+ // 插件已确认安装,再询问/校验 agent
319
+ if (!agentId && process.stdin.isTTY) {
320
+ agentId = await promptOptional('请输入 Agent ID(可选,用于多 agent 配置,直接回车跳过):') || undefined;
321
+ }
322
+
323
+ if (agentId) {
324
+ await checkAndCreateAgent(agentId, {yes});
325
+ }
326
+
327
+ log('正在写入配置...');
328
+ const akPath = agentId
329
+ ? `channels.${CHANNEL_ID}.accounts.${agentId}.appKey`
330
+ : `channels.${CHANNEL_ID}.appKey`;
331
+ const skPath = agentId
332
+ ? `channels.${CHANNEL_ID}.accounts.${agentId}.appSecret`
333
+ : `channels.${CHANNEL_ID}.appSecret`;
334
+ try {
335
+ run('openclaw', ['config', 'set', akPath, ak.trim()]);
336
+ run('openclaw', ['config', 'set', skPath, sk.trim()]);
337
+ } catch {
338
+ error('配置写入失败,请手动执行:');
339
+ console.log(` openclaw config set ${akPath} <your-app-key>`);
340
+ console.log(` openclaw config set ${skPath} <your-app-secret>`);
341
+ process.exit(1);
342
+ }
343
+ if (agentId) {
344
+ log(`配置写入成功(agent: ${agentId})`);
345
+ } else {
346
+ log('配置写入成功');
347
+ }
348
+
349
+ log('正在重启 OpenClaw Gateway...');
350
+ try {
351
+ run('openclaw', ['gateway', 'restart'], {silent: false});
352
+ } catch {
353
+ error('重启失败,可手动执行:');
354
+ console.log(' openclaw gateway restart');
355
+ }
356
+
357
+ console.log();
358
+ log('安装完成!扫描下方二维码访问:');
359
+ console.log();
360
+ printQRCode(QR_URL);
361
+ console.log();
362
+ console.log(` ${QR_URL}`);
363
+ console.log();
364
+ }
365
+
366
+ function help() {
367
+ console.log(`用法: npx @searchfe/openclaw-baiduapp <命令> [选项]
368
+
369
+ 命令:
370
+ install 安装百度App插件并配置 AK/SK
371
+
372
+ 选项:
373
+ --ak <appKey> App Key(必填,缺省时交互输入)
374
+ --sk <appSecret> App Secret(必填,缺省时交互输入,不回显)
375
+ --agent <agentId> Agent ID(可选,指定后写入 accounts.<agentId> 以支持多 agent 配置)
376
+ 交互模式下也会提示输入,直接回车跳过(写入根路径)
377
+ 指定后自动校验 agent 是否存在,不存在时提示确认新建
378
+ --yes, -y 自动确认所有交互提示(如 agent 不存在时自动新建,适合 CI/脚本环境)
379
+ --help, -h 显示帮助信息
380
+
381
+ 示例:
382
+ npx @searchfe/openclaw-baiduapp install --ak your-key --sk your-secret
383
+ npx @searchfe/openclaw-baiduapp install --agent my-agent --ak your-key --sk your-secret
384
+ npx @searchfe/openclaw-baiduapp install --agent my-agent --ak your-key --sk your-secret --yes
385
+ npx @searchfe/openclaw-baiduapp install`);
386
+ }
387
+
388
+ const {command} = parseArgs(process.argv);
389
+
390
+ switch (command) {
391
+ case 'install':
392
+ await install();
393
+ break;
394
+ case 'help':
395
+ case '--help':
396
+ case '-h':
397
+ help();
398
+ break;
399
+ default:
400
+ if (command) {
401
+ error(`未知命令: ${command}`);
402
+ }
403
+ help();
404
+ process.exit(command ? 1 : 0);
405
+ }
package/dist/index.js CHANGED
@@ -28254,7 +28254,18 @@ function parseV2MsgToInbound(inner, outer) {
28254
28254
  (item) => item && typeof item === "object" && item.type === "file"
28255
28255
  );
28256
28256
  const textContent = textItem?.data?.text?.content ?? "";
28257
- const files = fileItems.map((f) => ({ url: f.data.fileId, fileType: "file" }));
28257
+ const files = fileItems.map((f) => {
28258
+ const fileData = f.data?.file;
28259
+ if (!fileData) {
28260
+ return null;
28261
+ }
28262
+ const url = typeof fileData.url === "string" && fileData.url.trim() ? fileData.url.trim() : fileData.fileId;
28263
+ if (!url) {
28264
+ return null;
28265
+ }
28266
+ const fileType = typeof fileData.fileType === "string" && fileData.fileType.trim() ? fileData.fileType.trim() : "file";
28267
+ return { url, fileType };
28268
+ }).filter((f) => f !== null);
28258
28269
  return {
28259
28270
  msgtype: "text",
28260
28271
  msgid: typeof outer.msgid === "string" ? outer.msgid : void 0,
@@ -28495,6 +28506,7 @@ async function handleBaiduAppWebhookRequest(req, res) {
28495
28506
  // src/poller.ts
28496
28507
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
28497
28508
  var DEFAULT_POLL_REQUEST_TIMEOUT_MS = 1e4;
28509
+ var pollerLogger = createLogger("openclaw-baiduapp:poller");
28498
28510
  var accountPollers = /* @__PURE__ */ new Map();
28499
28511
  function buildPollingTextInboundMessage(content) {
28500
28512
  return {
@@ -28509,9 +28521,17 @@ function parseTextAndFilesFromList(list) {
28509
28521
  const fileItems = list.filter((entry) => entry.type === "file");
28510
28522
  const content = textItem?.type === "text" ? textItem.data.text.content : void 0;
28511
28523
  const files = fileItems.map((file) => {
28512
- const fileId = file.data.file.fileId;
28513
- return typeof fileId === "string" && fileId.trim() ? { url: fileId, fileType: "file" } : null;
28514
- }).filter((file) => file != null);
28524
+ if (file.type !== "file") {
28525
+ return null;
28526
+ }
28527
+ const fileData = file.data.file;
28528
+ const url = typeof fileData.url === "string" && fileData.url.trim() ? fileData.url.trim() : fileData.fileId;
28529
+ if (!url || !url.trim()) {
28530
+ return null;
28531
+ }
28532
+ const fileType = typeof fileData.fileType === "string" && fileData.fileType.trim() ? fileData.fileType.trim() : "file";
28533
+ return { url, fileType };
28534
+ }).filter((file) => file !== null);
28515
28535
  return { content, files };
28516
28536
  }
28517
28537
  async function dispatchPendingPollingMessages(data, target) {
@@ -28522,8 +28542,10 @@ async function dispatchPendingPollingMessages(data, target) {
28522
28542
  const list = Array.isArray(item.list) ? item.list : [];
28523
28543
  const { content, files } = parseTextAndFilesFromList(list);
28524
28544
  if (typeof content !== "string" && files.length === 0) {
28545
+ pollerLogger.debug(`dispatch skip: msgId=${item.msgId ?? "?"} agentId=${item.agentId ?? "none"} reason=no-content`);
28525
28546
  continue;
28526
28547
  }
28548
+ pollerLogger.info(`dispatch msg: msgId=${item.msgId ?? "?"} agentId=${item.agentId ?? "none"} files=${files.length} hasText=${typeof content === "string"}`);
28527
28549
  await processBaiduAppInboundMessage({
28528
28550
  target,
28529
28551
  msg: {
@@ -28608,6 +28630,7 @@ async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions)
28608
28630
  version: PLUGIN_VERSION
28609
28631
  };
28610
28632
  const body = JSON.stringify(payload);
28633
+ logger3.debug(`poll request: accountId=${account.accountId} url=${endpoint} sessionId=${payload.sessionId}`);
28611
28634
  const requestSignal = createAbortSignalController({
28612
28635
  signal: requestOptions?.signal,
28613
28636
  timeoutMs: requestOptions?.timeoutMs
@@ -28626,6 +28649,7 @@ async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions)
28626
28649
  } catch (error) {
28627
28650
  requestSignal.cleanup();
28628
28651
  if (requestSignal.didTimeout()) {
28652
+ logger3.warn(`poll timeout: accountId=${account.accountId} timeoutMs=${requestOptions?.timeoutMs}`);
28629
28653
  return buildPollResult({
28630
28654
  ok: false,
28631
28655
  error: {
@@ -28635,6 +28659,7 @@ async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions)
28635
28659
  });
28636
28660
  }
28637
28661
  if (isAbortError2(error)) {
28662
+ logger3.debug(`poll aborted: accountId=${account.accountId}`);
28638
28663
  return buildPollResult({
28639
28664
  ok: false,
28640
28665
  error: {
@@ -28643,17 +28668,20 @@ async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions)
28643
28668
  }
28644
28669
  });
28645
28670
  }
28671
+ const errMsg = error instanceof Error ? error.message : String(error);
28672
+ logger3.error(`poll request failed: accountId=${account.accountId} error=${errMsg}`);
28646
28673
  return buildPollResult({
28647
28674
  ok: false,
28648
28675
  error: {
28649
28676
  kind: "request-failed",
28650
- message: error instanceof Error ? error.message : String(error)
28677
+ message: errMsg
28651
28678
  }
28652
28679
  });
28653
28680
  }
28654
28681
  const responseText = await response.text();
28655
28682
  requestSignal.cleanup();
28656
28683
  if (!responseText) {
28684
+ logger3.warn(`poll empty response: accountId=${account.accountId} status=${response.status}`);
28657
28685
  return buildPollResult({
28658
28686
  ok: false,
28659
28687
  error: {
@@ -28666,6 +28694,7 @@ async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions)
28666
28694
  try {
28667
28695
  parsed = JSON.parse(responseText);
28668
28696
  } catch {
28697
+ logger3.warn(`poll invalid json: accountId=${account.accountId} status=${response.status}`);
28669
28698
  return buildPollResult({
28670
28699
  ok: false,
28671
28700
  error: {
@@ -28674,10 +28703,12 @@ async function pollBaiduAppChatlistOnce(account, loggerOptions, requestOptions)
28674
28703
  }
28675
28704
  });
28676
28705
  }
28706
+ const msgList = Array.isArray(parsed.data?.msgList) ? parsed.data.msgList : [];
28677
28707
  if (parsed.code !== 0) {
28678
- logger3.debug(`Baidu poll returned non-zero code=${String(parsed.code)}, message=${parsed.message}`);
28708
+ logger3.debug(`poll non-zero code: accountId=${account.accountId} code=${String(parsed.code)} message=${parsed.message}`);
28709
+ } else {
28710
+ logger3.debug(`poll ok: accountId=${account.accountId} status=${response.status} msgCount=${msgList.length}`);
28679
28711
  }
28680
- const msgList = Array.isArray(parsed.data?.msgList) ? parsed.data.msgList : [];
28681
28712
  return buildPollResult({
28682
28713
  ok: true,
28683
28714
  data: msgList
@@ -28687,6 +28718,7 @@ function scheduleNextPoll(account, state) {
28687
28718
  if (state.stopped) {
28688
28719
  return;
28689
28720
  }
28721
+ pollerLogger.debug(`schedule next poll: accountId=${account.accountId} intervalMs=${state.intervalMs}`);
28690
28722
  state.timer = setTimeout(() => {
28691
28723
  void runPollCycle(account, state);
28692
28724
  }, state.intervalMs);
@@ -28695,6 +28727,9 @@ async function runPollCycle(account, state) {
28695
28727
  if (state.stopped || state.pending) {
28696
28728
  return;
28697
28729
  }
28730
+ state.cycleCount += 1;
28731
+ const cycle = state.cycleCount;
28732
+ pollerLogger.debug(`poll cycle #${cycle} start: accountId=${account.accountId}`);
28698
28733
  const controller = new AbortController();
28699
28734
  state.activeController = controller;
28700
28735
  const pending = (async () => {
@@ -28704,9 +28739,18 @@ async function runPollCycle(account, state) {
28704
28739
  fetchImpl: state.fetchImpl,
28705
28740
  timeoutMs: state.requestTimeoutMs
28706
28741
  });
28742
+ if (!result2.ok && result2.error) {
28743
+ if (result2.error.kind !== "aborted") {
28744
+ pollerLogger.warn(`poll cycle #${cycle} error: accountId=${account.accountId} kind=${result2.error.kind} msg=${result2.error.message}`);
28745
+ }
28746
+ } else if (result2.data.length > 0) {
28747
+ pollerLogger.info(`poll cycle #${cycle} msgs: accountId=${account.accountId} count=${result2.data.length}`);
28748
+ }
28707
28749
  await dispatchPendingPollingMessages(result2.data, state.dispatchTarget);
28708
28750
  } catch (error) {
28709
28751
  if (!(state.stopped && isAbortError2(error))) {
28752
+ const errMsg = error instanceof Error ? error.message : String(error);
28753
+ pollerLogger.error(`poll cycle #${cycle} unhandled error: accountId=${account.accountId} error=${errMsg}`);
28710
28754
  state.onError?.(error);
28711
28755
  }
28712
28756
  } finally {
@@ -28724,18 +28768,23 @@ async function runPollCycle(account, state) {
28724
28768
  }
28725
28769
  function startAccountPolling(params) {
28726
28770
  if (accountPollers.has(params.account.accountId)) {
28771
+ pollerLogger.debug(`polling already running: accountId=${params.account.accountId}, skip`);
28727
28772
  return;
28728
28773
  }
28774
+ const intervalMs = params.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
28775
+ const requestTimeoutMs = params.requestTimeoutMs ?? DEFAULT_POLL_REQUEST_TIMEOUT_MS;
28776
+ pollerLogger.info(`start polling: accountId=${params.account.accountId} intervalMs=${intervalMs} timeoutMs=${requestTimeoutMs} apiBase=${params.account.apiBase}`);
28729
28777
  const state = {
28730
28778
  stopped: false,
28731
28779
  timer: null,
28732
28780
  activeController: null,
28733
28781
  pending: null,
28734
- intervalMs: params.intervalMs ?? DEFAULT_POLL_INTERVAL_MS,
28735
- requestTimeoutMs: params.requestTimeoutMs ?? DEFAULT_POLL_REQUEST_TIMEOUT_MS,
28782
+ intervalMs,
28783
+ requestTimeoutMs,
28736
28784
  fetchImpl: params.fetchImpl,
28737
28785
  onError: params.onError,
28738
- dispatchTarget: params.dispatchTarget
28786
+ dispatchTarget: params.dispatchTarget,
28787
+ cycleCount: 0
28739
28788
  };
28740
28789
  accountPollers.set(params.account.accountId, state);
28741
28790
  void runPollCycle(params.account, state);
@@ -28745,6 +28794,7 @@ function stopAccountPolling(accountId) {
28745
28794
  if (!state) {
28746
28795
  return;
28747
28796
  }
28797
+ pollerLogger.info(`stop polling: accountId=${accountId} totalCycles=${state.cycleCount}`);
28748
28798
  state.stopped = true;
28749
28799
  if (state.timer) {
28750
28800
  clearTimeout(state.timer);