Files
ysicing 3575a97de5 fix(ai-usage): 修正子代理回放判定并清理配额时代残留
修复子代理与 fork 会话的 Token 全量丢失:原判定要求 task_started 带
started_at 且不早于会话创建时间才结束回放,但子代理的 started_at 是父级
回合的开始时间,天然早于子会话文件,新版 Codex 更是完全不发该字段。改为
首个 task_started 无条件结束回放——实测 661 个真实 rollout 中没有任何一个
的 token_count 出现在它之前。本机语料 A/B:Token 13.60G → 14.53G(+6.8%),
用量记录 68997 → 73566。

其他修复:
- 两个 Provider 钉到专用串行执行器,冷扫描不再占用协作线程池。把池限制
  成一条线程实测,修复前并行的每秒采样在 43 秒扫描期间一次都没能推进。
- 按日期与模型聚合并定序输出,行数 701 → 272 且 id 唯一;顺带修掉冷扫描
  与检查点恢复顺序不一致导致每次刷新都产生"不相等"报告的问题。
- 缓存行加剪枝,覆盖归档搬走的 rollout、删除的项目目录与旧解析命名空间;
  检查点仅在末行未完时存第二份状态,Claude 事件按保留窗口裁剪。
- 复用 ISO8601 解析器,不再逐行创建。
- 数字与日期改跟随应用语言,弹窗与菜单栏共用同一套 Token 缩写。
- 窄弹窗热力图按设计尺寸铺开后横向滚动,此前被钉在面板宽度上永不溢出。
- 打开菜单栏项一并开启扫描,并为无数据状态补上悬停文案。
- 删除未被应用构造的配额 Provider、凭据读取与响应映射(约 330 行)及其
  全链路管线,并清掉 37 条孤立译文 × 8 个语言文件。
2026-09-23 08:50:41 +08:00

39 lines
1.4 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) 2026 ysicing
// SPDX-License-Identifier: AGPL-3.0-or-later
import Dispatch
import Foundation
import Testing
@testable import AIUsage
/// 两个 Provider 都把自己钉在 `ScanExecutor.shared` 上。冷启动扫描是几十秒的同步 IO,
/// 留在协作线程池里会把同池的 actor 一起卡住:把池限制成一条线程
/// (`LIBDISPATCH_COOPERATIVE_POOL_STRICT=1`)实测,去掉这个执行器后,
/// 并行的每秒采样在整段扫描期间一次都没能推进。
private actor PinnedToScanExecutor {
nonisolated var unownedExecutor: UnownedSerialExecutor {
ScanExecutor.shared.asUnownedSerialExecutor()
}
func currentQueueLabel() -> String {
String(cString: __dispatch_queue_get_label(nil))
}
}
@Suite struct ScanExecutorTests {
@Test func pinnedActorsRunOffTheCooperativePool() async {
let label = await PinnedToScanExecutor().currentQueueLabel()
#expect(label == "com.xstats.ai-usage-scan")
}
/// 串行执行器必须真的串行,否则两个 Provider 会同时抢磁盘和同一个缓存库。
@Test func workIsSerializedAcrossPinnedActors() async {
let first = PinnedToScanExecutor()
let second = PinnedToScanExecutor()
async let a = first.currentQueueLabel()
async let b = second.currentQueueLabel()
let labels = await [a, b]
#expect(labels == ["com.xstats.ai-usage-scan", "com.xstats.ai-usage-scan"])
}
}