tokentracker-cli 1.4.3 → 1.4.4
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 +4 -0
- package/dashboard/dist/assets/{AchievementBadge-3IYidbrI.js → AchievementBadge-slL-XBCu.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DmZ_ojyM.js → AchievementsPage-BeFkGc5M.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DIYrrzhw.js → AchievementsSection-BrGUIipD.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DMMExCe4.js → BadgeDetailModal-avmK1ODc.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-y6vZNRvC.js → BotAnimated-DaEPdEZi.js} +1 -1
- package/dashboard/dist/assets/{Card-LJX3G3zo.js → Card-CIZCccTA.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-BGBgSXNE.js → CloudCheckoutPage-BhRj0SYO.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-Dij2GTXi.js → CloudPage-CvaN8a54.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-BIky6Fk0.js → CommandPalette-BUNuXmYF.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-DbM99i0s.js → CommunityStatsModal-BCI0WHPr.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-C_NH8vgO.js → ConfirmModal-CT7IwtLR.js} +1 -1
- package/dashboard/dist/assets/{Controls-BEAj_kvr.js → Controls-CtnMyxNH.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DO7h2dfu.js → DashboardPage-BuzyITeL.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-PQXB8v0k.js → DateRangePopover-1Ps1-1dw.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-0JZ3OIWZ.js → DevicePage-Dt-dVQAG.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BpWoD5h9.js → DialogClose-D-DRTF1u.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-BOYDS1mM.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DoQgdh5w.js → DialogTitle-CiiRTn3_.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-0O87VjKv.js → ElectricLogo-k6RFJ2qC.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-9ph1h_AG.js → FadeIn-CNHg83aK.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DCZ92xbH.js → HeaderGithubStar-CIYW5dc3.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-CCKdmuOP.js → HoverTooltip-Dn80DyPM.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-CpyhpmdS.js → IpCheckPage-B29dYfoI.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-spvsPkYy.js → LandingPage-Dzc8_QyW.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-2nJ08ohw.js → LeaderboardPage-DXPcIhyQ.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-D6FSL3Qy.js → LeaderboardProBadge-DxEdj2Zq.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-DD1jX-rD.js → LeaderboardProfileModal-DnKY2223.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-DYqRrntL.js → LeaderboardProfilePage-CY94ngqy.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-DXHikunk.js → LimitsPage-AlvnAPCD.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BURiQAtP.js → LocalOnlyNotice-EHw7BeYS.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-Cv2ijoJx.js → LoginCard-E5E3i2kj.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DyOideDA.js → LoginModal-DxhQI9kh.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BKNvqvYi.js → LoginPage-DRwJn-Fw.js} +1 -1
- package/dashboard/dist/assets/{PetPage-BBmozE0L.js → PetPage-DIpVTdiC.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-DDnISLKM.js → PopoverPopup-Bjs7E91g.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-Da90GDcM.js → ProviderIcon-DqXIjym-.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DsETr50j.js → ResetPasswordPage-DxSRYQgP.js} +1 -1
- package/dashboard/dist/assets/{Select-pZDawyxt.js → Select-BfjrcaiM.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-D5wuuN7G.js → SelectItemText-Dpdlb0cU.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-sm-PARYj.js → SelfHostPage-Cnp_PMaD.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-D4f8h0sL.js → ServiceStatusPage-fBwG-qXq.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-xVLsTN4m.js → SessionsPage-BEk7AHdq.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Dt9iFheY.js → SettingsPage-BgML2jQR.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-BdWalPpd.js → SkillsPage-DcvVZ3Wn.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-d2_ml0tE.js → TokenGalaxy-D29yX2Hb.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-B4masxDW.js → TooltipPopup-CjzmNK2l.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-CceYpa2O.js → TrendMonitor-DfnQrsrx.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CSN0829H.js → WidgetsPage-CRYomYfk.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-gP4WfO_m.js → WrappedPage-CvQI5zUw.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BtJVmciH.js → __vite-browser-external-BZ-i40Kf.js} +2 -2
- package/dashboard/dist/assets/{agent-logos-wxccxfWz.js → agent-logos-CvOX7WXW.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-Bd5C77R2.js → arrow-left-BrQrZJtW.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-C1RqrK4A.js → arrow-right-fC11gmFY.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-B_b2i8hn.js → arrow-up-right-CCMEqQQp.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-C8eYygme.js → calendar-days-OaIQ06qD.js} +1 -1
- package/dashboard/dist/assets/{check-BlBHefQ7.js → check-D888uFpx.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-mjUspqZv.js → chevron-down-BJ0BpS_w.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-CEkMnWXf.js → cloud-checkout-BeOpEJTZ.js} +1 -1
- package/dashboard/dist/assets/{composite-CzXFY9IC.js → composite-CUuXvKos.js} +1 -1
- package/dashboard/dist/assets/{download-CQvg59w1.js → download-BJ7CKwx5.js} +1 -1
- package/dashboard/dist/assets/{external-link-CDYfIZ3Z.js → external-link-CDaZwJVN.js} +1 -1
- package/dashboard/dist/assets/{flame-C9NXQRil.js → flame-ChyUKs-9.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-fKy_3qpW.js → format-tokens-B6WX7zcH.js} +1 -1
- package/dashboard/dist/assets/{gift-WmV4SBGR.js → gift-CQlAZUJ5.js} +1 -1
- package/dashboard/dist/assets/{host-mode-C5MvZ7SV.js → host-mode-Gw6KD5OJ.js} +1 -1
- package/dashboard/dist/assets/{icons-B1EtsaZs.js → icons-C7gGTMuQ.js} +1 -1
- package/dashboard/dist/assets/{index-9EZ3o2RV.js → index-Bm2dDAVL.js} +1 -1
- package/dashboard/dist/assets/{index-DXNwBFNi.js → index-C0pbymQa.js} +1 -1
- package/dashboard/dist/assets/{index-BEecBW06.js → index-CLb64Tlj.js} +1 -1
- package/dashboard/dist/assets/{info-CWDhggIW.js → info-CvOmd88O.js} +1 -1
- package/dashboard/dist/assets/{link-2-DEWNkK7s.js → link-2-BNEPxvlF.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-DotT5gVp.js → loader-circle-bRHxCgY3.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-qZnGgJAz.js → modifiers.esm-D-X0-uvt.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-DOP5eqBD.js → monitor-smartphone-Bo7d-q73.js} +1 -1
- package/dashboard/dist/assets/{play-BUsybi9m.js → play-DqpGELN5.js} +1 -1
- package/dashboard/dist/assets/{plus-DAjojh_m.js → plus-2fA1tz7S.js} +1 -1
- package/dashboard/dist/assets/{provider-display-BCQQYPWD.js → provider-display-B1KigDYZ.js} +1 -1
- package/dashboard/dist/assets/{react-BgpqhoCj.js → react-DYja9tZG.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-C-PFpZSA.js → safePolygon-BerB8y-A.js} +1 -1
- package/dashboard/dist/assets/{search-W1A-6ugo.js → search-CU6UVI1L.js} +1 -1
- package/dashboard/dist/assets/{server-CE7pFjDr.js → server-CZR4PTdb.js} +1 -1
- package/dashboard/dist/assets/{shield-check-KL5xc2FR.js → shield-check-BGbNmrMP.js} +1 -1
- package/dashboard/dist/assets/{skills-api-DL_zLjKA.js → skills-api-CJLEzxDf.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-CehJQvFH.js → sortable.esm-CJ1NZWER.js} +1 -1
- package/dashboard/dist/assets/{store-BHAXkHPR.js → store-CJFdru_6.js} +1 -1
- package/dashboard/dist/assets/{terminal-Br5vyD6Q.js → terminal-ClyQ8tvQ.js} +1 -1
- package/dashboard/dist/assets/{trash-CzaCJP6G.js → trash-Dg2PWl5-.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DbJrecQw.js → use-community-stats-Ca995kap.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-DGhwmlX8.js → use-latest-request-guard-BKbMGVji.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-T7KWAWZ6.js → use-limits-display-prefs-CGyd8lvc.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-MZJhS328.js → use-native-settings-Dqeuqm9O.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-BDN24weA.js → use-ordered-list-Nj7R1iUu.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CGMbDpHQ.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-DUu16y8Z.js → use-session-efficiency-pref-C_H5BaGV.js} +1 -1
- package/dashboard/dist/assets/{use-transform-BczE46xm.js → use-transform-CC90hurD.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CjyDfH-M.js → use-usage-limits-DtaaE7D8.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-BgZmWbkx.js → useCurrency-CMHIL39_.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-dWENyY8z.js → usePositioner-CRSfrGQq.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-D7s1-Zuk.js → useTokenFormat-B5jhM_Kb.js} +1 -1
- package/dashboard/dist/assets/{zap-Dr__bKlq.js → zap-CKx8ySZs.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/sync.js +25 -3
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +334 -52
- package/dashboard/dist/assets/DialogDescription-ne_rYSD7.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-CfCY-K-a.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -10802,14 +10802,102 @@ function workbuddySqliteUsageSnapshot(row) {
|
|
|
10802
10802
|
};
|
|
10803
10803
|
}
|
|
10804
10804
|
|
|
10805
|
+
// WorkBuddy writes OpenAI/DeepSeek-shaped rawUsage on every LLM round-trip.
|
|
10806
|
+
// The totals pass and the project pass below need the same breakdown, so the
|
|
10807
|
+
// arithmetic lives here once. Callers treat total_tokens === 0 as "no billable
|
|
10808
|
+
// usage": every component is non-negative, so that is equivalent to testing
|
|
10809
|
+
// each field for zero.
|
|
10810
|
+
function workbuddyUsageDelta(rawUsage) {
|
|
10811
|
+
const promptTokens = toNonNegativeInt(rawUsage.prompt_tokens);
|
|
10812
|
+
const completionTokens = toNonNegativeInt(rawUsage.completion_tokens);
|
|
10813
|
+
const promptDetails =
|
|
10814
|
+
rawUsage.prompt_tokens_details && typeof rawUsage.prompt_tokens_details === "object"
|
|
10815
|
+
? rawUsage.prompt_tokens_details
|
|
10816
|
+
: {};
|
|
10817
|
+
const completionDetails =
|
|
10818
|
+
rawUsage.completion_tokens_details && typeof rawUsage.completion_tokens_details === "object"
|
|
10819
|
+
? rawUsage.completion_tokens_details
|
|
10820
|
+
: {};
|
|
10821
|
+
|
|
10822
|
+
// Cache reads are mirrored across up to three fields depending on which
|
|
10823
|
+
// upstream the auto-router used; take the largest non-zero mirror.
|
|
10824
|
+
const cacheRead = Math.max(
|
|
10825
|
+
toNonNegativeInt(rawUsage.cache_read_input_tokens),
|
|
10826
|
+
toNonNegativeInt(promptDetails.cached_tokens),
|
|
10827
|
+
toNonNegativeInt(rawUsage.prompt_cache_hit_tokens),
|
|
10828
|
+
);
|
|
10829
|
+
const cacheCreation = toNonNegativeInt(rawUsage.cache_creation_input_tokens);
|
|
10830
|
+
// prompt_tokens is the FULL prompt: subtract BOTH reads and writes so
|
|
10831
|
+
// input_tokens is pure non-cached input (no double-counting cache writes).
|
|
10832
|
+
const inputTokens = Math.max(0, promptTokens - cacheRead - cacheCreation);
|
|
10833
|
+
// completion_tokens INCLUDES reasoning (verified: total == prompt+completion).
|
|
10834
|
+
const reasoningTokens = Math.min(
|
|
10835
|
+
completionTokens,
|
|
10836
|
+
toNonNegativeInt(completionDetails.reasoning_tokens),
|
|
10837
|
+
);
|
|
10838
|
+
const outputTokens = Math.max(0, completionTokens - reasoningTokens);
|
|
10839
|
+
|
|
10840
|
+
return {
|
|
10841
|
+
input_tokens: inputTokens,
|
|
10842
|
+
cached_input_tokens: cacheRead,
|
|
10843
|
+
cache_creation_input_tokens: cacheCreation,
|
|
10844
|
+
output_tokens: outputTokens,
|
|
10845
|
+
reasoning_output_tokens: reasoningTokens,
|
|
10846
|
+
total_tokens: inputTokens + outputTokens + cacheRead + cacheCreation + reasoningTokens,
|
|
10847
|
+
conversation_count: 1,
|
|
10848
|
+
};
|
|
10849
|
+
}
|
|
10850
|
+
|
|
10851
|
+
// WorkBuddy stamps `cwd` on its records rather than in a session header, and a
|
|
10852
|
+
// session can open with a record that lacks it while later appends omit it
|
|
10853
|
+
// altogether. Resolve it from a bounded prefix of the file so every record in
|
|
10854
|
+
// the incremental window is attributed, not just the ones that happen to carry
|
|
10855
|
+
// their own cwd. A file with no recoverable cwd stays outside project
|
|
10856
|
+
// attribution — there is nothing to attribute it to.
|
|
10857
|
+
async function resolveWorkbuddyFileCwd(filePath, maxScanBytes = 8 * 1024 * 1024) {
|
|
10858
|
+
let stream;
|
|
10859
|
+
try {
|
|
10860
|
+
const stat = fssync.statSync(filePath);
|
|
10861
|
+
if (stat.size <= 0) return null;
|
|
10862
|
+
stream = fssync.createReadStream(filePath, {
|
|
10863
|
+
start: 0,
|
|
10864
|
+
end: Math.min(stat.size, maxScanBytes) - 1,
|
|
10865
|
+
});
|
|
10866
|
+
} catch {
|
|
10867
|
+
return null;
|
|
10868
|
+
}
|
|
10869
|
+
let cwd = null;
|
|
10870
|
+
try {
|
|
10871
|
+
for await (const record of physicalJsonlRecords(stream, {
|
|
10872
|
+
invalidUtf8: "record",
|
|
10873
|
+
maxRecordBytes: OMP_LIKE_MAX_RECORD_BYTES,
|
|
10874
|
+
})) {
|
|
10875
|
+
if (!record.line || !record.line.trim()) continue;
|
|
10876
|
+
let entry;
|
|
10877
|
+
try { entry = JSON.parse(record.line); } catch { continue; }
|
|
10878
|
+
if (entry && typeof entry === "object" && typeof entry.cwd === "string" && entry.cwd) {
|
|
10879
|
+
cwd = entry.cwd;
|
|
10880
|
+
break;
|
|
10881
|
+
}
|
|
10882
|
+
}
|
|
10883
|
+
} catch {
|
|
10884
|
+
// No recoverable cwd — the file stays un-attributed.
|
|
10885
|
+
} finally {
|
|
10886
|
+
try { stream.destroy(); } catch {}
|
|
10887
|
+
}
|
|
10888
|
+
return cwd;
|
|
10889
|
+
}
|
|
10890
|
+
|
|
10805
10891
|
async function parseWorkbuddyIncremental({
|
|
10806
10892
|
projectFiles,
|
|
10807
10893
|
cursors,
|
|
10808
10894
|
queuePath,
|
|
10895
|
+
projectQueuePath,
|
|
10809
10896
|
onProgress,
|
|
10810
10897
|
env,
|
|
10811
10898
|
defaultModel,
|
|
10812
10899
|
source = "workbuddy",
|
|
10900
|
+
publicRepoResolver,
|
|
10813
10901
|
} = {}) {
|
|
10814
10902
|
await ensureDir(path.dirname(queuePath));
|
|
10815
10903
|
// `source` doubles as the variant selector and the bucket source key, so the
|
|
@@ -10841,6 +10929,45 @@ async function parseWorkbuddyIncremental({
|
|
|
10841
10929
|
);
|
|
10842
10930
|
const detailedSessionsWithUsage = new Set();
|
|
10843
10931
|
|
|
10932
|
+
// The shared normalizers copy the bucket maps but alias each bucket/totals
|
|
10933
|
+
// object, and the enqueue helpers stamp queuedKey before appendFile runs.
|
|
10934
|
+
// Give the buckets this parser can mutate private copies so a failed append
|
|
10935
|
+
// leaves the caller's published state untouched — the same staging contract
|
|
10936
|
+
// the Devin reader uses. Callers retry from unchanged offsets, so a polluted
|
|
10937
|
+
// published state would add the same usage twice.
|
|
10938
|
+
const ownsWorkbuddyBucket = (key) =>
|
|
10939
|
+
(normalizeSourceInput(key.split(BUCKET_SEPARATOR)[0]) || DEFAULT_SOURCE) === variant;
|
|
10940
|
+
const ownsWorkbuddyProjectBucket = (key) => {
|
|
10941
|
+
// Project keys are `projectKey|source|hour`; a projectKey may itself
|
|
10942
|
+
// contain the separator, so read the source segment from the right.
|
|
10943
|
+
const last = key.lastIndexOf(BUCKET_SEPARATOR);
|
|
10944
|
+
const prev = last > 0 ? key.lastIndexOf(BUCKET_SEPARATOR, last - 1) : -1;
|
|
10945
|
+
const source = prev >= 0 ? key.slice(prev + 1, last) : "";
|
|
10946
|
+
return (normalizeSourceInput(source) || DEFAULT_SOURCE) === variant;
|
|
10947
|
+
};
|
|
10948
|
+
|
|
10949
|
+
// Project attribution runs as a second, independent pass over the same JSONL
|
|
10950
|
+
// files (see "Project pass" below). It keeps its own offsets and seen-set, so
|
|
10951
|
+
// an install that already has totals backfills project history on upgrade
|
|
10952
|
+
// without re-counting a single token in the totals queue.
|
|
10953
|
+
const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
|
|
10954
|
+
const projectState = projectEnabled ? normalizeProjectState(cursors?.projectHourly) : null;
|
|
10955
|
+
if (projectState) {
|
|
10956
|
+
projectState.buckets = stageProviderBuckets(projectState.buckets, ownsWorkbuddyProjectBucket);
|
|
10957
|
+
}
|
|
10958
|
+
const projectTouchedBuckets = projectEnabled ? new Set() : null;
|
|
10959
|
+
const projectMetaCache = projectEnabled ? new Map() : null;
|
|
10960
|
+
const publicRepoCache = projectEnabled ? new Map() : null;
|
|
10961
|
+
const projectSeenIds = new Set(
|
|
10962
|
+
Array.isArray(workbuddyState.projectSeenIds) ? workbuddyState.projectSeenIds : [],
|
|
10963
|
+
);
|
|
10964
|
+
const projectFileOffsets =
|
|
10965
|
+
workbuddyState.projectFileOffsets && typeof workbuddyState.projectFileOffsets === "object"
|
|
10966
|
+
? { ...workbuddyState.projectFileOffsets }
|
|
10967
|
+
: {};
|
|
10968
|
+
let projectBucketsQueued = 0;
|
|
10969
|
+
let projectCursorUpdated = false;
|
|
10970
|
+
|
|
10844
10971
|
const allFiles = Array.isArray(projectFiles)
|
|
10845
10972
|
? projectFiles
|
|
10846
10973
|
: resolveWorkbuddyProjectFiles(env || process.env, variant);
|
|
@@ -10866,10 +10993,15 @@ async function parseWorkbuddyIncremental({
|
|
|
10866
10993
|
detailedSessions,
|
|
10867
10994
|
updatedAt: new Date().toISOString(),
|
|
10868
10995
|
};
|
|
10869
|
-
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
10996
|
+
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0, projectBucketsQueued: 0, projectCursorUpdated: false };
|
|
10870
10997
|
}
|
|
10871
10998
|
|
|
10872
10999
|
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
11000
|
+
hourlyState.buckets = stageProviderBuckets(hourlyState.buckets, ownsWorkbuddyBucket);
|
|
11001
|
+
hourlyState.groupQueued =
|
|
11002
|
+
hourlyState.groupQueued && typeof hourlyState.groupQueued === "object"
|
|
11003
|
+
? { ...hourlyState.groupQueued }
|
|
11004
|
+
: {};
|
|
10873
11005
|
const touchedBuckets = new Set();
|
|
10874
11006
|
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
10875
11007
|
let recordsProcessed = 0;
|
|
@@ -10941,39 +11073,8 @@ async function parseWorkbuddyIncremental({
|
|
|
10941
11073
|
|
|
10942
11074
|
recordsProcessed++;
|
|
10943
11075
|
|
|
10944
|
-
const
|
|
10945
|
-
|
|
10946
|
-
const promptDetails =
|
|
10947
|
-
rawUsage.prompt_tokens_details && typeof rawUsage.prompt_tokens_details === "object"
|
|
10948
|
-
? rawUsage.prompt_tokens_details
|
|
10949
|
-
: {};
|
|
10950
|
-
const completionDetails =
|
|
10951
|
-
rawUsage.completion_tokens_details && typeof rawUsage.completion_tokens_details === "object"
|
|
10952
|
-
? rawUsage.completion_tokens_details
|
|
10953
|
-
: {};
|
|
10954
|
-
|
|
10955
|
-
// Cache reads are mirrored across up to three fields depending on which
|
|
10956
|
-
// upstream the auto-router used; take the largest non-zero mirror.
|
|
10957
|
-
const cacheRead = Math.max(
|
|
10958
|
-
toNonNegativeInt(rawUsage.cache_read_input_tokens),
|
|
10959
|
-
toNonNegativeInt(promptDetails.cached_tokens),
|
|
10960
|
-
toNonNegativeInt(rawUsage.prompt_cache_hit_tokens),
|
|
10961
|
-
);
|
|
10962
|
-
const cacheCreation = toNonNegativeInt(rawUsage.cache_creation_input_tokens);
|
|
10963
|
-
// prompt_tokens is the FULL prompt: subtract BOTH reads and writes so
|
|
10964
|
-
// input_tokens is pure non-cached input (no double-counting cache writes).
|
|
10965
|
-
const inputTokens = Math.max(0, promptTokens - cacheRead - cacheCreation);
|
|
10966
|
-
// completion_tokens INCLUDES reasoning (verified: total == prompt+completion).
|
|
10967
|
-
const reasoningTokens = Math.min(completionTokens, toNonNegativeInt(completionDetails.reasoning_tokens));
|
|
10968
|
-
const outputTokens = Math.max(0, completionTokens - reasoningTokens);
|
|
10969
|
-
|
|
10970
|
-
if (
|
|
10971
|
-
inputTokens === 0 &&
|
|
10972
|
-
outputTokens === 0 &&
|
|
10973
|
-
cacheRead === 0 &&
|
|
10974
|
-
cacheCreation === 0 &&
|
|
10975
|
-
reasoningTokens === 0
|
|
10976
|
-
) {
|
|
11076
|
+
const delta = workbuddyUsageDelta(rawUsage);
|
|
11077
|
+
if (delta.total_tokens === 0) {
|
|
10977
11078
|
seenIds.add(messageId);
|
|
10978
11079
|
continue;
|
|
10979
11080
|
}
|
|
@@ -10992,17 +11093,6 @@ async function parseWorkbuddyIncremental({
|
|
|
10992
11093
|
normalizeModelInput(entry.model) ||
|
|
10993
11094
|
fallbackModel;
|
|
10994
11095
|
|
|
10995
|
-
const delta = {
|
|
10996
|
-
input_tokens: inputTokens,
|
|
10997
|
-
cached_input_tokens: cacheRead,
|
|
10998
|
-
cache_creation_input_tokens: cacheCreation,
|
|
10999
|
-
output_tokens: outputTokens,
|
|
11000
|
-
reasoning_output_tokens: reasoningTokens,
|
|
11001
|
-
total_tokens:
|
|
11002
|
-
inputTokens + outputTokens + cacheRead + cacheCreation + reasoningTokens,
|
|
11003
|
-
conversation_count: 1,
|
|
11004
|
-
};
|
|
11005
|
-
|
|
11006
11096
|
const bucket = getHourlyBucket(hourlyState, variant, model, bucketStart);
|
|
11007
11097
|
addTotals(bucket.totals, delta);
|
|
11008
11098
|
touchedBuckets.add(bucketKey(variant, model, bucketStart));
|
|
@@ -11036,6 +11126,165 @@ async function parseWorkbuddyIncremental({
|
|
|
11036
11126
|
};
|
|
11037
11127
|
}
|
|
11038
11128
|
|
|
11129
|
+
// ── Project pass ───────────────────────────────────────────────────────
|
|
11130
|
+
// WorkBuddy stamps `cwd` on its records, so unlike the trace summaries below
|
|
11131
|
+
// there is nothing to infer: attribute each round-trip to the git repository
|
|
11132
|
+
// its cwd belongs to. This is a second loop over the same files with its own
|
|
11133
|
+
// cursor, so an install that already has totals backfills project history on
|
|
11134
|
+
// upgrade without touching (or double-counting) them.
|
|
11135
|
+
if (projectEnabled) {
|
|
11136
|
+
for (let fileIdx = 0; fileIdx < jsonlFiles.length; fileIdx++) {
|
|
11137
|
+
const filePath = jsonlFiles[fileIdx];
|
|
11138
|
+
let stat;
|
|
11139
|
+
try { stat = fssync.statSync(filePath); } catch { continue; }
|
|
11140
|
+
|
|
11141
|
+
const prevEntry = projectFileOffsets[filePath] || {};
|
|
11142
|
+
const prevSize = Number(prevEntry.size) || 0;
|
|
11143
|
+
const prevIno = prevEntry.ino;
|
|
11144
|
+
const inodeChanged = typeof prevIno === "number" && prevIno !== stat.ino;
|
|
11145
|
+
const truncated = stat.size < prevSize;
|
|
11146
|
+
const startOffset = truncated || inodeChanged ? 0 : prevSize;
|
|
11147
|
+
if (stat.size <= startOffset) continue;
|
|
11148
|
+
|
|
11149
|
+
// cwd is fixed for a session file's lifetime. Reuse the resolved value
|
|
11150
|
+
// while it is the same file, otherwise read it from the file head — not
|
|
11151
|
+
// from the incremental window, which may hold only appends that omit it.
|
|
11152
|
+
// Truncation (same inode, smaller size) must drop the cached cwd: the
|
|
11153
|
+
// replacement transcript may belong to a different checkout.
|
|
11154
|
+
let cwd =
|
|
11155
|
+
!inodeChanged && !truncated && typeof prevEntry.cwd === "string"
|
|
11156
|
+
? prevEntry.cwd
|
|
11157
|
+
: null;
|
|
11158
|
+
|
|
11159
|
+
// No recoverable cwd yet: do not advance past history. A later append
|
|
11160
|
+
// may stamp cwd, and early records must still be attributable then.
|
|
11161
|
+
// Check the unchanged-probe cache BEFORE the head scan so idle syncs do
|
|
11162
|
+
// not re-read up to 8 MiB of JSONL on every pass.
|
|
11163
|
+
if (
|
|
11164
|
+
!cwd &&
|
|
11165
|
+
Number(prevEntry.noCwdProbedSize) === stat.size &&
|
|
11166
|
+
prevEntry.ino === stat.ino &&
|
|
11167
|
+
Number(prevEntry.size) === startOffset
|
|
11168
|
+
) {
|
|
11169
|
+
continue;
|
|
11170
|
+
}
|
|
11171
|
+
if (!cwd) cwd = await resolveWorkbuddyFileCwd(filePath);
|
|
11172
|
+
if (!cwd) {
|
|
11173
|
+
projectCursorUpdated = true;
|
|
11174
|
+
projectFileOffsets[filePath] = {
|
|
11175
|
+
size: startOffset,
|
|
11176
|
+
mtimeMs: stat.mtimeMs,
|
|
11177
|
+
ino: stat.ino,
|
|
11178
|
+
noCwdProbedSize: stat.size,
|
|
11179
|
+
};
|
|
11180
|
+
continue;
|
|
11181
|
+
}
|
|
11182
|
+
|
|
11183
|
+
let projectRef = null;
|
|
11184
|
+
let projectKey = null;
|
|
11185
|
+
const projectContext = await resolveProjectContextForPath({
|
|
11186
|
+
startDir: wsl.mapWslCwdToUnc(cwd, filePath),
|
|
11187
|
+
projectMetaCache,
|
|
11188
|
+
publicRepoCache,
|
|
11189
|
+
publicRepoResolver,
|
|
11190
|
+
projectState,
|
|
11191
|
+
});
|
|
11192
|
+
projectRef = projectContext?.projectRef || null;
|
|
11193
|
+
projectKey = projectContext?.projectKey || null;
|
|
11194
|
+
|
|
11195
|
+
// cwd resolved but the repo is not yet public_verified (or has no git
|
|
11196
|
+
// checkout): keep the offset unadvanced so a later sync can retry, but
|
|
11197
|
+
// do not JSON-parse the transcript on every pass — there is nothing to
|
|
11198
|
+
// attribute until both identifiers exist.
|
|
11199
|
+
if (!projectKey || !projectRef) {
|
|
11200
|
+
projectCursorUpdated = true;
|
|
11201
|
+
projectFileOffsets[filePath] = {
|
|
11202
|
+
size: startOffset,
|
|
11203
|
+
mtimeMs: stat.mtimeMs,
|
|
11204
|
+
ino: stat.ino,
|
|
11205
|
+
cwd,
|
|
11206
|
+
};
|
|
11207
|
+
continue;
|
|
11208
|
+
}
|
|
11209
|
+
|
|
11210
|
+
const committedEndOffset = await scanOmpLikeTranscript(
|
|
11211
|
+
filePath,
|
|
11212
|
+
startOffset,
|
|
11213
|
+
OMP_LIKE_MAX_RECORD_BYTES,
|
|
11214
|
+
(entry) => {
|
|
11215
|
+
// Mirror the totals pass: a JSON null / non-object line must not
|
|
11216
|
+
// abort the whole WorkBuddy parse (and strand both cursors).
|
|
11217
|
+
if (!entry || typeof entry !== "object") return;
|
|
11218
|
+
|
|
11219
|
+
const provider = entry.providerData;
|
|
11220
|
+
const rawUsage = provider && typeof provider === "object" ? provider.rawUsage : null;
|
|
11221
|
+
if (!rawUsage || typeof rawUsage !== "object") return;
|
|
11222
|
+
|
|
11223
|
+
// Mirror the totals pass's suppression rules so the project queue
|
|
11224
|
+
// never reports a session the totals queue deliberately skipped.
|
|
11225
|
+
const sessionId =
|
|
11226
|
+
typeof entry.sessionId === "string" && entry.sessionId
|
|
11227
|
+
? entry.sessionId
|
|
11228
|
+
: path.basename(filePath, ".jsonl");
|
|
11229
|
+
const sqliteSession = sqliteSessions[sessionId];
|
|
11230
|
+
if (sqliteSession?.detailed || tracedSessionIds.has(sessionId)) return;
|
|
11231
|
+
|
|
11232
|
+
const tsMs =
|
|
11233
|
+
Number.isFinite(Number(entry.timestamp)) && Number(entry.timestamp) > 0
|
|
11234
|
+
? Number(entry.timestamp)
|
|
11235
|
+
: null;
|
|
11236
|
+
if (tsMs == null) return;
|
|
11237
|
+
|
|
11238
|
+
// Same response-level id preference as the totals pass, so both
|
|
11239
|
+
// queues dedup on identical keys.
|
|
11240
|
+
const messageId =
|
|
11241
|
+
typeof provider?.messageId === "string" && provider.messageId
|
|
11242
|
+
? provider.messageId
|
|
11243
|
+
: typeof entry.uuid === "string" && entry.uuid
|
|
11244
|
+
? entry.uuid
|
|
11245
|
+
: typeof entry.id === "string" && entry.id
|
|
11246
|
+
? entry.id
|
|
11247
|
+
: `${sessionId}:${tsMs}`;
|
|
11248
|
+
if (projectSeenIds.has(messageId)) return;
|
|
11249
|
+
projectSeenIds.add(messageId);
|
|
11250
|
+
// The totals pass also registers the raw entry id so an older or
|
|
11251
|
+
// rewritten copy of the same record cannot be counted twice; the
|
|
11252
|
+
// project pass has to keep the same alias.
|
|
11253
|
+
if (typeof entry.id === "string" && entry.id && entry.id !== messageId) {
|
|
11254
|
+
projectSeenIds.add(entry.id);
|
|
11255
|
+
}
|
|
11256
|
+
|
|
11257
|
+
const delta = workbuddyUsageDelta(rawUsage);
|
|
11258
|
+
if (delta.total_tokens === 0) return;
|
|
11259
|
+
|
|
11260
|
+
const bucketStart = toUtcHalfHourStart(new Date(tsMs).toISOString());
|
|
11261
|
+
if (!bucketStart) return;
|
|
11262
|
+
|
|
11263
|
+
const projectBucket = getProjectBucket(
|
|
11264
|
+
projectState,
|
|
11265
|
+
projectKey,
|
|
11266
|
+
source,
|
|
11267
|
+
bucketStart,
|
|
11268
|
+
projectRef,
|
|
11269
|
+
);
|
|
11270
|
+
addTotals(projectBucket.totals, delta);
|
|
11271
|
+
projectTouchedBuckets.add(projectBucketKey(projectKey, source, bucketStart));
|
|
11272
|
+
},
|
|
11273
|
+
);
|
|
11274
|
+
|
|
11275
|
+
// Commit only past records that were read in full: a record appended
|
|
11276
|
+
// while this pass was running, or an unfinished tail line, has to stay
|
|
11277
|
+
// visible to the next sync. Only advance after successful attribution.
|
|
11278
|
+
projectCursorUpdated = true;
|
|
11279
|
+
projectFileOffsets[filePath] = {
|
|
11280
|
+
size: committedEndOffset,
|
|
11281
|
+
mtimeMs: stat.mtimeMs,
|
|
11282
|
+
ino: stat.ino,
|
|
11283
|
+
cwd,
|
|
11284
|
+
};
|
|
11285
|
+
}
|
|
11286
|
+
}
|
|
11287
|
+
|
|
11039
11288
|
// Trace summaries are a fallback for sessions whose JSONL has no detailed
|
|
11040
11289
|
// providerData.rawUsage. A trace's modelInfo totals include the cache read
|
|
11041
11290
|
// portion of input, so split input into uncached input + cached input. Do
|
|
@@ -11294,6 +11543,23 @@ async function parseWorkbuddyIncremental({
|
|
|
11294
11543
|
? tracedSessionArr.slice(tracedSessionArr.length - 10_000)
|
|
11295
11544
|
: tracedSessionArr;
|
|
11296
11545
|
|
|
11546
|
+
const projectSeenArr = Array.from(projectSeenIds);
|
|
11547
|
+
const cappedProjectSeenIds =
|
|
11548
|
+
projectSeenArr.length > 10_000
|
|
11549
|
+
? projectSeenArr.slice(projectSeenArr.length - 10_000)
|
|
11550
|
+
: projectSeenArr;
|
|
11551
|
+
|
|
11552
|
+
// Publish only after both appends succeed: a throw here leaves `cursors`
|
|
11553
|
+
// untouched, so a retry re-derives the same contribution from the same
|
|
11554
|
+
// offsets instead of adding it on top of a half-applied state.
|
|
11555
|
+
projectBucketsQueued = projectEnabled
|
|
11556
|
+
? await enqueueTouchedProjectBuckets({
|
|
11557
|
+
projectQueuePath,
|
|
11558
|
+
projectState,
|
|
11559
|
+
projectTouchedBuckets,
|
|
11560
|
+
})
|
|
11561
|
+
: 0;
|
|
11562
|
+
|
|
11297
11563
|
const bucketsQueued = await enqueueTouchedBuckets({
|
|
11298
11564
|
queuePath,
|
|
11299
11565
|
hourlyState,
|
|
@@ -11302,6 +11568,10 @@ async function parseWorkbuddyIncremental({
|
|
|
11302
11568
|
const updatedAt = new Date().toISOString();
|
|
11303
11569
|
hourlyState.updatedAt = updatedAt;
|
|
11304
11570
|
cursors.hourly = hourlyState;
|
|
11571
|
+
if (projectState) {
|
|
11572
|
+
projectState.updatedAt = updatedAt;
|
|
11573
|
+
cursors.projectHourly = projectState;
|
|
11574
|
+
}
|
|
11305
11575
|
cursors[variant] = {
|
|
11306
11576
|
...workbuddyState,
|
|
11307
11577
|
seenIds: cappedSeen,
|
|
@@ -11310,10 +11580,22 @@ async function parseWorkbuddyIncremental({
|
|
|
11310
11580
|
fileOffsets,
|
|
11311
11581
|
sqliteSessions: cappedSqliteSessions,
|
|
11312
11582
|
detailedSessions: cappedDetailedSessions,
|
|
11583
|
+
...(projectEnabled
|
|
11584
|
+
? {
|
|
11585
|
+
projectSeenIds: cappedProjectSeenIds,
|
|
11586
|
+
projectFileOffsets,
|
|
11587
|
+
}
|
|
11588
|
+
: {}),
|
|
11313
11589
|
updatedAt,
|
|
11314
11590
|
};
|
|
11315
11591
|
|
|
11316
|
-
return {
|
|
11592
|
+
return {
|
|
11593
|
+
recordsProcessed,
|
|
11594
|
+
eventsAggregated,
|
|
11595
|
+
bucketsQueued,
|
|
11596
|
+
projectBucketsQueued,
|
|
11597
|
+
projectCursorUpdated: projectEnabled && projectCursorUpdated,
|
|
11598
|
+
};
|
|
11317
11599
|
}
|
|
11318
11600
|
|
|
11319
11601
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -14151,14 +14433,14 @@ function devinStringMap(value) {
|
|
|
14151
14433
|
// Stage only the bucket objects this parser can mutate. The shared
|
|
14152
14434
|
// normalizers copy the bucket maps but alias each bucket/totals object, and
|
|
14153
14435
|
// the enqueue helpers stamp queuedKey before appendFile runs — so a failed
|
|
14154
|
-
// append used to leave the caller's published state polluted. Only
|
|
14155
|
-
//
|
|
14436
|
+
// append used to leave the caller's published state polluted. Only entries
|
|
14437
|
+
// the calling parser owns get private copies; every other provider's buckets
|
|
14156
14438
|
// stay shared read-only references.
|
|
14157
|
-
function
|
|
14439
|
+
function stageProviderBuckets(buckets, isOwnedBucket) {
|
|
14158
14440
|
const staged = {};
|
|
14159
14441
|
for (const [key, bucket] of Object.entries(buckets || {})) {
|
|
14160
14442
|
staged[key] =
|
|
14161
|
-
bucket && typeof bucket === "object" &&
|
|
14443
|
+
bucket && typeof bucket === "object" && isOwnedBucket(key, bucket)
|
|
14162
14444
|
? {
|
|
14163
14445
|
...bucket,
|
|
14164
14446
|
totals:
|
|
@@ -14247,7 +14529,7 @@ async function parseDevinIncremental({
|
|
|
14247
14529
|
// rows recover either queue. Unrelated providers' buckets stay shared
|
|
14248
14530
|
// read-only references (this parser never writes their keys).
|
|
14249
14531
|
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
14250
|
-
hourlyState.buckets =
|
|
14532
|
+
hourlyState.buckets = stageProviderBuckets(
|
|
14251
14533
|
hourlyState.buckets,
|
|
14252
14534
|
(key) =>
|
|
14253
14535
|
(normalizeSourceInput(parseBucketKey(key).source) || DEFAULT_SOURCE) ===
|
|
@@ -14266,7 +14548,7 @@ async function parseDevinIncremental({
|
|
|
14266
14548
|
if (projectState) {
|
|
14267
14549
|
// Project bucket keys are `projectKey|source|hourStart`; this parser only
|
|
14268
14550
|
// ever looks up keys whose middle segment is the devin source.
|
|
14269
|
-
projectState.buckets =
|
|
14551
|
+
projectState.buckets = stageProviderBuckets(projectState.buckets, (key) => {
|
|
14270
14552
|
const last = key.lastIndexOf(BUCKET_SEPARATOR);
|
|
14271
14553
|
const prev = last > 0 ? key.lastIndexOf(BUCKET_SEPARATOR, last - 1) : -1;
|
|
14272
14554
|
const source = prev >= 0 ? key.slice(prev + 1, last) : "";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,K as a}from"./__vite-browser-external-BtJVmciH.js";import{u as p}from"./DialogTitle-DoQgdh5w.js";import{u as c}from"./composite-CzXFY9IC.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-BtJVmciH.js";import{h as r,i as t,p as s}from"./react-BgpqhoCj.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};
|