dskcode 0.1.27 → 0.1.28

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.js CHANGED
@@ -846,7 +846,15 @@ import { Box as Box5, Text as Text6 } from "ink";
846
846
  // src/provider/cost-tracker.ts
847
847
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
848
848
  import { join as join3 } from "path";
849
- var CostTracker = class {
849
+ var CostTracker = class _CostTracker {
850
+ /** 全局活跃实例表,用于进程退出时统一 flush。 */
851
+ static #instances = /* @__PURE__ */ new Set();
852
+ /** 进程退出兜底:统一 flush 所有活跃 CostTracker。 */
853
+ static async flushAll() {
854
+ await Promise.allSettled(
855
+ [..._CostTracker.#instances].map((t) => t.flush())
856
+ );
857
+ }
850
858
  #costDir;
851
859
  #budgetLimit;
852
860
  #tokenBudgetLimit;
@@ -872,6 +880,7 @@ var CostTracker = class {
872
880
  const today = getTodayStr();
873
881
  this.#todayDate = today;
874
882
  this.#todaySummary = createEmptyDailySummary(today);
883
+ _CostTracker.#instances.add(this);
875
884
  }
876
885
  // -----------------------------------------------------------------------
877
886
  // 核心方法
@@ -879,6 +888,7 @@ var CostTracker = class {
879
888
  /**
880
889
  * 记录一次 API 调用的 Token 使用量和成本。
881
890
  * 自动计算费用,累加到会话级和日级统计。
891
+ * 标记脏数据(dirty),由上层在合适的时机调用 flush() 持久化到磁盘。
882
892
  */
883
893
  record(usage, model) {
884
894
  const cost = calculateCost(usage, model);
@@ -987,8 +997,10 @@ var CostTracker = class {
987
997
  /**
988
998
  * 从磁盘加载历史成本数据。
989
999
  * 启动时调用,用于恢复今日和历史数据。
1000
+ * 副作用:确保成本目录存在(首次运行时创建 ~/.dskcode/costs)。
990
1001
  */
991
1002
  async load() {
1003
+ await this.#ensureCostDir();
992
1004
  const store = await this.#loadStore();
993
1005
  const today = getTodayStr();
994
1006
  if (store.daily[today]) {
@@ -998,14 +1010,19 @@ var CostTracker = class {
998
1010
  }
999
1011
  /**
1000
1012
  * 将脏数据持久化到磁盘。
1001
- * 通常在每次 record() 后异步调用,或在会话结束时主动调用。
1013
+ * 会话结束/进程退出时主动调用一次即可。需要每日数据精确时也可在每次
1014
+ * record() 后调用。并发安全:靠 #flushInProgress 互斥。
1002
1015
  */
1003
1016
  async flush() {
1004
1017
  if (!this.#dirty || this.#flushInProgress) return;
1005
1018
  this.#flushInProgress = true;
1006
1019
  this.#dirty = false;
1007
1020
  try {
1021
+ await this.#ensureCostDir();
1008
1022
  await this.#saveStore();
1023
+ } catch (err) {
1024
+ this.#dirty = true;
1025
+ console.error("[CostTracker] flush \u5931\u8D25:", err);
1009
1026
  } finally {
1010
1027
  this.#flushInProgress = false;
1011
1028
  }
@@ -1033,6 +1050,14 @@ var CostTracker = class {
1033
1050
  this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
1034
1051
  this.#sessionRecords.length = 0;
1035
1052
  }
1053
+ /**
1054
+ * 销毁实例:从全局实例表移除并 flush。
1055
+ * 一般用不到,仅在测试或显式生命周期管理时使用。
1056
+ */
1057
+ async dispose() {
1058
+ _CostTracker.#instances.delete(this);
1059
+ await this.flush();
1060
+ }
1036
1061
  // -----------------------------------------------------------------------
1037
1062
  // 内部方法
1038
1063
  // -----------------------------------------------------------------------
@@ -1085,6 +1110,10 @@ var CostTracker = class {
1085
1110
  return { version: 1, daily: {} };
1086
1111
  }
1087
1112
  }
1113
+ /** 确保成本目录存在(首次运行时创建 ~/.dskcode/costs) */
1114
+ async #ensureCostDir() {
1115
+ await mkdir3(this.#costDir, { recursive: true });
1116
+ }
1088
1117
  /** 保存成本存储到磁盘 */
1089
1118
  async #saveStore() {
1090
1119
  const store = await this.#loadStore();
@@ -1096,7 +1125,6 @@ var CostTracker = class {
1096
1125
  for (const date of datesToRemove) {
1097
1126
  delete store.daily[date];
1098
1127
  }
1099
- await mkdir3(this.#costDir, { recursive: true });
1100
1128
  const filePath = join3(this.#costDir, "history.json");
1101
1129
  await writeFile2(filePath, JSON.stringify(store, null, 2), "utf-8");
1102
1130
  }
@@ -2413,6 +2441,8 @@ ${item.result.diff.patch}`;
2413
2441
  }
2414
2442
  const elapsed = Date.now() - startTime;
2415
2443
  void this.#persist();
2444
+ await this.#costTracker.flush().catch(() => {
2445
+ });
2416
2446
  yield { type: "done", elapsed };
2417
2447
  }
2418
2448
  async #executeBatch(calls) {
@@ -4630,10 +4660,11 @@ function ChatSession({
4630
4660
  const cmdLower = trimmed.toLowerCase();
4631
4661
  if (cmdLower === "/rewind" || cmdLower.startsWith("/rewind ")) {
4632
4662
  if (isStreaming || rewinding) {
4663
+ const reason = rewinding ? "\u56DE\u9000\u4E2D" : "\u751F\u6210\u4E2D";
4633
4664
  setDisplayMessages((prev) => [
4634
4665
  ...prev,
4635
4666
  { role: "user", content: trimmed },
4636
- { role: "assistant", content: "\u26A0 \u6B63\u5728\u751F\u6210\u6216\u56DE\u9000\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5 /rewind\u3002" }
4667
+ { role: "assistant", content: `\u26A0 \u6B63\u5728${reason}\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5 /rewind\u3002` }
4637
4668
  ]);
4638
4669
  setInput("");
4639
4670
  return;
@@ -7081,10 +7112,18 @@ var ExitCode = {
7081
7112
  // src/index.ts
7082
7113
  var sigintCount = 0;
7083
7114
  var sigintTimer = null;
7115
+ async function gracefulExit(code) {
7116
+ try {
7117
+ await CostTracker.flushAll();
7118
+ } catch {
7119
+ }
7120
+ process.exit(code);
7121
+ }
7084
7122
  process.on("SIGINT", () => {
7085
7123
  sigintCount++;
7086
7124
  if (sigintCount >= 2) {
7087
- process.exit(ExitCode.SIGINT);
7125
+ void gracefulExit(ExitCode.SIGINT);
7126
+ return;
7088
7127
  }
7089
7128
  process.stdout.write("\n \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode\n");
7090
7129
  if (sigintTimer) clearTimeout(sigintTimer);
@@ -7092,18 +7131,25 @@ process.on("SIGINT", () => {
7092
7131
  sigintCount = 0;
7093
7132
  }, 1500);
7094
7133
  });
7134
+ process.on("SIGTERM", () => {
7135
+ void gracefulExit(ExitCode.SIGINT);
7136
+ });
7095
7137
  var program = createCli();
7096
7138
  try {
7097
7139
  await program.parseAsync(process.argv);
7140
+ await CostTracker.flushAll();
7098
7141
  } catch (err) {
7099
7142
  const error = err;
7100
7143
  if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
7144
+ await CostTracker.flushAll();
7101
7145
  process.exit(error.exitCode ?? ExitCode.SUCCESS);
7102
7146
  }
7103
7147
  if (typeof error.exitCode === "number") {
7148
+ await CostTracker.flushAll();
7104
7149
  process.exit(error.exitCode);
7105
7150
  }
7106
7151
  console.error(String(err));
7152
+ await CostTracker.flushAll();
7107
7153
  process.exit(ExitCode.GENERAL_ERROR);
7108
7154
  }
7109
7155
  //# sourceMappingURL=index.js.map