tokentracker-cli 0.90.2 → 0.91.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.de.md +7 -5
- package/README.ja.md +9 -5
- package/README.ko.md +9 -5
- package/README.md +7 -5
- package/README.zh-CN.md +6 -4
- package/dashboard/dist/assets/{AchievementBadge-BJl0ibLg.js → AchievementBadge-LHOuekQ3.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-CV6aNe3g.js → AchievementsPage-DNzeYWBl.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DxLHu9EV.js → AchievementsSection-Ch5dImt8.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-ConoHhIL.js → BadgeDetailModal-CuikWkUy.js} +1 -1
- package/dashboard/dist/assets/{Card-C_VQxG6G.js → Card-Chwjmxjz.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-_Mfn4MnU.js → ClawdAnimated-BdB7aycS.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-CCRCnYY9.js → CommandPalette-BMwuSEWM.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-DuICwpdM.js → CommunityStatsModal-eEDF-xn8.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CI27robt.js → ConfirmModal-Be7f6XdL.js} +1 -1
- package/dashboard/dist/assets/{Controls-Cwu0gtgj.js → Controls-CtBFPCBY.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-GsFvPie1.js → DashboardPage-M0p-SU-f.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BSK76UQE.js → DevicePage-CAfcUond.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BXQlPs02.js → DialogClose-kd1wIljN.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-CjJLpfCL.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DyvhHV1-.js → DialogTitle-DhB-tbnZ.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DaP6F4f8.js → FadeIn-B8eRWBdy.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CKB5puCd.js → HeaderGithubStar-DmWep8uP.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BG6k61dF.js → HoverTooltip-DDyTZli0.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Oqt1J9S2.js → IpCheckPage-CN8hYm4o.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-45qoUn79.js → LandingPage-B3cHASqK.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-ZgXnaI_N.js → LeaderboardAvatar-CEcLXrJL.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-C7E_LrFk.js → LeaderboardPage-H6HW9hon.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-D6PUcj3b.js → LeaderboardProfileModal-B_5PyjRA.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-VcC9DK_B.js → LeaderboardProfilePage-DR9KWLWv.js} +3 -3
- package/dashboard/dist/assets/{LimitsPage-B9K0RFQD.js → LimitsPage-Da0hRxO_.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BGsfKEM9.js → LocalOnlyNotice-DuTdva_n.js} +1 -1
- package/dashboard/dist/assets/LoginCard-DOL3osrj.js +1 -0
- package/dashboard/dist/assets/{LoginModal-N3pWGciC.js → LoginModal-DBmaQL2L.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-TGgL0aXF.js → LoginPage-Dk08VrkC.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DPi7OT_4.js → PetPage-BbCIdpzx.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-BkptZaPd.js → PopoverPopup-C1fJZ4qt.js} +1 -1
- package/dashboard/dist/assets/ProviderIcon-FfLD-f73.js +1 -0
- package/dashboard/dist/assets/{ResetPasswordPage-DQ4qdjBD.js → ResetPasswordPage-Bg7eVc6L.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-CuTrxHXS.js → SegmentedControl-CFNHcUz7.js} +1 -1
- package/dashboard/dist/assets/{Select-_Txi2j1D.js → Select-B0Q0eDcJ.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DVKhj-YN.js → SelectItemText-BVbLYxSQ.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-ChflNSK0.js → ServiceStatusPage-DCZH2kz5.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-CHv6xK2J.js → SessionsPage-DO5Et7ss.js} +2 -2
- package/dashboard/dist/assets/{SettingsPage-DkWAYALG.js → SettingsPage-afgxGfzT.js} +2 -2
- package/dashboard/dist/assets/{SkillsPage-DnTSlBl-.js → SkillsPage-DDKiHCF7.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-yAMCaU-k.js → TokenGalaxy-DV1Xoe5z.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-BeDIqB0k.js → ToolbarRootContext-DNckd9Qz.js} +1 -1
- package/dashboard/dist/assets/TrendMonitor-CliMJxCC.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-y_qKZ9Gv.js → WidgetsPage-BrwG6YMM.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BGUGI6vk.js → WrappedPage-Ft9YJd0D.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-CRvi_9hb.js → __vite-browser-external-C6_BtvDo.js} +4 -3
- package/dashboard/dist/assets/agent-logos-yYNu1ZUy.js +1 -0
- package/dashboard/dist/assets/{arrow-up-right-CjEesyJI.js → arrow-up-right-B_K57Vbg.js} +1 -1
- package/dashboard/dist/assets/{check-KDrH1gwE.js → check-xH63o2xJ.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-DseqFm-D.js → chevron-right-D3F2rAfr.js} +1 -1
- package/dashboard/dist/assets/{copy-CFJ1QKAQ.js → copy-WPfJGGI0.js} +1 -1
- package/dashboard/dist/assets/{download-BQUNGUdh.js → download-BgtWtEXI.js} +1 -1
- package/dashboard/dist/assets/{external-link-BCEOLLH2.js → external-link-DSHGoZRM.js} +1 -1
- package/dashboard/dist/assets/{flame-BigtoGg2.js → flame-CNGl6Qac.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CS1GUpAV.js → format-tokens-jVm2UX7Y.js} +1 -1
- package/dashboard/dist/assets/{icons-DpZ1a8Ct.js → icons-B1JZg-Id.js} +1 -1
- package/dashboard/dist/assets/{index-DqkwD6eO.js → index-BTZibjLi.js} +1 -1
- package/dashboard/dist/assets/{index-lGAAw1Rv.js → index-BjD81Qce.js} +1 -1
- package/dashboard/dist/assets/{index-DFU_yVc5.js → index-CUFHknAy.js} +1 -1
- package/dashboard/dist/assets/{info-Br_jtGtO.js → info-CLM460KM.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-CkqO1LMQ.js → limits-providers-CFO4O1qh.js} +1 -1
- package/dashboard/dist/assets/{link-2-D-nAhkk-.js → link-2-BHOsUnAI.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CuNPuDRR.js → loader-circle-CgcIQi92.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-dlvwitfg.js → maximize-2-DvqfuJEA.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-ORha_lPI.js → modifiers.esm-CNiwIkPh.js} +1 -1
- package/dashboard/dist/assets/module-CV1mYFsv.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-CI2qvlMB.js → monitor-smartphone-BLutpxrG.js} +1 -1
- package/dashboard/dist/assets/provider-display-BxIgWUEa.js +42 -0
- package/dashboard/dist/assets/{react-Dy9iWvqT.js → react-CM-ozVnh.js} +1 -1
- package/dashboard/dist/assets/{search-Do7tzc3k.js → search-4T07INKt.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CJ-8EFac.js → skills-api-EXMD3LD1.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BKQg8NHC.js → sortable.esm-jC6mPJiL.js} +1 -1
- package/dashboard/dist/assets/{store-SUT-n3k3.js → store-CvSEdgf2.js} +1 -1
- package/dashboard/dist/assets/{terminal-BX4DOxd6.js → terminal-D9fyE2pP.js} +1 -1
- package/dashboard/dist/assets/{trash-2-VERlG247.js → trash-2-QLUsAY3l.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-BX6_XUdu.js → use-community-stats-Chrt3kpU.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-BpC3tYRw.js → use-limits-display-prefs-BJJd750e.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-CX-LREPo.js → use-ordered-list-BBJp5lFv.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-BvXZCGFC.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-B1G8hzVM.js → use-session-efficiency-pref-BXbvx2Ht.js} +1 -1
- package/dashboard/dist/assets/{use-transform-DNCqLbUD.js → use-transform-BVp_MUsU.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-Cvbvf30m.js → use-usage-limits-Dk3EkeIs.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-EciQmJkm.js → useCurrency-Bi88Sd__.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-sLkmam8C.js → useScrollLock-if34g2H-.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-nb9na-2-.js → useTokenFormat-C2cz-5qJ.js} +1 -1
- package/dashboard/dist/assets/{zap-BQOWCMov.js → zap-D1yHmcQR.js} +1 -1
- package/dashboard/dist/index.html +28 -24
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +12 -1
- package/src/commands/status.js +59 -0
- package/src/commands/sync.js +220 -4
- package/src/lib/account-usage-dedup.js +267 -0
- package/src/lib/local-api.js +4 -0
- package/src/lib/pricing/curated-overrides.json +15 -9
- package/src/lib/pricing/index.js +38 -2
- package/src/lib/pricing/matcher.js +1 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +853 -14
- package/src/lib/source-metadata.js +1 -1
- package/src/lib/trae-cn-config.js +539 -0
- package/dashboard/dist/assets/DialogDescription-M_lg2jhD.js +0 -1
- package/dashboard/dist/assets/LoginCard-j5HVFRdP.js +0 -1
- package/dashboard/dist/assets/ProviderIcon-zaz4nwxI.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-BthLuYwy.js +0 -19
- package/dashboard/dist/assets/agent-logos-DwHwn_cb.js +0 -1
- package/dashboard/dist/assets/module-BRJxSJmz.js +0 -3
- package/dashboard/dist/assets/provider-display-BNCdZ83n.js +0 -42
- package/dashboard/dist/assets/use-reduced-motion-DbebVWBw.js +0 -1
package/dashboard/dist/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Token Tracker LLMs Index
|
|
2
2
|
|
|
3
3
|
Token Tracker is a free, open-source, local-first AI coding usage and cost
|
|
4
|
-
tracker for
|
|
4
|
+
tracker for 32 tools. Its distinctive desktop experience includes a pixel pet
|
|
5
5
|
powered by real token activity, four desktop widgets, and achievements. The
|
|
6
6
|
same data powers a cross-platform CLI, a native macOS menu bar app, a native
|
|
7
7
|
Windows system tray app, and a dashboard. It records token counts and
|
|
@@ -23,7 +23,7 @@ timestamps, never prompts, responses, or source code.
|
|
|
23
23
|
- [npm package](https://www.npmjs.com/package/tokentracker-cli)
|
|
24
24
|
- [Latest release](https://github.com/xiufengsun/TokenTracker/releases/latest)
|
|
25
25
|
|
|
26
|
-
## Supported AI coding tools (
|
|
26
|
+
## Supported AI coding tools (33)
|
|
27
27
|
- Claude Code
|
|
28
28
|
- Codex CLI
|
|
29
29
|
- Cursor
|
|
@@ -41,6 +41,7 @@ timestamps, never prompts, responses, or source code.
|
|
|
41
41
|
- Grok Build
|
|
42
42
|
- oh-my-pi
|
|
43
43
|
- pi
|
|
44
|
+
- Prime Agent
|
|
44
45
|
- Craft Agents
|
|
45
46
|
- Reasonix
|
|
46
47
|
- Kilo CLI
|
|
@@ -55,6 +56,7 @@ timestamps, never prompts, responses, or source code.
|
|
|
55
56
|
- AnythingLLM Desktop
|
|
56
57
|
- Claude Science
|
|
57
58
|
- DeepSeek Harness
|
|
59
|
+
- TRAE Work CN
|
|
58
60
|
|
|
59
61
|
## Capabilities
|
|
60
62
|
- Desktop pet that reacts to real token burn, streaks, and rest
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
76
76
|
}
|
|
77
77
|
</script>
|
|
78
|
-
<script type="module" crossorigin src="/assets/__vite-browser-external-
|
|
78
|
+
<script type="module" crossorigin src="/assets/__vite-browser-external-C6_BtvDo.js"></script>
|
|
79
79
|
<link rel="stylesheet" crossorigin href="/assets/__vite-browser-external-CRYifSM4.css">
|
|
80
80
|
</head>
|
|
81
81
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Local-first AI coding token usage and cost tracker for
|
|
3
|
+
"version": "0.91.3",
|
|
4
|
+
"description": "Local-first AI coding token usage and cost tracker for 33 tools, with a dashboard, desktop pet, widgets, achievements, and native macOS/Windows apps.",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tokentracker-cli": "bin/tracker.js",
|
package/src/commands/init.js
CHANGED
|
@@ -65,6 +65,7 @@ const {
|
|
|
65
65
|
resolveOmpAgentDir,
|
|
66
66
|
resolvePiAgentDir,
|
|
67
67
|
piAgentDirCollidesWithOmp,
|
|
68
|
+
resolvePrimeAgentDir,
|
|
68
69
|
resolveAnythingllmDbPath,
|
|
69
70
|
resolveReasonixHome,
|
|
70
71
|
resolveTraeStoragePath,
|
|
@@ -102,7 +103,7 @@ const DIVIDER = "----------------------------------------------";
|
|
|
102
103
|
const DEFAULT_DASHBOARD_URL = "https://www.tokentracker.cc";
|
|
103
104
|
|
|
104
105
|
// Single source of truth for the welcome screen's provider count + sample list.
|
|
105
|
-
// test/discovery-metadata.test.js keeps this aligned with public
|
|
106
|
+
// test/discovery-metadata.test.js keeps this aligned with public 32-tool copy.
|
|
106
107
|
const SUPPORTED_PROVIDERS = [
|
|
107
108
|
"Claude Code",
|
|
108
109
|
"Codex CLI",
|
|
@@ -121,6 +122,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
121
122
|
"Grok Build",
|
|
122
123
|
"oh-my-pi",
|
|
123
124
|
"pi",
|
|
125
|
+
"Prime Agent",
|
|
124
126
|
"Craft Agents",
|
|
125
127
|
"Reasonix",
|
|
126
128
|
"Kilo CLI",
|
|
@@ -135,6 +137,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
135
137
|
"AnythingLLM Desktop",
|
|
136
138
|
"Claude Science",
|
|
137
139
|
"DeepSeek Harness",
|
|
140
|
+
"TRAE Work CN",
|
|
138
141
|
];
|
|
139
142
|
|
|
140
143
|
async function cmdInit(argv) {
|
|
@@ -710,6 +713,14 @@ async function applyIntegrationSetup({
|
|
|
710
713
|
}
|
|
711
714
|
}
|
|
712
715
|
|
|
716
|
+
// Prime Agent: metadata-only passive reader — no hook installation needed.
|
|
717
|
+
{
|
|
718
|
+
const primeAgentDir = resolvePrimeAgentDir(process.env);
|
|
719
|
+
if (primeAgentDir && fssync.existsSync(path.join(primeAgentDir, "sessions"))) {
|
|
720
|
+
summary.push({ label: "Prime Agent", status: "detected", detail: "Passive usage reader (no hook needed)" });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
713
724
|
// Craft Agents: passive reader — no hook installation needed.
|
|
714
725
|
// TokenTracker reads ~/.craft-agent/workspaces/<id>/sessions/**/session.jsonl
|
|
715
726
|
// (and any user-relocated workspace listed in ~/.craft-agent/config.json).
|
package/src/commands/status.js
CHANGED
|
@@ -60,6 +60,8 @@ const {
|
|
|
60
60
|
resolvePiSessionFiles,
|
|
61
61
|
resolvePiAgentDir,
|
|
62
62
|
piAgentDirCollidesWithOmp,
|
|
63
|
+
resolvePrimeAgentSessionFiles,
|
|
64
|
+
resolvePrimeAgentDir,
|
|
63
65
|
resolveCraftSessionFiles,
|
|
64
66
|
resolveCraftConfigDir,
|
|
65
67
|
resolveReasonixHome,
|
|
@@ -87,6 +89,13 @@ const {
|
|
|
87
89
|
resolveCopilotAppDbPaths,
|
|
88
90
|
probeWslDistros,
|
|
89
91
|
} = require("../lib/rollout");
|
|
92
|
+
const {
|
|
93
|
+
TRAE_CN_USAGE_ENV,
|
|
94
|
+
isTraeCnUsageEnabled,
|
|
95
|
+
resolveTraeCnStoragePath,
|
|
96
|
+
readTraeCnAuthFromStorage,
|
|
97
|
+
extractTraeCnToken,
|
|
98
|
+
} = require("../lib/trae-cn-config");
|
|
90
99
|
const wsl = require("../lib/wsl-probe");
|
|
91
100
|
const { getWslMode, isInvalidWslMode, shouldProbeWsl, discoverWslHome } = wsl;
|
|
92
101
|
const { resolveInstallPaths, resolveZcodeNativeDbPath } = require("../lib/install-resolver");
|
|
@@ -427,6 +436,11 @@ async function cmdStatus(argv = []) {
|
|
|
427
436
|
const piInstalled = !piCollides && Boolean(piAgentDir) && fssync.existsSync(path.join(piAgentDir, "sessions"));
|
|
428
437
|
const piFiles = piInstalled ? resolvePiSessionFiles(process.env) : [];
|
|
429
438
|
|
|
439
|
+
// Prime Agent — passive scan only (no hooks).
|
|
440
|
+
const primeAgentDir = resolvePrimeAgentDir(process.env);
|
|
441
|
+
const primeAgentInstalled = Boolean(primeAgentDir) && fssync.existsSync(path.join(primeAgentDir, "sessions"));
|
|
442
|
+
const primeAgentFiles = primeAgentInstalled ? resolvePrimeAgentSessionFiles(process.env) : [];
|
|
443
|
+
|
|
430
444
|
// Craft Agents — passive scan only (no hooks).
|
|
431
445
|
const craftConfigDir = resolveCraftConfigDir(process.env);
|
|
432
446
|
const craftInstalled = Boolean(craftConfigDir && fssync.existsSync(craftConfigDir));
|
|
@@ -635,6 +649,34 @@ async function cmdStatus(argv = []) {
|
|
|
635
649
|
? readTraeEntitlementFromStorage(traeStoragePath)
|
|
636
650
|
: null;
|
|
637
651
|
|
|
652
|
+
// Trae SOLO CN — opt-in usage-API reader. Unlike the passive entitlement
|
|
653
|
+
// reader above, this source sends the locally stored sign-in JWT to TRAE's
|
|
654
|
+
// official API, so status surfaces the opt-in flag, the resolved storage
|
|
655
|
+
// path, and whether the auth blob decrypts — the three inputs a "why is my
|
|
656
|
+
// TRAE CN data missing" diagnosis needs.
|
|
657
|
+
// A resolvable default path does NOT mean the app is installed - the macOS
|
|
658
|
+
// resolver always derives one. Match the sync path semantics exactly:
|
|
659
|
+
// installed iff the storage file actually exists on disk.
|
|
660
|
+
const traeCnStoragePath = resolveTraeCnStoragePath({ env: process.env, home });
|
|
661
|
+
const traeCnInstalled = Boolean(
|
|
662
|
+
traeCnStoragePath && fssync.existsSync(traeCnStoragePath),
|
|
663
|
+
);
|
|
664
|
+
let traeCnAuthState = "not-signed-in";
|
|
665
|
+
if (traeCnInstalled) {
|
|
666
|
+
try {
|
|
667
|
+
const traeCnAuth = readTraeCnAuthFromStorage({ env: process.env, home, platform: process.platform });
|
|
668
|
+
if (traeCnAuth) {
|
|
669
|
+
extractTraeCnToken(traeCnAuth);
|
|
670
|
+
traeCnAuthState = "readable";
|
|
671
|
+
}
|
|
672
|
+
} catch (error) {
|
|
673
|
+
// Distinguish a real IO failure (unreadable) from present-but-bad
|
|
674
|
+
// data (malformed); neither ever carries storage contents.
|
|
675
|
+
traeCnAuthState = error?.code === "TRAE_CN_STORAGE_UNREADABLE" ? "unreadable" : "malformed";
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const traeCnUsageOptIn = isTraeCnUsageEnabled(process.env);
|
|
679
|
+
|
|
638
680
|
// Grok Build (xAI TUI)
|
|
639
681
|
const grokHookState = await probeGrokHookState({ home, trackerDir, env: process.env });
|
|
640
682
|
const grokSessions = grokHookState.hasGrokInstall || grokHookState.sessionsDir
|
|
@@ -873,6 +915,9 @@ async function cmdStatus(argv = []) {
|
|
|
873
915
|
pi: piInstalled
|
|
874
916
|
? { installed: true, files: piFiles.length }
|
|
875
917
|
: { installed: false },
|
|
918
|
+
prime_agent: primeAgentInstalled
|
|
919
|
+
? { installed: true, files: primeAgentFiles.length }
|
|
920
|
+
: { installed: false },
|
|
876
921
|
craft: craftInstalled
|
|
877
922
|
? { installed: true, files: craftFiles.length }
|
|
878
923
|
: { installed: false },
|
|
@@ -923,6 +968,14 @@ async function cmdStatus(argv = []) {
|
|
|
923
968
|
...(traeEntitlement ? { entitlement: traeEntitlement } : {}),
|
|
924
969
|
}
|
|
925
970
|
: { installed: false },
|
|
971
|
+
"trae-cn": traeCnInstalled
|
|
972
|
+
? {
|
|
973
|
+
installed: true,
|
|
974
|
+
detail: traeCnStoragePath,
|
|
975
|
+
auth: traeCnAuthState,
|
|
976
|
+
usage_opt_in: traeCnUsageOptIn,
|
|
977
|
+
}
|
|
978
|
+
: { installed: false },
|
|
926
979
|
grok_build: grokInstalled
|
|
927
980
|
? {
|
|
928
981
|
installed: true,
|
|
@@ -1014,6 +1067,9 @@ async function cmdStatus(argv = []) {
|
|
|
1014
1067
|
piInstalled
|
|
1015
1068
|
? `- pi: passive reader (${piFiles.length} session jsonl file${piFiles.length !== 1 ? "s" : ""} found)`
|
|
1016
1069
|
: null,
|
|
1070
|
+
primeAgentInstalled
|
|
1071
|
+
? `- Prime Agent: passive reader (${primeAgentFiles.length} session jsonl file${primeAgentFiles.length !== 1 ? "s" : ""} found)`
|
|
1072
|
+
: null,
|
|
1017
1073
|
craftInstalled
|
|
1018
1074
|
? `- Craft Agents: passive reader (${craftFiles.length} session jsonl file${craftFiles.length !== 1 ? "s" : ""} found)`
|
|
1019
1075
|
: null,
|
|
@@ -1092,6 +1148,9 @@ async function cmdStatus(argv = []) {
|
|
|
1092
1148
|
traeEntitlement
|
|
1093
1149
|
? `- Trae SOLO plan: ${formatTraeEntitlementLine(traeEntitlement)}`
|
|
1094
1150
|
: null,
|
|
1151
|
+
traeCnInstalled
|
|
1152
|
+
? `- Trae SOLO CN: usage sync ${traeCnUsageOptIn ? "opted in" : `off (set ${TRAE_CN_USAGE_ENV}=1 to enable)`}, auth ${traeCnAuthState} (${traeCnStoragePath})`
|
|
1153
|
+
: null,
|
|
1095
1154
|
...(() => {
|
|
1096
1155
|
if (!hermesInstalled) return [];
|
|
1097
1156
|
return [`- Hermes Agent: state.db found (${hermesPath})`];
|
package/src/commands/sync.js
CHANGED
|
@@ -78,6 +78,8 @@ const {
|
|
|
78
78
|
resolvePiSessionFiles,
|
|
79
79
|
parsePiIncremental,
|
|
80
80
|
piAgentDirCollidesWithOmp,
|
|
81
|
+
resolvePrimeAgentSessionFiles,
|
|
82
|
+
parsePrimeAgentIncremental,
|
|
81
83
|
resolveCraftSessionFiles,
|
|
82
84
|
parseCraftIncremental,
|
|
83
85
|
resolveReasonixTelemetryFiles,
|
|
@@ -110,6 +112,7 @@ const {
|
|
|
110
112
|
resolveDroidModel,
|
|
111
113
|
resolveDshSessionFiles,
|
|
112
114
|
parseDshIncremental,
|
|
115
|
+
parseTraeCnApiIncremental,
|
|
113
116
|
bucketKey,
|
|
114
117
|
toUtcHalfHourStart,
|
|
115
118
|
totalsKey,
|
|
@@ -132,6 +135,11 @@ const {
|
|
|
132
135
|
parseCursorCsv,
|
|
133
136
|
} = require("../lib/cursor-config");
|
|
134
137
|
const { purgeProjectUsage } = require("../lib/project-usage-purge");
|
|
138
|
+
const {
|
|
139
|
+
fetchTraeCnUsageWithAuth,
|
|
140
|
+
resolveTraeCnStoragePath,
|
|
141
|
+
isTraeCnUsageEnabled,
|
|
142
|
+
} = require("../lib/trae-cn-config");
|
|
135
143
|
const {
|
|
136
144
|
isCodexSessionCursorPath,
|
|
137
145
|
isCursorStoreRetry,
|
|
@@ -289,6 +297,7 @@ const AUTO_SYNC_SOURCES = new Set([
|
|
|
289
297
|
"qoder-cn",
|
|
290
298
|
"reasonix",
|
|
291
299
|
"roocode",
|
|
300
|
+
"trae-cn",
|
|
292
301
|
"workbuddy",
|
|
293
302
|
"zcode",
|
|
294
303
|
"zed",
|
|
@@ -450,6 +459,15 @@ async function cmdSync(argv, context = {}) {
|
|
|
450
459
|
const lockWaitOptions = context && typeof context === "object"
|
|
451
460
|
? context.lockWaitOptions
|
|
452
461
|
: undefined;
|
|
462
|
+
// Narrow test-only seam for TRAE Work CN: deterministic injected fetch + a
|
|
463
|
+
// fixed "now" for the rolling range. Production keeps the global fetch and
|
|
464
|
+
// current time; no public CLI flag/config is introduced.
|
|
465
|
+
const traeCnFetchImpl = context && typeof context === "object"
|
|
466
|
+
? context.traeCnFetchImpl
|
|
467
|
+
: undefined;
|
|
468
|
+
const traeCnNowMs = context && typeof context === "object"
|
|
469
|
+
? context.traeCnNowMs
|
|
470
|
+
: undefined;
|
|
453
471
|
const syncDiagnostics = diagnostics && typeof diagnostics === "object" ? diagnostics : null;
|
|
454
472
|
const home = os.homedir();
|
|
455
473
|
const { trackerDir } = await resolveTrackerPaths({ home });
|
|
@@ -1661,6 +1679,93 @@ async function cmdSync(argv, context = {}) {
|
|
|
1661
1679
|
}
|
|
1662
1680
|
}
|
|
1663
1681
|
|
|
1682
|
+
// ── Trae Work CN (国内版) — account-level usage API ──
|
|
1683
|
+
// A simple explicit rolling 30-day window captured once per sync (no
|
|
1684
|
+
// cursor checkpoints / full-history import / page retries / background
|
|
1685
|
+
// requests). Runs only when the user has opted in via
|
|
1686
|
+
// TOKENTRACKER_TRAE_CN_USAGE=1 (the read transmits the locally stored
|
|
1687
|
+
// sign-in JWT to TRAE's official endpoint, so it is never default-on),
|
|
1688
|
+
// the sync is non-lightweight, the source is allowed, and the CN storage
|
|
1689
|
+
// file exists — which excludes both ordinary background and `--auto
|
|
1690
|
+
// --background --all-local-sources`. Fetching goes through the
|
|
1691
|
+
// storage-backed helper so its single 401/403 reread/retry is used; an
|
|
1692
|
+
// empty response is a successful no-op; each window is bounded to 100
|
|
1693
|
+
// pages/2,000 rows and an over-capacity window is split into staggered
|
|
1694
|
+
// sub-windows (still over capacity at the finest allowed granularity, or
|
|
1695
|
+
// any other fetch/page/schema/auth failure) skips the parser and leaves
|
|
1696
|
+
// prior data untouched while unrelated providers continue.
|
|
1697
|
+
let traeCnResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1698
|
+
if (
|
|
1699
|
+
!isBackgroundLightweightSync &&
|
|
1700
|
+
isTraeCnUsageEnabled(process.env) &&
|
|
1701
|
+
sourceAllowed("trae-cn")
|
|
1702
|
+
) {
|
|
1703
|
+
const nowMs = Number.isFinite(traeCnNowMs) ? traeCnNowMs : Date.now();
|
|
1704
|
+
const fetchImpl = typeof traeCnFetchImpl === "function" ? traeCnFetchImpl : fetch;
|
|
1705
|
+
const endTime = Math.floor(nowMs / 1000);
|
|
1706
|
+
// Align the REAL fetch start down to a half-hour boundary so the API
|
|
1707
|
+
// range fully contains every bucket it can touch (a raw 08:37 start
|
|
1708
|
+
// would only partially cover the 08:30 bucket) - session bucket floors
|
|
1709
|
+
// never straddle the queried range.
|
|
1710
|
+
const HALF_HOUR_SEC = 30 * 60;
|
|
1711
|
+
const startTime = Math.max(
|
|
1712
|
+
1,
|
|
1713
|
+
Math.floor((endTime - 30 * 24 * 60 * 60) / HALF_HOUR_SEC) * HALF_HOUR_SEC,
|
|
1714
|
+
);
|
|
1715
|
+
try {
|
|
1716
|
+
// Absent storage means "not signed in" — a silent skip, not an error.
|
|
1717
|
+
const traeCnStoragePath = resolveTraeCnStoragePath({ env: process.env, home });
|
|
1718
|
+
if (traeCnStoragePath && fssync.existsSync(traeCnStoragePath)) {
|
|
1719
|
+
if (progress?.enabled) {
|
|
1720
|
+
progress.start(`Fetching TRAE Work CN usage...`);
|
|
1721
|
+
}
|
|
1722
|
+
const traeCnUsage = await fetchTraeCnUsageWithAuth({
|
|
1723
|
+
start_time: startTime,
|
|
1724
|
+
end_time: endTime,
|
|
1725
|
+
fetchImpl,
|
|
1726
|
+
env: process.env,
|
|
1727
|
+
home,
|
|
1728
|
+
});
|
|
1729
|
+
const traeCnSessions = Array.isArray(traeCnUsage?.sessions) ? traeCnUsage.sessions : [];
|
|
1730
|
+
// An empty payload parses as a pure no-op: the TRAE absence
|
|
1731
|
+
// contract is NOT PROVEN (no evidence that a missing session means
|
|
1732
|
+
// deleted/zero), so an empty response asserts nothing - no usage
|
|
1733
|
+
// mutation and no session states. Non-empty snapshots append one
|
|
1734
|
+
// canonical session-state observation per CHANGED session (the
|
|
1735
|
+
// cloud LWW upsert reconciles cross-device versions).
|
|
1736
|
+
{
|
|
1737
|
+
if (progress?.enabled && traeCnSessions.length > 0) {
|
|
1738
|
+
progress.start(
|
|
1739
|
+
`Parsing TRAE Work CN ${renderBar(0)} 0/${formatNumber(
|
|
1740
|
+
traeCnSessions.length,
|
|
1741
|
+
)} records | buckets 0`,
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
traeCnResult = await parseTraeCnApiIncremental({
|
|
1745
|
+
sessions: traeCnSessions,
|
|
1746
|
+
cursors,
|
|
1747
|
+
queuePath,
|
|
1748
|
+
onProgress: makeProviderProgress("TRAE Work CN"),
|
|
1749
|
+
windowStartMs: startTime * 1000,
|
|
1750
|
+
windowEndMs: endTime * 1000,
|
|
1751
|
+
// The LOGICAL fetch stamp carried into every session state's
|
|
1752
|
+
// snapshot_verified_at: the same clock that produced the query
|
|
1753
|
+
// range, not the later enqueue moment (append-only queue
|
|
1754
|
+
// replays it verbatim, so transport retries never fake
|
|
1755
|
+
// freshness).
|
|
1756
|
+
snapshotVerifiedAtMs: nowMs,
|
|
1757
|
+
});
|
|
1758
|
+
// A partially malformed snapshot throws inside the parser (fail
|
|
1759
|
+
// closed - it must not become the authoritative window state);
|
|
1760
|
+
// warnProviderParseFailure below reports it without sensitive
|
|
1761
|
+
// data while unrelated providers continue.
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
warnProviderParseFailure("TRAE Work CN", err, opts);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1664
1769
|
// ── Kiro (SQLite-based, with JSONL fallback; dual-install aware) ──
|
|
1665
1770
|
let kiroResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1666
1771
|
if (sourceAllowed("kiro")) {
|
|
@@ -2079,6 +2184,34 @@ async function cmdSync(argv, context = {}) {
|
|
|
2079
2184
|
}
|
|
2080
2185
|
}
|
|
2081
2186
|
|
|
2187
|
+
// ── Prime Agent — passive ~/.prime/agent/sessions/*.jsonl usage reader ──
|
|
2188
|
+
let primeAgentResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
2189
|
+
const primeAgentFiles = sourceAllowed("prime-agent")
|
|
2190
|
+
? mergeBothFileSources({ resolveFiles: resolvePrimeAgentSessionFiles, env: process.env })
|
|
2191
|
+
: [];
|
|
2192
|
+
if (primeAgentFiles.length > 0) {
|
|
2193
|
+
if (progress?.enabled) {
|
|
2194
|
+
progress.start(`Parsing Prime Agent ${renderBar(0)} | buckets 0`);
|
|
2195
|
+
}
|
|
2196
|
+
try {
|
|
2197
|
+
primeAgentResult = await parsePrimeAgentIncremental({
|
|
2198
|
+
sessionFiles: primeAgentFiles,
|
|
2199
|
+
cursors,
|
|
2200
|
+
queuePath,
|
|
2201
|
+
env: process.env,
|
|
2202
|
+
onProgress: (p) => {
|
|
2203
|
+
if (!progress?.enabled) return;
|
|
2204
|
+
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
2205
|
+
progress.update(
|
|
2206
|
+
`Parsing Prime Agent ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
2207
|
+
);
|
|
2208
|
+
},
|
|
2209
|
+
});
|
|
2210
|
+
} catch (err) {
|
|
2211
|
+
warnProviderParseFailure("Prime Agent", err, opts);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2082
2215
|
// ── Craft Agents (passive ~/.craft-agent + workspaces session.jsonl reader) ──
|
|
2083
2216
|
let craftResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
2084
2217
|
const craftFiles = sourceAllowed("craft")
|
|
@@ -2127,7 +2260,12 @@ async function cmdSync(argv, context = {}) {
|
|
|
2127
2260
|
}
|
|
2128
2261
|
|
|
2129
2262
|
// ── Grok Build (xAI) ──
|
|
2130
|
-
let grokResult = {
|
|
2263
|
+
let grokResult = {
|
|
2264
|
+
recordsProcessed: 0,
|
|
2265
|
+
eventsAggregated: 0,
|
|
2266
|
+
bucketsQueued: 0,
|
|
2267
|
+
projectBucketsQueued: 0,
|
|
2268
|
+
};
|
|
2131
2269
|
// Full passive scan of all Grok sessions (historical + any not covered by hook)
|
|
2132
2270
|
const grokSessions = sourceAllowed("grok") ? resolveGrokBuildSessions(process.env) : [];
|
|
2133
2271
|
const grokSessionInputs = [...grokSessions];
|
|
@@ -2149,6 +2287,11 @@ async function cmdSync(argv, context = {}) {
|
|
|
2149
2287
|
sessionId: hookSessionId,
|
|
2150
2288
|
sessionDir:
|
|
2151
2289
|
typeof grokHookSignal.sessionDir === "string" ? grokHookSignal.sessionDir : undefined,
|
|
2290
|
+
cwd: typeof grokHookSignal.cwd === "string" ? grokHookSignal.cwd : undefined,
|
|
2291
|
+
encodedCwd:
|
|
2292
|
+
typeof grokHookSignal.sessionDir === "string" && grokHookSignal.sessionDir
|
|
2293
|
+
? path.basename(path.dirname(grokHookSignal.sessionDir))
|
|
2294
|
+
: undefined,
|
|
2152
2295
|
updatesPath:
|
|
2153
2296
|
typeof grokHookSignal.updatesPath === "string" ? grokHookSignal.updatesPath : undefined,
|
|
2154
2297
|
signalsPath:
|
|
@@ -2178,6 +2321,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2178
2321
|
sessions: grokSessionInputs,
|
|
2179
2322
|
cursors,
|
|
2180
2323
|
queuePath,
|
|
2324
|
+
projectQueuePath,
|
|
2181
2325
|
env: process.env,
|
|
2182
2326
|
onProgress: (p) => {
|
|
2183
2327
|
if (!progress?.enabled) return;
|
|
@@ -2194,6 +2338,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
2194
2338
|
recordsProcessed: grokResult.recordsProcessed + grokScanResult.recordsProcessed,
|
|
2195
2339
|
eventsAggregated: grokResult.eventsAggregated + grokScanResult.eventsAggregated,
|
|
2196
2340
|
bucketsQueued: grokResult.bucketsQueued + grokScanResult.bucketsQueued,
|
|
2341
|
+
projectBucketsQueued:
|
|
2342
|
+
(grokResult.projectBucketsQueued || 0) + (grokScanResult.projectBucketsQueued || 0),
|
|
2197
2343
|
};
|
|
2198
2344
|
}
|
|
2199
2345
|
if (isFullSourceScan && opts.repairGrok) {
|
|
@@ -2554,6 +2700,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2554
2700
|
qoderCnResult.recordsProcessed +
|
|
2555
2701
|
claudeScienceResult.recordsProcessed +
|
|
2556
2702
|
cursorResult.recordsProcessed +
|
|
2703
|
+
traeCnResult.recordsProcessed +
|
|
2557
2704
|
kiroResult.recordsProcessed +
|
|
2558
2705
|
kiroCliResult.recordsProcessed +
|
|
2559
2706
|
hermesResult.recordsProcessed +
|
|
@@ -2563,6 +2710,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2563
2710
|
workbuddyResult.recordsProcessed +
|
|
2564
2711
|
ompResult.recordsProcessed +
|
|
2565
2712
|
piResult.recordsProcessed +
|
|
2713
|
+
primeAgentResult.recordsProcessed +
|
|
2566
2714
|
craftResult.recordsProcessed +
|
|
2567
2715
|
reasonixResult.recordsProcessed +
|
|
2568
2716
|
grokResult.recordsProcessed +
|
|
@@ -2588,6 +2736,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2588
2736
|
qoderCnResult.bucketsQueued +
|
|
2589
2737
|
claudeScienceResult.bucketsQueued +
|
|
2590
2738
|
cursorResult.bucketsQueued +
|
|
2739
|
+
traeCnResult.bucketsQueued +
|
|
2591
2740
|
kiroResult.bucketsQueued +
|
|
2592
2741
|
kiroCliResult.bucketsQueued +
|
|
2593
2742
|
hermesResult.bucketsQueued +
|
|
@@ -2597,6 +2746,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2597
2746
|
workbuddyResult.bucketsQueued +
|
|
2598
2747
|
ompResult.bucketsQueued +
|
|
2599
2748
|
piResult.bucketsQueued +
|
|
2749
|
+
primeAgentResult.bucketsQueued +
|
|
2600
2750
|
craftResult.bucketsQueued +
|
|
2601
2751
|
reasonixResult.bucketsQueued +
|
|
2602
2752
|
grokResult.bucketsQueued +
|
|
@@ -2618,6 +2768,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2618
2768
|
cursorStore.requiresCommit !== true &&
|
|
2619
2769
|
totalParsed === 0 &&
|
|
2620
2770
|
totalBuckets === 0 &&
|
|
2771
|
+
!(grokResult.projectBucketsQueued > 0) &&
|
|
2621
2772
|
!codexColdAuditDue &&
|
|
2622
2773
|
!codexFallbackRetryRan &&
|
|
2623
2774
|
!grokHookSignalConsumed &&
|
|
@@ -2940,6 +3091,8 @@ function recordCodexColdScanAudit(
|
|
|
2940
3091
|
module.exports = {
|
|
2941
3092
|
cmdSync,
|
|
2942
3093
|
acquireSyncLock,
|
|
3094
|
+
readQueueBatch,
|
|
3095
|
+
drainQueueToCloud,
|
|
2943
3096
|
migrateCursorUnknownBuckets,
|
|
2944
3097
|
migrateRolloutCumulativeDeltaBuckets,
|
|
2945
3098
|
repairCodebuddyLogJsonlOverlap,
|
|
@@ -3440,7 +3593,10 @@ async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePa
|
|
|
3440
3593
|
for (let batch = 0; batch < maxBatches; batch++) {
|
|
3441
3594
|
if (offset >= queueSize) break;
|
|
3442
3595
|
const result = await readQueueBatch(queuePath, offset, limit);
|
|
3443
|
-
|
|
3596
|
+
// A states-only tail (e.g. a retry batch whose bucket rows already
|
|
3597
|
+
// uploaded) is still a meaningful upload: it advances the canonical
|
|
3598
|
+
// session corrections cloud-side.
|
|
3599
|
+
if (result.buckets.length === 0 && result.sessionStates.length === 0) break;
|
|
3444
3600
|
|
|
3445
3601
|
const root = baseUrl.replace(/\/$/, "");
|
|
3446
3602
|
const anonKey = process.env.TOKENTRACKER_INSFORGE_ANON_KEY || "";
|
|
@@ -3453,7 +3609,12 @@ async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePa
|
|
|
3453
3609
|
const res = await fetch(`${root}/functions/${INGEST_SLUG}`, {
|
|
3454
3610
|
method: "POST",
|
|
3455
3611
|
headers,
|
|
3456
|
-
body: JSON.stringify({
|
|
3612
|
+
body: JSON.stringify({
|
|
3613
|
+
hourly: result.buckets,
|
|
3614
|
+
...(result.sessionStates.length > 0
|
|
3615
|
+
? { account_session_states: result.sessionStates }
|
|
3616
|
+
: {}),
|
|
3617
|
+
}),
|
|
3457
3618
|
});
|
|
3458
3619
|
|
|
3459
3620
|
const rawText = await res.text().catch(() => "");
|
|
@@ -3490,6 +3651,26 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
3490
3651
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
3491
3652
|
|
|
3492
3653
|
const bucketMap = new Map();
|
|
3654
|
+
// Account session states (kind: "account_session_state") are queue
|
|
3655
|
+
// control records, not usage rows: collect them separately so they ride
|
|
3656
|
+
// the same upload (after the bucket rows in file order) without entering
|
|
3657
|
+
// the bucket stream. Keyed by (source, session_id) LAST-WINS: the
|
|
3658
|
+
// append-only queue writes a session's newer observation after its older
|
|
3659
|
+
// one, and only the latest per session may reach the ingest batch (a
|
|
3660
|
+
// duplicate would make the cloud ON CONFLICT affect one row twice).
|
|
3661
|
+
// snapshot_verified_at rides along verbatim from the ORIGINAL fetch, so a
|
|
3662
|
+
// transport retry never fakes logical freshness.
|
|
3663
|
+
//
|
|
3664
|
+
// Batching: session-state records count toward the SAME per-batch record
|
|
3665
|
+
// cap (maxBuckets) as bucket rows. The ingest edge rejects a request with
|
|
3666
|
+
// more than 500 states (HTTP 400); before they counted, a states-heavy
|
|
3667
|
+
// queue (a fresh device's first 30-day TRAE sync appends one record per
|
|
3668
|
+
// observed session) was read in ONE uncapped batch - the 400 left the
|
|
3669
|
+
// queue offset untouched and every retry re-read the identical oversized
|
|
3670
|
+
// request, a permanent upload failure. With the default batchSize of 200
|
|
3671
|
+
// (and the MAX_INGEST_BUCKETS=500 clamp above), every request stays under
|
|
3672
|
+
// the edge cap while batching follows the existing design.
|
|
3673
|
+
const sessionStateMap = new Map();
|
|
3493
3674
|
let offset = startOffset;
|
|
3494
3675
|
let linesRead = 0;
|
|
3495
3676
|
for await (const line of rl) {
|
|
@@ -3502,6 +3683,37 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
3502
3683
|
} catch (_e) {
|
|
3503
3684
|
continue;
|
|
3504
3685
|
}
|
|
3686
|
+
if (bucket?.kind === "account_session_state") {
|
|
3687
|
+
if (typeof bucket.source === "string" && typeof bucket.session_id === "string" && bucket.session_id.trim()) {
|
|
3688
|
+
const ssSource = bucket.source.trim().toLowerCase();
|
|
3689
|
+
sessionStateMap.set(ssSource + "|" + bucket.session_id.trim(), {
|
|
3690
|
+
source: ssSource,
|
|
3691
|
+
session_id: bucket.session_id.trim(),
|
|
3692
|
+
model: typeof bucket.model === "string" ? bucket.model : "",
|
|
3693
|
+
bucket_start: typeof bucket.bucket_start === "string" ? bucket.bucket_start : "",
|
|
3694
|
+
input_tokens: Number(bucket.input_tokens) || 0,
|
|
3695
|
+
output_tokens: Number(bucket.output_tokens) || 0,
|
|
3696
|
+
cached_input_tokens: Number(bucket.cached_input_tokens) || 0,
|
|
3697
|
+
cache_creation_input_tokens: Number(bucket.cache_creation_input_tokens) || 0,
|
|
3698
|
+
reasoning_output_tokens: Number(bucket.reasoning_output_tokens) || 0,
|
|
3699
|
+
total_tokens: Number(bucket.total_tokens) || 0,
|
|
3700
|
+
...(typeof bucket.snapshot_verified_at === "string" && bucket.snapshot_verified_at
|
|
3701
|
+
? { snapshot_verified_at: bucket.snapshot_verified_at }
|
|
3702
|
+
: {}),
|
|
3703
|
+
});
|
|
3704
|
+
// Counted like a bucket row: the record rides THIS batch and its
|
|
3705
|
+
// bytes are covered by nextOffset, so the next batch resumes right
|
|
3706
|
+
// after it.
|
|
3707
|
+
linesRead += 1;
|
|
3708
|
+
if (linesRead >= maxBuckets) break;
|
|
3709
|
+
}
|
|
3710
|
+
continue;
|
|
3711
|
+
}
|
|
3712
|
+
// Pre-release watermark records from this feature branch's dev queues
|
|
3713
|
+
// are obsolete control records: drop them instead of letting them enter
|
|
3714
|
+
// the bucket stream (they carry no hour_start and would be skipped
|
|
3715
|
+
// downstream anyway, but be explicit).
|
|
3716
|
+
if (bucket?.kind === "account_sync_watermark") continue;
|
|
3505
3717
|
const hourStart = typeof bucket?.hour_start === "string" ? bucket.hour_start : null;
|
|
3506
3718
|
if (!hourStart) continue;
|
|
3507
3719
|
const source = (typeof bucket?.source === "string" ? bucket.source.trim().toLowerCase() : "") || "codex";
|
|
@@ -3521,7 +3733,11 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
3521
3733
|
|
|
3522
3734
|
rl.close();
|
|
3523
3735
|
stream.close?.();
|
|
3524
|
-
return {
|
|
3736
|
+
return {
|
|
3737
|
+
buckets: Array.from(bucketMap.values()),
|
|
3738
|
+
sessionStates: Array.from(sessionStateMap.values()),
|
|
3739
|
+
nextOffset: offset,
|
|
3740
|
+
};
|
|
3525
3741
|
}
|
|
3526
3742
|
|
|
3527
3743
|
function normalizeGrokRepairSource(value) {
|