tokentracker-cli 1.4.1 → 1.4.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 +5 -4
- package/README.ja.md +5 -4
- package/README.ko.md +5 -4
- package/README.md +5 -4
- package/README.zh-CN.md +5 -4
- package/dashboard/dist/assets/{AchievementBadge-DWECUOAX.js → AchievementBadge-3IYidbrI.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-Bakfwwn_.js → AchievementsPage-DmZ_ojyM.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DiEX2_5U.js → AchievementsSection-DIYrrzhw.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-ClVMnxVW.js → BadgeDetailModal-DMMExCe4.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-D7eyrAPs.js → BotAnimated-y6vZNRvC.js} +1 -1
- package/dashboard/dist/assets/{Card-CsAQxZZ8.js → Card-LJX3G3zo.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-BAN-DtXB.js → CloudCheckoutPage-BGBgSXNE.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-D9sNlUAy.js → CloudPage-Dij2GTXi.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-D9TNT6un.js → CommandPalette-BIky6Fk0.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CotRRQNH.js → CommunityStatsModal-DbM99i0s.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-C_il5anp.js → ConfirmModal-C_NH8vgO.js} +1 -1
- package/dashboard/dist/assets/{Controls-Bd1kODP4.js → Controls-BEAj_kvr.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-De0EL8Hn.js → DashboardPage-DO7h2dfu.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-quw6hpxy.js → DateRangePopover-PQXB8v0k.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-CP20lVYv.js → DevicePage-0JZ3OIWZ.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BEUL9bD6.js → DialogClose-BpWoD5h9.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-ne_rYSD7.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-C--sXGjf.js → DialogTitle-DoQgdh5w.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-DhP7S1Cu.js → ElectricLogo-0O87VjKv.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BPH15_jE.js → FadeIn-9ph1h_AG.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DziE8s6w.js → HeaderGithubStar-DCZ92xbH.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-tQyY2CuV.js → HoverTooltip-CCKdmuOP.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BjoB05O7.js → IpCheckPage-CpyhpmdS.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Cav49COr.js → LandingPage-spvsPkYy.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-CHjFKLvs.js → LeaderboardPage-2nJ08ohw.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-D97MfK5W.js → LeaderboardProBadge-D6FSL3Qy.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-NNTwtWSu.js → LeaderboardProfileModal-DD1jX-rD.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-xI81GH3D.js → LeaderboardProfilePage-DYqRrntL.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BPOo3FOW.js → LimitsPage-DXHikunk.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-j8Tijts2.js → LocalOnlyNotice-BURiQAtP.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-C7_n6J7k.js → LoginCard-Cv2ijoJx.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DwlNscw5.js → LoginModal-DyOideDA.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BqhWatt2.js → LoginPage-BKNvqvYi.js} +1 -1
- package/dashboard/dist/assets/{PetPage-CJlC0V0i.js → PetPage-BBmozE0L.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-wleZ4DTI.js → PopoverPopup-DDnISLKM.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-biLVsOGr.js → ProviderIcon-Da90GDcM.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-5Sl45OsN.js → ResetPasswordPage-DsETr50j.js} +1 -1
- package/dashboard/dist/assets/{Select-77l2QNqa.js → Select-pZDawyxt.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BQbmtjd3.js → SelectItemText-D5wuuN7G.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-C8hlgVu4.js → SelfHostPage-sm-PARYj.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-DrY9Mef8.js → ServiceStatusPage-D4f8h0sL.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-BDTsH3Mk.js → SessionsPage-xVLsTN4m.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-C_id9Go3.js → SettingsPage-Dt9iFheY.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-kb6_5uUk.js → SkillsPage-BdWalPpd.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-BoS0yUFS.js → TokenGalaxy-d2_ml0tE.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-LV1aWIRZ.js → TooltipPopup-B4masxDW.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-DN-nwcSA.js → TrendMonitor-CceYpa2O.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-C6lvwceS.js → WidgetsPage-CSN0829H.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-CfC39wWP.js → WrappedPage-gP4WfO_m.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-DpZbUcLL.js → __vite-browser-external-BtJVmciH.js} +2 -2
- package/dashboard/dist/assets/{agent-logos-CvcdCGj5.js → agent-logos-wxccxfWz.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-BX4LX5TF.js → arrow-left-Bd5C77R2.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-Aktjo2RL.js → arrow-right-C1RqrK4A.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-kkQt6Du0.js → arrow-up-right-B_b2i8hn.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-OvLTNZBH.js → calendar-days-C8eYygme.js} +1 -1
- package/dashboard/dist/assets/{check-v5Ie379p.js → check-BlBHefQ7.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-CPlz1cYW.js → chevron-down-mjUspqZv.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-C_Rhiwno.js → cloud-checkout-CEkMnWXf.js} +1 -1
- package/dashboard/dist/assets/{composite-DD_boZdD.js → composite-CzXFY9IC.js} +1 -1
- package/dashboard/dist/assets/{download-NqHXZ-zn.js → download-CQvg59w1.js} +1 -1
- package/dashboard/dist/assets/{external-link-B0D03J0f.js → external-link-CDYfIZ3Z.js} +1 -1
- package/dashboard/dist/assets/{flame-av87we58.js → flame-C9NXQRil.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-B43Ehkbb.js → format-tokens-fKy_3qpW.js} +1 -1
- package/dashboard/dist/assets/{gift-CZus7M21.js → gift-WmV4SBGR.js} +1 -1
- package/dashboard/dist/assets/{host-mode-Dd9fzAdq.js → host-mode-C5MvZ7SV.js} +1 -1
- package/dashboard/dist/assets/{icons-CnScwnf_.js → icons-B1EtsaZs.js} +1 -1
- package/dashboard/dist/assets/{index-Cj9NdXOj.js → index-9EZ3o2RV.js} +1 -1
- package/dashboard/dist/assets/{index-D3XPaYEL.js → index-BEecBW06.js} +1 -1
- package/dashboard/dist/assets/{index-BZTIoCof.js → index-DXNwBFNi.js} +1 -1
- package/dashboard/dist/assets/{info-BUmCmjwd.js → info-CWDhggIW.js} +1 -1
- package/dashboard/dist/assets/{link-2-t-wSLB2a.js → link-2-DEWNkK7s.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-DM-FdXeX.js → loader-circle-DotT5gVp.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-D5AKZNa7.js → modifiers.esm-qZnGgJAz.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-CDHFLZS-.js → monitor-smartphone-DOP5eqBD.js} +1 -1
- package/dashboard/dist/assets/{play-BIQH--yZ.js → play-BUsybi9m.js} +1 -1
- package/dashboard/dist/assets/{plus-CztG5eum.js → plus-DAjojh_m.js} +1 -1
- package/dashboard/dist/assets/{provider-display-B2gZ38Oh.js → provider-display-BCQQYPWD.js} +2 -2
- package/dashboard/dist/assets/{react-D1u6HsZx.js → react-BgpqhoCj.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-ClG4WM06.js → safePolygon-C-PFpZSA.js} +1 -1
- package/dashboard/dist/assets/{search-NNy_fIbJ.js → search-W1A-6ugo.js} +1 -1
- package/dashboard/dist/assets/{server-N94HJtJY.js → server-CE7pFjDr.js} +1 -1
- package/dashboard/dist/assets/{shield-check-BGfaKXG9.js → shield-check-KL5xc2FR.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CWydQc4G.js → skills-api-DL_zLjKA.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BF83ljWQ.js → sortable.esm-CehJQvFH.js} +1 -1
- package/dashboard/dist/assets/{store-l5WfCa7P.js → store-BHAXkHPR.js} +1 -1
- package/dashboard/dist/assets/{terminal-CRBThKUQ.js → terminal-Br5vyD6Q.js} +1 -1
- package/dashboard/dist/assets/{trash-BXN5tg81.js → trash-CzaCJP6G.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-dHketgyF.js → use-community-stats-DbJrecQw.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-CdM1Pn_p.js → use-latest-request-guard-DGhwmlX8.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-dOm4SCFU.js → use-limits-display-prefs-T7KWAWZ6.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-GL9uAidm.js → use-native-settings-MZJhS328.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-Btz_NSiA.js → use-ordered-list-BDN24weA.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CfCY-K-a.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-BPZel3HL.js → use-session-efficiency-pref-DUu16y8Z.js} +1 -1
- package/dashboard/dist/assets/{use-transform-D2xePGbE.js → use-transform-BczE46xm.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-vEmyPwMY.js → use-usage-limits-CjyDfH-M.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DnhS0llv.js → useCurrency-BgZmWbkx.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-7yozJJJ0.js → usePositioner-dWENyY8z.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-DvjyXv8i.js → useTokenFormat-D7s1-Zuk.js} +1 -1
- package/dashboard/dist/assets/{zap-CElRpX2w.js → zap-Dr__bKlq.js} +1 -1
- package/dashboard/dist/index.html +13 -11
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +3 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/doctor.js +15 -1
- package/src/commands/init.js +9 -0
- package/src/commands/status.js +20 -2
- package/src/commands/sync.js +25 -0
- package/src/lib/cloud-device-token.js +77 -10
- package/src/lib/diagnostics.js +4 -1
- package/src/lib/pricing/index.js +2 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +402 -0
- package/dashboard/dist/assets/DialogDescription-DpkD2qwW.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-D9n2I_eN.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -17079,6 +17079,405 @@ async function parseReasonixIncremental({ telemetryFiles, cursors, queuePath, on
|
|
|
17079
17079
|
return { recordsProcessed, eventsAggregated, bucketsQueued };
|
|
17080
17080
|
}
|
|
17081
17081
|
|
|
17082
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17083
|
+
// Muse Code (Meta) — passive session.jsonl reader.
|
|
17084
|
+
//
|
|
17085
|
+
// ~/.local/share/muse/sessions/YYYY/MM/DD/<session>/session.jsonl
|
|
17086
|
+
// ~/.local/share/muse/sessions/YYYY/MM/DD/<session>/subagent/<id>/session.jsonl
|
|
17087
|
+
//
|
|
17088
|
+
// Every record is `{id, stream, sequence, recorded_at (epoch µs), payload_type,
|
|
17089
|
+
// payload}`. Per-model-step usage rides on a `runtime.session` record whose
|
|
17090
|
+
// `payload.event.kind` is `goal_usage_attribution`:
|
|
17091
|
+
//
|
|
17092
|
+
// quantity: {unit:"tokens", reported:true, input_tokens, output_tokens,
|
|
17093
|
+
// cached_tokens, reasoning_tokens, main_llm_steps}
|
|
17094
|
+
//
|
|
17095
|
+
// Only `usage_family:"provider"` + `reported:true` rows are model usage (tool and
|
|
17096
|
+
// reminder rows are zero placeholders). The usage row names no model: the model
|
|
17097
|
+
// comes from the `run.model.configured` record of the same `run_id`.
|
|
17098
|
+
//
|
|
17099
|
+
// Token semantics (verified against the session snapshot `cost_ledger`, where
|
|
17100
|
+
// `input_tokens` equals the sum of these rows and `cache_read_tokens` equals
|
|
17101
|
+
// `cached_tokens`): `input_tokens` INCLUDES cache reads, and `reasoning_tokens`
|
|
17102
|
+
// is a subset of `output_tokens`. Subagent logs are separate streams whose usage
|
|
17103
|
+
// is not repeated in the parent log, so each file is counted once. No prompt,
|
|
17104
|
+
// response or path text is read or stored; only counts, timestamps and the
|
|
17105
|
+
// model id.
|
|
17106
|
+
function resolveMuseHome(env = process.env) {
|
|
17107
|
+
if (env.TOKENTRACKER_MUSE_HOME) return expandHomePath(env.TOKENTRACKER_MUSE_HOME, env);
|
|
17108
|
+
return path.join(env.HOME || require("node:os").homedir(), ".local", "share", "muse");
|
|
17109
|
+
}
|
|
17110
|
+
|
|
17111
|
+
const MUSE_SCAN_MAX_DEPTH = 8;
|
|
17112
|
+
const MUSE_READ_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
17113
|
+
const MUSE_WATERMARK_IDS_MAX = 100;
|
|
17114
|
+
|
|
17115
|
+
function collectMuseSessionFiles(dir, files, depth = 0) {
|
|
17116
|
+
if (depth > MUSE_SCAN_MAX_DEPTH) return;
|
|
17117
|
+
let entries;
|
|
17118
|
+
try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
17119
|
+
for (const entry of entries) {
|
|
17120
|
+
// Dot directories (.msp-view-v1) hold derived views, not the session log.
|
|
17121
|
+
if (entry.name.startsWith(".")) continue;
|
|
17122
|
+
const full = path.join(dir, entry.name);
|
|
17123
|
+
if (entry.isDirectory()) collectMuseSessionFiles(full, files, depth + 1);
|
|
17124
|
+
else if (entry.isFile() && entry.name === "session.jsonl") files.push(full);
|
|
17125
|
+
}
|
|
17126
|
+
}
|
|
17127
|
+
|
|
17128
|
+
function resolveMuseSessionFiles(env = process.env) {
|
|
17129
|
+
const sessionsDir = path.join(resolveMuseHome(env), "sessions");
|
|
17130
|
+
const files = [];
|
|
17131
|
+
collectMuseSessionFiles(sessionsDir, files);
|
|
17132
|
+
return files;
|
|
17133
|
+
}
|
|
17134
|
+
|
|
17135
|
+
// The model Muse is configured to use, for rows whose run has no model record
|
|
17136
|
+
// in the portion of the log we read. Reads only the `model` string.
|
|
17137
|
+
function resolveMuseDefaultModel(env = process.env) {
|
|
17138
|
+
const fallback = "muse";
|
|
17139
|
+
try {
|
|
17140
|
+
const configHome = env.XDG_CONFIG_HOME || path.join(env.HOME || require("node:os").homedir(), ".config");
|
|
17141
|
+
const settings = JSON.parse(fssync.readFileSync(path.join(configHome, "muse", "settings.json"), "utf8"));
|
|
17142
|
+
return typeof settings?.model === "string" && settings.model.trim() ? settings.model.trim() : fallback;
|
|
17143
|
+
} catch {
|
|
17144
|
+
return fallback;
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
|
|
17148
|
+
// A permission transaction wraps its records as JSON strings under `children`;
|
|
17149
|
+
// unwrap them so a model or usage record is never missed inside a frame.
|
|
17150
|
+
function* museRecords(entry) {
|
|
17151
|
+
if (entry && Array.isArray(entry.children) && entry.retained_frame) {
|
|
17152
|
+
for (const child of entry.children) {
|
|
17153
|
+
if (!child || typeof child.record_json !== "string") continue;
|
|
17154
|
+
try { yield JSON.parse(child.record_json); } catch { /* skip */ }
|
|
17155
|
+
}
|
|
17156
|
+
return;
|
|
17157
|
+
}
|
|
17158
|
+
yield entry;
|
|
17159
|
+
}
|
|
17160
|
+
|
|
17161
|
+
|
|
17162
|
+
const MUSE_HEAD_FINGERPRINT_BYTES = 256;
|
|
17163
|
+
const MUSE_PREFIX_FINGERPRINT_MAX_BYTES = 8 * 1024 * 1024;
|
|
17164
|
+
|
|
17165
|
+
// Fingerprints for Muse session logs so an in-place rewrite is not mistaken
|
|
17166
|
+
// for an append. Prefer a hash of the entire consumed prefix (capped); fall
|
|
17167
|
+
// back to head+tail samples only when the prefix exceeds the cap.
|
|
17168
|
+
function museRangeFingerprint(fd, offset, length) {
|
|
17169
|
+
const n = Math.max(0, Number(length) || 0);
|
|
17170
|
+
if (n <= 0) return null;
|
|
17171
|
+
const buf = Buffer.alloc(n);
|
|
17172
|
+
const read = fssync.readSync(fd, buf, 0, n, Math.max(0, Number(offset) || 0));
|
|
17173
|
+
if (read <= 0) return null;
|
|
17174
|
+
return crypto.createHash("sha256").update(buf.subarray(0, read)).digest("hex");
|
|
17175
|
+
}
|
|
17176
|
+
|
|
17177
|
+
function museHeadFingerprint(fd, size) {
|
|
17178
|
+
return museRangeFingerprint(fd, 0, Math.min(MUSE_HEAD_FINGERPRINT_BYTES, Math.max(0, Number(size) || 0)));
|
|
17179
|
+
}
|
|
17180
|
+
|
|
17181
|
+
function musePrefixFingerprint(fd, size) {
|
|
17182
|
+
const total = Math.max(0, Number(size) || 0);
|
|
17183
|
+
if (total <= 0) return null;
|
|
17184
|
+
if (total <= MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
|
|
17185
|
+
const hash = crypto.createHash("sha256");
|
|
17186
|
+
let offset = 0;
|
|
17187
|
+
const chunk = Buffer.alloc(Math.min(64 * 1024, total));
|
|
17188
|
+
while (offset < total) {
|
|
17189
|
+
const want = Math.min(chunk.length, total - offset);
|
|
17190
|
+
const read = fssync.readSync(fd, chunk, 0, want, offset);
|
|
17191
|
+
if (read <= 0) break;
|
|
17192
|
+
hash.update(chunk.subarray(0, read));
|
|
17193
|
+
offset += read;
|
|
17194
|
+
}
|
|
17195
|
+
return offset === total ? hash.digest("hex") : null;
|
|
17196
|
+
}
|
|
17197
|
+
// Large logs: sample head and tail of the consumed prefix.
|
|
17198
|
+
const head = museHeadFingerprint(fd, total);
|
|
17199
|
+
const tailLen = Math.min(MUSE_HEAD_FINGERPRINT_BYTES, total);
|
|
17200
|
+
const tail = museRangeFingerprint(fd, total - tailLen, tailLen);
|
|
17201
|
+
return head && tail ? `${head}:${tail}` : null;
|
|
17202
|
+
}
|
|
17203
|
+
|
|
17204
|
+
function museResumeLooksAppendOnly(fd, prevSize, prev, currentPrefixFp, currentMtimeMs, currentSize) {
|
|
17205
|
+
if (prev?.forceRewind === true) return false;
|
|
17206
|
+
if (!(prevSize > 0)) return true;
|
|
17207
|
+
if (typeof prev?.prefixFp === "string" && prev.prefixFp && prev.prefixFp !== currentPrefixFp) return false;
|
|
17208
|
+
// Legacy cursors without prefixFp: reject when the head sample diverges.
|
|
17209
|
+
if (!prev?.prefixFp && typeof prev?.headFp === "string" && prev.headFp) {
|
|
17210
|
+
const head = museHeadFingerprint(fd, prevSize);
|
|
17211
|
+
if (head && head !== prev.headFp) return false;
|
|
17212
|
+
}
|
|
17213
|
+
// Equal-size rewrite above the full-prefix cap (or any equal-size rewrite that
|
|
17214
|
+
// preserves sampled bytes) still bumps mtime on real filesystems. Only apply
|
|
17215
|
+
// when the size is unchanged — growth from an append also changes mtime.
|
|
17216
|
+
if (
|
|
17217
|
+
Number(currentSize) === Number(prevSize) &&
|
|
17218
|
+
Number.isFinite(Number(prev?.mtimeMs)) &&
|
|
17219
|
+
Number.isFinite(Number(currentMtimeMs)) &&
|
|
17220
|
+
Number(prev.mtimeMs) !== Number(currentMtimeMs)
|
|
17221
|
+
) {
|
|
17222
|
+
return false;
|
|
17223
|
+
}
|
|
17224
|
+
// Resume offset must be a JSONL record boundary.
|
|
17225
|
+
const buf = Buffer.alloc(1);
|
|
17226
|
+
const read = fssync.readSync(fd, buf, 0, 1, prevSize - 1);
|
|
17227
|
+
return read === 1 && buf[0] === 0x0a;
|
|
17228
|
+
}
|
|
17229
|
+
|
|
17230
|
+
|
|
17231
|
+
// Subagent logs live under .../sessions/<session>/subagent/... . Matching any
|
|
17232
|
+
// ancestor named "subagent" would misclassify roots when the Muse home path
|
|
17233
|
+
// itself contains that segment (e.g. TOKENTRACKER_MUSE_HOME=/data/subagent/muse).
|
|
17234
|
+
function isMuseSubagentSessionFile(filePath, env = process.env) {
|
|
17235
|
+
const sessionsRoot = path.join(resolveMuseHome(env), "sessions");
|
|
17236
|
+
const relative = path.relative(sessionsRoot, filePath);
|
|
17237
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
17238
|
+
// Outside the configured sessions root — fall back to a local segment check
|
|
17239
|
+
// that still requires the subagent directory to be a parent of session.jsonl.
|
|
17240
|
+
const parts = filePath.split(path.sep);
|
|
17241
|
+
const sessionIdx = parts.lastIndexOf("session.jsonl");
|
|
17242
|
+
return sessionIdx > 0 && parts.slice(0, sessionIdx).includes("subagent");
|
|
17243
|
+
}
|
|
17244
|
+
return relative.split(path.sep).includes("subagent");
|
|
17245
|
+
}
|
|
17246
|
+
|
|
17247
|
+
async function parseMuseIncremental({ sessionFiles, cursors, queuePath, onProgress, env } = {}) {
|
|
17248
|
+
await ensureDir(path.dirname(queuePath));
|
|
17249
|
+
const state = cursors.muse && typeof cursors.muse === "object" ? cursors.muse : {};
|
|
17250
|
+
const seenIds = new Set(Array.isArray(state.seenIds) ? state.seenIds : []);
|
|
17251
|
+
const fileOffsets =
|
|
17252
|
+
state.fileOffsets && typeof state.fileOffsets === "object" ? { ...state.fileOffsets } : {};
|
|
17253
|
+
const files = Array.isArray(sessionFiles) ? sessionFiles : resolveMuseSessionFiles(env || process.env);
|
|
17254
|
+
const defaultModel = resolveMuseDefaultModel(env || process.env);
|
|
17255
|
+
|
|
17256
|
+
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
17257
|
+
const touchedBuckets = new Set();
|
|
17258
|
+
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
17259
|
+
let recordsProcessed = 0;
|
|
17260
|
+
let eventsAggregated = 0;
|
|
17261
|
+
|
|
17262
|
+
const handleRecord = (rec, fileState) => {
|
|
17263
|
+
if (!rec || typeof rec !== "object") return;
|
|
17264
|
+
const payload = rec.payload;
|
|
17265
|
+
if (!payload || typeof payload !== "object") return;
|
|
17266
|
+
if (rec.payload_type === "run.model.configured") {
|
|
17267
|
+
const modelRec = payload.record;
|
|
17268
|
+
const runId = modelRec?.run_stream?.id;
|
|
17269
|
+
if (typeof modelRec?.model_id === "string" && modelRec.model_id) {
|
|
17270
|
+
if (runId) fileState.runModels[runId] = modelRec.model_id;
|
|
17271
|
+
fileState.model = modelRec.model_id;
|
|
17272
|
+
}
|
|
17273
|
+
return;
|
|
17274
|
+
}
|
|
17275
|
+
if (rec.payload_type !== "runtime.session") return;
|
|
17276
|
+
const event = payload.event;
|
|
17277
|
+
if (!event || event.kind !== "goal_usage_attribution") return;
|
|
17278
|
+
const usage = event.record;
|
|
17279
|
+
const quantity = usage?.quantity;
|
|
17280
|
+
if (!quantity || usage.usage_family !== "provider" || quantity.reported !== true) return;
|
|
17281
|
+
const usageId = typeof usage.usage_id === "string" ? usage.usage_id : null;
|
|
17282
|
+
if (!usageId || seenIds.has(usageId)) return;
|
|
17283
|
+
// A rewritten log is re-read from the start, and `seenIds` keeps only the
|
|
17284
|
+
// newest 10k ids. Rows older than the last one counted from this file are
|
|
17285
|
+
// already in the queue, so skip them even if their id was evicted. Rows at
|
|
17286
|
+
// exactly that timestamp are skipped only when their id was counted, so a
|
|
17287
|
+
// new row that shares the timestamp is kept.
|
|
17288
|
+
const micros = Number(rec.recorded_at);
|
|
17289
|
+
if (fileState.skipThrough && Number.isFinite(micros)) {
|
|
17290
|
+
if (micros < fileState.skipThrough) return;
|
|
17291
|
+
if (micros === fileState.skipThrough && fileState.skipIds.has(usageId)) return;
|
|
17292
|
+
}
|
|
17293
|
+
|
|
17294
|
+
recordsProcessed++;
|
|
17295
|
+
const inputTotal = toNonNegativeInt(quantity.input_tokens);
|
|
17296
|
+
const output = toNonNegativeInt(quantity.output_tokens);
|
|
17297
|
+
const cached = Math.min(toNonNegativeInt(quantity.cached_tokens), inputTotal);
|
|
17298
|
+
const reasoning = Math.min(toNonNegativeInt(quantity.reasoning_tokens), output);
|
|
17299
|
+
seenIds.add(usageId);
|
|
17300
|
+
if (inputTotal === 0 && output === 0) return;
|
|
17301
|
+
|
|
17302
|
+
if (!Number.isFinite(micros) || micros <= 0) return;
|
|
17303
|
+
const bucketStart = toUtcHalfHourStart(new Date(Math.floor(micros / 1000)).toISOString());
|
|
17304
|
+
if (!bucketStart) return;
|
|
17305
|
+
|
|
17306
|
+
const input = inputTotal - cached;
|
|
17307
|
+
const model = fileState.runModels[payload.run_id] || fileState.model || defaultModel;
|
|
17308
|
+
// One conversation per root session, charged to its first usage record.
|
|
17309
|
+
// Subagent logs are part of their parent's conversation.
|
|
17310
|
+
const conversations = fileState.counted || fileState.isSubagent ? 0 : 1;
|
|
17311
|
+
fileState.counted = true;
|
|
17312
|
+
const bucket = getHourlyBucket(hourlyState, "muse", model, bucketStart);
|
|
17313
|
+
addTotals(bucket.totals, {
|
|
17314
|
+
input_tokens: input,
|
|
17315
|
+
cached_input_tokens: cached,
|
|
17316
|
+
cache_creation_input_tokens: 0,
|
|
17317
|
+
output_tokens: output,
|
|
17318
|
+
reasoning_output_tokens: reasoning,
|
|
17319
|
+
total_tokens: input + cached + output,
|
|
17320
|
+
conversation_count: conversations,
|
|
17321
|
+
});
|
|
17322
|
+
touchedBuckets.add(bucketKey("muse", model, bucketStart));
|
|
17323
|
+
eventsAggregated++;
|
|
17324
|
+
// Advance only for rows that reached a bucket, remembering the ids that
|
|
17325
|
+
// share the newest timestamp.
|
|
17326
|
+
if (micros > fileState.watermark) {
|
|
17327
|
+
fileState.watermark = micros;
|
|
17328
|
+
fileState.watermarkIds = [usageId];
|
|
17329
|
+
} else if (micros === fileState.watermark && fileState.watermarkIds.length < MUSE_WATERMARK_IDS_MAX) {
|
|
17330
|
+
fileState.watermarkIds.push(usageId);
|
|
17331
|
+
}
|
|
17332
|
+
};
|
|
17333
|
+
|
|
17334
|
+
for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
|
|
17335
|
+
const filePath = files[fileIdx];
|
|
17336
|
+
// Open first and stat the descriptor, so the size and inode describe the
|
|
17337
|
+
// file that is actually read (no check-then-open race).
|
|
17338
|
+
let fd;
|
|
17339
|
+
try { fd = fssync.openSync(filePath, "r"); } catch { continue; }
|
|
17340
|
+
let stat;
|
|
17341
|
+
try { stat = fssync.fstatSync(fd); } catch {
|
|
17342
|
+
try { fssync.closeSync(fd); } catch { /* ignore */ }
|
|
17343
|
+
continue;
|
|
17344
|
+
}
|
|
17345
|
+
|
|
17346
|
+
const prev = fileOffsets[filePath] || {};
|
|
17347
|
+
const prevSize = Number(prev.size) || 0;
|
|
17348
|
+
const inodeChanged = typeof prev.ino === "number" && prev.ino !== stat.ino;
|
|
17349
|
+
const prefixFp = musePrefixFingerprint(fd, Math.min(prevSize, stat.size));
|
|
17350
|
+
const headFp = museHeadFingerprint(fd, stat.size);
|
|
17351
|
+
// Same-inode compaction can shrink then grow past prevSize, rewrite the
|
|
17352
|
+
// middle at equal size, or be observed while empty mid-rewrite. Validate
|
|
17353
|
+
// the consumed-prefix fingerprint (and forceRewind) before append-only.
|
|
17354
|
+
const appendOnly = !inodeChanged && stat.size >= prevSize &&
|
|
17355
|
+
museResumeLooksAppendOnly(fd, prevSize, prev, prefixFp, stat.mtimeMs, stat.size);
|
|
17356
|
+
const rewound = !appendOnly;
|
|
17357
|
+
const startOffset = rewound ? 0 : prevSize;
|
|
17358
|
+
if (stat.size <= startOffset) {
|
|
17359
|
+
const emptyObserved = stat.size <= 0 && prevSize > 0;
|
|
17360
|
+
fileOffsets[filePath] = {
|
|
17361
|
+
...prev,
|
|
17362
|
+
size: Math.min(prevSize, stat.size),
|
|
17363
|
+
mtimeMs: stat.mtimeMs,
|
|
17364
|
+
ino: stat.ino,
|
|
17365
|
+
headFp,
|
|
17366
|
+
prefixFp: stat.size > 0 ? musePrefixFingerprint(fd, stat.size) : null,
|
|
17367
|
+
// An empty observation between truncate and rewrite must not clear
|
|
17368
|
+
// watermark-based replay protection on the next population.
|
|
17369
|
+
forceRewind: emptyObserved || prev.forceRewind === true || rewound,
|
|
17370
|
+
};
|
|
17371
|
+
try { fssync.closeSync(fd); } catch { /* ignore */ }
|
|
17372
|
+
continue;
|
|
17373
|
+
}
|
|
17374
|
+
|
|
17375
|
+
const fileState = {
|
|
17376
|
+
// Keep known model attribution across rewinds: compaction may drop the
|
|
17377
|
+
// original run.model.configured rows while later usage for those runs
|
|
17378
|
+
// remains. Replayed configuration records still overwrite these maps.
|
|
17379
|
+
model: prev.model || null,
|
|
17380
|
+
runModels: { ...(prev.runModels || {}) },
|
|
17381
|
+
// A rewritten log keeps its flag: its usage ids were already counted.
|
|
17382
|
+
counted: prev.counted === true,
|
|
17383
|
+
// Latest usage timestamp (epoch µs) counted from this file; on a rewrite
|
|
17384
|
+
// it becomes the point below which rows are not counted again.
|
|
17385
|
+
watermark: Number(prev.watermark) || 0,
|
|
17386
|
+
watermarkIds: Array.isArray(prev.watermarkIds) ? prev.watermarkIds.slice(0, MUSE_WATERMARK_IDS_MAX) : [],
|
|
17387
|
+
skipThrough: rewound ? Number(prev.watermark) || 0 : 0,
|
|
17388
|
+
skipIds: new Set(rewound && Array.isArray(prev.watermarkIds) ? prev.watermarkIds : []),
|
|
17389
|
+
isSubagent: isMuseSubagentSessionFile(filePath, env || process.env),
|
|
17390
|
+
};
|
|
17391
|
+
|
|
17392
|
+
let consumed = 0;
|
|
17393
|
+
// Hash the bytes we actually parse so a rewrite between read and cursor
|
|
17394
|
+
// save cannot invent a matching fingerprint for unread content.
|
|
17395
|
+
const consumedHash = crypto.createHash("sha256");
|
|
17396
|
+
let hashedPrefix = startOffset === 0;
|
|
17397
|
+
if (startOffset > 0 && startOffset <= MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
|
|
17398
|
+
// Include the already-trusted prefix so the saved fingerprint covers
|
|
17399
|
+
// the full consumed region, not only this sync's delta.
|
|
17400
|
+
let off = 0;
|
|
17401
|
+
const pre = Buffer.alloc(Math.min(64 * 1024, startOffset));
|
|
17402
|
+
while (off < startOffset) {
|
|
17403
|
+
const want = Math.min(pre.length, startOffset - off);
|
|
17404
|
+
const read = fssync.readSync(fd, pre, 0, want, off);
|
|
17405
|
+
if (read <= 0) break;
|
|
17406
|
+
consumedHash.update(pre.subarray(0, read));
|
|
17407
|
+
off += read;
|
|
17408
|
+
}
|
|
17409
|
+
hashedPrefix = off === startOffset;
|
|
17410
|
+
} else if (startOffset > MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
|
|
17411
|
+
hashedPrefix = false;
|
|
17412
|
+
}
|
|
17413
|
+
{
|
|
17414
|
+
let position = startOffset;
|
|
17415
|
+
let carry = Buffer.alloc(0);
|
|
17416
|
+
while (position < stat.size) {
|
|
17417
|
+
const length = Math.min(MUSE_READ_CHUNK_BYTES, stat.size - position);
|
|
17418
|
+
const chunk = Buffer.alloc(length);
|
|
17419
|
+
const read = fssync.readSync(fd, chunk, 0, length, position);
|
|
17420
|
+
if (read <= 0) break;
|
|
17421
|
+
position += read;
|
|
17422
|
+
const data = carry.length ? Buffer.concat([carry, chunk.subarray(0, read)]) : chunk.subarray(0, read);
|
|
17423
|
+
// Only complete lines are consumed, so a record still being written is
|
|
17424
|
+
// read whole on the next sync instead of being skipped.
|
|
17425
|
+
const lastNewline = data.lastIndexOf(0x0a);
|
|
17426
|
+
if (lastNewline < 0) { carry = data; continue; }
|
|
17427
|
+
const complete = data.subarray(0, lastNewline + 1);
|
|
17428
|
+
carry = Buffer.from(data.subarray(lastNewline + 1));
|
|
17429
|
+
if (hashedPrefix) consumedHash.update(complete);
|
|
17430
|
+
consumed += complete.length;
|
|
17431
|
+
for (const line of complete.toString("utf8").split("\n")) {
|
|
17432
|
+
if (!line.trim()) continue;
|
|
17433
|
+
let entry;
|
|
17434
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
17435
|
+
for (const rec of museRecords(entry)) handleRecord(rec, fileState);
|
|
17436
|
+
}
|
|
17437
|
+
}
|
|
17438
|
+
}
|
|
17439
|
+
const runModelKeys = Object.keys(fileState.runModels);
|
|
17440
|
+
const keptRunModels = {};
|
|
17441
|
+
// Bound the cursor: later records reference recent runs.
|
|
17442
|
+
for (const key of runModelKeys.slice(-50)) keptRunModels[key] = fileState.runModels[key];
|
|
17443
|
+
const consumedSize = startOffset + consumed;
|
|
17444
|
+
// Fingerprint from the same descriptor we read — never reopen (CodeQL
|
|
17445
|
+
// file-system-race) and never trust a post-read rewrite.
|
|
17446
|
+
let savedPrefixFp = null;
|
|
17447
|
+
if (hashedPrefix && consumedSize <= MUSE_PREFIX_FINGERPRINT_MAX_BYTES) {
|
|
17448
|
+
savedPrefixFp = consumedHash.digest("hex");
|
|
17449
|
+
} else {
|
|
17450
|
+
try { savedPrefixFp = musePrefixFingerprint(fd, consumedSize); } catch { /* best-effort */ }
|
|
17451
|
+
}
|
|
17452
|
+
fileOffsets[filePath] = {
|
|
17453
|
+
size: consumedSize,
|
|
17454
|
+
mtimeMs: stat.mtimeMs,
|
|
17455
|
+
ino: stat.ino,
|
|
17456
|
+
headFp,
|
|
17457
|
+
prefixFp: savedPrefixFp,
|
|
17458
|
+
forceRewind: false,
|
|
17459
|
+
model: fileState.model,
|
|
17460
|
+
runModels: keptRunModels,
|
|
17461
|
+
counted: fileState.counted,
|
|
17462
|
+
watermark: fileState.watermark,
|
|
17463
|
+
watermarkIds: fileState.watermarkIds,
|
|
17464
|
+
};
|
|
17465
|
+
try { fssync.closeSync(fd); } catch { /* ignore */ }
|
|
17466
|
+
if (cb) {
|
|
17467
|
+
cb({ index: fileIdx + 1, total: files.length, recordsProcessed, eventsAggregated, bucketsQueued: touchedBuckets.size });
|
|
17468
|
+
}
|
|
17469
|
+
}
|
|
17470
|
+
|
|
17471
|
+
const seenArr = Array.from(seenIds);
|
|
17472
|
+
const cappedSeen = seenArr.length > 10_000 ? seenArr.slice(seenArr.length - 10_000) : seenArr;
|
|
17473
|
+
const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
|
|
17474
|
+
const updatedAt = new Date().toISOString();
|
|
17475
|
+
hourlyState.updatedAt = updatedAt;
|
|
17476
|
+
cursors.hourly = hourlyState;
|
|
17477
|
+
cursors.muse = { ...state, seenIds: cappedSeen, fileOffsets, updatedAt };
|
|
17478
|
+
return { recordsProcessed, eventsAggregated, bucketsQueued };
|
|
17479
|
+
}
|
|
17480
|
+
|
|
17082
17481
|
async function parseCraftIncremental({
|
|
17083
17482
|
sessionFiles,
|
|
17084
17483
|
cursors,
|
|
@@ -24865,6 +25264,9 @@ module.exports = {
|
|
|
24865
25264
|
resolveReasonixTelemetryFiles,
|
|
24866
25265
|
normalizeReasonixModel,
|
|
24867
25266
|
parseReasonixIncremental,
|
|
25267
|
+
resolveMuseHome,
|
|
25268
|
+
resolveMuseSessionFiles,
|
|
25269
|
+
parseMuseIncremental,
|
|
24868
25270
|
// Exposed for regression tests covering cache-token accounting.
|
|
24869
25271
|
normalizeGeminiTokens,
|
|
24870
25272
|
normalizeOpencodeTokens,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,K as a}from"./__vite-browser-external-DpZbUcLL.js";import{u as p}from"./DialogTitle-C--sXGjf.js";import{u as c}from"./composite-DD_boZdD.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as o}from"./__vite-browser-external-DpZbUcLL.js";import{h as r,i as t,p as s}from"./react-D1u6HsZx.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};
|