@zxh126/pi-qqbot 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -117,7 +117,7 @@ Some extension commands never print into the conversation — they call `ctx.ui.
117
117
 
118
118
  | Command | What happens |
119
119
  |---|---|
120
- | `/usage [range]`, `/cost [range]` | re-dispatched as `/qq usage <range>`: the bridge runs the original `/usage` command, then reads the **fresh** `customType: "usage-report"` entry — first from `sessionManager.getEntries()`, and if that view has not picked up the append yet, straight from the tail of the session file — and renders it as text (requests, tokens, cache ratio, cost, per-model). **Usage unchanged** — keep sending `/usage today`. |
120
+ | `/usage [range]`, `/cost [range]` | re-dispatched as `/qq usage <range>`: the bridge runs the original `/usage` command, then **polls** for the fresh `customType: "usage-report"` entry for up to 2 s (the append lands ~100 ms *after* `sendUserMessage()` resolves, so a single read always misses it), falling back to the tail of the session file, and renders it as text (requests, tokens, cache ratio, cost, per-model). **Usage unchanged** — keep sending `/usage today`. |
121
121
  | `/usage rate <value>`, `/usage rate auto` | really executed (they write `~/.pi/agent/pi-usage-report.json` / refresh the cached rate); the bridge then reads those files back and reports the resulting rate, so an invalid value shows up as "unchanged" |
122
122
  | `/mcp`, `/llama` | answered: they need host-TUI selection dialogs / browser login, so nothing is dispatched |
123
123
 
@@ -115,7 +115,7 @@ TUI/RPC 前端。在 QQ 里的表现就是「已派发,然后没下文」。
115
115
 
116
116
  | 命令 | 现在发生什么 |
117
117
  |---|---|
118
- | `/usage [范围]`、`/cost [范围]` | 改派为 `/qq usage <范围>`:桥接先重跑原 `/usage` 命令,再取**本次新写入**的 `customType: "usage-report"` 条目(先读 `sessionManager.getEntries()` 的增量;发现另一个扩展的 `appendEntry` 还没反映到该视图时,直接读会话文件尾部),渲染成文本回 QQ(请求数/总 token/缓存比/费用/按模型)。**用法不变**,照旧发 `/usage today` |
118
+ | `/usage [范围]`、`/cost [范围]` | 改派为 `/qq usage <范围>`:桥接先重跑原 `/usage` 命令,再**轮询等待**本次新写入的 `customType: "usage-report"` 条目(`appendEntry` 比 `sendUserMessage()` 返回晚约 100 ms,只读一次必然落空),最多等 2 秒,仍未命中才退回读会话文件尾部;然后渲染成文本回 QQ(请求数/总 token/缓存比/费用/按模型)。**用法不变**,照旧发 `/usage today` |
119
119
  | `/mcp`、`/llama` | 直接回执:它们需要主机 TUI 的选择框 / 浏览器登录,不会派发 |
120
120
  | `/usage rate <值>`、`/usage rate auto` | 会真的执行(写 `~/.pi/agent/pi-usage-report.json` / 刷新汇率缓存),桥接再读回文件确认新汇率,无效值会体现在“汇率未变”上;只发 `/usage rate` 与 `/usage help` 则本地直接回执,不执行命令 |
121
121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zxh126/pi-qqbot",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "type": "module",
5
5
  "description": "Bridge the Pi coding agent into QQ private chats, with remote tool-call approval",
6
6
  "license": "MIT",
package/slash.ts CHANGED
@@ -382,6 +382,13 @@ export function pickerExpired(picker: PickerState | null): boolean {
382
382
  /** pi-usage-report 写入 session 的 custom entry 类型(耦合点,约定值) */
383
383
  const USAGE_ENTRY_TYPE = "usage-report";
384
384
 
385
+ /**
386
+ * appendEntry 的写入比 `sendUserMessage` 的 resolve 晚(实测 promise 183ms 返回,
387
+ * entry 在之后 ~100ms 才出现在 getEntries 里),所以必须轮询而不是只读一次。
388
+ */
389
+ const USAGE_WAIT_TIMEOUT_MS = 2000;
390
+ const USAGE_POLL_INTERVAL_MS = 100;
391
+
385
392
  /** pi-usage-report 的配置/汇率缓存(读回来确认:命令真的生效了,而不是口头应下) */
386
393
  const USAGE_SETTINGS_FILE = "pi-usage-report.json";
387
394
  const USAGE_RATE_FILE = "pi-usage-report-rate.json";
@@ -483,7 +490,7 @@ function readUsageEntryFromFile(file: string | undefined, since: number): any |
483
490
  } catch {
484
491
  continue;
485
492
  }
486
- if (entry?.type !== "custom" || entry?.customType !== USAGE_ENTRY_TYPE) continue;
493
+ if (!isUsageEntry(entry)) continue;
487
494
  const when = entryTime(entry?.data?.generatedAt ?? entry?.timestamp);
488
495
  return Number.isFinite(when) && when >= since ? entry : undefined;
489
496
  }
@@ -504,6 +511,28 @@ function localStamp(value: unknown): string {
504
511
  return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
505
512
  }
506
513
 
514
+ const isUsageEntry = (entry: any): boolean =>
515
+ entry?.type === "custom" && entry?.customType === USAGE_ENTRY_TYPE;
516
+
517
+ const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
518
+
519
+ /**
520
+ * 等本次 /usage 写的报告出现:每 100ms 查一次 getEntries(最多 2s),
521
+ * 全程未命中再试一次会话文件(custom entry 可能很晚才落盘)。
522
+ */
523
+ async function waitForUsageEntry(
524
+ ctx: any,
525
+ opts: { before: Set<unknown>; since: number; file?: string },
526
+ ): Promise<any | undefined> {
527
+ const deadline = Date.now() + USAGE_WAIT_TIMEOUT_MS;
528
+ while (Date.now() < deadline) {
529
+ const fresh = [...sessionEntries(ctx)].reverse().find((entry: any) => isUsageEntry(entry) && !opts.before.has(entry?.id));
530
+ if (fresh) return fresh;
531
+ await sleep(USAGE_POLL_INTERVAL_MS);
532
+ }
533
+ return readUsageEntryFromFile(opts.file, opts.since);
534
+ }
535
+
507
536
  /** 把 pi-usage-report 的 ReportData 渲染成 QQ 可读的纯文本 */
508
537
  function formatUsage(data: any): string {
509
538
  if (!data?.totals) return "用量报告为空。";
@@ -687,12 +716,8 @@ export async function runBridge(
687
716
  reply(`统计失败:${String((error as any)?.message ?? error).slice(0, 200)}`);
688
717
  return;
689
718
  }
690
- // 先看 sessionManager 的增量,再退回读会话文件(appendEntry 可能还没同步到视图)
691
- const entry =
692
- [...sessionEntries(ctx)]
693
- .reverse()
694
- .find((e: any) => e?.type === "custom" && e?.customType === USAGE_ENTRY_TYPE && !before.has(e?.id)) ??
695
- readUsageEntryFromFile(file, since);
719
+ // 先轮询 getEntries 的增量(appendEntry 晚于 await 返回),超时再退回读会话文件
720
+ const entry = await waitForUsageEntry(ctx, { before, since, file });
696
721
  if (!entry) {
697
722
  reply("没拿到用量报告:/usage 已执行但找不到报告数据(可重试一次;若反复出现请把 pi-qqbot.log 尾部发我)。");
698
723
  return;