tokentracker-cli 0.97.2 → 0.99.0
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/dashboard/dist/assets/AchievementBadge-kRsjlB3p.js +1 -0
- package/dashboard/dist/assets/{AchievementsPage-CnkSNpJx.js → AchievementsPage-C3xybY0k.js} +2 -2
- package/dashboard/dist/assets/AchievementsSection-y5mok2w0.js +1 -0
- package/dashboard/dist/assets/{BadgeDetailModal-BJBrgn24.js → BadgeDetailModal-IFLXYuKp.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-BJM20N9T.js → BotAnimated-D6x0PDt2.js} +1 -1
- package/dashboard/dist/assets/{Card-BRWOMoBk.js → Card-CiKwEyVu.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-CtX8cne4.js → ClawdAnimated-BQFcAIOy.js} +2 -2
- package/dashboard/dist/assets/CommandPalette-BvpX8PM_.js +1 -0
- package/dashboard/dist/assets/CommunityStatsModal-D5rNlwfO.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-ZZXg_OLV.js → ConfirmModal-CR0Evu8-.js} +1 -1
- package/dashboard/dist/assets/{Controls-VDvgw_ed.js → Controls-lyLUkbJW.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-X8CLr_au.js +1 -0
- package/dashboard/dist/assets/{DevicePage-CP5m8azV.js → DevicePage-RCIANm45.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-8pTCallP.js → DialogClose-Bplj_718.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-C2EzE7DU.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-BWYdq4TT.js → DialogTitle-Ckzf1tdi.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CEJfaKnL.js → FadeIn-C2Jqe95f.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-Bg2vijF8.js → HeaderGithubStar-DgE7Cd5u.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-B03JQ3PO.js → HoverTooltip-CEmo0N-P.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DYZucBOf.js → IpCheckPage-BwkpsLaF.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-m-byhnVx.js → LandingPage-CA2klwbE.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-JtGhJC1s.js → LeaderboardAvatar-B_PUenGZ.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-D6HGW3jX.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-Cm4DDrcY.js → LeaderboardProfileModal-Bfo1dSsS.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-bKbMm856.js → LeaderboardProfilePage-2OqwrqOs.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-B271tZOj.js → LimitsPage-BHrkLXHZ.js} +5 -5
- package/dashboard/dist/assets/{LocalOnlyNotice-DKIxuBP0.js → LocalOnlyNotice-C45MmWYt.js} +1 -1
- package/dashboard/dist/assets/LoginCard-BikBibax.js +1 -0
- package/dashboard/dist/assets/{LoginModal-PxTgIoLk.js → LoginModal-D0Aq85Zg.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-uEUnU7NP.js → LoginPage-B-kQaDQ_.js} +1 -1
- package/dashboard/dist/assets/PetPage-ZgPREiwD.js +1 -0
- package/dashboard/dist/assets/{PopoverPopup-DWIMb8D6.js → PopoverPopup-eQgg3h1i.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-DqqwXu3b.js → ProviderIcon-nDBPxn0N.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BVuJTR9A.js → ResetPasswordPage-CjsOtiGr.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-iHtWKWtc.js → SegmentedControl-6NcttkEH.js} +1 -1
- package/dashboard/dist/assets/{Select-Cr9wq8mD.js → Select-CzEhPICe.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BWnP4yuq.js → SelectItemText-BwvzfXwj.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-DLuiUUYV.js → ServiceStatusPage-C35wpxrT.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-CBAH4ieT.js → SessionsPage-a30adkhf.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-C_7ZPBUF.js +2 -0
- package/dashboard/dist/assets/SkillsPage-DsxkFnrV.js +1 -0
- package/dashboard/dist/assets/TokenGalaxy-D_bXRotA.js +4426 -0
- package/dashboard/dist/assets/ToolbarRootContext-CL7ma7ba.js +1 -0
- package/dashboard/dist/assets/{TrendMonitor-CCzwGDst.js → TrendMonitor-QZBF2IEY.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-CcWCidAd.js → WidgetsPage-CWlxR0H-.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-B6dn8eyh.js → WrappedPage-D8XQ72nF.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-CTGqjrCe.js +45 -0
- package/dashboard/dist/assets/{agent-logos-Dh2k8E_G.js → agent-logos-BscA25fS.js} +1 -1
- package/dashboard/dist/assets/arrow-up-right-DTxCEGbR.js +1 -0
- package/dashboard/dist/assets/check-CCEoh2wj.js +1 -0
- package/dashboard/dist/assets/{composite-CSBcryzA.js → composite-B2s880f5.js} +1 -1
- package/dashboard/dist/assets/copy-CJVQglwG.js +1 -0
- package/dashboard/dist/assets/download-Cn-OpYm6.js +1 -0
- package/dashboard/dist/assets/external-link-BnYlUn56.js +1 -0
- package/dashboard/dist/assets/flame-D4yFLV1L.js +1 -0
- package/dashboard/dist/assets/{format-tokens-CqiCmYtk.js → format-tokens-CBUNbBvp.js} +1 -1
- package/dashboard/dist/assets/{icons-DD8OvYVG.js → icons-CcgNgAvb.js} +1 -1
- package/dashboard/dist/assets/{index-DLLivlEh.js → index-CTjeDkbu.js} +1 -1
- package/dashboard/dist/assets/{index-21lDN4rA.js → index-CkELwUyg.js} +1 -1
- package/dashboard/dist/assets/{index-Cm8jn8Va.js → index-ZWeEKbo-.js} +1 -1
- package/dashboard/dist/assets/info-DoehRSQU.js +1 -0
- package/dashboard/dist/assets/link-2-D8xSsPqy.js +1 -0
- package/dashboard/dist/assets/loader-circle-DMu0-X5r.js +1 -0
- package/dashboard/dist/assets/maximize-2-DLyYsZvE.js +1 -0
- package/dashboard/dist/assets/{modifiers.esm-Cwj0l9Zb.js → modifiers.esm-Dm5Xy2jA.js} +1 -1
- package/dashboard/dist/assets/module-B5tW98jG.js +3 -0
- package/dashboard/dist/assets/monitor-smartphone-5uZGQNGJ.js +1 -0
- package/dashboard/dist/assets/plus-8fnU0dkt.js +1 -0
- package/dashboard/dist/assets/provider-display-B3ed-pSc.js +42 -0
- package/dashboard/dist/assets/{react-xH4nkyuj.js → react-Gft01KYk.js} +1 -1
- package/dashboard/dist/assets/search-jqzJg2kG.js +1 -0
- package/dashboard/dist/assets/{skills-api-B7lb1cjO.js → skills-api-Bwn_xcHV.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BUPRsWL6.js → sortable.esm-CYjzLCp7.js} +1 -1
- package/dashboard/dist/assets/{store-DneqOW0h.js → store-CgJHe9jU.js} +1 -1
- package/dashboard/dist/assets/terminal-CZ5j8yEQ.js +1 -0
- package/dashboard/dist/assets/trash-DtMLjI_S.js +1 -0
- package/dashboard/dist/assets/{use-community-stats-DB98TGn9.js → use-community-stats-Px3dHGlR.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-DK3KulKx.js → use-latest-request-guard-B7Vw4Wsd.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-BACCRUMZ.js → use-limits-display-prefs-DWWrcMPi.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DnKFdI9a.js → use-ordered-list-CevbB79U.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CCuEY-79.js +1 -0
- package/dashboard/dist/assets/use-session-efficiency-pref-DoIaI0vm.js +1 -0
- package/dashboard/dist/assets/{use-transform-DweC2iKa.js → use-transform-qRwUetOk.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CN5xWIBs.js → use-usage-limits-DlP7yg78.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CFjvWawL.js → useCurrency-DRUe8YoY.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-Cf81HqWi.js → useTokenFormat-CmLbznUC.js} +1 -1
- package/dashboard/dist/assets/zap-CS80c_uN.js +1 -0
- 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 +15 -4
- package/src/lib/cloud-account.js +120 -1
- package/src/lib/grok-limits.js +37 -0
- package/src/lib/heatmap-compact.js +114 -0
- package/src/lib/local-api.js +11 -10
- package/src/lib/opencode-config.js +20 -1
- package/src/lib/pricing/curated-overrides.json +22 -0
- package/src/lib/pricing/index.js +2 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1085 -146
- package/src/lib/usage-limits.js +7 -1
- package/dashboard/dist/assets/AchievementBadge-Bf2T612R.js +0 -1
- package/dashboard/dist/assets/AchievementsSection-CPwsy04Y.js +0 -1
- package/dashboard/dist/assets/CommandPalette-C8BwOnSn.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-BGMbgSjq.js +0 -1
- package/dashboard/dist/assets/DashboardPage-BcJGzQWK.js +0 -1
- package/dashboard/dist/assets/DialogDescription-DRQEfAWH.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-vVDKFLhl.js +0 -2
- package/dashboard/dist/assets/LoginCard-DA46RXDo.js +0 -1
- package/dashboard/dist/assets/PetPage-DDPZtxMw.js +0 -1
- package/dashboard/dist/assets/SettingsPage-Djku-i8K.js +0 -2
- package/dashboard/dist/assets/SkillsPage-DtBRoB8R.js +0 -1
- package/dashboard/dist/assets/TokenGalaxy-nvyPu5eV.js +0 -4314
- package/dashboard/dist/assets/ToolbarRootContext-DhpHv55w.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-sJsF0ioo.js +0 -45
- package/dashboard/dist/assets/arrow-up-right-BLDQog1A.js +0 -1
- package/dashboard/dist/assets/check-DNq_ySSn.js +0 -1
- package/dashboard/dist/assets/copy-DHRrNtRb.js +0 -1
- package/dashboard/dist/assets/download-CluyujgG.js +0 -1
- package/dashboard/dist/assets/external-link-Cf12tdLf.js +0 -1
- package/dashboard/dist/assets/flame-CJEYinxo.js +0 -1
- package/dashboard/dist/assets/info-dv3Y0z8Q.js +0 -1
- package/dashboard/dist/assets/link-2-CWHBxVuk.js +0 -1
- package/dashboard/dist/assets/loader-circle-9l5p9Dwa.js +0 -1
- package/dashboard/dist/assets/maximize-2-DQ2t3MJB.js +0 -1
- package/dashboard/dist/assets/module-DUSXqJtA.js +0 -3
- package/dashboard/dist/assets/monitor-smartphone-B-9at3iV.js +0 -1
- package/dashboard/dist/assets/plus-C_CTQjDD.js +0 -1
- package/dashboard/dist/assets/provider-display-XJQod7k9.js +0 -42
- package/dashboard/dist/assets/search-Ut41Fw1G.js +0 -1
- package/dashboard/dist/assets/terminal-DPQyZyHR.js +0 -1
- package/dashboard/dist/assets/trash-CVdV1bBR.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-BfdrKOsS.js +0 -1
- package/dashboard/dist/assets/use-session-efficiency-pref-BTm6RoSm.js +0 -1
- package/dashboard/dist/assets/zap-Vx_j5oJN.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -4188,6 +4188,35 @@ function recordProjectMeta(projectState, meta) {
|
|
|
4188
4188
|
projectState.projects[projectKey] = next;
|
|
4189
4189
|
}
|
|
4190
4190
|
|
|
4191
|
+
function antigravityProjectAssignmentChanged(previous, projectContext) {
|
|
4192
|
+
const projectKey = projectContext?.projectKey || null;
|
|
4193
|
+
const projectRef = projectContext?.projectRef || null;
|
|
4194
|
+
const priorProjectKeys = new Set(
|
|
4195
|
+
Object.values(previous?.contributions || {})
|
|
4196
|
+
.map((contribution) => contribution?.projectKey)
|
|
4197
|
+
.filter((value) => typeof value === "string" && value.length > 0),
|
|
4198
|
+
);
|
|
4199
|
+
if (typeof previous?.projectKey === "string" && previous.projectKey) {
|
|
4200
|
+
priorProjectKeys.add(previous.projectKey);
|
|
4201
|
+
}
|
|
4202
|
+
const priorProjectRefs = new Set(
|
|
4203
|
+
Object.values(previous?.contributions || {})
|
|
4204
|
+
.map((contribution) => contribution?.projectRef)
|
|
4205
|
+
.filter((value) => typeof value === "string" && value.length > 0),
|
|
4206
|
+
);
|
|
4207
|
+
if (typeof previous?.projectRef === "string" && previous.projectRef) {
|
|
4208
|
+
priorProjectRefs.add(previous.projectRef);
|
|
4209
|
+
}
|
|
4210
|
+
return (
|
|
4211
|
+
priorProjectKeys.size !== (projectKey ? 1 : 0) ||
|
|
4212
|
+
(projectKey && !priorProjectKeys.has(projectKey)) ||
|
|
4213
|
+
priorProjectRefs.size !== (projectRef ? 1 : 0) ||
|
|
4214
|
+
(projectRef && !priorProjectRefs.has(projectRef)) ||
|
|
4215
|
+
(typeof previous?.projectStatus === "string" &&
|
|
4216
|
+
previous.projectStatus !== (projectContext?.status || null))
|
|
4217
|
+
);
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4191
4220
|
async function resolveProjectContextForFile({
|
|
4192
4221
|
filePath,
|
|
4193
4222
|
projectMetaCache,
|
|
@@ -15792,34 +15821,73 @@ function resolveCraftDefaultModel() {
|
|
|
15792
15821
|
|
|
15793
15822
|
// Reasonix persists content-free cumulative usage beside each session JSONL.
|
|
15794
15823
|
// Reading only these telemetry sidecars keeps prompts and tool output private.
|
|
15795
|
-
function
|
|
15824
|
+
function reasonixHomeCandidates(env = process.env) {
|
|
15796
15825
|
if (env.TOKENTRACKER_REASONIX_HOME) {
|
|
15797
|
-
return expandHomePath(env.TOKENTRACKER_REASONIX_HOME, env);
|
|
15826
|
+
return [expandHomePath(env.TOKENTRACKER_REASONIX_HOME, env)];
|
|
15798
15827
|
}
|
|
15799
15828
|
if (env.REASONIX_STATE_HOME) {
|
|
15800
|
-
return expandHomePath(env.REASONIX_STATE_HOME, env);
|
|
15829
|
+
return [expandHomePath(env.REASONIX_STATE_HOME, env)];
|
|
15801
15830
|
}
|
|
15802
|
-
|
|
15803
|
-
|
|
15831
|
+
// Windows installs of Git Bash / MSYS / conda export a HOME of their own
|
|
15832
|
+
// (often a POSIX-shaped path), so preferring it silently sends the scan to a
|
|
15833
|
+
// directory that does not exist and Reasonix drops out of `status` entirely
|
|
15834
|
+
// with no "skipped" line to explain it. Match resolveCopilotDbPaths and take
|
|
15835
|
+
// USERPROFILE first on win32.
|
|
15836
|
+
const home =
|
|
15837
|
+
process.platform === "win32"
|
|
15838
|
+
? env.USERPROFILE || env.HOME || require("node:os").homedir()
|
|
15839
|
+
: env.HOME || require("node:os").homedir();
|
|
15840
|
+
const candidates = [path.join(home, ".reasonix")];
|
|
15841
|
+
// On Windows the dot-directory is not where the data lives: Reasonix keeps
|
|
15842
|
+
// sessions and memory under %APPDATA%\reasonix (no leading dot) and only the
|
|
15843
|
+
// cache under %LOCALAPPDATA%. Taking USERPROFILE over a shell HOME was not
|
|
15844
|
+
// enough for #641 because ~/.reasonix does not exist on Windows at all, so
|
|
15845
|
+
// the provider read as "not installed" and vanished from status without a
|
|
15846
|
+
// skipped line. The cache root is deliberately left out.
|
|
15847
|
+
if (process.platform === "win32" && env.APPDATA) {
|
|
15848
|
+
candidates.push(path.join(env.APPDATA, "reasonix"));
|
|
15849
|
+
}
|
|
15850
|
+
return candidates;
|
|
15851
|
+
}
|
|
15852
|
+
|
|
15853
|
+
// The home used for "is Reasonix installed" (src/commands/status.js,
|
|
15854
|
+
// src/commands/init.js): the first candidate that exists, else the first, so
|
|
15855
|
+
// the caller still has a path to report.
|
|
15856
|
+
function resolveReasonixHome(env = process.env) {
|
|
15857
|
+
const candidates = reasonixHomeCandidates(env);
|
|
15858
|
+
return candidates.find((dir) => fssync.existsSync(dir)) || candidates[0];
|
|
15804
15859
|
}
|
|
15805
15860
|
|
|
15806
|
-
|
|
15861
|
+
// Depth bound because the scan starts at the Reasonix root rather than at two
|
|
15862
|
+
// known subdirectories; it is a guard against a pathological tree, not a layout
|
|
15863
|
+
// assumption. The known layout (projects/<p>/sessions) sits at depth 3.
|
|
15864
|
+
const REASONIX_SCAN_MAX_DEPTH = 8;
|
|
15865
|
+
|
|
15866
|
+
function collectReasonixTelemetryFiles(dir, files, depth = 0) {
|
|
15867
|
+
if (depth > REASONIX_SCAN_MAX_DEPTH) return;
|
|
15807
15868
|
if (!fssync.existsSync(dir)) return;
|
|
15808
15869
|
let entries;
|
|
15809
15870
|
try { entries = fssync.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
15810
15871
|
for (const entry of entries) {
|
|
15811
15872
|
const full = path.join(dir, entry.name);
|
|
15812
|
-
if (entry.isDirectory()) collectReasonixTelemetryFiles(full, files);
|
|
15873
|
+
if (entry.isDirectory()) collectReasonixTelemetryFiles(full, files, depth + 1);
|
|
15813
15874
|
else if (entry.isFile() && entry.name.endsWith(".jsonl.telemetry.json")) files.push(full);
|
|
15814
15875
|
}
|
|
15815
15876
|
}
|
|
15816
15877
|
|
|
15817
15878
|
function resolveReasonixTelemetryFiles(env = process.env) {
|
|
15818
|
-
|
|
15879
|
+
// Recurse from each root instead of from a hardcoded projects/ and sessions/.
|
|
15880
|
+
// The #641 reporter supplied a screenshot of the data location, not a
|
|
15881
|
+
// directory listing, so the layout under %APPDATA%\reasonix is unconfirmed —
|
|
15882
|
+
// and a renamed subdirectory would otherwise cost another release to notice.
|
|
15819
15883
|
const files = [];
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15884
|
+
const seenHomes = new Set();
|
|
15885
|
+
for (const home of reasonixHomeCandidates(env)) {
|
|
15886
|
+
if (seenHomes.has(home)) continue;
|
|
15887
|
+
seenHomes.add(home);
|
|
15888
|
+
collectReasonixTelemetryFiles(home, files);
|
|
15889
|
+
}
|
|
15890
|
+
return Array.from(new Set(files)).sort((a, b) => a.localeCompare(b));
|
|
15823
15891
|
}
|
|
15824
15892
|
|
|
15825
15893
|
function readReasonixSnapshot(filePath) {
|
|
@@ -19411,27 +19479,236 @@ function resolveAntigravityBrainDirs(geminiHome) {
|
|
|
19411
19479
|
];
|
|
19412
19480
|
}
|
|
19413
19481
|
|
|
19414
|
-
|
|
19482
|
+
function antigravityPathWithin(filePath, root) {
|
|
19483
|
+
if (typeof filePath !== "string" || typeof root !== "string") return false;
|
|
19484
|
+
const relative = path.relative(root, filePath);
|
|
19485
|
+
return Boolean(
|
|
19486
|
+
relative &&
|
|
19487
|
+
relative !== ".." &&
|
|
19488
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
19489
|
+
!path.isAbsolute(relative),
|
|
19490
|
+
);
|
|
19491
|
+
}
|
|
19492
|
+
|
|
19493
|
+
async function listAntigravitySessionFilesWithStatus(brainDir, knownFiles = null) {
|
|
19415
19494
|
const out = [];
|
|
19416
|
-
|
|
19417
|
-
|
|
19495
|
+
const known = knownFiles instanceof Set ? knownFiles : new Set(knownFiles || []);
|
|
19496
|
+
if (!brainDir || typeof brainDir !== "string") {
|
|
19497
|
+
return { files: out, complete: false };
|
|
19498
|
+
}
|
|
19499
|
+
|
|
19500
|
+
let entries;
|
|
19501
|
+
try {
|
|
19502
|
+
entries = await fs.readdir(brainDir, { withFileTypes: true });
|
|
19503
|
+
} catch (err) {
|
|
19504
|
+
// A missing optional install directory is an authoritative empty inventory
|
|
19505
|
+
// only when it has never contained a tracked session. If it has, the
|
|
19506
|
+
// directory may be temporarily unavailable and must not trigger retraction.
|
|
19507
|
+
return {
|
|
19508
|
+
files: out,
|
|
19509
|
+
complete:
|
|
19510
|
+
err?.code === "ENOENT" &&
|
|
19511
|
+
!Array.from(known).some((filePath) => antigravityPathWithin(filePath, brainDir)),
|
|
19512
|
+
};
|
|
19513
|
+
}
|
|
19514
|
+
|
|
19515
|
+
let complete = true;
|
|
19418
19516
|
for (const entry of entries) {
|
|
19419
19517
|
if (!entry.isDirectory()) continue;
|
|
19420
19518
|
const logsDir = path.join(brainDir, entry.name, ".system_generated", "logs");
|
|
19421
19519
|
const transcriptPath = path.join(logsDir, "transcript.jsonl");
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19520
|
+
let st;
|
|
19521
|
+
try {
|
|
19522
|
+
st = await fs.stat(transcriptPath);
|
|
19523
|
+
} catch (err) {
|
|
19524
|
+
// ENOENT means this session directory currently has no transcript (or
|
|
19525
|
+
// the transcript was deleted). Other errors make the scan incomplete.
|
|
19526
|
+
if (err?.code !== "ENOENT") complete = false;
|
|
19527
|
+
continue;
|
|
19425
19528
|
}
|
|
19529
|
+
if (st.isFile()) out.push(transcriptPath);
|
|
19426
19530
|
}
|
|
19531
|
+
|
|
19427
19532
|
out.sort((a, b) => a.localeCompare(b));
|
|
19428
|
-
return out;
|
|
19533
|
+
return { files: out, complete };
|
|
19429
19534
|
}
|
|
19430
19535
|
|
|
19431
|
-
async function
|
|
19536
|
+
async function listAntigravitySessionFiles(brainDir) {
|
|
19537
|
+
return (await listAntigravitySessionFilesWithStatus(brainDir)).files;
|
|
19538
|
+
}
|
|
19539
|
+
|
|
19540
|
+
async function listAntigravityTranscriptsWithStatus(geminiHome, knownFiles = null) {
|
|
19432
19541
|
const dirs = resolveAntigravityBrainDirs(geminiHome);
|
|
19433
|
-
const
|
|
19434
|
-
|
|
19542
|
+
const results = await Promise.all(
|
|
19543
|
+
dirs.map((dir) => listAntigravitySessionFilesWithStatus(dir, knownFiles)),
|
|
19544
|
+
);
|
|
19545
|
+
return {
|
|
19546
|
+
files: results.flatMap((result) => result.files),
|
|
19547
|
+
complete: results.every((result) => result.complete),
|
|
19548
|
+
};
|
|
19549
|
+
}
|
|
19550
|
+
|
|
19551
|
+
async function listAntigravityTranscripts(geminiHome) {
|
|
19552
|
+
return (await listAntigravityTranscriptsWithStatus(geminiHome)).files;
|
|
19553
|
+
}
|
|
19554
|
+
|
|
19555
|
+
const ANTIGRAVITY_CURSOR_VERSION = 2;
|
|
19556
|
+
// Bump when extractAntigravityGenInfo starts reading records it used to
|
|
19557
|
+
// discard. Kept apart from the cursor version: a cursor-version mismatch routes
|
|
19558
|
+
// to the legacy migration, which treats the cursor as having no per-file ledger.
|
|
19559
|
+
const ANTIGRAVITY_EXTRACTOR_REVISION = 1;
|
|
19560
|
+
const ANTIGRAVITY_MAX_CONTRIBUTIONS = 4096;
|
|
19561
|
+
|
|
19562
|
+
function hashAntigravityTranscript(buffer) {
|
|
19563
|
+
if (!Buffer.isBuffer(buffer)) return null;
|
|
19564
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
19565
|
+
}
|
|
19566
|
+
|
|
19567
|
+
function antigravityTranscriptPrefixMatches(buffer, previous) {
|
|
19568
|
+
if (!Buffer.isBuffer(buffer) || !previous || typeof previous.transcriptHash !== "string") {
|
|
19569
|
+
return false;
|
|
19570
|
+
}
|
|
19571
|
+
const previousSize = Number(previous.size);
|
|
19572
|
+
if (!Number.isSafeInteger(previousSize) || previousSize < 0 || buffer.length < previousSize) {
|
|
19573
|
+
return false;
|
|
19574
|
+
}
|
|
19575
|
+
return hashAntigravityTranscript(buffer.subarray(0, previousSize)) === previous.transcriptHash;
|
|
19576
|
+
}
|
|
19577
|
+
|
|
19578
|
+
async function readAntigravityTranscriptBytes(filePath) {
|
|
19579
|
+
try {
|
|
19580
|
+
return await fs.readFile(filePath);
|
|
19581
|
+
} catch (_error) {
|
|
19582
|
+
return null;
|
|
19583
|
+
}
|
|
19584
|
+
}
|
|
19585
|
+
|
|
19586
|
+
async function statAntigravityDatabase(dbPath) {
|
|
19587
|
+
const result = {
|
|
19588
|
+
dbExists: false,
|
|
19589
|
+
dbInode: 0,
|
|
19590
|
+
dbMtimeMs: 0,
|
|
19591
|
+
dbCtimeMs: 0,
|
|
19592
|
+
dbSize: 0,
|
|
19593
|
+
dbWalInode: 0,
|
|
19594
|
+
dbWalMtimeMs: 0,
|
|
19595
|
+
dbWalSize: 0,
|
|
19596
|
+
dbJournalInode: 0,
|
|
19597
|
+
dbJournalMtimeMs: 0,
|
|
19598
|
+
dbJournalSize: 0,
|
|
19599
|
+
};
|
|
19600
|
+
if (!dbPath) return result;
|
|
19601
|
+
for (const [suffix, prefix] of [
|
|
19602
|
+
["", "db"],
|
|
19603
|
+
["-wal", "dbWal"],
|
|
19604
|
+
["-journal", "dbJournal"],
|
|
19605
|
+
]) {
|
|
19606
|
+
const st = await fs.stat(`${dbPath}${suffix}`).catch(() => null);
|
|
19607
|
+
if (!st || !st.isFile()) continue;
|
|
19608
|
+
if (suffix === "") result.dbExists = true;
|
|
19609
|
+
result[`${prefix}MtimeMs`] = Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
|
|
19610
|
+
if (suffix === "") {
|
|
19611
|
+
result.dbInode = Number.isFinite(st.ino) ? st.ino : 0;
|
|
19612
|
+
result.dbCtimeMs = Number.isFinite(st.ctimeMs) ? st.ctimeMs : 0;
|
|
19613
|
+
} else {
|
|
19614
|
+
result[`${prefix}Inode`] = Number.isFinite(st.ino) ? st.ino : 0;
|
|
19615
|
+
}
|
|
19616
|
+
result[`${prefix}Size`] = Number.isFinite(st.size) ? st.size : 0;
|
|
19617
|
+
}
|
|
19618
|
+
return result;
|
|
19619
|
+
}
|
|
19620
|
+
|
|
19621
|
+
// Neither the transcript nor the database changes when the extractor does, so
|
|
19622
|
+
// without this stamp a session read before an extractor fix keeps its totals.
|
|
19623
|
+
function antigravityExtractorStale(previous, dbPath) {
|
|
19624
|
+
return Boolean(
|
|
19625
|
+
dbPath &&
|
|
19626
|
+
previous &&
|
|
19627
|
+
previous.cursorVersion === ANTIGRAVITY_CURSOR_VERSION &&
|
|
19628
|
+
previous.extractorRevision !== ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
19629
|
+
);
|
|
19630
|
+
}
|
|
19631
|
+
|
|
19632
|
+
function sameAntigravityDatabase(previous, current) {
|
|
19633
|
+
if (!previous || !current) return false;
|
|
19634
|
+
// SQLite updates the -shm read marks when a reader opens a WAL database.
|
|
19635
|
+
// Those mtime changes do not represent new usage metadata and must not force
|
|
19636
|
+
// a full transcript reconciliation on every sync.
|
|
19637
|
+
return [
|
|
19638
|
+
"dbExists",
|
|
19639
|
+
"dbInode",
|
|
19640
|
+
"dbMtimeMs",
|
|
19641
|
+
"dbCtimeMs",
|
|
19642
|
+
"dbSize",
|
|
19643
|
+
"dbWalInode",
|
|
19644
|
+
"dbWalMtimeMs",
|
|
19645
|
+
"dbWalSize",
|
|
19646
|
+
"dbJournalInode",
|
|
19647
|
+
"dbJournalMtimeMs",
|
|
19648
|
+
"dbJournalSize",
|
|
19649
|
+
].every((field) => Number(previous[field] || 0) === Number(current[field] || 0));
|
|
19650
|
+
}
|
|
19651
|
+
|
|
19652
|
+
function capAntigravityContributions(contributions) {
|
|
19653
|
+
if (!contributions || typeof contributions !== "object") {
|
|
19654
|
+
return { contributions: {}, complete: true };
|
|
19655
|
+
}
|
|
19656
|
+
const entries = Object.entries(contributions);
|
|
19657
|
+
if (entries.length <= ANTIGRAVITY_MAX_CONTRIBUTIONS) {
|
|
19658
|
+
return { contributions, complete: true };
|
|
19659
|
+
}
|
|
19660
|
+
|
|
19661
|
+
entries.sort(([, left], [, right]) => {
|
|
19662
|
+
const leftTime = Date.parse(left?.bucketStart || "");
|
|
19663
|
+
const rightTime = Date.parse(right?.bucketStart || "");
|
|
19664
|
+
if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
|
|
19665
|
+
return rightTime - leftTime;
|
|
19666
|
+
}
|
|
19667
|
+
if (Number.isFinite(rightTime) !== Number.isFinite(leftTime)) {
|
|
19668
|
+
return Number.isFinite(rightTime) ? 1 : -1;
|
|
19669
|
+
}
|
|
19670
|
+
return 0;
|
|
19671
|
+
});
|
|
19672
|
+
|
|
19673
|
+
return {
|
|
19674
|
+
contributions: Object.fromEntries(entries.slice(0, ANTIGRAVITY_MAX_CONTRIBUTIONS)),
|
|
19675
|
+
complete: false,
|
|
19676
|
+
};
|
|
19677
|
+
}
|
|
19678
|
+
|
|
19679
|
+
function resetAntigravityBucketsForRebuild({
|
|
19680
|
+
hourlyState,
|
|
19681
|
+
projectState,
|
|
19682
|
+
sources,
|
|
19683
|
+
touchedBuckets,
|
|
19684
|
+
projectTouchedBuckets,
|
|
19685
|
+
}) {
|
|
19686
|
+
if (!sources || sources.size === 0) return;
|
|
19687
|
+
|
|
19688
|
+
for (const [key, bucket] of Object.entries(hourlyState?.buckets || {})) {
|
|
19689
|
+
if (!bucket || !bucket.totals) continue;
|
|
19690
|
+
const bucketSource = normalizeSourceInput(parseBucketKey(key).source);
|
|
19691
|
+
if (!sources.has(bucketSource)) continue;
|
|
19692
|
+
bucket.totals = initTotals();
|
|
19693
|
+
bucket.queuedKey = null;
|
|
19694
|
+
bucket.retractedUnknownKey = null;
|
|
19695
|
+
touchedBuckets.add(key);
|
|
19696
|
+
}
|
|
19697
|
+
|
|
19698
|
+
for (const key of Object.keys(hourlyState?.groupQueued || {})) {
|
|
19699
|
+
const groupSource = normalizeSourceInput(key.split(BUCKET_SEPARATOR)[0]);
|
|
19700
|
+
if (sources.has(groupSource)) delete hourlyState.groupQueued[key];
|
|
19701
|
+
}
|
|
19702
|
+
|
|
19703
|
+
if (!projectState || !projectTouchedBuckets) return;
|
|
19704
|
+
for (const [key, bucket] of Object.entries(projectState.buckets || {})) {
|
|
19705
|
+
const keyParts = key.split(BUCKET_SEPARATOR);
|
|
19706
|
+
const bucketSource = normalizeSourceInput(bucket?.source || keyParts.at(-2));
|
|
19707
|
+
if (!sources.has(bucketSource) || !bucket?.totals) continue;
|
|
19708
|
+
bucket.totals = initTotals();
|
|
19709
|
+
bucket.queuedKey = null;
|
|
19710
|
+
projectTouchedBuckets.add(key);
|
|
19711
|
+
}
|
|
19435
19712
|
}
|
|
19436
19713
|
|
|
19437
19714
|
async function parseAntigravityIncremental({
|
|
@@ -19442,6 +19719,10 @@ async function parseAntigravityIncremental({
|
|
|
19442
19719
|
onProgress,
|
|
19443
19720
|
source,
|
|
19444
19721
|
publicRepoResolver,
|
|
19722
|
+
// Source-wide rebuilds may retract sessions absent from sessionFiles. Only
|
|
19723
|
+
// permit that when discovery explicitly completed; direct parser callers
|
|
19724
|
+
// retain the historical complete-inventory default for compatibility.
|
|
19725
|
+
inventoryComplete = true,
|
|
19445
19726
|
}) {
|
|
19446
19727
|
await ensureDir(path.dirname(queuePath));
|
|
19447
19728
|
let filesProcessed = 0;
|
|
@@ -19450,17 +19731,147 @@ async function parseAntigravityIncremental({
|
|
|
19450
19731
|
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
19451
19732
|
const files = Array.isArray(sessionFiles) ? sessionFiles : [];
|
|
19452
19733
|
const totalFiles = files.length;
|
|
19453
|
-
|
|
19734
|
+
// Keep every mutation private until both queue appends succeed. The generic
|
|
19735
|
+
// normalizers intentionally preserve bucket object references for other
|
|
19736
|
+
// parsers, so Antigravity must deep-clone its working state here; otherwise a
|
|
19737
|
+
// queue failure can leave a committed cursor pointing at already-mutated
|
|
19738
|
+
// hourly totals.
|
|
19739
|
+
const hourlyState = structuredClone(normalizeHourlyState(cursors?.hourly));
|
|
19454
19740
|
const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
|
|
19455
|
-
const projectState = projectEnabled
|
|
19741
|
+
const projectState = projectEnabled
|
|
19742
|
+
? structuredClone(normalizeProjectState(cursors?.projectHourly))
|
|
19743
|
+
: null;
|
|
19456
19744
|
const projectTouchedBuckets = projectEnabled ? new Set() : null;
|
|
19457
19745
|
const projectMetaCache = projectEnabled ? new Map() : null;
|
|
19458
19746
|
const publicRepoCache = projectEnabled ? new Map() : null;
|
|
19459
19747
|
const touchedBuckets = new Set();
|
|
19460
19748
|
const defaultSource = normalizeSourceInput(source) || "antigravity";
|
|
19749
|
+
const fileCursors =
|
|
19750
|
+
cursors.files && typeof cursors.files === "object" && !Array.isArray(cursors.files)
|
|
19751
|
+
? structuredClone(cursors.files)
|
|
19752
|
+
: {};
|
|
19461
19753
|
|
|
19462
|
-
|
|
19463
|
-
|
|
19754
|
+
const fileSources = new Set();
|
|
19755
|
+
const fileSourceByPath = new Map();
|
|
19756
|
+
for (const entry of files) {
|
|
19757
|
+
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
19758
|
+
const fileSource =
|
|
19759
|
+
typeof entry === "string"
|
|
19760
|
+
? defaultSource
|
|
19761
|
+
: normalizeSourceInput(entry?.source) || defaultSource;
|
|
19762
|
+
if (filePath) fileSourceByPath.set(filePath, fileSource);
|
|
19763
|
+
fileSources.add(fileSource);
|
|
19764
|
+
}
|
|
19765
|
+
if (fileSources.size === 0) fileSources.add(defaultSource);
|
|
19766
|
+
for (const [filePath, prev] of Object.entries(fileCursors)) {
|
|
19767
|
+
if (!resolveAntigravityDbPath(filePath)) continue;
|
|
19768
|
+
const previousSource = normalizeSourceInput(prev?.source);
|
|
19769
|
+
if (previousSource) fileSources.add(previousSource);
|
|
19770
|
+
}
|
|
19771
|
+
|
|
19772
|
+
// Legacy cursors have no per-file contribution ledger. Rebuild when every
|
|
19773
|
+
// legacy file is still present; if discovery is incomplete, preserve the old
|
|
19774
|
+
// aggregate and migrate live files against transcript-only baselines below.
|
|
19775
|
+
const liveFilePaths = new Set(
|
|
19776
|
+
files
|
|
19777
|
+
.map((entry) => (typeof entry === "string" ? entry : entry?.path))
|
|
19778
|
+
.filter(Boolean),
|
|
19779
|
+
);
|
|
19780
|
+
const incompleteContributionPaths = new Set();
|
|
19781
|
+
const deferredIncompletePaths = new Set();
|
|
19782
|
+
let needsFullRebuild = false;
|
|
19783
|
+
for (const [filePath, prev] of Object.entries(fileCursors)) {
|
|
19784
|
+
if (!resolveAntigravityDbPath(filePath) || !prev || Number(prev.lastLine || 0) <= 0) {
|
|
19785
|
+
continue;
|
|
19786
|
+
}
|
|
19787
|
+
const isLegacy =
|
|
19788
|
+
prev.cursorVersion !== ANTIGRAVITY_CURSOR_VERSION ||
|
|
19789
|
+
!prev.contributions ||
|
|
19790
|
+
typeof prev.contributions !== "object";
|
|
19791
|
+
if (!isLegacy && prev.contributionsComplete === false) {
|
|
19792
|
+
incompleteContributionPaths.add(filePath);
|
|
19793
|
+
}
|
|
19794
|
+
}
|
|
19795
|
+
|
|
19796
|
+
// Project reassignment is another full-ledger change: a capped ledger cannot
|
|
19797
|
+
// retract contributions that fell outside its retained window. Resolve
|
|
19798
|
+
// incomplete files against a private project state before deciding whether a
|
|
19799
|
+
// source-wide rebuild is required.
|
|
19800
|
+
const preflightProjectState =
|
|
19801
|
+
projectEnabled && incompleteContributionPaths.size > 0
|
|
19802
|
+
? structuredClone(projectState)
|
|
19803
|
+
: null;
|
|
19804
|
+
const preflightProjectMetaCache = preflightProjectState ? new Map() : null;
|
|
19805
|
+
const preflightPublicRepoCache = preflightProjectState ? new Map() : null;
|
|
19806
|
+
|
|
19807
|
+
// A capped contribution ledger is safe while its transcript/SQLite identity
|
|
19808
|
+
// is unchanged. If an incomplete ledger needs reconciliation, rebuild the
|
|
19809
|
+
// complete source baseline before applying any per-file deltas. When source
|
|
19810
|
+
// discovery is incomplete, defer the file instead: resetting the source
|
|
19811
|
+
// would erase contributions belonging to sessions omitted by discovery.
|
|
19812
|
+
if (!needsFullRebuild && incompleteContributionPaths.size > 0) {
|
|
19813
|
+
for (const filePath of incompleteContributionPaths) {
|
|
19814
|
+
if (!liveFilePaths.has(filePath)) {
|
|
19815
|
+
if (!inventoryComplete) {
|
|
19816
|
+
deferredIncompletePaths.add(filePath);
|
|
19817
|
+
} else {
|
|
19818
|
+
needsFullRebuild = true;
|
|
19819
|
+
}
|
|
19820
|
+
if (needsFullRebuild) break;
|
|
19821
|
+
continue;
|
|
19822
|
+
}
|
|
19823
|
+
const prev = cursors.files[filePath];
|
|
19824
|
+
const st = await fs.stat(filePath).catch(() => null);
|
|
19825
|
+
const dbPath = resolveAntigravityDbPath(filePath);
|
|
19826
|
+
const dbIdentity = await statAntigravityDatabase(dbPath);
|
|
19827
|
+
const sameInode = st && prev.inode === (st.ino || 0);
|
|
19828
|
+
const transcriptCanAppend =
|
|
19829
|
+
sameInode &&
|
|
19830
|
+
(Number.isFinite(st.size) ? st.size : 0) >= Number(prev.size || 0) &&
|
|
19831
|
+
antigravityTranscriptPrefixMatches(
|
|
19832
|
+
await readAntigravityTranscriptBytes(filePath),
|
|
19833
|
+
prev,
|
|
19834
|
+
);
|
|
19835
|
+
const sameSource =
|
|
19836
|
+
!fileSourceByPath.has(filePath) ||
|
|
19837
|
+
(prev.source == null ? defaultSource : prev.source) === fileSourceByPath.get(filePath);
|
|
19838
|
+
const sameDb = sameAntigravityDatabase(prev, dbIdentity);
|
|
19839
|
+
const staleExtractor = antigravityExtractorStale(prev, dbPath);
|
|
19840
|
+
let projectChanged = false;
|
|
19841
|
+
if (preflightProjectState) {
|
|
19842
|
+
const projectContext = await resolveProjectContextForFile({
|
|
19843
|
+
filePath,
|
|
19844
|
+
projectMetaCache: preflightProjectMetaCache,
|
|
19845
|
+
publicRepoCache: preflightPublicRepoCache,
|
|
19846
|
+
publicRepoResolver,
|
|
19847
|
+
projectState: preflightProjectState,
|
|
19848
|
+
});
|
|
19849
|
+
projectChanged = antigravityProjectAssignmentChanged(prev, projectContext);
|
|
19850
|
+
}
|
|
19851
|
+
if (!transcriptCanAppend || !sameDb || staleExtractor || !sameSource || projectChanged) {
|
|
19852
|
+
if (!inventoryComplete) {
|
|
19853
|
+
deferredIncompletePaths.add(filePath);
|
|
19854
|
+
} else {
|
|
19855
|
+
needsFullRebuild = true;
|
|
19856
|
+
}
|
|
19857
|
+
if (needsFullRebuild) break;
|
|
19858
|
+
}
|
|
19859
|
+
}
|
|
19860
|
+
}
|
|
19861
|
+
|
|
19862
|
+
if (needsFullRebuild) {
|
|
19863
|
+
for (const filePath of Object.keys(fileCursors)) {
|
|
19864
|
+
if (resolveAntigravityDbPath(filePath) && !liveFilePaths.has(filePath)) {
|
|
19865
|
+
delete fileCursors[filePath];
|
|
19866
|
+
}
|
|
19867
|
+
}
|
|
19868
|
+
resetAntigravityBucketsForRebuild({
|
|
19869
|
+
hourlyState,
|
|
19870
|
+
projectState,
|
|
19871
|
+
sources: fileSources,
|
|
19872
|
+
touchedBuckets,
|
|
19873
|
+
projectTouchedBuckets,
|
|
19874
|
+
});
|
|
19464
19875
|
}
|
|
19465
19876
|
|
|
19466
19877
|
for (let idx = 0; idx < files.length; idx++) {
|
|
@@ -19472,17 +19883,140 @@ async function parseAntigravityIncremental({
|
|
|
19472
19883
|
? defaultSource
|
|
19473
19884
|
: normalizeSourceInput(entry?.source) || defaultSource;
|
|
19474
19885
|
const st = await fs.stat(filePath).catch(() => null);
|
|
19475
|
-
if (!st || !st.isFile())
|
|
19886
|
+
if (!st || !st.isFile()) {
|
|
19887
|
+
if (needsFullRebuild) {
|
|
19888
|
+
throw new Error(`Antigravity transcript disappeared during rebuild: ${filePath}`);
|
|
19889
|
+
}
|
|
19890
|
+
continue;
|
|
19891
|
+
}
|
|
19476
19892
|
|
|
19477
19893
|
const key = filePath;
|
|
19478
|
-
|
|
19894
|
+
if (deferredIncompletePaths.has(key)) continue;
|
|
19895
|
+
const prev = fileCursors[key] || null;
|
|
19896
|
+
|
|
19479
19897
|
const inode = st.ino || 0;
|
|
19480
19898
|
const size = Number.isFinite(st.size) ? st.size : 0;
|
|
19481
19899
|
const mtimeMs = Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
|
|
19900
|
+
const ctimeMs = Number.isFinite(st.ctimeMs) ? st.ctimeMs : 0;
|
|
19482
19901
|
|
|
19483
|
-
const
|
|
19484
|
-
prev &&
|
|
19485
|
-
|
|
19902
|
+
const isLegacy = Boolean(
|
|
19903
|
+
prev &&
|
|
19904
|
+
(prev.cursorVersion !== ANTIGRAVITY_CURSOR_VERSION ||
|
|
19905
|
+
!prev.contributions ||
|
|
19906
|
+
typeof prev.contributions !== "object"),
|
|
19907
|
+
);
|
|
19908
|
+
|
|
19909
|
+
const dbPath = resolveAntigravityDbPath(filePath);
|
|
19910
|
+
const dbIdentity = await statAntigravityDatabase(dbPath);
|
|
19911
|
+
|
|
19912
|
+
const sameInode = prev && prev.inode === inode;
|
|
19913
|
+
const sameTranscriptStats =
|
|
19914
|
+
sameInode &&
|
|
19915
|
+
prev.size === size &&
|
|
19916
|
+
prev.mtimeMs === mtimeMs &&
|
|
19917
|
+
Number(prev.ctimeMs || 0) === ctimeMs;
|
|
19918
|
+
const sameDb = prev && sameAntigravityDatabase(prev, dbIdentity);
|
|
19919
|
+
const sameSource =
|
|
19920
|
+
!prev || (prev.source == null ? defaultSource : prev.source) === fileSource;
|
|
19921
|
+
const currentVersion = !isLegacy && prev?.cursorVersion === ANTIGRAVITY_CURSOR_VERSION;
|
|
19922
|
+
const staleExtractor = !isLegacy && antigravityExtractorStale(prev, dbPath);
|
|
19923
|
+
let transcriptBytes = null;
|
|
19924
|
+
let transcriptHash = typeof prev?.transcriptHash === "string" ? prev.transcriptHash : null;
|
|
19925
|
+
let sameTranscriptContent = sameTranscriptStats;
|
|
19926
|
+
let transcriptCanAppend = false;
|
|
19927
|
+
|
|
19928
|
+
// Stat metadata is only a fast hint. On any apparent file change, compare
|
|
19929
|
+
// the bytes so atomic replacement, truncation, and edits before the cursor
|
|
19930
|
+
// cannot be mistaken for an append. A cursor without this hash is treated
|
|
19931
|
+
// conservatively and gets one reconciliation pass.
|
|
19932
|
+
const transcriptReadRequired =
|
|
19933
|
+
!sameTranscriptStats || !currentVersion || !prev?.transcriptHash || needsFullRebuild;
|
|
19934
|
+
if (transcriptReadRequired) {
|
|
19935
|
+
transcriptBytes = await readAntigravityTranscriptBytes(filePath);
|
|
19936
|
+
const afterReadStat = await fs.stat(filePath).catch(() => null);
|
|
19937
|
+
if (
|
|
19938
|
+
!Buffer.isBuffer(transcriptBytes) ||
|
|
19939
|
+
!afterReadStat ||
|
|
19940
|
+
!afterReadStat.isFile() ||
|
|
19941
|
+
(afterReadStat.ino || 0) !== inode ||
|
|
19942
|
+
transcriptBytes.length !== size ||
|
|
19943
|
+
afterReadStat.size !== size ||
|
|
19944
|
+
afterReadStat.mtimeMs !== mtimeMs ||
|
|
19945
|
+
(Number.isFinite(afterReadStat.ctimeMs) ? afterReadStat.ctimeMs : 0) !== ctimeMs
|
|
19946
|
+
) {
|
|
19947
|
+
throw new Error(`Antigravity transcript changed while being read: ${filePath}`);
|
|
19948
|
+
}
|
|
19949
|
+
transcriptHash = hashAntigravityTranscript(transcriptBytes);
|
|
19950
|
+
sameTranscriptContent =
|
|
19951
|
+
Boolean(
|
|
19952
|
+
(prev?.transcriptHash && transcriptHash && prev.transcriptHash === transcriptHash && prev.size === size) ||
|
|
19953
|
+
(!prev?.transcriptHash && sameTranscriptStats),
|
|
19954
|
+
);
|
|
19955
|
+
transcriptCanAppend =
|
|
19956
|
+
Boolean(
|
|
19957
|
+
prev &&
|
|
19958
|
+
transcriptBytes &&
|
|
19959
|
+
size >= Number(prev.size || 0) &&
|
|
19960
|
+
(prev.transcriptHash
|
|
19961
|
+
? antigravityTranscriptPrefixMatches(transcriptBytes, prev)
|
|
19962
|
+
: sameInode),
|
|
19963
|
+
);
|
|
19964
|
+
}
|
|
19965
|
+
|
|
19966
|
+
const projectContext = projectEnabled
|
|
19967
|
+
? await resolveProjectContextForFile({
|
|
19968
|
+
filePath,
|
|
19969
|
+
projectMetaCache,
|
|
19970
|
+
publicRepoCache,
|
|
19971
|
+
publicRepoResolver,
|
|
19972
|
+
projectState,
|
|
19973
|
+
})
|
|
19974
|
+
: null;
|
|
19975
|
+
const projectRef = projectContext?.projectRef || null;
|
|
19976
|
+
const projectKey = projectContext?.projectKey || null;
|
|
19977
|
+
const projectChanged =
|
|
19978
|
+
projectEnabled && antigravityProjectAssignmentChanged(prev, projectContext);
|
|
19979
|
+
const baselineLine = Number(prev?.baselineLine || (isLegacy ? prev?.lastLine || 0 : 0));
|
|
19980
|
+
|
|
19981
|
+
// Legacy migration: "no retroactive claim". The file's history stays in the
|
|
19982
|
+
// old aggregate untouched. If unchanged, stamp v2 cursor with contributions: {}
|
|
19983
|
+
// and queue no buckets.
|
|
19984
|
+
if (!needsFullRebuild && isLegacy && (sameTranscriptStats || sameTranscriptContent)) {
|
|
19985
|
+
const contributionState = capAntigravityContributions({});
|
|
19986
|
+
fileCursors[key] = {
|
|
19987
|
+
...prev,
|
|
19988
|
+
inode,
|
|
19989
|
+
size,
|
|
19990
|
+
mtimeMs,
|
|
19991
|
+
ctimeMs,
|
|
19992
|
+
source: fileSource,
|
|
19993
|
+
transcriptHash:
|
|
19994
|
+
transcriptHash ||
|
|
19995
|
+
(transcriptBytes
|
|
19996
|
+
? hashAntigravityTranscript(transcriptBytes)
|
|
19997
|
+
: prev?.transcriptHash || null),
|
|
19998
|
+
...dbIdentity,
|
|
19999
|
+
projectKey: projectRef ? projectKey : null,
|
|
20000
|
+
projectRef: projectRef || null,
|
|
20001
|
+
projectStatus: projectContext?.status || null,
|
|
20002
|
+
projectConfigPath: projectContext?.configPath || null,
|
|
20003
|
+
projectConfigMtimeMs: projectContext?.configMtimeMs ?? null,
|
|
20004
|
+
projectConfigSize: projectContext?.configSize ?? null,
|
|
20005
|
+
projectReconciliationDeferred: false,
|
|
20006
|
+
baselineLine,
|
|
20007
|
+
cursorVersion: ANTIGRAVITY_CURSOR_VERSION,
|
|
20008
|
+
extractorRevision: ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
20009
|
+
lastLine: Number(prev.lastLine || 0),
|
|
20010
|
+
contextTokens: Number(prev.contextTokens || 0),
|
|
20011
|
+
previousContextTokens: Number(prev.previousContextTokens || 0),
|
|
20012
|
+
currentModel: prev.currentModel,
|
|
20013
|
+
lastPlannerModel: prev.lastPlannerModel,
|
|
20014
|
+
usageSource: prev.usageSource || (dbIdentity ? "sqlite" : "estimated"),
|
|
20015
|
+
dbReadOk: prev.dbReadOk !== false,
|
|
20016
|
+
contributions: contributionState.contributions,
|
|
20017
|
+
contributionsComplete: contributionState.complete,
|
|
20018
|
+
updatedAt: new Date().toISOString(),
|
|
20019
|
+
};
|
|
19486
20020
|
filesProcessed += 1;
|
|
19487
20021
|
if (cb) {
|
|
19488
20022
|
cb({
|
|
@@ -19497,54 +20031,250 @@ async function parseAntigravityIncremental({
|
|
|
19497
20031
|
continue;
|
|
19498
20032
|
}
|
|
19499
20033
|
|
|
19500
|
-
const
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
20034
|
+
const metadataRetryNeeded = currentVersion && Boolean(dbPath) && prev?.dbReadOk !== true;
|
|
20035
|
+
if (
|
|
20036
|
+
sameTranscriptStats &&
|
|
20037
|
+
sameTranscriptContent &&
|
|
20038
|
+
sameDb &&
|
|
20039
|
+
sameSource &&
|
|
20040
|
+
currentVersion &&
|
|
20041
|
+
!staleExtractor &&
|
|
20042
|
+
!metadataRetryNeeded &&
|
|
20043
|
+
!projectChanged &&
|
|
20044
|
+
!needsFullRebuild
|
|
20045
|
+
) {
|
|
20046
|
+
filesProcessed += 1;
|
|
20047
|
+
if (cb) {
|
|
20048
|
+
cb({
|
|
20049
|
+
index: idx + 1,
|
|
20050
|
+
total: totalFiles,
|
|
19511
20051
|
filePath,
|
|
19512
|
-
|
|
19513
|
-
|
|
19514
|
-
|
|
19515
|
-
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
|
|
19519
|
-
const projectKey = projectContext?.projectKey || null;
|
|
20052
|
+
filesProcessed,
|
|
20053
|
+
eventsAggregated,
|
|
20054
|
+
bucketsQueued: touchedBuckets.size,
|
|
20055
|
+
});
|
|
20056
|
+
}
|
|
20057
|
+
continue;
|
|
20058
|
+
}
|
|
19520
20059
|
|
|
19521
|
-
|
|
19522
|
-
|
|
19523
|
-
|
|
19524
|
-
|
|
19525
|
-
|
|
19526
|
-
|
|
19527
|
-
|
|
19528
|
-
|
|
19529
|
-
|
|
19530
|
-
|
|
19531
|
-
|
|
19532
|
-
|
|
19533
|
-
|
|
19534
|
-
|
|
19535
|
-
|
|
19536
|
-
|
|
20060
|
+
// Reconcile historical contributions whenever the cursor needs migration or
|
|
20061
|
+
// SQLite changed, even when new transcript lines were appended in the same
|
|
20062
|
+
// sync. Otherwise the incremental branch stamps a migrated cursor without
|
|
20063
|
+
// repairing its old, incomplete totals. Legacy cursors have no retroactive
|
|
20064
|
+
// claim and are not routed through reconciliation.
|
|
20065
|
+
const needsReconcile =
|
|
20066
|
+
!needsFullRebuild &&
|
|
20067
|
+
!isLegacy &&
|
|
20068
|
+
prev &&
|
|
20069
|
+
(metadataRetryNeeded ||
|
|
20070
|
+
!currentVersion ||
|
|
20071
|
+
!sameDb ||
|
|
20072
|
+
staleExtractor ||
|
|
20073
|
+
!sameSource ||
|
|
20074
|
+
projectChanged ||
|
|
20075
|
+
(!sameTranscriptContent && !transcriptCanAppend));
|
|
20076
|
+
|
|
20077
|
+
// Reconciliation reparses the complete transcript. Even when stat metadata
|
|
20078
|
+
// looked unchanged, project/SQLite changes can select this path; obtain a
|
|
20079
|
+
// stable snapshot here rather than letting parseAntigravityFile silently
|
|
20080
|
+
// turn a transient read failure into an empty transcript.
|
|
20081
|
+
if (!transcriptBytes && (needsFullRebuild || needsReconcile)) {
|
|
20082
|
+
transcriptBytes = await readAntigravityTranscriptBytes(filePath);
|
|
20083
|
+
const afterReadStat = await fs.stat(filePath).catch(() => null);
|
|
20084
|
+
if (
|
|
20085
|
+
!Buffer.isBuffer(transcriptBytes) ||
|
|
20086
|
+
!afterReadStat ||
|
|
20087
|
+
!afterReadStat.isFile() ||
|
|
20088
|
+
(afterReadStat.ino || 0) !== inode ||
|
|
20089
|
+
transcriptBytes.length !== size ||
|
|
20090
|
+
afterReadStat.size !== size ||
|
|
20091
|
+
afterReadStat.mtimeMs !== mtimeMs ||
|
|
20092
|
+
(Number.isFinite(afterReadStat.ctimeMs) ? afterReadStat.ctimeMs : 0) !== ctimeMs
|
|
20093
|
+
) {
|
|
20094
|
+
throw new Error(`Antigravity transcript changed while being read: ${filePath}`);
|
|
20095
|
+
}
|
|
20096
|
+
transcriptHash = hashAntigravityTranscript(transcriptBytes);
|
|
20097
|
+
sameTranscriptContent =
|
|
20098
|
+
Boolean(transcriptHash && prev?.transcriptHash === transcriptHash && prev.size === size);
|
|
20099
|
+
transcriptCanAppend = Boolean(
|
|
20100
|
+
prev &&
|
|
20101
|
+
size >= Number(prev.size || 0) &&
|
|
20102
|
+
antigravityTranscriptPrefixMatches(transcriptBytes, prev),
|
|
20103
|
+
);
|
|
20104
|
+
}
|
|
19537
20105
|
|
|
19538
|
-
|
|
20106
|
+
const protectExactState = needsReconcile && prev?.usageSource === "sqlite";
|
|
20107
|
+
const hourlyStateBeforeReconcile = protectExactState ? structuredClone(hourlyState) : null;
|
|
20108
|
+
const projectStateBeforeReconcile =
|
|
20109
|
+
protectExactState && projectState ? structuredClone(projectState) : null;
|
|
20110
|
+
const touchedBeforeReconcile = protectExactState ? new Set(touchedBuckets) : null;
|
|
20111
|
+
const projectTouchedBeforeReconcile =
|
|
20112
|
+
protectExactState && projectTouchedBuckets ? new Set(projectTouchedBuckets) : null;
|
|
20113
|
+
let result;
|
|
20114
|
+
|
|
20115
|
+
if (needsFullRebuild || needsReconcile) {
|
|
20116
|
+
if (
|
|
20117
|
+
!needsFullRebuild &&
|
|
20118
|
+
prev &&
|
|
20119
|
+
prev.contributions &&
|
|
20120
|
+
typeof prev.contributions === "object"
|
|
20121
|
+
) {
|
|
20122
|
+
for (const c of Object.values(prev.contributions)) {
|
|
20123
|
+
if (!c || !c.totals) continue;
|
|
20124
|
+
const oldBucket = getHourlyBucket(hourlyState, c.source, c.model, c.bucketStart);
|
|
20125
|
+
subtractTotals(oldBucket.totals, c.totals);
|
|
20126
|
+
touchedBuckets.add(bucketKey(c.source, c.model, c.bucketStart));
|
|
20127
|
+
if (
|
|
20128
|
+
projectEnabled &&
|
|
20129
|
+
c.projectKey &&
|
|
20130
|
+
projectState &&
|
|
20131
|
+
projectTouchedBuckets
|
|
20132
|
+
) {
|
|
20133
|
+
const oldProjectBucket = getProjectBucket(
|
|
20134
|
+
projectState,
|
|
20135
|
+
c.projectKey,
|
|
20136
|
+
c.source,
|
|
20137
|
+
c.bucketStart,
|
|
20138
|
+
c.projectRef || null,
|
|
20139
|
+
);
|
|
20140
|
+
subtractTotals(oldProjectBucket.totals, c.totals);
|
|
20141
|
+
projectTouchedBuckets.add(projectBucketKey(c.projectKey, c.source, c.bucketStart));
|
|
20142
|
+
}
|
|
20143
|
+
}
|
|
20144
|
+
}
|
|
20145
|
+
result = await parseAntigravityFile({
|
|
20146
|
+
filePath,
|
|
20147
|
+
...(transcriptBytes ? { rawBuffer: transcriptBytes } : {}),
|
|
20148
|
+
lastLine: 0,
|
|
20149
|
+
baselineLine: needsFullRebuild ? 0 : baselineLine,
|
|
20150
|
+
watermarkLine: Number(prev?.lastLine || 0),
|
|
20151
|
+
initialUsageSource: prev?.usageSource,
|
|
20152
|
+
hourlyState,
|
|
20153
|
+
touchedBuckets,
|
|
20154
|
+
source: fileSource,
|
|
20155
|
+
projectState,
|
|
20156
|
+
projectTouchedBuckets,
|
|
20157
|
+
projectRef,
|
|
20158
|
+
projectKey,
|
|
20159
|
+
});
|
|
20160
|
+
} else {
|
|
20161
|
+
const canResume = sameTranscriptContent || transcriptCanAppend;
|
|
20162
|
+
const lastLine = canResume ? Number(prev?.lastLine || 0) : 0;
|
|
20163
|
+
const initialContextTokens = canResume ? Number(prev?.contextTokens || 0) : 0;
|
|
20164
|
+
const initialPrevContext = canResume ? Number(prev?.previousContextTokens || 0) : 0;
|
|
20165
|
+
const initialModel =
|
|
20166
|
+
canResume && typeof prev?.currentModel === "string" ? prev.currentModel : null;
|
|
20167
|
+
const initialLastPlannerModel =
|
|
20168
|
+
canResume && typeof prev?.lastPlannerModel === "string"
|
|
20169
|
+
? prev.lastPlannerModel
|
|
20170
|
+
: null;
|
|
20171
|
+
const initialUsageSource =
|
|
20172
|
+
canResume && typeof prev?.usageSource === "string" ? prev.usageSource : null;
|
|
20173
|
+
|
|
20174
|
+
result = await parseAntigravityFile({
|
|
20175
|
+
filePath,
|
|
20176
|
+
...(transcriptBytes ? { rawBuffer: transcriptBytes } : {}),
|
|
20177
|
+
lastLine,
|
|
20178
|
+
baselineLine,
|
|
20179
|
+
initialContextTokens,
|
|
20180
|
+
initialPrevContext,
|
|
20181
|
+
initialModel,
|
|
20182
|
+
initialLastPlannerModel,
|
|
20183
|
+
initialUsageSource,
|
|
20184
|
+
hourlyState,
|
|
20185
|
+
touchedBuckets,
|
|
20186
|
+
source: fileSource,
|
|
20187
|
+
projectState,
|
|
20188
|
+
projectTouchedBuckets,
|
|
20189
|
+
projectRef,
|
|
20190
|
+
projectKey,
|
|
20191
|
+
});
|
|
20192
|
+
|
|
20193
|
+
const mergedContributions = { ...(prev?.contributions || {}) };
|
|
20194
|
+
for (const [cKey, cVal] of Object.entries(result.contributions || {})) {
|
|
20195
|
+
if (!mergedContributions[cKey]) {
|
|
20196
|
+
mergedContributions[cKey] = { ...cVal, totals: { ...cVal.totals } };
|
|
20197
|
+
} else {
|
|
20198
|
+
addTotals(mergedContributions[cKey].totals, cVal.totals);
|
|
20199
|
+
}
|
|
20200
|
+
}
|
|
20201
|
+
result.contributions = mergedContributions;
|
|
20202
|
+
}
|
|
20203
|
+
|
|
20204
|
+
if (result.deferred && prev) {
|
|
20205
|
+
if (needsFullRebuild && !hourlyStateBeforeReconcile) {
|
|
20206
|
+
throw new Error(`Cannot rebuild Antigravity transcript while SQLite is unavailable: ${filePath}`);
|
|
20207
|
+
}
|
|
20208
|
+
if (hourlyStateBeforeReconcile) {
|
|
20209
|
+
for (const field of Object.keys(hourlyState)) delete hourlyState[field];
|
|
20210
|
+
Object.assign(hourlyState, hourlyStateBeforeReconcile);
|
|
20211
|
+
if (touchedBeforeReconcile) {
|
|
20212
|
+
touchedBuckets.clear();
|
|
20213
|
+
for (const touched of touchedBeforeReconcile) touchedBuckets.add(touched);
|
|
20214
|
+
}
|
|
20215
|
+
}
|
|
20216
|
+
if (projectState && projectStateBeforeReconcile) {
|
|
20217
|
+
for (const field of Object.keys(projectState)) delete projectState[field];
|
|
20218
|
+
Object.assign(projectState, projectStateBeforeReconcile);
|
|
20219
|
+
if (projectTouchedBeforeReconcile) {
|
|
20220
|
+
projectTouchedBuckets.clear();
|
|
20221
|
+
for (const touched of projectTouchedBeforeReconcile) projectTouchedBuckets.add(touched);
|
|
20222
|
+
}
|
|
20223
|
+
}
|
|
20224
|
+
fileCursors[key] = {
|
|
20225
|
+
...prev,
|
|
20226
|
+
inode,
|
|
20227
|
+
size,
|
|
20228
|
+
mtimeMs,
|
|
20229
|
+
ctimeMs,
|
|
20230
|
+
source: fileSource,
|
|
20231
|
+
transcriptHash,
|
|
20232
|
+
...dbIdentity,
|
|
20233
|
+
dbReadOk: false,
|
|
20234
|
+
updatedAt: new Date().toISOString(),
|
|
20235
|
+
};
|
|
20236
|
+
filesProcessed += 1;
|
|
20237
|
+
if (cb) {
|
|
20238
|
+
cb({
|
|
20239
|
+
index: idx + 1,
|
|
20240
|
+
total: totalFiles,
|
|
20241
|
+
filePath,
|
|
20242
|
+
filesProcessed,
|
|
20243
|
+
eventsAggregated,
|
|
20244
|
+
bucketsQueued: touchedBuckets.size,
|
|
20245
|
+
});
|
|
20246
|
+
}
|
|
20247
|
+
continue;
|
|
20248
|
+
}
|
|
20249
|
+
|
|
20250
|
+
const contributionState = capAntigravityContributions(result.contributions);
|
|
20251
|
+
fileCursors[key] = {
|
|
19539
20252
|
inode,
|
|
19540
20253
|
size,
|
|
19541
20254
|
mtimeMs,
|
|
20255
|
+
ctimeMs,
|
|
20256
|
+
source: fileSource,
|
|
20257
|
+
transcriptHash,
|
|
20258
|
+
...dbIdentity,
|
|
20259
|
+
projectKey: projectRef ? projectKey : null,
|
|
20260
|
+
projectRef: projectRef || null,
|
|
20261
|
+
projectStatus: projectContext?.status || null,
|
|
20262
|
+
projectConfigPath: projectContext?.configPath || null,
|
|
20263
|
+
projectConfigMtimeMs: projectContext?.configMtimeMs ?? null,
|
|
20264
|
+
projectConfigSize: projectContext?.configSize ?? null,
|
|
20265
|
+
projectReconciliationDeferred: false,
|
|
20266
|
+
baselineLine: needsFullRebuild ? 0 : baselineLine,
|
|
20267
|
+
cursorVersion: ANTIGRAVITY_CURSOR_VERSION,
|
|
20268
|
+
extractorRevision: ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
19542
20269
|
lastLine: result.lastLine,
|
|
19543
20270
|
contextTokens: result.contextTokens,
|
|
19544
20271
|
previousContextTokens: result.previousContextTokens,
|
|
19545
20272
|
currentModel: result.currentModel,
|
|
19546
20273
|
lastPlannerModel: result.lastPlannerModel,
|
|
19547
20274
|
usageSource: result.usageSource,
|
|
20275
|
+
dbReadOk: result.dbReadOk !== false,
|
|
20276
|
+
contributions: contributionState.contributions,
|
|
20277
|
+
contributionsComplete: contributionState.complete,
|
|
19548
20278
|
updatedAt: new Date().toISOString(),
|
|
19549
20279
|
};
|
|
19550
20280
|
|
|
@@ -19568,6 +20298,7 @@ async function parseAntigravityIncremental({
|
|
|
19568
20298
|
? await enqueueTouchedProjectBuckets({ projectQueuePath, projectState, projectTouchedBuckets })
|
|
19569
20299
|
: 0;
|
|
19570
20300
|
hourlyState.updatedAt = new Date().toISOString();
|
|
20301
|
+
cursors.files = fileCursors;
|
|
19571
20302
|
cursors.hourly = hourlyState;
|
|
19572
20303
|
if (projectState) {
|
|
19573
20304
|
projectState.updatedAt = new Date().toISOString();
|
|
@@ -19578,78 +20309,164 @@ async function parseAntigravityIncremental({
|
|
|
19578
20309
|
}
|
|
19579
20310
|
|
|
19580
20311
|
function decodeAntigravityVarint(buf, offset) {
|
|
20312
|
+
if (!Buffer.isBuffer(buf) || !Number.isSafeInteger(offset) || offset < 0) {
|
|
20313
|
+
throw new RangeError("invalid Antigravity protobuf offset");
|
|
20314
|
+
}
|
|
20315
|
+
const start = offset;
|
|
19581
20316
|
let res = 0;
|
|
19582
|
-
let
|
|
19583
|
-
|
|
20317
|
+
for (let count = 0; count < 10; count++) {
|
|
20318
|
+
if (offset >= buf.length) throw new RangeError("truncated Antigravity protobuf varint");
|
|
19584
20319
|
const b = buf[offset++];
|
|
19585
|
-
|
|
19586
|
-
if (
|
|
19587
|
-
|
|
20320
|
+
// 64 bits is nine 7-bit groups plus one bit of a tenth byte.
|
|
20321
|
+
if (count === 9 && b > 1) throw new RangeError("Antigravity protobuf varint exceeds 64 bits");
|
|
20322
|
+
res += (b & 0x7f) * 2 ** (count * 7);
|
|
20323
|
+
if (!(b & 0x80)) {
|
|
20324
|
+
if (Number.isSafeInteger(res)) return [res, offset];
|
|
20325
|
+
// Antigravity writes 2^64 - 1 in fields the extractor never reads. The
|
|
20326
|
+
// float sum has already lost precision, so re-read the bytes exactly.
|
|
20327
|
+
let exact = 0n;
|
|
20328
|
+
for (let i = start; i < offset; i++) {
|
|
20329
|
+
exact |= BigInt(buf[i] & 0x7f) << BigInt((i - start) * 7);
|
|
20330
|
+
}
|
|
20331
|
+
return [exact, offset];
|
|
20332
|
+
}
|
|
19588
20333
|
}
|
|
19589
|
-
|
|
20334
|
+
throw new RangeError("overlong Antigravity protobuf varint");
|
|
19590
20335
|
}
|
|
19591
20336
|
|
|
19592
20337
|
function findAntigravityProtoFields(buf) {
|
|
20338
|
+
if (!Buffer.isBuffer(buf)) throw new RangeError("invalid Antigravity protobuf payload");
|
|
19593
20339
|
const fields = [];
|
|
19594
20340
|
let offset = 0;
|
|
19595
20341
|
while (offset < buf.length) {
|
|
19596
20342
|
const [tag, next] = decodeAntigravityVarint(buf, offset);
|
|
20343
|
+
if (typeof tag !== "number") throw new RangeError("unsafe Antigravity protobuf field tag");
|
|
19597
20344
|
offset = next;
|
|
19598
|
-
const fieldNum = tag
|
|
20345
|
+
const fieldNum = Math.floor(tag / 8);
|
|
19599
20346
|
const wireType = tag & 7;
|
|
20347
|
+
if (fieldNum <= 0) throw new RangeError("invalid Antigravity protobuf field number");
|
|
19600
20348
|
if (wireType === 0) {
|
|
20349
|
+
// May be a BigInt; token reads use Number.isSafeInteger, so it is never billed.
|
|
19601
20350
|
const [val, vNext] = decodeAntigravityVarint(buf, offset);
|
|
19602
20351
|
offset = vNext;
|
|
19603
20352
|
fields.push({ num: fieldNum, val });
|
|
19604
20353
|
} else if (wireType === 2) {
|
|
19605
20354
|
const [len, lNext] = decodeAntigravityVarint(buf, offset);
|
|
19606
20355
|
offset = lNext;
|
|
20356
|
+
if (len > buf.length - offset) {
|
|
20357
|
+
throw new RangeError("truncated Antigravity protobuf field");
|
|
20358
|
+
}
|
|
19607
20359
|
fields.push({ num: fieldNum, val: buf.subarray(offset, offset + len) });
|
|
19608
20360
|
offset += len;
|
|
19609
20361
|
} else if (wireType === 1) {
|
|
20362
|
+
if (buf.length - offset < 8) throw new RangeError("truncated Antigravity protobuf field");
|
|
19610
20363
|
offset += 8;
|
|
19611
20364
|
} else if (wireType === 5) {
|
|
20365
|
+
if (buf.length - offset < 4) throw new RangeError("truncated Antigravity protobuf field");
|
|
19612
20366
|
offset += 4;
|
|
19613
20367
|
} else {
|
|
19614
|
-
|
|
20368
|
+
throw new RangeError("unsupported Antigravity protobuf wire type");
|
|
19615
20369
|
}
|
|
19616
20370
|
}
|
|
19617
20371
|
return fields;
|
|
19618
20372
|
}
|
|
19619
20373
|
|
|
19620
20374
|
function extractAntigravityGenInfo(buf) {
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
20375
|
+
try {
|
|
20376
|
+
const root = findAntigravityProtoFields(buf);
|
|
20377
|
+
const f1 = root.find((f) => f.num === 1)?.val;
|
|
20378
|
+
if (!f1) return null;
|
|
20379
|
+
|
|
20380
|
+
const inner = findAntigravityProtoFields(f1);
|
|
20381
|
+
let model = null;
|
|
20382
|
+
const f19 = inner.find((f) => f.num === 19)?.val;
|
|
20383
|
+
if (f19) model = Buffer.from(f19).toString("utf8").trim();
|
|
20384
|
+
|
|
20385
|
+
let contextTokens = 0;
|
|
20386
|
+
const f9 = inner.find((f) => f.num === 9)?.val;
|
|
20387
|
+
if (f9) {
|
|
20388
|
+
const f10 = findAntigravityProtoFields(f9).find((f) => f.num === 10)?.val;
|
|
20389
|
+
if (f10) {
|
|
20390
|
+
const tok = findAntigravityProtoFields(f10).find((f) => f.num === 1)?.val;
|
|
20391
|
+
if (Number.isSafeInteger(tok)) contextTokens = tok;
|
|
20392
|
+
}
|
|
20393
|
+
}
|
|
19629
20394
|
|
|
19630
|
-
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
|
|
19634
|
-
|
|
19635
|
-
const
|
|
19636
|
-
if (
|
|
20395
|
+
let lastStepIndex = null;
|
|
20396
|
+
for (const f of inner) {
|
|
20397
|
+
if (f.num !== 20 || !f.val) continue;
|
|
20398
|
+
const kv = findAntigravityProtoFields(f.val);
|
|
20399
|
+
const k = kv.find((x) => x.num === 1)?.val;
|
|
20400
|
+
const v = kv.find((x) => x.num === 2)?.val;
|
|
20401
|
+
if (k && Buffer.from(k).toString("utf8") === "last_step_index" && v) {
|
|
20402
|
+
const rawStepIndex = Buffer.from(v).toString("utf8").trim();
|
|
20403
|
+
if (/^\d+$/.test(rawStepIndex)) {
|
|
20404
|
+
const parsed = Number(rawStepIndex);
|
|
20405
|
+
if (Number.isSafeInteger(parsed)) lastStepIndex = parsed;
|
|
20406
|
+
}
|
|
20407
|
+
}
|
|
19637
20408
|
}
|
|
19638
|
-
}
|
|
19639
20409
|
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
19644
|
-
|
|
19645
|
-
|
|
19646
|
-
|
|
19647
|
-
|
|
19648
|
-
|
|
20410
|
+
let uncachedInput;
|
|
20411
|
+
let cachedInput;
|
|
20412
|
+
let outputTokens;
|
|
20413
|
+
let textOutput;
|
|
20414
|
+
let reasoningOutput;
|
|
20415
|
+
let hasUsageMetadata = false;
|
|
20416
|
+
|
|
20417
|
+
const f4 = inner.find((f) => f.num === 4)?.val;
|
|
20418
|
+
if (f4) {
|
|
20419
|
+
// Antigravity internal usage message layout (Field 4):
|
|
20420
|
+
// - f4.1: system / tool instruction prefix tokens (per-model fixed prefix)
|
|
20421
|
+
// - f4.2: user / prompt tokens for this turn
|
|
20422
|
+
// - f4.3: total output tokens checksum (f4.9 text + f4.10 reasoning)
|
|
20423
|
+
// - f4.5: cached prompt tokens (cache read)
|
|
20424
|
+
// - f4.9: text output tokens
|
|
20425
|
+
// - f4.10: reasoning / thought tokens
|
|
20426
|
+
const f4fields = findAntigravityProtoFields(f4);
|
|
20427
|
+
const sTok = f4fields.find((f) => f.num === 1)?.val;
|
|
20428
|
+
const pTok = f4fields.find((f) => f.num === 2)?.val;
|
|
20429
|
+
const cTok = f4fields.find((f) => f.num === 5)?.val;
|
|
20430
|
+
const oTok = f4fields.find((f) => f.num === 3)?.val;
|
|
20431
|
+
const tTok = f4fields.find((f) => f.num === 9)?.val;
|
|
20432
|
+
const rTok = f4fields.find((f) => f.num === 10)?.val;
|
|
20433
|
+
if (
|
|
20434
|
+
Number.isSafeInteger(sTok) ||
|
|
20435
|
+
Number.isSafeInteger(pTok) ||
|
|
20436
|
+
Number.isSafeInteger(cTok) ||
|
|
20437
|
+
Number.isSafeInteger(oTok) ||
|
|
20438
|
+
Number.isSafeInteger(tTok) ||
|
|
20439
|
+
Number.isSafeInteger(rTok)
|
|
20440
|
+
) {
|
|
20441
|
+
hasUsageMetadata = true;
|
|
20442
|
+
const sysTokens = Number.isSafeInteger(sTok) ? sTok : 0;
|
|
20443
|
+
const promptTokens = Number.isSafeInteger(pTok) ? pTok : 0;
|
|
20444
|
+
uncachedInput = sysTokens + promptTokens;
|
|
20445
|
+
cachedInput = Number.isSafeInteger(cTok) ? cTok : 0;
|
|
20446
|
+
outputTokens = Number.isSafeInteger(oTok) ? oTok : 0;
|
|
20447
|
+
textOutput = Number.isSafeInteger(tTok) ? tTok : 0;
|
|
20448
|
+
reasoningOutput = Number.isSafeInteger(rTok) ? rTok : 0;
|
|
20449
|
+
}
|
|
19649
20450
|
}
|
|
19650
|
-
}
|
|
19651
20451
|
|
|
19652
|
-
|
|
20452
|
+
return {
|
|
20453
|
+
model,
|
|
20454
|
+
contextTokens,
|
|
20455
|
+
lastStepIndex,
|
|
20456
|
+
...(hasUsageMetadata
|
|
20457
|
+
? {
|
|
20458
|
+
uncachedInput,
|
|
20459
|
+
cachedInput,
|
|
20460
|
+
outputTokens,
|
|
20461
|
+
textOutput,
|
|
20462
|
+
reasoningOutput,
|
|
20463
|
+
hasUsageMetadata: true,
|
|
20464
|
+
}
|
|
20465
|
+
: {}),
|
|
20466
|
+
};
|
|
20467
|
+
} catch (_error) {
|
|
20468
|
+
return null;
|
|
20469
|
+
}
|
|
19653
20470
|
}
|
|
19654
20471
|
|
|
19655
20472
|
function resolveAntigravityDbPath(transcriptPath) {
|
|
@@ -19661,32 +20478,67 @@ function resolveAntigravityDbPath(transcriptPath) {
|
|
|
19661
20478
|
|
|
19662
20479
|
function readAntigravityConversationDb(dbPath) {
|
|
19663
20480
|
if (!dbPath) return null;
|
|
20481
|
+
let snapshot = null;
|
|
20482
|
+
let effectiveDbPath = dbPath;
|
|
20483
|
+
// Antigravity keeps active generation metadata in SQLite WAL files. Reading
|
|
20484
|
+
// a UNC/WSL database directly is both lock-prone and can miss rows that have
|
|
20485
|
+
// not checkpointed into the main file, so copy the database and sidecars as
|
|
20486
|
+
// one local read view first.
|
|
20487
|
+
if (isUncPath(dbPath)) {
|
|
20488
|
+
try {
|
|
20489
|
+
snapshot = snapshotSqliteDb(dbPath);
|
|
20490
|
+
effectiveDbPath = snapshot.path;
|
|
20491
|
+
} catch (_error) {
|
|
20492
|
+
// Preserve the existing direct-read fallback for transient snapshot or
|
|
20493
|
+
// permission failures.
|
|
20494
|
+
}
|
|
20495
|
+
}
|
|
19664
20496
|
try {
|
|
20497
|
+
if (!fssync.existsSync(effectiveDbPath)) return null;
|
|
19665
20498
|
const rows = readSqliteJsonRows(
|
|
19666
|
-
|
|
20499
|
+
effectiveDbPath,
|
|
19667
20500
|
"SELECT idx, quote(data) as hex FROM gen_metadata ORDER BY idx",
|
|
19668
|
-
{
|
|
20501
|
+
{
|
|
20502
|
+
readOnly: true,
|
|
20503
|
+
throwOnReadFailure: true,
|
|
20504
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
20505
|
+
timeout: 15_000,
|
|
20506
|
+
},
|
|
19669
20507
|
);
|
|
19670
|
-
if (!Array.isArray(rows)
|
|
20508
|
+
if (!Array.isArray(rows)) return null;
|
|
19671
20509
|
|
|
19672
20510
|
const stepMap = new Map();
|
|
19673
20511
|
for (const r of rows) {
|
|
19674
20512
|
if (!r || typeof r.hex !== "string" || !r.hex.startsWith("X'")) continue;
|
|
19675
20513
|
const buf = Buffer.from(r.hex.slice(2, -1), "hex");
|
|
19676
20514
|
const info = extractAntigravityGenInfo(buf);
|
|
19677
|
-
if (
|
|
20515
|
+
if (
|
|
20516
|
+
info &&
|
|
20517
|
+
info.lastStepIndex != null &&
|
|
20518
|
+
(info.hasUsageMetadata || info.contextTokens > 0)
|
|
20519
|
+
) {
|
|
19678
20520
|
stepMap.set(info.lastStepIndex + 1, info);
|
|
19679
20521
|
}
|
|
19680
20522
|
}
|
|
19681
|
-
|
|
20523
|
+
// An empty map means the database was read successfully but has no usable
|
|
20524
|
+
// metadata yet. Keep it distinct from null, which signals a transient read
|
|
20525
|
+
// failure and must be retried on the next sync.
|
|
20526
|
+
return stepMap;
|
|
19682
20527
|
} catch (_) {
|
|
19683
20528
|
return null;
|
|
20529
|
+
} finally {
|
|
20530
|
+
if (snapshot) snapshot.cleanup();
|
|
19684
20531
|
}
|
|
19685
20532
|
}
|
|
19686
20533
|
|
|
19687
20534
|
async function parseAntigravityFile({
|
|
19688
20535
|
filePath,
|
|
19689
|
-
|
|
20536
|
+
rawBuffer = null,
|
|
20537
|
+
lastLine = 0,
|
|
20538
|
+
maxLine = null,
|
|
20539
|
+
baselineLine = 0,
|
|
20540
|
+
watermarkLine = 0,
|
|
20541
|
+
applyBuckets = true,
|
|
19690
20542
|
initialContextTokens,
|
|
19691
20543
|
initialPrevContext,
|
|
19692
20544
|
initialModel,
|
|
@@ -19700,7 +20552,12 @@ async function parseAntigravityFile({
|
|
|
19700
20552
|
projectRef,
|
|
19701
20553
|
projectKey,
|
|
19702
20554
|
}) {
|
|
19703
|
-
const raw =
|
|
20555
|
+
const raw = Buffer.isBuffer(rawBuffer)
|
|
20556
|
+
? rawBuffer.toString("utf8")
|
|
20557
|
+
: await fs.readFile(filePath, "utf8").catch(() => null);
|
|
20558
|
+
if (raw === null) {
|
|
20559
|
+
throw new Error(`Antigravity transcript could not be read: ${filePath}`);
|
|
20560
|
+
}
|
|
19704
20561
|
if (!raw.trim()) {
|
|
19705
20562
|
return {
|
|
19706
20563
|
lastLine: 0,
|
|
@@ -19710,14 +20567,22 @@ async function parseAntigravityFile({
|
|
|
19710
20567
|
currentModel: null,
|
|
19711
20568
|
lastPlannerModel: null,
|
|
19712
20569
|
usageSource: "estimated",
|
|
20570
|
+
dbReadOk: true,
|
|
20571
|
+
contributions: {},
|
|
19713
20572
|
};
|
|
19714
20573
|
}
|
|
19715
20574
|
|
|
19716
|
-
const
|
|
20575
|
+
const allLines = raw
|
|
19717
20576
|
.split(/\r?\n/)
|
|
19718
20577
|
.map((line) => line.trim())
|
|
19719
20578
|
.filter(Boolean);
|
|
20579
|
+
const endLimit =
|
|
20580
|
+
Number.isFinite(maxLine) && maxLine >= 0
|
|
20581
|
+
? Math.min(allLines.length, maxLine)
|
|
20582
|
+
: allLines.length;
|
|
20583
|
+
const lines = allLines.slice(0, endLimit);
|
|
19720
20584
|
let eventsAggregated = 0;
|
|
20585
|
+
const contributions = {};
|
|
19721
20586
|
const dbPath = resolveAntigravityDbPath(filePath);
|
|
19722
20587
|
const stepMap = dbPath ? readAntigravityConversationDb(dbPath) : null;
|
|
19723
20588
|
// Resume cached context-token total + model so historical lines (i < lastLine)
|
|
@@ -19728,11 +20593,30 @@ async function parseAntigravityFile({
|
|
|
19728
20593
|
const cachedTokens = Number.isFinite(initialContextTokens) ? initialContextTokens : 0;
|
|
19729
20594
|
const cachedPrev = Number.isFinite(initialPrevContext) ? initialPrevContext : 0;
|
|
19730
20595
|
const cachedModel = typeof initialModel === "string" ? initialModel : null;
|
|
20596
|
+
const dbReadFailed = Boolean(dbPath) && stepMap === null;
|
|
20597
|
+
if (dbReadFailed && initialUsageSource === "sqlite") {
|
|
20598
|
+
return {
|
|
20599
|
+
lastLine: Math.min(Number.isFinite(lastLine) ? lastLine : 0, lines.length),
|
|
20600
|
+
eventsAggregated: 0,
|
|
20601
|
+
contextTokens: cachedTokens,
|
|
20602
|
+
previousContextTokens: cachedPrev,
|
|
20603
|
+
currentModel: cachedModel,
|
|
20604
|
+
lastPlannerModel:
|
|
20605
|
+
typeof initialLastPlannerModel === "string" ? initialLastPlannerModel : cachedModel,
|
|
20606
|
+
usageSource: "sqlite",
|
|
20607
|
+
dbReadOk: false,
|
|
20608
|
+
deferred: true,
|
|
20609
|
+
contributions: {},
|
|
20610
|
+
};
|
|
20611
|
+
}
|
|
19731
20612
|
const sqliteCursor = initialUsageSource === "sqlite";
|
|
20613
|
+
const hasSqliteMetadata = Boolean(stepMap && stepMap.size > 0);
|
|
19732
20614
|
const resumed =
|
|
19733
|
-
canResume &&
|
|
20615
|
+
canResume &&
|
|
20616
|
+
(cachedTokens > 0 || cachedModel !== null) &&
|
|
20617
|
+
(!hasSqliteMetadata || sqliteCursor);
|
|
19734
20618
|
const scanStart = resumed ? lastLine : 0;
|
|
19735
|
-
let currentModel = resumed ? cachedModel : null;
|
|
20619
|
+
let currentModel = resumed ? cachedModel : typeof initialModel === "string" ? initialModel : null;
|
|
19736
20620
|
if (!currentModel) {
|
|
19737
20621
|
currentModel = await readAntigravityDefaultModel(filePath);
|
|
19738
20622
|
}
|
|
@@ -19780,7 +20664,12 @@ async function parseAntigravityFile({
|
|
|
19780
20664
|
|
|
19781
20665
|
if (!isNewEvent) {
|
|
19782
20666
|
if (parsed.type === "PLANNER_RESPONSE") {
|
|
19783
|
-
if (
|
|
20667
|
+
if (dbTurn?.hasUsageMetadata) {
|
|
20668
|
+
contextTokens =
|
|
20669
|
+
dbContextTokens > 0
|
|
20670
|
+
? dbContextTokens
|
|
20671
|
+
: (dbTurn.uncachedInput || 0) + (dbTurn.cachedInput || 0);
|
|
20672
|
+
} else if (dbContextTokens > 0) {
|
|
19784
20673
|
contextTokens = dbContextTokens;
|
|
19785
20674
|
}
|
|
19786
20675
|
previousContextTokens = contextTokens;
|
|
@@ -19812,52 +20701,97 @@ async function parseAntigravityFile({
|
|
|
19812
20701
|
let billedPlanner = false;
|
|
19813
20702
|
|
|
19814
20703
|
if (parsed.type === "PLANNER_RESPONSE") {
|
|
19815
|
-
|
|
19816
|
-
|
|
20704
|
+
if (dbTurn?.hasUsageMetadata) {
|
|
20705
|
+
const uncached = dbTurn.uncachedInput || 0;
|
|
20706
|
+
const cached = dbTurn.cachedInput || 0;
|
|
20707
|
+
const reasoning = dbTurn.reasoningOutput || 0;
|
|
20708
|
+
let output = 0;
|
|
20709
|
+
if (Number.isFinite(dbTurn.textOutput) && dbTurn.textOutput > 0) {
|
|
20710
|
+
output = dbTurn.textOutput;
|
|
20711
|
+
} else if (Number.isFinite(dbTurn.outputTokens)) {
|
|
20712
|
+
output = Math.max(0, dbTurn.outputTokens - reasoning);
|
|
20713
|
+
}
|
|
19817
20714
|
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
20715
|
+
delta.input_tokens = uncached;
|
|
20716
|
+
delta.cached_input_tokens = cached;
|
|
20717
|
+
delta.output_tokens = output;
|
|
20718
|
+
delta.reasoning_output_tokens = reasoning;
|
|
20719
|
+
delta.total_tokens = uncached + cached + output + reasoning;
|
|
20720
|
+
delta.billable_total_tokens = delta.total_tokens;
|
|
20721
|
+
delta.conversation_count = 1;
|
|
20722
|
+
billedPlanner = delta.total_tokens > 0;
|
|
19825
20723
|
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
20724
|
+
contextTokens = dbContextTokens > 0 ? dbContextTokens : uncached + cached;
|
|
20725
|
+
} else {
|
|
20726
|
+
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
20727
|
+
const thinking = typeof parsed.thinking === "string" ? parsed.thinking : "";
|
|
19829
20728
|
|
|
19830
|
-
|
|
19831
|
-
|
|
19832
|
-
|
|
19833
|
-
|
|
19834
|
-
|
|
19835
|
-
|
|
19836
|
-
|
|
20729
|
+
if (dbContextTokens > 0) {
|
|
20730
|
+
contextTokens = dbContextTokens;
|
|
20731
|
+
}
|
|
20732
|
+
if (lastPlannerModel && model !== lastPlannerModel) {
|
|
20733
|
+
previousContextTokens = 0;
|
|
20734
|
+
}
|
|
20735
|
+
const inputDelta = Math.max(0, contextTokens - previousContextTokens);
|
|
20736
|
+
|
|
20737
|
+
const outputTokens =
|
|
20738
|
+
antigravityValueTokens(content) + antigravityValueTokens(parsed.tool_calls);
|
|
20739
|
+
const reasoningTokens = antigravityValueTokens(thinking);
|
|
20740
|
+
|
|
20741
|
+
delta.input_tokens = inputDelta;
|
|
20742
|
+
delta.output_tokens = outputTokens;
|
|
20743
|
+
delta.reasoning_output_tokens = reasoningTokens;
|
|
20744
|
+
delta.total_tokens = inputDelta + outputTokens + reasoningTokens;
|
|
20745
|
+
delta.billable_total_tokens = delta.total_tokens;
|
|
20746
|
+
delta.conversation_count = 1;
|
|
20747
|
+
billedPlanner = delta.total_tokens > 0;
|
|
20748
|
+
}
|
|
19837
20749
|
}
|
|
19838
20750
|
|
|
19839
|
-
if (!billedPlanner) {
|
|
20751
|
+
if (!billedPlanner || i < baselineLine) {
|
|
20752
|
+
if (billedPlanner) {
|
|
20753
|
+
previousContextTokens = contextTokens;
|
|
20754
|
+
lastPlannerModel = model;
|
|
20755
|
+
}
|
|
19840
20756
|
contextTokens += eventContextTokens;
|
|
19841
20757
|
lastCompletedLine = i + 1;
|
|
19842
20758
|
continue;
|
|
19843
20759
|
}
|
|
19844
20760
|
|
|
19845
|
-
const
|
|
19846
|
-
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
if (projectKey && projectState && projectTouchedBuckets) {
|
|
19850
|
-
const projectBucket = getProjectBucket(
|
|
19851
|
-
projectState,
|
|
19852
|
-
projectKey,
|
|
20761
|
+
const cKey = `${source}|${model}|${bucketStart}|${projectKey || ""}`;
|
|
20762
|
+
if (!contributions[cKey]) {
|
|
20763
|
+
contributions[cKey] = {
|
|
19853
20764
|
source,
|
|
20765
|
+
model,
|
|
19854
20766
|
bucketStart,
|
|
19855
|
-
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
20767
|
+
projectKey: projectKey || null,
|
|
20768
|
+
projectRef: projectRef || null,
|
|
20769
|
+
totals: initTotals(),
|
|
20770
|
+
};
|
|
20771
|
+
}
|
|
20772
|
+
addTotals(contributions[cKey].totals, delta);
|
|
20773
|
+
|
|
20774
|
+
if (applyBuckets !== false && hourlyState && touchedBuckets) {
|
|
20775
|
+
const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
|
|
20776
|
+
addTotals(bucket.totals, delta);
|
|
20777
|
+
touchedBuckets.add(bucketKey(source, model, bucketStart));
|
|
20778
|
+
|
|
20779
|
+
if (projectKey && projectState && projectTouchedBuckets) {
|
|
20780
|
+
const projectBucket = getProjectBucket(
|
|
20781
|
+
projectState,
|
|
20782
|
+
projectKey,
|
|
20783
|
+
source,
|
|
20784
|
+
bucketStart,
|
|
20785
|
+
projectRef,
|
|
20786
|
+
);
|
|
20787
|
+
addTotals(projectBucket.totals, delta);
|
|
20788
|
+
projectTouchedBuckets.add(projectBucketKey(projectKey, source, bucketStart));
|
|
20789
|
+
}
|
|
20790
|
+
}
|
|
20791
|
+
const isNewForCounter = watermarkLine > 0 ? i >= watermarkLine : true;
|
|
20792
|
+
if (isNewForCounter) {
|
|
20793
|
+
eventsAggregated += 1;
|
|
19859
20794
|
}
|
|
19860
|
-
eventsAggregated += 1;
|
|
19861
20795
|
// Snapshot the pre-planner context first. The planner's own content+tool_calls
|
|
19862
20796
|
// (eventContextTokens, added below) become part of the next turn's history,
|
|
19863
20797
|
// so they MUST be billed as input on the next planner — don't fold them into
|
|
@@ -19875,7 +20809,9 @@ async function parseAntigravityFile({
|
|
|
19875
20809
|
previousContextTokens,
|
|
19876
20810
|
currentModel,
|
|
19877
20811
|
lastPlannerModel,
|
|
19878
|
-
usageSource:
|
|
20812
|
+
usageSource: hasSqliteMetadata ? "sqlite" : "estimated",
|
|
20813
|
+
dbReadOk: !dbPath || stepMap !== null,
|
|
20814
|
+
contributions,
|
|
19879
20815
|
};
|
|
19880
20816
|
}
|
|
19881
20817
|
|
|
@@ -22033,9 +22969,12 @@ module.exports = {
|
|
|
22033
22969
|
parseGrokBuildIncremental,
|
|
22034
22970
|
|
|
22035
22971
|
// Antigravity (Google Gemini) - Session logs parser
|
|
22972
|
+
ANTIGRAVITY_CURSOR_VERSION,
|
|
22973
|
+
ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
22036
22974
|
resolveAntigravityBrainDirs,
|
|
22037
22975
|
listAntigravitySessionFiles,
|
|
22038
22976
|
listAntigravityTranscripts,
|
|
22977
|
+
listAntigravityTranscriptsWithStatus,
|
|
22039
22978
|
parseAntigravityIncremental,
|
|
22040
22979
|
estimateAntigravityTokens,
|
|
22041
22980
|
isCjkCodePoint,
|