tokentracker-cli 0.97.1 → 0.98.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/README.de.md +3 -3
- package/README.ja.md +3 -3
- package/README.ko.md +3 -3
- package/README.md +7 -5
- package/README.zh-CN.md +3 -3
- package/dashboard/dist/assets/AchievementBadge-DlZklawB.js +1 -0
- package/dashboard/dist/assets/{AchievementsPage-CrqvRxnb.js → AchievementsPage-CprbOKnt.js} +2 -2
- package/dashboard/dist/assets/AchievementsSection-BKLJBRKw.js +1 -0
- package/dashboard/dist/assets/{BadgeDetailModal-CquST3dj.js → BadgeDetailModal-CfVXLh_t.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-EXhdUHSg.js → BotAnimated-wzqeGbEY.js} +1 -1
- package/dashboard/dist/assets/{Card-Il2WWgOG.js → Card-B-gcOAn7.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-DioF5wa-.js → ClawdAnimated-1c5ezBBN.js} +2 -2
- package/dashboard/dist/assets/CommandPalette-CD8ggEF6.js +1 -0
- package/dashboard/dist/assets/CommunityStatsModal-b2fPhi1E.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-BHWQLUCM.js → ConfirmModal-ARBkT8AC.js} +1 -1
- package/dashboard/dist/assets/{Controls--LFhExZX.js → Controls-WFXDjIYG.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-XtiZa2JN.js +1 -0
- package/dashboard/dist/assets/{DevicePage-RKTirqz0.js → DevicePage-BCwSpNXa.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-DPFsTKmJ.js → DialogClose-D2PE0TAc.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-5LunWukT.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DolLpmFL.js → DialogTitle-Bby0zSCw.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-92GGNCPe.js → FadeIn-Cx1hl4so.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-GfXudotT.js → HeaderGithubStar-my4zR6Pp.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-n7gbPEsq.js → HoverTooltip-DOiGAPLE.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BaQOTbXi.js → IpCheckPage-4SdHlFuA.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-D3JcvxW_.js → LandingPage-bSWc9xiS.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-tVaWJXZ3.js → LeaderboardAvatar-EN4117E5.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-Bwjf-xZl.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-DmvcCVS7.js → LeaderboardProfileModal-B1mgVtfP.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-VSv3IlzS.js → LeaderboardProfilePage-DVt5S3lY.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-CtxgT20z.js → LimitsPage-BCXjJE1B.js} +5 -5
- package/dashboard/dist/assets/{LocalOnlyNotice-O9o586G8.js → LocalOnlyNotice-CzeH4OoJ.js} +1 -1
- package/dashboard/dist/assets/LoginCard-aiqSQInP.js +1 -0
- package/dashboard/dist/assets/{LoginModal-CJ3g7tBc.js → LoginModal-DdGKIi8h.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DPWzPywx.js → LoginPage-D7xGSoMn.js} +1 -1
- package/dashboard/dist/assets/PetPage-Dlb6rNpF.js +1 -0
- package/dashboard/dist/assets/{PopoverPopup-nL3YOLPO.js → PopoverPopup-Xbruj7Zi.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-C5RX_xPk.js → ProviderIcon-BPxacBzj.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DW_NCwA_.js → ResetPasswordPage-9wgPnLRj.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-CS6wQtck.js → SegmentedControl-CYFcZFNE.js} +1 -1
- package/dashboard/dist/assets/{Select-YqlqvbT5.js → Select-zpFEMqpk.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BahqsiIc.js → SelectItemText-Bldm5Y0C.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-DyhutLUy.js → ServiceStatusPage-dgtwFazV.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-wbvi4EHi.js → SessionsPage-DHfpv53M.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-DcGToOT7.js +2 -0
- package/dashboard/dist/assets/SkillsPage-DiQDLw2l.js +1 -0
- package/dashboard/dist/assets/TokenGalaxy-GDx7ftMU.js +4426 -0
- package/dashboard/dist/assets/ToolbarRootContext-PoCcP2AV.js +1 -0
- package/dashboard/dist/assets/{TrendMonitor-BtBcCjx-.js → TrendMonitor-CIfq_yk8.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BSNl2WIL.js → WidgetsPage-Dwy-E3Lf.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-d5oiNV4T.js → WrappedPage-Ce7NV6Ij.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-D-WLjvZW.js +45 -0
- package/dashboard/dist/assets/{agent-logos-CCVzO3uU.js → agent-logos-BY8n7b9g.js} +1 -1
- package/dashboard/dist/assets/arrow-up-right-BDiV6Wlk.js +1 -0
- package/dashboard/dist/assets/check-Chyo6ujc.js +1 -0
- package/dashboard/dist/assets/{composite-CWz1s64R.js → composite-DgBqMs_Z.js} +1 -1
- package/dashboard/dist/assets/copy-ENRv4pAy.js +1 -0
- package/dashboard/dist/assets/download-D3y3gd-z.js +1 -0
- package/dashboard/dist/assets/external-link-VxSAxWnH.js +1 -0
- package/dashboard/dist/assets/flame-CPdTWeRo.js +1 -0
- package/dashboard/dist/assets/{format-tokens-Bs2YotMr.js → format-tokens-BF2G89Q7.js} +1 -1
- package/dashboard/dist/assets/{icons-OKyij2y_.js → icons-D6rfdVyV.js} +1 -1
- package/dashboard/dist/assets/{index-CN7xY7j_.js → index-4bHcp2Ep.js} +1 -1
- package/dashboard/dist/assets/{index-C8nY1OaT.js → index-CgmpxGym.js} +1 -1
- package/dashboard/dist/assets/{index-DOwPCQx2.js → index-mzMCUT_4.js} +1 -1
- package/dashboard/dist/assets/info-BMso9PcY.js +1 -0
- package/dashboard/dist/assets/link-2-Bg34XOcd.js +1 -0
- package/dashboard/dist/assets/loader-circle-C4Vul1Yx.js +1 -0
- package/dashboard/dist/assets/maximize-2-BBqcwFFo.js +1 -0
- package/dashboard/dist/assets/{modifiers.esm-D_aVVjx5.js → modifiers.esm-B2F4Iiey.js} +1 -1
- package/dashboard/dist/assets/module-B5tW98jG.js +3 -0
- package/dashboard/dist/assets/monitor-smartphone-k1Ja3ngF.js +1 -0
- package/dashboard/dist/assets/plus-BaCIejxc.js +1 -0
- package/dashboard/dist/assets/provider-display-BYxmO9Bu.js +42 -0
- package/dashboard/dist/assets/{react-jS0-5kwE.js → react-xGx5RIqz.js} +1 -1
- package/dashboard/dist/assets/search-LZQC7j6a.js +1 -0
- package/dashboard/dist/assets/{skills-api-PwUMTdmV.js → skills-api-yLfQkApo.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BDdw7tkz.js → sortable.esm-B8XRHrmA.js} +1 -1
- package/dashboard/dist/assets/{store-CFdu9yv_.js → store-DfUIKvz3.js} +1 -1
- package/dashboard/dist/assets/terminal-CsFh6eZS.js +1 -0
- package/dashboard/dist/assets/trash-CVk10mDa.js +1 -0
- package/dashboard/dist/assets/{use-community-stats-D55oU9bu.js → use-community-stats-DP7lJyDV.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BO89yQ3n.js → use-latest-request-guard-B7uLArxV.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-C358N7B0.js → use-limits-display-prefs-DG97cWcv.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-B9ZYDS1K.js → use-ordered-list-ChQc2v-Q.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-BoFF1l9U.js +1 -0
- package/dashboard/dist/assets/use-session-efficiency-pref-CtF9JAlF.js +1 -0
- package/dashboard/dist/assets/{use-transform-9S4Ruq_s.js → use-transform-DVIBk7FO.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CpnM5V06.js → use-usage-limits-BYQuJ9Ud.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-BKJGW_4l.js → useCurrency-CNkzJCP9.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-CTB5svOB.js → useTokenFormat-CU3MtXtS.js} +1 -1
- package/dashboard/dist/assets/zap-D5k595AR.js +1 -0
- package/dashboard/dist/index.html +13 -11
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +3 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +1 -0
- package/src/commands/status.js +13 -0
- package/src/commands/sync.js +61 -4
- package/src/lib/cloud-account.js +108 -0
- package/src/lib/grok-limits.js +37 -0
- package/src/lib/opencode-config.js +20 -1
- package/src/lib/pricing/curated-overrides.json +22 -0
- package/src/lib/pricing/index.js +9 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1244 -168
- package/src/lib/usage-limits.js +7 -1
- package/dashboard/dist/assets/AchievementBadge-BdvqY4Qb.js +0 -1
- package/dashboard/dist/assets/AchievementsSection-wyLAM9N7.js +0 -1
- package/dashboard/dist/assets/CommandPalette-CaDx06tM.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-BbYWzM07.js +0 -1
- package/dashboard/dist/assets/DashboardPage-DuqJOpHL.js +0 -1
- package/dashboard/dist/assets/DialogDescription-DgaV8Gtk.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-NqHcStnk.js +0 -2
- package/dashboard/dist/assets/LoginCard-JCejigVu.js +0 -1
- package/dashboard/dist/assets/PetPage-MSJ3jseK.js +0 -1
- package/dashboard/dist/assets/SettingsPage-HGSwpV1O.js +0 -2
- package/dashboard/dist/assets/SkillsPage-BbVT9oyh.js +0 -1
- package/dashboard/dist/assets/TokenGalaxy-Cej6MLu6.js +0 -4314
- package/dashboard/dist/assets/ToolbarRootContext-D9pPY73U.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-CjrP5KfC.js +0 -45
- package/dashboard/dist/assets/arrow-up-right-AKItXMyh.js +0 -1
- package/dashboard/dist/assets/check-BQw5n0V0.js +0 -1
- package/dashboard/dist/assets/copy-CF8XNmv3.js +0 -1
- package/dashboard/dist/assets/download-BZvLU_hD.js +0 -1
- package/dashboard/dist/assets/external-link-bZrcVkeE.js +0 -1
- package/dashboard/dist/assets/flame-CMHolW1z.js +0 -1
- package/dashboard/dist/assets/info-CU7bMBhU.js +0 -1
- package/dashboard/dist/assets/link-2-wwoUOujz.js +0 -1
- package/dashboard/dist/assets/loader-circle-BK3Ltc-j.js +0 -1
- package/dashboard/dist/assets/maximize-2-DIPnLqR8.js +0 -1
- package/dashboard/dist/assets/module-DUSXqJtA.js +0 -3
- package/dashboard/dist/assets/monitor-smartphone-4NOM9BHb.js +0 -1
- package/dashboard/dist/assets/plus-JqSTJ28Y.js +0 -1
- package/dashboard/dist/assets/provider-display-CEFlg9ho.js +0 -42
- package/dashboard/dist/assets/search-CbBetPN7.js +0 -1
- package/dashboard/dist/assets/terminal-Dh7Sh6IG.js +0 -1
- package/dashboard/dist/assets/trash-B6_OaEVL.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-DWHVI51t.js +0 -1
- package/dashboard/dist/assets/use-session-efficiency-pref-CwGRDyVq.js +0 -1
- package/dashboard/dist/assets/zap-DGdBuT6m.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,
|
|
@@ -10896,8 +10925,10 @@ function resolveOmpAgentDir(env = process.env) {
|
|
|
10896
10925
|
return ompHome ? path.join(ompHome, "agent") : null;
|
|
10897
10926
|
}
|
|
10898
10927
|
|
|
10899
|
-
|
|
10900
|
-
|
|
10928
|
+
// Session-file discovery is shared with OmO (omo), which persists the exact
|
|
10929
|
+
// same on-disk layout under its own agent dir:
|
|
10930
|
+
// <agentDir>/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl
|
|
10931
|
+
function collectPiStyleSessionFiles(agentDir) {
|
|
10901
10932
|
if (!agentDir) return [];
|
|
10902
10933
|
const sessionsDir = path.join(agentDir, "sessions");
|
|
10903
10934
|
if (!fssync.existsSync(sessionsDir)) return [];
|
|
@@ -10922,6 +10953,10 @@ function resolveOmpSessionFiles(env = process.env) {
|
|
|
10922
10953
|
return files;
|
|
10923
10954
|
}
|
|
10924
10955
|
|
|
10956
|
+
function resolveOmpSessionFiles(env = process.env) {
|
|
10957
|
+
return collectPiStyleSessionFiles(resolveOmpAgentDir(env));
|
|
10958
|
+
}
|
|
10959
|
+
|
|
10925
10960
|
// Subagent transcripts live in a directory named after the session file:
|
|
10926
10961
|
// ~/.omp/agent/sessions/<cwd>/<session>.jsonl (main agent)
|
|
10927
10962
|
// ~/.omp/agent/sessions/<cwd>/<session>/<Agent>.jsonl (task subagent)
|
|
@@ -10930,8 +10965,8 @@ function resolveOmpSessionFiles(env = process.env) {
|
|
|
10930
10965
|
// rel path <= 2 segments → main, deeper → subagent/advisor), so we mirror
|
|
10931
10966
|
// that: everything below the cwd level is subagent traffic. Session dirs also
|
|
10932
10967
|
// hold non-JSONL artefacts (*.bash-original.log, *.md) — skipped by extension.
|
|
10933
|
-
|
|
10934
|
-
|
|
10968
|
+
// OmO nests the same way, so it reuses this walker.
|
|
10969
|
+
function collectPiStyleSubagentFiles(agentDir) {
|
|
10935
10970
|
if (!agentDir) return [];
|
|
10936
10971
|
const sessionsDir = path.join(agentDir, "sessions");
|
|
10937
10972
|
if (!fssync.existsSync(sessionsDir)) return [];
|
|
@@ -10979,20 +11014,24 @@ function resolveOmpSubagentFiles(env = process.env) {
|
|
|
10979
11014
|
return files;
|
|
10980
11015
|
}
|
|
10981
11016
|
|
|
11017
|
+
function resolveOmpSubagentFiles(env = process.env) {
|
|
11018
|
+
return collectPiStyleSubagentFiles(resolveOmpAgentDir(env));
|
|
11019
|
+
}
|
|
11020
|
+
|
|
10982
11021
|
function resolveOmpDefaultModel() {
|
|
10983
11022
|
// oh-my-pi has no global default model setting; model is per-message.
|
|
10984
11023
|
return "omp-unknown";
|
|
10985
11024
|
}
|
|
10986
11025
|
|
|
10987
|
-
const
|
|
11026
|
+
const PI_STYLE_HEADER_SCAN_MAX_BYTES = 65536;
|
|
10988
11027
|
|
|
10989
|
-
async function
|
|
11028
|
+
async function readPiStyleSessionCwd(filePath) {
|
|
10990
11029
|
let stream;
|
|
10991
11030
|
try {
|
|
10992
11031
|
stream = fssync.createReadStream(filePath, {
|
|
10993
11032
|
encoding: "utf8",
|
|
10994
11033
|
start: 0,
|
|
10995
|
-
end:
|
|
11034
|
+
end: PI_STYLE_HEADER_SCAN_MAX_BYTES,
|
|
10996
11035
|
});
|
|
10997
11036
|
} catch {
|
|
10998
11037
|
return null;
|
|
@@ -11013,6 +11052,95 @@ async function resolveOmpFileCwd(filePath) {
|
|
|
11013
11052
|
return null;
|
|
11014
11053
|
}
|
|
11015
11054
|
|
|
11055
|
+
async function resolveOmpFileCwd(filePath) {
|
|
11056
|
+
return readPiStyleSessionCwd(filePath);
|
|
11057
|
+
}
|
|
11058
|
+
|
|
11059
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11060
|
+
// OmO (omo) — passive JSONL reader (~/.omo/agent/sessions/**/*.jsonl)
|
|
11061
|
+
//
|
|
11062
|
+
// OmO shares oh-my-pi's session persistence format, so it reuses the same
|
|
11063
|
+
// collectors and parser. Layout:
|
|
11064
|
+
// ~/.omo/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl
|
|
11065
|
+
//
|
|
11066
|
+
// First line is the type:"session" header (carries the real, unencoded cwd).
|
|
11067
|
+
// Only type:"message" lines with message.role=="assistant" carry usage:
|
|
11068
|
+
//
|
|
11069
|
+
// {
|
|
11070
|
+
// "type": "message",
|
|
11071
|
+
// "id": "7ae3734f", ← 8-char dedup key
|
|
11072
|
+
// "timestamp": "2026-08-27T23:28:16.699Z",
|
|
11073
|
+
// "message": {
|
|
11074
|
+
// "role": "assistant",
|
|
11075
|
+
// "provider": "xai",
|
|
11076
|
+
// "model": "grok-4.6",
|
|
11077
|
+
// "usage": {
|
|
11078
|
+
// "input": 28646, "output": 630, "cacheRead": 512, "cacheWrite": 0,
|
|
11079
|
+
// "reasoning": 218, "totalTokens": 29788,
|
|
11080
|
+
// "cost": { ... } ← OmO's own estimate; ignored, we price it
|
|
11081
|
+
// },
|
|
11082
|
+
// "timestamp": 1787873284870 ← ms epoch, preferred for bucketing
|
|
11083
|
+
// }
|
|
11084
|
+
// }
|
|
11085
|
+
//
|
|
11086
|
+
// Two deliberate differences from oh-my-pi:
|
|
11087
|
+
// 1. The reasoning field is `reasoning`, not `reasoningTokens`.
|
|
11088
|
+
// 2. Reasoning is a SUBSET of `output`, and `totalTokens` excludes it —
|
|
11089
|
+
// verified across a 2,586-message corpus where
|
|
11090
|
+
// input+output+cacheRead+cacheWrite === totalTokens for every row, and
|
|
11091
|
+
// `usage.cost` bills no separate reasoning component. So omo follows the
|
|
11092
|
+
// Codex convention: reasoning_output_tokens is informational and must not
|
|
11093
|
+
// be billed on top of output (see computeRowCost in lib/pricing/index.js).
|
|
11094
|
+
//
|
|
11095
|
+
// OmO is a router — the upstream model name is recorded per message and there
|
|
11096
|
+
// is no global default (fallback: "omo-unknown").
|
|
11097
|
+
//
|
|
11098
|
+
// Path overrides are TokenTracker-only (TOKENTRACKER_OMO_AGENT_DIR /
|
|
11099
|
+
// TOKENTRACKER_OMO_HOME / OMO_HOME). PI_CONFIG_DIR and PI_CODING_AGENT_DIR are
|
|
11100
|
+
// deliberately NOT honored here: those belong to pi/omp, and routing them to a
|
|
11101
|
+
// third provider would reintroduce the ambiguity decidePiCodingAgentDirOwner
|
|
11102
|
+
// exists to resolve.
|
|
11103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11104
|
+
|
|
11105
|
+
function resolveOmoHome(env = process.env) {
|
|
11106
|
+
if (env.TOKENTRACKER_OMO_HOME) return expandHomePath(env.TOKENTRACKER_OMO_HOME, env);
|
|
11107
|
+
if (env.OMO_HOME) return expandHomePath(env.OMO_HOME, env);
|
|
11108
|
+
const home = env.HOME || require("node:os").homedir();
|
|
11109
|
+
if (process.platform === "win32") {
|
|
11110
|
+
return pickWin32ProviderPath({
|
|
11111
|
+
env,
|
|
11112
|
+
nativeValue: path.join(home, ".omo"),
|
|
11113
|
+
wslProviderDir: ".omo",
|
|
11114
|
+
});
|
|
11115
|
+
}
|
|
11116
|
+
return path.join(home, ".omo");
|
|
11117
|
+
}
|
|
11118
|
+
|
|
11119
|
+
function resolveOmoAgentDir(env = process.env) {
|
|
11120
|
+
if (env.TOKENTRACKER_OMO_AGENT_DIR) {
|
|
11121
|
+
return expandHomePath(env.TOKENTRACKER_OMO_AGENT_DIR, env);
|
|
11122
|
+
}
|
|
11123
|
+
const omoHome = resolveOmoHome(env);
|
|
11124
|
+
return omoHome ? path.join(omoHome, "agent") : null;
|
|
11125
|
+
}
|
|
11126
|
+
|
|
11127
|
+
function resolveOmoSessionFiles(env = process.env) {
|
|
11128
|
+
return collectPiStyleSessionFiles(resolveOmoAgentDir(env));
|
|
11129
|
+
}
|
|
11130
|
+
|
|
11131
|
+
function resolveOmoSubagentFiles(env = process.env) {
|
|
11132
|
+
return collectPiStyleSubagentFiles(resolveOmoAgentDir(env));
|
|
11133
|
+
}
|
|
11134
|
+
|
|
11135
|
+
function resolveOmoDefaultModel() {
|
|
11136
|
+
// OmO has no global default model setting; model is per-message.
|
|
11137
|
+
return "omo-unknown";
|
|
11138
|
+
}
|
|
11139
|
+
|
|
11140
|
+
async function resolveOmoFileCwd(filePath) {
|
|
11141
|
+
return readPiStyleSessionCwd(filePath);
|
|
11142
|
+
}
|
|
11143
|
+
|
|
11016
11144
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11017
11145
|
// Kilo Code VS Code extension — passive reader for VS Code-family
|
|
11018
11146
|
// globalStorage/kilocode.kilo-code/tasks/<uuid>/ui_messages.json files.
|
|
@@ -14594,7 +14722,23 @@ async function parseKilocodeIncremental({
|
|
|
14594
14722
|
return { recordsProcessed, eventsAggregated, bucketsQueued };
|
|
14595
14723
|
}
|
|
14596
14724
|
|
|
14597
|
-
|
|
14725
|
+
// Reads the first present numeric field. oh-my-pi emits `reasoningTokens`;
|
|
14726
|
+
// OmO emits `reasoning`. Listing the accepted keys per provider keeps each
|
|
14727
|
+
// one's accounting exact instead of guessing across both spellings.
|
|
14728
|
+
function pickUsageInt(usage, fields) {
|
|
14729
|
+
for (const field of fields) {
|
|
14730
|
+
const raw = usage?.[field];
|
|
14731
|
+
if (raw == null) continue;
|
|
14732
|
+
const value = Number(raw);
|
|
14733
|
+
if (Number.isFinite(value)) return toNonNegativeInt(value);
|
|
14734
|
+
}
|
|
14735
|
+
return 0;
|
|
14736
|
+
}
|
|
14737
|
+
|
|
14738
|
+
// Shared implementation for the oh-my-pi session format. omp and omo both
|
|
14739
|
+
// persist it verbatim, so they differ only in where the sessions live, which
|
|
14740
|
+
// cursor namespace they own, and how reasoning tokens are spelled.
|
|
14741
|
+
async function parseOmpLikeIncremental({
|
|
14598
14742
|
sessionFiles,
|
|
14599
14743
|
subagentFiles,
|
|
14600
14744
|
cursors,
|
|
@@ -14604,41 +14748,50 @@ async function parseOmpIncremental({
|
|
|
14604
14748
|
onProgress,
|
|
14605
14749
|
env,
|
|
14606
14750
|
defaultModel,
|
|
14751
|
+
stateKey,
|
|
14752
|
+
source,
|
|
14753
|
+
resolveSessionFiles,
|
|
14754
|
+
resolveSubagentFiles,
|
|
14755
|
+
resolveDefaultModel,
|
|
14756
|
+
resolveFileCwd,
|
|
14757
|
+
reasoningFields,
|
|
14758
|
+
reasoningIncludedInOutput = false,
|
|
14607
14759
|
} = {}) {
|
|
14608
14760
|
await ensureDir(path.dirname(queuePath));
|
|
14609
14761
|
const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
|
|
14610
|
-
const
|
|
14611
|
-
|
|
14762
|
+
const providerState =
|
|
14763
|
+
cursors[stateKey] && typeof cursors[stateKey] === "object" ? cursors[stateKey] : {};
|
|
14764
|
+
const seenIds = new Set(Array.isArray(providerState.seenIds) ? providerState.seenIds : []);
|
|
14612
14765
|
const projectSeenIds = new Set(
|
|
14613
|
-
Array.isArray(
|
|
14766
|
+
Array.isArray(providerState.projectSeenIds) ? providerState.projectSeenIds : [],
|
|
14614
14767
|
);
|
|
14615
14768
|
const fileOffsets =
|
|
14616
|
-
|
|
14617
|
-
? { ...
|
|
14769
|
+
providerState.fileOffsets && typeof providerState.fileOffsets === "object"
|
|
14770
|
+
? { ...providerState.fileOffsets }
|
|
14618
14771
|
: {};
|
|
14619
14772
|
const projectFileOffsets =
|
|
14620
|
-
|
|
14621
|
-
? { ...
|
|
14773
|
+
providerState.projectFileOffsets && typeof providerState.projectFileOffsets === "object"
|
|
14774
|
+
? { ...providerState.projectFileOffsets }
|
|
14622
14775
|
: {};
|
|
14623
14776
|
|
|
14624
14777
|
const mainFiles = Array.isArray(sessionFiles)
|
|
14625
14778
|
? sessionFiles
|
|
14626
|
-
:
|
|
14779
|
+
: resolveSessionFiles(env || process.env);
|
|
14627
14780
|
// Subagent transcripts share the session format and count toward the same
|
|
14628
|
-
//
|
|
14781
|
+
// provider totals; they're discovered separately because they nest below the
|
|
14629
14782
|
// cwd level. When the caller supplies explicit sessionFiles (tests), don't
|
|
14630
14783
|
// auto-resolve — keep the parse hermetic.
|
|
14631
14784
|
const subFiles = Array.isArray(subagentFiles)
|
|
14632
14785
|
? subagentFiles
|
|
14633
14786
|
: Array.isArray(sessionFiles)
|
|
14634
14787
|
? []
|
|
14635
|
-
:
|
|
14788
|
+
: resolveSubagentFiles(env || process.env);
|
|
14636
14789
|
const files = [...mainFiles, ...subFiles];
|
|
14637
|
-
const fallbackModel = defaultModel ||
|
|
14790
|
+
const fallbackModel = defaultModel || resolveDefaultModel();
|
|
14638
14791
|
|
|
14639
14792
|
if (files.length === 0) {
|
|
14640
|
-
cursors
|
|
14641
|
-
...
|
|
14793
|
+
cursors[stateKey] = {
|
|
14794
|
+
...providerState,
|
|
14642
14795
|
seenIds: Array.from(seenIds),
|
|
14643
14796
|
fileOffsets,
|
|
14644
14797
|
...(projectEnabled
|
|
@@ -14716,7 +14869,7 @@ async function parseOmpIncremental({
|
|
|
14716
14869
|
const output = toNonNegativeInt(usage.output);
|
|
14717
14870
|
const cacheRead = toNonNegativeInt(usage.cacheRead);
|
|
14718
14871
|
const cacheWrite = toNonNegativeInt(usage.cacheWrite);
|
|
14719
|
-
const reasoningTokens =
|
|
14872
|
+
const reasoningTokens = pickUsageInt(usage, reasoningFields);
|
|
14720
14873
|
|
|
14721
14874
|
if (
|
|
14722
14875
|
input === 0 &&
|
|
@@ -14749,10 +14902,12 @@ async function parseOmpIncremental({
|
|
|
14749
14902
|
if (!bucketStart) continue;
|
|
14750
14903
|
|
|
14751
14904
|
// Use provided totalTokens when available; otherwise sum all components.
|
|
14905
|
+
// OmO folds reasoning into output, so the fallback must not add it again.
|
|
14752
14906
|
const totalTokens =
|
|
14753
14907
|
Number.isFinite(Number(usage.totalTokens)) && Number(usage.totalTokens) > 0
|
|
14754
14908
|
? toNonNegativeInt(usage.totalTokens)
|
|
14755
|
-
: input + output + cacheRead + cacheWrite +
|
|
14909
|
+
: input + output + cacheRead + cacheWrite +
|
|
14910
|
+
(reasoningIncludedInOutput ? 0 : reasoningTokens);
|
|
14756
14911
|
|
|
14757
14912
|
const model = normalizeModelInput(msg.model) || fallbackModel;
|
|
14758
14913
|
|
|
@@ -14766,9 +14921,9 @@ async function parseOmpIncremental({
|
|
|
14766
14921
|
conversation_count: 1,
|
|
14767
14922
|
};
|
|
14768
14923
|
|
|
14769
|
-
const bucket = getHourlyBucket(hourlyState,
|
|
14924
|
+
const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
|
|
14770
14925
|
addTotals(bucket.totals, delta);
|
|
14771
|
-
touchedBuckets.add(bucketKey(
|
|
14926
|
+
touchedBuckets.add(bucketKey(source, model, bucketStart));
|
|
14772
14927
|
seenIds.add(entryId);
|
|
14773
14928
|
eventsAggregated++;
|
|
14774
14929
|
|
|
@@ -14809,7 +14964,7 @@ async function parseOmpIncremental({
|
|
|
14809
14964
|
const startOffset = stat.size < prevSize || inodeChanged ? 0 : prevSize;
|
|
14810
14965
|
if (stat.size <= startOffset) continue;
|
|
14811
14966
|
|
|
14812
|
-
const cwd = await
|
|
14967
|
+
const cwd = await resolveFileCwd(filePath);
|
|
14813
14968
|
const projectContext = cwd
|
|
14814
14969
|
? await resolveProjectContextForPath({
|
|
14815
14970
|
startDir: wsl.mapWslCwdToUnc(cwd, filePath),
|
|
@@ -14848,7 +15003,7 @@ async function parseOmpIncremental({
|
|
|
14848
15003
|
const output = toNonNegativeInt(usage.output);
|
|
14849
15004
|
const cacheRead = toNonNegativeInt(usage.cacheRead);
|
|
14850
15005
|
const cacheWrite = toNonNegativeInt(usage.cacheWrite);
|
|
14851
|
-
const reasoningTokens =
|
|
15006
|
+
const reasoningTokens = pickUsageInt(usage, reasoningFields);
|
|
14852
15007
|
if (
|
|
14853
15008
|
input === 0 &&
|
|
14854
15009
|
output === 0 &&
|
|
@@ -14878,7 +15033,8 @@ async function parseOmpIncremental({
|
|
|
14878
15033
|
const totalTokens =
|
|
14879
15034
|
Number.isFinite(Number(usage.totalTokens)) && Number(usage.totalTokens) > 0
|
|
14880
15035
|
? toNonNegativeInt(usage.totalTokens)
|
|
14881
|
-
: input + output + cacheRead + cacheWrite +
|
|
15036
|
+
: input + output + cacheRead + cacheWrite +
|
|
15037
|
+
(reasoningIncludedInOutput ? 0 : reasoningTokens);
|
|
14882
15038
|
const delta = {
|
|
14883
15039
|
input_tokens: input,
|
|
14884
15040
|
cached_input_tokens: cacheRead,
|
|
@@ -14891,12 +15047,12 @@ async function parseOmpIncremental({
|
|
|
14891
15047
|
const projectBucket = getProjectBucket(
|
|
14892
15048
|
projectState,
|
|
14893
15049
|
projectKey,
|
|
14894
|
-
|
|
15050
|
+
source,
|
|
14895
15051
|
bucketStart,
|
|
14896
15052
|
projectRef,
|
|
14897
15053
|
);
|
|
14898
15054
|
addTotals(projectBucket.totals, delta);
|
|
14899
|
-
projectTouchedBuckets.add(projectBucketKey(projectKey,
|
|
15055
|
+
projectTouchedBuckets.add(projectBucketKey(projectKey, source, bucketStart));
|
|
14900
15056
|
projectSeenIds.add(entryId);
|
|
14901
15057
|
}
|
|
14902
15058
|
}
|
|
@@ -14941,8 +15097,8 @@ async function parseOmpIncremental({
|
|
|
14941
15097
|
projectState.updatedAt = updatedAt;
|
|
14942
15098
|
cursors.projectHourly = projectState;
|
|
14943
15099
|
}
|
|
14944
|
-
cursors
|
|
14945
|
-
...
|
|
15100
|
+
cursors[stateKey] = {
|
|
15101
|
+
...providerState,
|
|
14946
15102
|
seenIds: cappedSeen,
|
|
14947
15103
|
fileOffsets,
|
|
14948
15104
|
...(projectEnabled
|
|
@@ -14957,6 +15113,33 @@ async function parseOmpIncremental({
|
|
|
14957
15113
|
return { recordsProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
|
|
14958
15114
|
}
|
|
14959
15115
|
|
|
15116
|
+
async function parseOmpIncremental(options = {}) {
|
|
15117
|
+
return parseOmpLikeIncremental({
|
|
15118
|
+
...options,
|
|
15119
|
+
stateKey: "omp",
|
|
15120
|
+
source: "omp",
|
|
15121
|
+
resolveSessionFiles: resolveOmpSessionFiles,
|
|
15122
|
+
resolveSubagentFiles: resolveOmpSubagentFiles,
|
|
15123
|
+
resolveDefaultModel: resolveOmpDefaultModel,
|
|
15124
|
+
resolveFileCwd: resolveOmpFileCwd,
|
|
15125
|
+
reasoningFields: ["reasoningTokens"],
|
|
15126
|
+
});
|
|
15127
|
+
}
|
|
15128
|
+
|
|
15129
|
+
async function parseOmoIncremental(options = {}) {
|
|
15130
|
+
return parseOmpLikeIncremental({
|
|
15131
|
+
...options,
|
|
15132
|
+
stateKey: "omo",
|
|
15133
|
+
source: "omo",
|
|
15134
|
+
resolveSessionFiles: resolveOmoSessionFiles,
|
|
15135
|
+
resolveSubagentFiles: resolveOmoSubagentFiles,
|
|
15136
|
+
resolveDefaultModel: resolveOmoDefaultModel,
|
|
15137
|
+
resolveFileCwd: resolveOmoFileCwd,
|
|
15138
|
+
reasoningFields: ["reasoningTokens", "reasoning"],
|
|
15139
|
+
reasoningIncludedInOutput: true,
|
|
15140
|
+
});
|
|
15141
|
+
}
|
|
15142
|
+
|
|
14960
15143
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
14961
15144
|
// pi (@mariozechner/pi-coding-agent) — passive JSONL reader
|
|
14962
15145
|
// (~/.pi/agent/sessions/**/*.jsonl)
|
|
@@ -15007,6 +15190,13 @@ function piAgentDirCollidesWithOmp(env = process.env) {
|
|
|
15007
15190
|
return path.resolve(piAgentDir) === path.resolve(ompAgentDir);
|
|
15008
15191
|
}
|
|
15009
15192
|
|
|
15193
|
+
function omoAgentDirCollidesWithOmp(env = process.env) {
|
|
15194
|
+
const omoAgentDir = resolveOmoAgentDir(env);
|
|
15195
|
+
const ompAgentDir = resolveOmpAgentDir(env);
|
|
15196
|
+
if (!omoAgentDir || !ompAgentDir) return false;
|
|
15197
|
+
return path.resolve(omoAgentDir) === path.resolve(ompAgentDir);
|
|
15198
|
+
}
|
|
15199
|
+
|
|
15010
15200
|
function resolvePiSessionFiles(env = process.env) {
|
|
15011
15201
|
const agentDir = resolvePiAgentDir(env);
|
|
15012
15202
|
if (!agentDir) return [];
|
|
@@ -15638,7 +15828,15 @@ function resolveReasonixHome(env = process.env) {
|
|
|
15638
15828
|
if (env.REASONIX_STATE_HOME) {
|
|
15639
15829
|
return expandHomePath(env.REASONIX_STATE_HOME, env);
|
|
15640
15830
|
}
|
|
15641
|
-
|
|
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();
|
|
15642
15840
|
return path.join(home, ".reasonix");
|
|
15643
15841
|
}
|
|
15644
15842
|
|
|
@@ -19250,27 +19448,236 @@ function resolveAntigravityBrainDirs(geminiHome) {
|
|
|
19250
19448
|
];
|
|
19251
19449
|
}
|
|
19252
19450
|
|
|
19253
|
-
|
|
19451
|
+
function antigravityPathWithin(filePath, root) {
|
|
19452
|
+
if (typeof filePath !== "string" || typeof root !== "string") return false;
|
|
19453
|
+
const relative = path.relative(root, filePath);
|
|
19454
|
+
return Boolean(
|
|
19455
|
+
relative &&
|
|
19456
|
+
relative !== ".." &&
|
|
19457
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
19458
|
+
!path.isAbsolute(relative),
|
|
19459
|
+
);
|
|
19460
|
+
}
|
|
19461
|
+
|
|
19462
|
+
async function listAntigravitySessionFilesWithStatus(brainDir, knownFiles = null) {
|
|
19254
19463
|
const out = [];
|
|
19255
|
-
|
|
19256
|
-
|
|
19464
|
+
const known = knownFiles instanceof Set ? knownFiles : new Set(knownFiles || []);
|
|
19465
|
+
if (!brainDir || typeof brainDir !== "string") {
|
|
19466
|
+
return { files: out, complete: false };
|
|
19467
|
+
}
|
|
19468
|
+
|
|
19469
|
+
let entries;
|
|
19470
|
+
try {
|
|
19471
|
+
entries = await fs.readdir(brainDir, { withFileTypes: true });
|
|
19472
|
+
} catch (err) {
|
|
19473
|
+
// A missing optional install directory is an authoritative empty inventory
|
|
19474
|
+
// only when it has never contained a tracked session. If it has, the
|
|
19475
|
+
// directory may be temporarily unavailable and must not trigger retraction.
|
|
19476
|
+
return {
|
|
19477
|
+
files: out,
|
|
19478
|
+
complete:
|
|
19479
|
+
err?.code === "ENOENT" &&
|
|
19480
|
+
!Array.from(known).some((filePath) => antigravityPathWithin(filePath, brainDir)),
|
|
19481
|
+
};
|
|
19482
|
+
}
|
|
19483
|
+
|
|
19484
|
+
let complete = true;
|
|
19257
19485
|
for (const entry of entries) {
|
|
19258
19486
|
if (!entry.isDirectory()) continue;
|
|
19259
19487
|
const logsDir = path.join(brainDir, entry.name, ".system_generated", "logs");
|
|
19260
19488
|
const transcriptPath = path.join(logsDir, "transcript.jsonl");
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19489
|
+
let st;
|
|
19490
|
+
try {
|
|
19491
|
+
st = await fs.stat(transcriptPath);
|
|
19492
|
+
} catch (err) {
|
|
19493
|
+
// ENOENT means this session directory currently has no transcript (or
|
|
19494
|
+
// the transcript was deleted). Other errors make the scan incomplete.
|
|
19495
|
+
if (err?.code !== "ENOENT") complete = false;
|
|
19496
|
+
continue;
|
|
19264
19497
|
}
|
|
19498
|
+
if (st.isFile()) out.push(transcriptPath);
|
|
19265
19499
|
}
|
|
19500
|
+
|
|
19266
19501
|
out.sort((a, b) => a.localeCompare(b));
|
|
19267
|
-
return out;
|
|
19502
|
+
return { files: out, complete };
|
|
19268
19503
|
}
|
|
19269
19504
|
|
|
19270
|
-
async function
|
|
19505
|
+
async function listAntigravitySessionFiles(brainDir) {
|
|
19506
|
+
return (await listAntigravitySessionFilesWithStatus(brainDir)).files;
|
|
19507
|
+
}
|
|
19508
|
+
|
|
19509
|
+
async function listAntigravityTranscriptsWithStatus(geminiHome, knownFiles = null) {
|
|
19271
19510
|
const dirs = resolveAntigravityBrainDirs(geminiHome);
|
|
19272
|
-
const
|
|
19273
|
-
|
|
19511
|
+
const results = await Promise.all(
|
|
19512
|
+
dirs.map((dir) => listAntigravitySessionFilesWithStatus(dir, knownFiles)),
|
|
19513
|
+
);
|
|
19514
|
+
return {
|
|
19515
|
+
files: results.flatMap((result) => result.files),
|
|
19516
|
+
complete: results.every((result) => result.complete),
|
|
19517
|
+
};
|
|
19518
|
+
}
|
|
19519
|
+
|
|
19520
|
+
async function listAntigravityTranscripts(geminiHome) {
|
|
19521
|
+
return (await listAntigravityTranscriptsWithStatus(geminiHome)).files;
|
|
19522
|
+
}
|
|
19523
|
+
|
|
19524
|
+
const ANTIGRAVITY_CURSOR_VERSION = 2;
|
|
19525
|
+
// Bump when extractAntigravityGenInfo starts reading records it used to
|
|
19526
|
+
// discard. Kept apart from the cursor version: a cursor-version mismatch routes
|
|
19527
|
+
// to the legacy migration, which treats the cursor as having no per-file ledger.
|
|
19528
|
+
const ANTIGRAVITY_EXTRACTOR_REVISION = 1;
|
|
19529
|
+
const ANTIGRAVITY_MAX_CONTRIBUTIONS = 4096;
|
|
19530
|
+
|
|
19531
|
+
function hashAntigravityTranscript(buffer) {
|
|
19532
|
+
if (!Buffer.isBuffer(buffer)) return null;
|
|
19533
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
19534
|
+
}
|
|
19535
|
+
|
|
19536
|
+
function antigravityTranscriptPrefixMatches(buffer, previous) {
|
|
19537
|
+
if (!Buffer.isBuffer(buffer) || !previous || typeof previous.transcriptHash !== "string") {
|
|
19538
|
+
return false;
|
|
19539
|
+
}
|
|
19540
|
+
const previousSize = Number(previous.size);
|
|
19541
|
+
if (!Number.isSafeInteger(previousSize) || previousSize < 0 || buffer.length < previousSize) {
|
|
19542
|
+
return false;
|
|
19543
|
+
}
|
|
19544
|
+
return hashAntigravityTranscript(buffer.subarray(0, previousSize)) === previous.transcriptHash;
|
|
19545
|
+
}
|
|
19546
|
+
|
|
19547
|
+
async function readAntigravityTranscriptBytes(filePath) {
|
|
19548
|
+
try {
|
|
19549
|
+
return await fs.readFile(filePath);
|
|
19550
|
+
} catch (_error) {
|
|
19551
|
+
return null;
|
|
19552
|
+
}
|
|
19553
|
+
}
|
|
19554
|
+
|
|
19555
|
+
async function statAntigravityDatabase(dbPath) {
|
|
19556
|
+
const result = {
|
|
19557
|
+
dbExists: false,
|
|
19558
|
+
dbInode: 0,
|
|
19559
|
+
dbMtimeMs: 0,
|
|
19560
|
+
dbCtimeMs: 0,
|
|
19561
|
+
dbSize: 0,
|
|
19562
|
+
dbWalInode: 0,
|
|
19563
|
+
dbWalMtimeMs: 0,
|
|
19564
|
+
dbWalSize: 0,
|
|
19565
|
+
dbJournalInode: 0,
|
|
19566
|
+
dbJournalMtimeMs: 0,
|
|
19567
|
+
dbJournalSize: 0,
|
|
19568
|
+
};
|
|
19569
|
+
if (!dbPath) return result;
|
|
19570
|
+
for (const [suffix, prefix] of [
|
|
19571
|
+
["", "db"],
|
|
19572
|
+
["-wal", "dbWal"],
|
|
19573
|
+
["-journal", "dbJournal"],
|
|
19574
|
+
]) {
|
|
19575
|
+
const st = await fs.stat(`${dbPath}${suffix}`).catch(() => null);
|
|
19576
|
+
if (!st || !st.isFile()) continue;
|
|
19577
|
+
if (suffix === "") result.dbExists = true;
|
|
19578
|
+
result[`${prefix}MtimeMs`] = Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
|
|
19579
|
+
if (suffix === "") {
|
|
19580
|
+
result.dbInode = Number.isFinite(st.ino) ? st.ino : 0;
|
|
19581
|
+
result.dbCtimeMs = Number.isFinite(st.ctimeMs) ? st.ctimeMs : 0;
|
|
19582
|
+
} else {
|
|
19583
|
+
result[`${prefix}Inode`] = Number.isFinite(st.ino) ? st.ino : 0;
|
|
19584
|
+
}
|
|
19585
|
+
result[`${prefix}Size`] = Number.isFinite(st.size) ? st.size : 0;
|
|
19586
|
+
}
|
|
19587
|
+
return result;
|
|
19588
|
+
}
|
|
19589
|
+
|
|
19590
|
+
// Neither the transcript nor the database changes when the extractor does, so
|
|
19591
|
+
// without this stamp a session read before an extractor fix keeps its totals.
|
|
19592
|
+
function antigravityExtractorStale(previous, dbPath) {
|
|
19593
|
+
return Boolean(
|
|
19594
|
+
dbPath &&
|
|
19595
|
+
previous &&
|
|
19596
|
+
previous.cursorVersion === ANTIGRAVITY_CURSOR_VERSION &&
|
|
19597
|
+
previous.extractorRevision !== ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
19598
|
+
);
|
|
19599
|
+
}
|
|
19600
|
+
|
|
19601
|
+
function sameAntigravityDatabase(previous, current) {
|
|
19602
|
+
if (!previous || !current) return false;
|
|
19603
|
+
// SQLite updates the -shm read marks when a reader opens a WAL database.
|
|
19604
|
+
// Those mtime changes do not represent new usage metadata and must not force
|
|
19605
|
+
// a full transcript reconciliation on every sync.
|
|
19606
|
+
return [
|
|
19607
|
+
"dbExists",
|
|
19608
|
+
"dbInode",
|
|
19609
|
+
"dbMtimeMs",
|
|
19610
|
+
"dbCtimeMs",
|
|
19611
|
+
"dbSize",
|
|
19612
|
+
"dbWalInode",
|
|
19613
|
+
"dbWalMtimeMs",
|
|
19614
|
+
"dbWalSize",
|
|
19615
|
+
"dbJournalInode",
|
|
19616
|
+
"dbJournalMtimeMs",
|
|
19617
|
+
"dbJournalSize",
|
|
19618
|
+
].every((field) => Number(previous[field] || 0) === Number(current[field] || 0));
|
|
19619
|
+
}
|
|
19620
|
+
|
|
19621
|
+
function capAntigravityContributions(contributions) {
|
|
19622
|
+
if (!contributions || typeof contributions !== "object") {
|
|
19623
|
+
return { contributions: {}, complete: true };
|
|
19624
|
+
}
|
|
19625
|
+
const entries = Object.entries(contributions);
|
|
19626
|
+
if (entries.length <= ANTIGRAVITY_MAX_CONTRIBUTIONS) {
|
|
19627
|
+
return { contributions, complete: true };
|
|
19628
|
+
}
|
|
19629
|
+
|
|
19630
|
+
entries.sort(([, left], [, right]) => {
|
|
19631
|
+
const leftTime = Date.parse(left?.bucketStart || "");
|
|
19632
|
+
const rightTime = Date.parse(right?.bucketStart || "");
|
|
19633
|
+
if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
|
|
19634
|
+
return rightTime - leftTime;
|
|
19635
|
+
}
|
|
19636
|
+
if (Number.isFinite(rightTime) !== Number.isFinite(leftTime)) {
|
|
19637
|
+
return Number.isFinite(rightTime) ? 1 : -1;
|
|
19638
|
+
}
|
|
19639
|
+
return 0;
|
|
19640
|
+
});
|
|
19641
|
+
|
|
19642
|
+
return {
|
|
19643
|
+
contributions: Object.fromEntries(entries.slice(0, ANTIGRAVITY_MAX_CONTRIBUTIONS)),
|
|
19644
|
+
complete: false,
|
|
19645
|
+
};
|
|
19646
|
+
}
|
|
19647
|
+
|
|
19648
|
+
function resetAntigravityBucketsForRebuild({
|
|
19649
|
+
hourlyState,
|
|
19650
|
+
projectState,
|
|
19651
|
+
sources,
|
|
19652
|
+
touchedBuckets,
|
|
19653
|
+
projectTouchedBuckets,
|
|
19654
|
+
}) {
|
|
19655
|
+
if (!sources || sources.size === 0) return;
|
|
19656
|
+
|
|
19657
|
+
for (const [key, bucket] of Object.entries(hourlyState?.buckets || {})) {
|
|
19658
|
+
if (!bucket || !bucket.totals) continue;
|
|
19659
|
+
const bucketSource = normalizeSourceInput(parseBucketKey(key).source);
|
|
19660
|
+
if (!sources.has(bucketSource)) continue;
|
|
19661
|
+
bucket.totals = initTotals();
|
|
19662
|
+
bucket.queuedKey = null;
|
|
19663
|
+
bucket.retractedUnknownKey = null;
|
|
19664
|
+
touchedBuckets.add(key);
|
|
19665
|
+
}
|
|
19666
|
+
|
|
19667
|
+
for (const key of Object.keys(hourlyState?.groupQueued || {})) {
|
|
19668
|
+
const groupSource = normalizeSourceInput(key.split(BUCKET_SEPARATOR)[0]);
|
|
19669
|
+
if (sources.has(groupSource)) delete hourlyState.groupQueued[key];
|
|
19670
|
+
}
|
|
19671
|
+
|
|
19672
|
+
if (!projectState || !projectTouchedBuckets) return;
|
|
19673
|
+
for (const [key, bucket] of Object.entries(projectState.buckets || {})) {
|
|
19674
|
+
const keyParts = key.split(BUCKET_SEPARATOR);
|
|
19675
|
+
const bucketSource = normalizeSourceInput(bucket?.source || keyParts.at(-2));
|
|
19676
|
+
if (!sources.has(bucketSource) || !bucket?.totals) continue;
|
|
19677
|
+
bucket.totals = initTotals();
|
|
19678
|
+
bucket.queuedKey = null;
|
|
19679
|
+
projectTouchedBuckets.add(key);
|
|
19680
|
+
}
|
|
19274
19681
|
}
|
|
19275
19682
|
|
|
19276
19683
|
async function parseAntigravityIncremental({
|
|
@@ -19281,6 +19688,10 @@ async function parseAntigravityIncremental({
|
|
|
19281
19688
|
onProgress,
|
|
19282
19689
|
source,
|
|
19283
19690
|
publicRepoResolver,
|
|
19691
|
+
// Source-wide rebuilds may retract sessions absent from sessionFiles. Only
|
|
19692
|
+
// permit that when discovery explicitly completed; direct parser callers
|
|
19693
|
+
// retain the historical complete-inventory default for compatibility.
|
|
19694
|
+
inventoryComplete = true,
|
|
19284
19695
|
}) {
|
|
19285
19696
|
await ensureDir(path.dirname(queuePath));
|
|
19286
19697
|
let filesProcessed = 0;
|
|
@@ -19289,17 +19700,147 @@ async function parseAntigravityIncremental({
|
|
|
19289
19700
|
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
19290
19701
|
const files = Array.isArray(sessionFiles) ? sessionFiles : [];
|
|
19291
19702
|
const totalFiles = files.length;
|
|
19292
|
-
|
|
19703
|
+
// Keep every mutation private until both queue appends succeed. The generic
|
|
19704
|
+
// normalizers intentionally preserve bucket object references for other
|
|
19705
|
+
// parsers, so Antigravity must deep-clone its working state here; otherwise a
|
|
19706
|
+
// queue failure can leave a committed cursor pointing at already-mutated
|
|
19707
|
+
// hourly totals.
|
|
19708
|
+
const hourlyState = structuredClone(normalizeHourlyState(cursors?.hourly));
|
|
19293
19709
|
const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
|
|
19294
|
-
const projectState = projectEnabled
|
|
19710
|
+
const projectState = projectEnabled
|
|
19711
|
+
? structuredClone(normalizeProjectState(cursors?.projectHourly))
|
|
19712
|
+
: null;
|
|
19295
19713
|
const projectTouchedBuckets = projectEnabled ? new Set() : null;
|
|
19296
19714
|
const projectMetaCache = projectEnabled ? new Map() : null;
|
|
19297
19715
|
const publicRepoCache = projectEnabled ? new Map() : null;
|
|
19298
19716
|
const touchedBuckets = new Set();
|
|
19299
19717
|
const defaultSource = normalizeSourceInput(source) || "antigravity";
|
|
19718
|
+
const fileCursors =
|
|
19719
|
+
cursors.files && typeof cursors.files === "object" && !Array.isArray(cursors.files)
|
|
19720
|
+
? structuredClone(cursors.files)
|
|
19721
|
+
: {};
|
|
19300
19722
|
|
|
19301
|
-
|
|
19302
|
-
|
|
19723
|
+
const fileSources = new Set();
|
|
19724
|
+
const fileSourceByPath = new Map();
|
|
19725
|
+
for (const entry of files) {
|
|
19726
|
+
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
19727
|
+
const fileSource =
|
|
19728
|
+
typeof entry === "string"
|
|
19729
|
+
? defaultSource
|
|
19730
|
+
: normalizeSourceInput(entry?.source) || defaultSource;
|
|
19731
|
+
if (filePath) fileSourceByPath.set(filePath, fileSource);
|
|
19732
|
+
fileSources.add(fileSource);
|
|
19733
|
+
}
|
|
19734
|
+
if (fileSources.size === 0) fileSources.add(defaultSource);
|
|
19735
|
+
for (const [filePath, prev] of Object.entries(fileCursors)) {
|
|
19736
|
+
if (!resolveAntigravityDbPath(filePath)) continue;
|
|
19737
|
+
const previousSource = normalizeSourceInput(prev?.source);
|
|
19738
|
+
if (previousSource) fileSources.add(previousSource);
|
|
19739
|
+
}
|
|
19740
|
+
|
|
19741
|
+
// Legacy cursors have no per-file contribution ledger. Rebuild when every
|
|
19742
|
+
// legacy file is still present; if discovery is incomplete, preserve the old
|
|
19743
|
+
// aggregate and migrate live files against transcript-only baselines below.
|
|
19744
|
+
const liveFilePaths = new Set(
|
|
19745
|
+
files
|
|
19746
|
+
.map((entry) => (typeof entry === "string" ? entry : entry?.path))
|
|
19747
|
+
.filter(Boolean),
|
|
19748
|
+
);
|
|
19749
|
+
const incompleteContributionPaths = new Set();
|
|
19750
|
+
const deferredIncompletePaths = new Set();
|
|
19751
|
+
let needsFullRebuild = false;
|
|
19752
|
+
for (const [filePath, prev] of Object.entries(fileCursors)) {
|
|
19753
|
+
if (!resolveAntigravityDbPath(filePath) || !prev || Number(prev.lastLine || 0) <= 0) {
|
|
19754
|
+
continue;
|
|
19755
|
+
}
|
|
19756
|
+
const isLegacy =
|
|
19757
|
+
prev.cursorVersion !== ANTIGRAVITY_CURSOR_VERSION ||
|
|
19758
|
+
!prev.contributions ||
|
|
19759
|
+
typeof prev.contributions !== "object";
|
|
19760
|
+
if (!isLegacy && prev.contributionsComplete === false) {
|
|
19761
|
+
incompleteContributionPaths.add(filePath);
|
|
19762
|
+
}
|
|
19763
|
+
}
|
|
19764
|
+
|
|
19765
|
+
// Project reassignment is another full-ledger change: a capped ledger cannot
|
|
19766
|
+
// retract contributions that fell outside its retained window. Resolve
|
|
19767
|
+
// incomplete files against a private project state before deciding whether a
|
|
19768
|
+
// source-wide rebuild is required.
|
|
19769
|
+
const preflightProjectState =
|
|
19770
|
+
projectEnabled && incompleteContributionPaths.size > 0
|
|
19771
|
+
? structuredClone(projectState)
|
|
19772
|
+
: null;
|
|
19773
|
+
const preflightProjectMetaCache = preflightProjectState ? new Map() : null;
|
|
19774
|
+
const preflightPublicRepoCache = preflightProjectState ? new Map() : null;
|
|
19775
|
+
|
|
19776
|
+
// A capped contribution ledger is safe while its transcript/SQLite identity
|
|
19777
|
+
// is unchanged. If an incomplete ledger needs reconciliation, rebuild the
|
|
19778
|
+
// complete source baseline before applying any per-file deltas. When source
|
|
19779
|
+
// discovery is incomplete, defer the file instead: resetting the source
|
|
19780
|
+
// would erase contributions belonging to sessions omitted by discovery.
|
|
19781
|
+
if (!needsFullRebuild && incompleteContributionPaths.size > 0) {
|
|
19782
|
+
for (const filePath of incompleteContributionPaths) {
|
|
19783
|
+
if (!liveFilePaths.has(filePath)) {
|
|
19784
|
+
if (!inventoryComplete) {
|
|
19785
|
+
deferredIncompletePaths.add(filePath);
|
|
19786
|
+
} else {
|
|
19787
|
+
needsFullRebuild = true;
|
|
19788
|
+
}
|
|
19789
|
+
if (needsFullRebuild) break;
|
|
19790
|
+
continue;
|
|
19791
|
+
}
|
|
19792
|
+
const prev = cursors.files[filePath];
|
|
19793
|
+
const st = await fs.stat(filePath).catch(() => null);
|
|
19794
|
+
const dbPath = resolveAntigravityDbPath(filePath);
|
|
19795
|
+
const dbIdentity = await statAntigravityDatabase(dbPath);
|
|
19796
|
+
const sameInode = st && prev.inode === (st.ino || 0);
|
|
19797
|
+
const transcriptCanAppend =
|
|
19798
|
+
sameInode &&
|
|
19799
|
+
(Number.isFinite(st.size) ? st.size : 0) >= Number(prev.size || 0) &&
|
|
19800
|
+
antigravityTranscriptPrefixMatches(
|
|
19801
|
+
await readAntigravityTranscriptBytes(filePath),
|
|
19802
|
+
prev,
|
|
19803
|
+
);
|
|
19804
|
+
const sameSource =
|
|
19805
|
+
!fileSourceByPath.has(filePath) ||
|
|
19806
|
+
(prev.source == null ? defaultSource : prev.source) === fileSourceByPath.get(filePath);
|
|
19807
|
+
const sameDb = sameAntigravityDatabase(prev, dbIdentity);
|
|
19808
|
+
const staleExtractor = antigravityExtractorStale(prev, dbPath);
|
|
19809
|
+
let projectChanged = false;
|
|
19810
|
+
if (preflightProjectState) {
|
|
19811
|
+
const projectContext = await resolveProjectContextForFile({
|
|
19812
|
+
filePath,
|
|
19813
|
+
projectMetaCache: preflightProjectMetaCache,
|
|
19814
|
+
publicRepoCache: preflightPublicRepoCache,
|
|
19815
|
+
publicRepoResolver,
|
|
19816
|
+
projectState: preflightProjectState,
|
|
19817
|
+
});
|
|
19818
|
+
projectChanged = antigravityProjectAssignmentChanged(prev, projectContext);
|
|
19819
|
+
}
|
|
19820
|
+
if (!transcriptCanAppend || !sameDb || staleExtractor || !sameSource || projectChanged) {
|
|
19821
|
+
if (!inventoryComplete) {
|
|
19822
|
+
deferredIncompletePaths.add(filePath);
|
|
19823
|
+
} else {
|
|
19824
|
+
needsFullRebuild = true;
|
|
19825
|
+
}
|
|
19826
|
+
if (needsFullRebuild) break;
|
|
19827
|
+
}
|
|
19828
|
+
}
|
|
19829
|
+
}
|
|
19830
|
+
|
|
19831
|
+
if (needsFullRebuild) {
|
|
19832
|
+
for (const filePath of Object.keys(fileCursors)) {
|
|
19833
|
+
if (resolveAntigravityDbPath(filePath) && !liveFilePaths.has(filePath)) {
|
|
19834
|
+
delete fileCursors[filePath];
|
|
19835
|
+
}
|
|
19836
|
+
}
|
|
19837
|
+
resetAntigravityBucketsForRebuild({
|
|
19838
|
+
hourlyState,
|
|
19839
|
+
projectState,
|
|
19840
|
+
sources: fileSources,
|
|
19841
|
+
touchedBuckets,
|
|
19842
|
+
projectTouchedBuckets,
|
|
19843
|
+
});
|
|
19303
19844
|
}
|
|
19304
19845
|
|
|
19305
19846
|
for (let idx = 0; idx < files.length; idx++) {
|
|
@@ -19311,17 +19852,140 @@ async function parseAntigravityIncremental({
|
|
|
19311
19852
|
? defaultSource
|
|
19312
19853
|
: normalizeSourceInput(entry?.source) || defaultSource;
|
|
19313
19854
|
const st = await fs.stat(filePath).catch(() => null);
|
|
19314
|
-
if (!st || !st.isFile())
|
|
19855
|
+
if (!st || !st.isFile()) {
|
|
19856
|
+
if (needsFullRebuild) {
|
|
19857
|
+
throw new Error(`Antigravity transcript disappeared during rebuild: ${filePath}`);
|
|
19858
|
+
}
|
|
19859
|
+
continue;
|
|
19860
|
+
}
|
|
19315
19861
|
|
|
19316
19862
|
const key = filePath;
|
|
19317
|
-
|
|
19863
|
+
if (deferredIncompletePaths.has(key)) continue;
|
|
19864
|
+
const prev = fileCursors[key] || null;
|
|
19865
|
+
|
|
19318
19866
|
const inode = st.ino || 0;
|
|
19319
19867
|
const size = Number.isFinite(st.size) ? st.size : 0;
|
|
19320
19868
|
const mtimeMs = Number.isFinite(st.mtimeMs) ? st.mtimeMs : 0;
|
|
19869
|
+
const ctimeMs = Number.isFinite(st.ctimeMs) ? st.ctimeMs : 0;
|
|
19321
19870
|
|
|
19322
|
-
const
|
|
19323
|
-
prev &&
|
|
19324
|
-
|
|
19871
|
+
const isLegacy = Boolean(
|
|
19872
|
+
prev &&
|
|
19873
|
+
(prev.cursorVersion !== ANTIGRAVITY_CURSOR_VERSION ||
|
|
19874
|
+
!prev.contributions ||
|
|
19875
|
+
typeof prev.contributions !== "object"),
|
|
19876
|
+
);
|
|
19877
|
+
|
|
19878
|
+
const dbPath = resolveAntigravityDbPath(filePath);
|
|
19879
|
+
const dbIdentity = await statAntigravityDatabase(dbPath);
|
|
19880
|
+
|
|
19881
|
+
const sameInode = prev && prev.inode === inode;
|
|
19882
|
+
const sameTranscriptStats =
|
|
19883
|
+
sameInode &&
|
|
19884
|
+
prev.size === size &&
|
|
19885
|
+
prev.mtimeMs === mtimeMs &&
|
|
19886
|
+
Number(prev.ctimeMs || 0) === ctimeMs;
|
|
19887
|
+
const sameDb = prev && sameAntigravityDatabase(prev, dbIdentity);
|
|
19888
|
+
const sameSource =
|
|
19889
|
+
!prev || (prev.source == null ? defaultSource : prev.source) === fileSource;
|
|
19890
|
+
const currentVersion = !isLegacy && prev?.cursorVersion === ANTIGRAVITY_CURSOR_VERSION;
|
|
19891
|
+
const staleExtractor = !isLegacy && antigravityExtractorStale(prev, dbPath);
|
|
19892
|
+
let transcriptBytes = null;
|
|
19893
|
+
let transcriptHash = typeof prev?.transcriptHash === "string" ? prev.transcriptHash : null;
|
|
19894
|
+
let sameTranscriptContent = sameTranscriptStats;
|
|
19895
|
+
let transcriptCanAppend = false;
|
|
19896
|
+
|
|
19897
|
+
// Stat metadata is only a fast hint. On any apparent file change, compare
|
|
19898
|
+
// the bytes so atomic replacement, truncation, and edits before the cursor
|
|
19899
|
+
// cannot be mistaken for an append. A cursor without this hash is treated
|
|
19900
|
+
// conservatively and gets one reconciliation pass.
|
|
19901
|
+
const transcriptReadRequired =
|
|
19902
|
+
!sameTranscriptStats || !currentVersion || !prev?.transcriptHash || needsFullRebuild;
|
|
19903
|
+
if (transcriptReadRequired) {
|
|
19904
|
+
transcriptBytes = await readAntigravityTranscriptBytes(filePath);
|
|
19905
|
+
const afterReadStat = await fs.stat(filePath).catch(() => null);
|
|
19906
|
+
if (
|
|
19907
|
+
!Buffer.isBuffer(transcriptBytes) ||
|
|
19908
|
+
!afterReadStat ||
|
|
19909
|
+
!afterReadStat.isFile() ||
|
|
19910
|
+
(afterReadStat.ino || 0) !== inode ||
|
|
19911
|
+
transcriptBytes.length !== size ||
|
|
19912
|
+
afterReadStat.size !== size ||
|
|
19913
|
+
afterReadStat.mtimeMs !== mtimeMs ||
|
|
19914
|
+
(Number.isFinite(afterReadStat.ctimeMs) ? afterReadStat.ctimeMs : 0) !== ctimeMs
|
|
19915
|
+
) {
|
|
19916
|
+
throw new Error(`Antigravity transcript changed while being read: ${filePath}`);
|
|
19917
|
+
}
|
|
19918
|
+
transcriptHash = hashAntigravityTranscript(transcriptBytes);
|
|
19919
|
+
sameTranscriptContent =
|
|
19920
|
+
Boolean(
|
|
19921
|
+
(prev?.transcriptHash && transcriptHash && prev.transcriptHash === transcriptHash && prev.size === size) ||
|
|
19922
|
+
(!prev?.transcriptHash && sameTranscriptStats),
|
|
19923
|
+
);
|
|
19924
|
+
transcriptCanAppend =
|
|
19925
|
+
Boolean(
|
|
19926
|
+
prev &&
|
|
19927
|
+
transcriptBytes &&
|
|
19928
|
+
size >= Number(prev.size || 0) &&
|
|
19929
|
+
(prev.transcriptHash
|
|
19930
|
+
? antigravityTranscriptPrefixMatches(transcriptBytes, prev)
|
|
19931
|
+
: sameInode),
|
|
19932
|
+
);
|
|
19933
|
+
}
|
|
19934
|
+
|
|
19935
|
+
const projectContext = projectEnabled
|
|
19936
|
+
? await resolveProjectContextForFile({
|
|
19937
|
+
filePath,
|
|
19938
|
+
projectMetaCache,
|
|
19939
|
+
publicRepoCache,
|
|
19940
|
+
publicRepoResolver,
|
|
19941
|
+
projectState,
|
|
19942
|
+
})
|
|
19943
|
+
: null;
|
|
19944
|
+
const projectRef = projectContext?.projectRef || null;
|
|
19945
|
+
const projectKey = projectContext?.projectKey || null;
|
|
19946
|
+
const projectChanged =
|
|
19947
|
+
projectEnabled && antigravityProjectAssignmentChanged(prev, projectContext);
|
|
19948
|
+
const baselineLine = Number(prev?.baselineLine || (isLegacy ? prev?.lastLine || 0 : 0));
|
|
19949
|
+
|
|
19950
|
+
// Legacy migration: "no retroactive claim". The file's history stays in the
|
|
19951
|
+
// old aggregate untouched. If unchanged, stamp v2 cursor with contributions: {}
|
|
19952
|
+
// and queue no buckets.
|
|
19953
|
+
if (!needsFullRebuild && isLegacy && (sameTranscriptStats || sameTranscriptContent)) {
|
|
19954
|
+
const contributionState = capAntigravityContributions({});
|
|
19955
|
+
fileCursors[key] = {
|
|
19956
|
+
...prev,
|
|
19957
|
+
inode,
|
|
19958
|
+
size,
|
|
19959
|
+
mtimeMs,
|
|
19960
|
+
ctimeMs,
|
|
19961
|
+
source: fileSource,
|
|
19962
|
+
transcriptHash:
|
|
19963
|
+
transcriptHash ||
|
|
19964
|
+
(transcriptBytes
|
|
19965
|
+
? hashAntigravityTranscript(transcriptBytes)
|
|
19966
|
+
: prev?.transcriptHash || null),
|
|
19967
|
+
...dbIdentity,
|
|
19968
|
+
projectKey: projectRef ? projectKey : null,
|
|
19969
|
+
projectRef: projectRef || null,
|
|
19970
|
+
projectStatus: projectContext?.status || null,
|
|
19971
|
+
projectConfigPath: projectContext?.configPath || null,
|
|
19972
|
+
projectConfigMtimeMs: projectContext?.configMtimeMs ?? null,
|
|
19973
|
+
projectConfigSize: projectContext?.configSize ?? null,
|
|
19974
|
+
projectReconciliationDeferred: false,
|
|
19975
|
+
baselineLine,
|
|
19976
|
+
cursorVersion: ANTIGRAVITY_CURSOR_VERSION,
|
|
19977
|
+
extractorRevision: ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
19978
|
+
lastLine: Number(prev.lastLine || 0),
|
|
19979
|
+
contextTokens: Number(prev.contextTokens || 0),
|
|
19980
|
+
previousContextTokens: Number(prev.previousContextTokens || 0),
|
|
19981
|
+
currentModel: prev.currentModel,
|
|
19982
|
+
lastPlannerModel: prev.lastPlannerModel,
|
|
19983
|
+
usageSource: prev.usageSource || (dbIdentity ? "sqlite" : "estimated"),
|
|
19984
|
+
dbReadOk: prev.dbReadOk !== false,
|
|
19985
|
+
contributions: contributionState.contributions,
|
|
19986
|
+
contributionsComplete: contributionState.complete,
|
|
19987
|
+
updatedAt: new Date().toISOString(),
|
|
19988
|
+
};
|
|
19325
19989
|
filesProcessed += 1;
|
|
19326
19990
|
if (cb) {
|
|
19327
19991
|
cb({
|
|
@@ -19336,54 +20000,250 @@ async function parseAntigravityIncremental({
|
|
|
19336
20000
|
continue;
|
|
19337
20001
|
}
|
|
19338
20002
|
|
|
19339
|
-
const
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
20003
|
+
const metadataRetryNeeded = currentVersion && Boolean(dbPath) && prev?.dbReadOk !== true;
|
|
20004
|
+
if (
|
|
20005
|
+
sameTranscriptStats &&
|
|
20006
|
+
sameTranscriptContent &&
|
|
20007
|
+
sameDb &&
|
|
20008
|
+
sameSource &&
|
|
20009
|
+
currentVersion &&
|
|
20010
|
+
!staleExtractor &&
|
|
20011
|
+
!metadataRetryNeeded &&
|
|
20012
|
+
!projectChanged &&
|
|
20013
|
+
!needsFullRebuild
|
|
20014
|
+
) {
|
|
20015
|
+
filesProcessed += 1;
|
|
20016
|
+
if (cb) {
|
|
20017
|
+
cb({
|
|
20018
|
+
index: idx + 1,
|
|
20019
|
+
total: totalFiles,
|
|
19350
20020
|
filePath,
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
const projectKey = projectContext?.projectKey || null;
|
|
20021
|
+
filesProcessed,
|
|
20022
|
+
eventsAggregated,
|
|
20023
|
+
bucketsQueued: touchedBuckets.size,
|
|
20024
|
+
});
|
|
20025
|
+
}
|
|
20026
|
+
continue;
|
|
20027
|
+
}
|
|
19359
20028
|
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
20029
|
+
// Reconcile historical contributions whenever the cursor needs migration or
|
|
20030
|
+
// SQLite changed, even when new transcript lines were appended in the same
|
|
20031
|
+
// sync. Otherwise the incremental branch stamps a migrated cursor without
|
|
20032
|
+
// repairing its old, incomplete totals. Legacy cursors have no retroactive
|
|
20033
|
+
// claim and are not routed through reconciliation.
|
|
20034
|
+
const needsReconcile =
|
|
20035
|
+
!needsFullRebuild &&
|
|
20036
|
+
!isLegacy &&
|
|
20037
|
+
prev &&
|
|
20038
|
+
(metadataRetryNeeded ||
|
|
20039
|
+
!currentVersion ||
|
|
20040
|
+
!sameDb ||
|
|
20041
|
+
staleExtractor ||
|
|
20042
|
+
!sameSource ||
|
|
20043
|
+
projectChanged ||
|
|
20044
|
+
(!sameTranscriptContent && !transcriptCanAppend));
|
|
20045
|
+
|
|
20046
|
+
// Reconciliation reparses the complete transcript. Even when stat metadata
|
|
20047
|
+
// looked unchanged, project/SQLite changes can select this path; obtain a
|
|
20048
|
+
// stable snapshot here rather than letting parseAntigravityFile silently
|
|
20049
|
+
// turn a transient read failure into an empty transcript.
|
|
20050
|
+
if (!transcriptBytes && (needsFullRebuild || needsReconcile)) {
|
|
20051
|
+
transcriptBytes = await readAntigravityTranscriptBytes(filePath);
|
|
20052
|
+
const afterReadStat = await fs.stat(filePath).catch(() => null);
|
|
20053
|
+
if (
|
|
20054
|
+
!Buffer.isBuffer(transcriptBytes) ||
|
|
20055
|
+
!afterReadStat ||
|
|
20056
|
+
!afterReadStat.isFile() ||
|
|
20057
|
+
(afterReadStat.ino || 0) !== inode ||
|
|
20058
|
+
transcriptBytes.length !== size ||
|
|
20059
|
+
afterReadStat.size !== size ||
|
|
20060
|
+
afterReadStat.mtimeMs !== mtimeMs ||
|
|
20061
|
+
(Number.isFinite(afterReadStat.ctimeMs) ? afterReadStat.ctimeMs : 0) !== ctimeMs
|
|
20062
|
+
) {
|
|
20063
|
+
throw new Error(`Antigravity transcript changed while being read: ${filePath}`);
|
|
20064
|
+
}
|
|
20065
|
+
transcriptHash = hashAntigravityTranscript(transcriptBytes);
|
|
20066
|
+
sameTranscriptContent =
|
|
20067
|
+
Boolean(transcriptHash && prev?.transcriptHash === transcriptHash && prev.size === size);
|
|
20068
|
+
transcriptCanAppend = Boolean(
|
|
20069
|
+
prev &&
|
|
20070
|
+
size >= Number(prev.size || 0) &&
|
|
20071
|
+
antigravityTranscriptPrefixMatches(transcriptBytes, prev),
|
|
20072
|
+
);
|
|
20073
|
+
}
|
|
19376
20074
|
|
|
19377
|
-
|
|
20075
|
+
const protectExactState = needsReconcile && prev?.usageSource === "sqlite";
|
|
20076
|
+
const hourlyStateBeforeReconcile = protectExactState ? structuredClone(hourlyState) : null;
|
|
20077
|
+
const projectStateBeforeReconcile =
|
|
20078
|
+
protectExactState && projectState ? structuredClone(projectState) : null;
|
|
20079
|
+
const touchedBeforeReconcile = protectExactState ? new Set(touchedBuckets) : null;
|
|
20080
|
+
const projectTouchedBeforeReconcile =
|
|
20081
|
+
protectExactState && projectTouchedBuckets ? new Set(projectTouchedBuckets) : null;
|
|
20082
|
+
let result;
|
|
20083
|
+
|
|
20084
|
+
if (needsFullRebuild || needsReconcile) {
|
|
20085
|
+
if (
|
|
20086
|
+
!needsFullRebuild &&
|
|
20087
|
+
prev &&
|
|
20088
|
+
prev.contributions &&
|
|
20089
|
+
typeof prev.contributions === "object"
|
|
20090
|
+
) {
|
|
20091
|
+
for (const c of Object.values(prev.contributions)) {
|
|
20092
|
+
if (!c || !c.totals) continue;
|
|
20093
|
+
const oldBucket = getHourlyBucket(hourlyState, c.source, c.model, c.bucketStart);
|
|
20094
|
+
subtractTotals(oldBucket.totals, c.totals);
|
|
20095
|
+
touchedBuckets.add(bucketKey(c.source, c.model, c.bucketStart));
|
|
20096
|
+
if (
|
|
20097
|
+
projectEnabled &&
|
|
20098
|
+
c.projectKey &&
|
|
20099
|
+
projectState &&
|
|
20100
|
+
projectTouchedBuckets
|
|
20101
|
+
) {
|
|
20102
|
+
const oldProjectBucket = getProjectBucket(
|
|
20103
|
+
projectState,
|
|
20104
|
+
c.projectKey,
|
|
20105
|
+
c.source,
|
|
20106
|
+
c.bucketStart,
|
|
20107
|
+
c.projectRef || null,
|
|
20108
|
+
);
|
|
20109
|
+
subtractTotals(oldProjectBucket.totals, c.totals);
|
|
20110
|
+
projectTouchedBuckets.add(projectBucketKey(c.projectKey, c.source, c.bucketStart));
|
|
20111
|
+
}
|
|
20112
|
+
}
|
|
20113
|
+
}
|
|
20114
|
+
result = await parseAntigravityFile({
|
|
20115
|
+
filePath,
|
|
20116
|
+
...(transcriptBytes ? { rawBuffer: transcriptBytes } : {}),
|
|
20117
|
+
lastLine: 0,
|
|
20118
|
+
baselineLine: needsFullRebuild ? 0 : baselineLine,
|
|
20119
|
+
watermarkLine: Number(prev?.lastLine || 0),
|
|
20120
|
+
initialUsageSource: prev?.usageSource,
|
|
20121
|
+
hourlyState,
|
|
20122
|
+
touchedBuckets,
|
|
20123
|
+
source: fileSource,
|
|
20124
|
+
projectState,
|
|
20125
|
+
projectTouchedBuckets,
|
|
20126
|
+
projectRef,
|
|
20127
|
+
projectKey,
|
|
20128
|
+
});
|
|
20129
|
+
} else {
|
|
20130
|
+
const canResume = sameTranscriptContent || transcriptCanAppend;
|
|
20131
|
+
const lastLine = canResume ? Number(prev?.lastLine || 0) : 0;
|
|
20132
|
+
const initialContextTokens = canResume ? Number(prev?.contextTokens || 0) : 0;
|
|
20133
|
+
const initialPrevContext = canResume ? Number(prev?.previousContextTokens || 0) : 0;
|
|
20134
|
+
const initialModel =
|
|
20135
|
+
canResume && typeof prev?.currentModel === "string" ? prev.currentModel : null;
|
|
20136
|
+
const initialLastPlannerModel =
|
|
20137
|
+
canResume && typeof prev?.lastPlannerModel === "string"
|
|
20138
|
+
? prev.lastPlannerModel
|
|
20139
|
+
: null;
|
|
20140
|
+
const initialUsageSource =
|
|
20141
|
+
canResume && typeof prev?.usageSource === "string" ? prev.usageSource : null;
|
|
20142
|
+
|
|
20143
|
+
result = await parseAntigravityFile({
|
|
20144
|
+
filePath,
|
|
20145
|
+
...(transcriptBytes ? { rawBuffer: transcriptBytes } : {}),
|
|
20146
|
+
lastLine,
|
|
20147
|
+
baselineLine,
|
|
20148
|
+
initialContextTokens,
|
|
20149
|
+
initialPrevContext,
|
|
20150
|
+
initialModel,
|
|
20151
|
+
initialLastPlannerModel,
|
|
20152
|
+
initialUsageSource,
|
|
20153
|
+
hourlyState,
|
|
20154
|
+
touchedBuckets,
|
|
20155
|
+
source: fileSource,
|
|
20156
|
+
projectState,
|
|
20157
|
+
projectTouchedBuckets,
|
|
20158
|
+
projectRef,
|
|
20159
|
+
projectKey,
|
|
20160
|
+
});
|
|
20161
|
+
|
|
20162
|
+
const mergedContributions = { ...(prev?.contributions || {}) };
|
|
20163
|
+
for (const [cKey, cVal] of Object.entries(result.contributions || {})) {
|
|
20164
|
+
if (!mergedContributions[cKey]) {
|
|
20165
|
+
mergedContributions[cKey] = { ...cVal, totals: { ...cVal.totals } };
|
|
20166
|
+
} else {
|
|
20167
|
+
addTotals(mergedContributions[cKey].totals, cVal.totals);
|
|
20168
|
+
}
|
|
20169
|
+
}
|
|
20170
|
+
result.contributions = mergedContributions;
|
|
20171
|
+
}
|
|
20172
|
+
|
|
20173
|
+
if (result.deferred && prev) {
|
|
20174
|
+
if (needsFullRebuild && !hourlyStateBeforeReconcile) {
|
|
20175
|
+
throw new Error(`Cannot rebuild Antigravity transcript while SQLite is unavailable: ${filePath}`);
|
|
20176
|
+
}
|
|
20177
|
+
if (hourlyStateBeforeReconcile) {
|
|
20178
|
+
for (const field of Object.keys(hourlyState)) delete hourlyState[field];
|
|
20179
|
+
Object.assign(hourlyState, hourlyStateBeforeReconcile);
|
|
20180
|
+
if (touchedBeforeReconcile) {
|
|
20181
|
+
touchedBuckets.clear();
|
|
20182
|
+
for (const touched of touchedBeforeReconcile) touchedBuckets.add(touched);
|
|
20183
|
+
}
|
|
20184
|
+
}
|
|
20185
|
+
if (projectState && projectStateBeforeReconcile) {
|
|
20186
|
+
for (const field of Object.keys(projectState)) delete projectState[field];
|
|
20187
|
+
Object.assign(projectState, projectStateBeforeReconcile);
|
|
20188
|
+
if (projectTouchedBeforeReconcile) {
|
|
20189
|
+
projectTouchedBuckets.clear();
|
|
20190
|
+
for (const touched of projectTouchedBeforeReconcile) projectTouchedBuckets.add(touched);
|
|
20191
|
+
}
|
|
20192
|
+
}
|
|
20193
|
+
fileCursors[key] = {
|
|
20194
|
+
...prev,
|
|
20195
|
+
inode,
|
|
20196
|
+
size,
|
|
20197
|
+
mtimeMs,
|
|
20198
|
+
ctimeMs,
|
|
20199
|
+
source: fileSource,
|
|
20200
|
+
transcriptHash,
|
|
20201
|
+
...dbIdentity,
|
|
20202
|
+
dbReadOk: false,
|
|
20203
|
+
updatedAt: new Date().toISOString(),
|
|
20204
|
+
};
|
|
20205
|
+
filesProcessed += 1;
|
|
20206
|
+
if (cb) {
|
|
20207
|
+
cb({
|
|
20208
|
+
index: idx + 1,
|
|
20209
|
+
total: totalFiles,
|
|
20210
|
+
filePath,
|
|
20211
|
+
filesProcessed,
|
|
20212
|
+
eventsAggregated,
|
|
20213
|
+
bucketsQueued: touchedBuckets.size,
|
|
20214
|
+
});
|
|
20215
|
+
}
|
|
20216
|
+
continue;
|
|
20217
|
+
}
|
|
20218
|
+
|
|
20219
|
+
const contributionState = capAntigravityContributions(result.contributions);
|
|
20220
|
+
fileCursors[key] = {
|
|
19378
20221
|
inode,
|
|
19379
20222
|
size,
|
|
19380
20223
|
mtimeMs,
|
|
20224
|
+
ctimeMs,
|
|
20225
|
+
source: fileSource,
|
|
20226
|
+
transcriptHash,
|
|
20227
|
+
...dbIdentity,
|
|
20228
|
+
projectKey: projectRef ? projectKey : null,
|
|
20229
|
+
projectRef: projectRef || null,
|
|
20230
|
+
projectStatus: projectContext?.status || null,
|
|
20231
|
+
projectConfigPath: projectContext?.configPath || null,
|
|
20232
|
+
projectConfigMtimeMs: projectContext?.configMtimeMs ?? null,
|
|
20233
|
+
projectConfigSize: projectContext?.configSize ?? null,
|
|
20234
|
+
projectReconciliationDeferred: false,
|
|
20235
|
+
baselineLine: needsFullRebuild ? 0 : baselineLine,
|
|
20236
|
+
cursorVersion: ANTIGRAVITY_CURSOR_VERSION,
|
|
20237
|
+
extractorRevision: ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
19381
20238
|
lastLine: result.lastLine,
|
|
19382
20239
|
contextTokens: result.contextTokens,
|
|
19383
20240
|
previousContextTokens: result.previousContextTokens,
|
|
19384
20241
|
currentModel: result.currentModel,
|
|
19385
20242
|
lastPlannerModel: result.lastPlannerModel,
|
|
19386
20243
|
usageSource: result.usageSource,
|
|
20244
|
+
dbReadOk: result.dbReadOk !== false,
|
|
20245
|
+
contributions: contributionState.contributions,
|
|
20246
|
+
contributionsComplete: contributionState.complete,
|
|
19387
20247
|
updatedAt: new Date().toISOString(),
|
|
19388
20248
|
};
|
|
19389
20249
|
|
|
@@ -19407,6 +20267,7 @@ async function parseAntigravityIncremental({
|
|
|
19407
20267
|
? await enqueueTouchedProjectBuckets({ projectQueuePath, projectState, projectTouchedBuckets })
|
|
19408
20268
|
: 0;
|
|
19409
20269
|
hourlyState.updatedAt = new Date().toISOString();
|
|
20270
|
+
cursors.files = fileCursors;
|
|
19410
20271
|
cursors.hourly = hourlyState;
|
|
19411
20272
|
if (projectState) {
|
|
19412
20273
|
projectState.updatedAt = new Date().toISOString();
|
|
@@ -19417,78 +20278,164 @@ async function parseAntigravityIncremental({
|
|
|
19417
20278
|
}
|
|
19418
20279
|
|
|
19419
20280
|
function decodeAntigravityVarint(buf, offset) {
|
|
20281
|
+
if (!Buffer.isBuffer(buf) || !Number.isSafeInteger(offset) || offset < 0) {
|
|
20282
|
+
throw new RangeError("invalid Antigravity protobuf offset");
|
|
20283
|
+
}
|
|
20284
|
+
const start = offset;
|
|
19420
20285
|
let res = 0;
|
|
19421
|
-
let
|
|
19422
|
-
|
|
20286
|
+
for (let count = 0; count < 10; count++) {
|
|
20287
|
+
if (offset >= buf.length) throw new RangeError("truncated Antigravity protobuf varint");
|
|
19423
20288
|
const b = buf[offset++];
|
|
19424
|
-
|
|
19425
|
-
if (
|
|
19426
|
-
|
|
20289
|
+
// 64 bits is nine 7-bit groups plus one bit of a tenth byte.
|
|
20290
|
+
if (count === 9 && b > 1) throw new RangeError("Antigravity protobuf varint exceeds 64 bits");
|
|
20291
|
+
res += (b & 0x7f) * 2 ** (count * 7);
|
|
20292
|
+
if (!(b & 0x80)) {
|
|
20293
|
+
if (Number.isSafeInteger(res)) return [res, offset];
|
|
20294
|
+
// Antigravity writes 2^64 - 1 in fields the extractor never reads. The
|
|
20295
|
+
// float sum has already lost precision, so re-read the bytes exactly.
|
|
20296
|
+
let exact = 0n;
|
|
20297
|
+
for (let i = start; i < offset; i++) {
|
|
20298
|
+
exact |= BigInt(buf[i] & 0x7f) << BigInt((i - start) * 7);
|
|
20299
|
+
}
|
|
20300
|
+
return [exact, offset];
|
|
20301
|
+
}
|
|
19427
20302
|
}
|
|
19428
|
-
|
|
20303
|
+
throw new RangeError("overlong Antigravity protobuf varint");
|
|
19429
20304
|
}
|
|
19430
20305
|
|
|
19431
20306
|
function findAntigravityProtoFields(buf) {
|
|
20307
|
+
if (!Buffer.isBuffer(buf)) throw new RangeError("invalid Antigravity protobuf payload");
|
|
19432
20308
|
const fields = [];
|
|
19433
20309
|
let offset = 0;
|
|
19434
20310
|
while (offset < buf.length) {
|
|
19435
20311
|
const [tag, next] = decodeAntigravityVarint(buf, offset);
|
|
20312
|
+
if (typeof tag !== "number") throw new RangeError("unsafe Antigravity protobuf field tag");
|
|
19436
20313
|
offset = next;
|
|
19437
|
-
const fieldNum = tag
|
|
20314
|
+
const fieldNum = Math.floor(tag / 8);
|
|
19438
20315
|
const wireType = tag & 7;
|
|
20316
|
+
if (fieldNum <= 0) throw new RangeError("invalid Antigravity protobuf field number");
|
|
19439
20317
|
if (wireType === 0) {
|
|
20318
|
+
// May be a BigInt; token reads use Number.isSafeInteger, so it is never billed.
|
|
19440
20319
|
const [val, vNext] = decodeAntigravityVarint(buf, offset);
|
|
19441
20320
|
offset = vNext;
|
|
19442
20321
|
fields.push({ num: fieldNum, val });
|
|
19443
20322
|
} else if (wireType === 2) {
|
|
19444
20323
|
const [len, lNext] = decodeAntigravityVarint(buf, offset);
|
|
19445
20324
|
offset = lNext;
|
|
20325
|
+
if (len > buf.length - offset) {
|
|
20326
|
+
throw new RangeError("truncated Antigravity protobuf field");
|
|
20327
|
+
}
|
|
19446
20328
|
fields.push({ num: fieldNum, val: buf.subarray(offset, offset + len) });
|
|
19447
20329
|
offset += len;
|
|
19448
20330
|
} else if (wireType === 1) {
|
|
20331
|
+
if (buf.length - offset < 8) throw new RangeError("truncated Antigravity protobuf field");
|
|
19449
20332
|
offset += 8;
|
|
19450
20333
|
} else if (wireType === 5) {
|
|
20334
|
+
if (buf.length - offset < 4) throw new RangeError("truncated Antigravity protobuf field");
|
|
19451
20335
|
offset += 4;
|
|
19452
20336
|
} else {
|
|
19453
|
-
|
|
20337
|
+
throw new RangeError("unsupported Antigravity protobuf wire type");
|
|
19454
20338
|
}
|
|
19455
20339
|
}
|
|
19456
20340
|
return fields;
|
|
19457
20341
|
}
|
|
19458
20342
|
|
|
19459
20343
|
function extractAntigravityGenInfo(buf) {
|
|
19460
|
-
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
20344
|
+
try {
|
|
20345
|
+
const root = findAntigravityProtoFields(buf);
|
|
20346
|
+
const f1 = root.find((f) => f.num === 1)?.val;
|
|
20347
|
+
if (!f1) return null;
|
|
20348
|
+
|
|
20349
|
+
const inner = findAntigravityProtoFields(f1);
|
|
20350
|
+
let model = null;
|
|
20351
|
+
const f19 = inner.find((f) => f.num === 19)?.val;
|
|
20352
|
+
if (f19) model = Buffer.from(f19).toString("utf8").trim();
|
|
20353
|
+
|
|
20354
|
+
let contextTokens = 0;
|
|
20355
|
+
const f9 = inner.find((f) => f.num === 9)?.val;
|
|
20356
|
+
if (f9) {
|
|
20357
|
+
const f10 = findAntigravityProtoFields(f9).find((f) => f.num === 10)?.val;
|
|
20358
|
+
if (f10) {
|
|
20359
|
+
const tok = findAntigravityProtoFields(f10).find((f) => f.num === 1)?.val;
|
|
20360
|
+
if (Number.isSafeInteger(tok)) contextTokens = tok;
|
|
20361
|
+
}
|
|
20362
|
+
}
|
|
19468
20363
|
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
const
|
|
19475
|
-
if (
|
|
20364
|
+
let lastStepIndex = null;
|
|
20365
|
+
for (const f of inner) {
|
|
20366
|
+
if (f.num !== 20 || !f.val) continue;
|
|
20367
|
+
const kv = findAntigravityProtoFields(f.val);
|
|
20368
|
+
const k = kv.find((x) => x.num === 1)?.val;
|
|
20369
|
+
const v = kv.find((x) => x.num === 2)?.val;
|
|
20370
|
+
if (k && Buffer.from(k).toString("utf8") === "last_step_index" && v) {
|
|
20371
|
+
const rawStepIndex = Buffer.from(v).toString("utf8").trim();
|
|
20372
|
+
if (/^\d+$/.test(rawStepIndex)) {
|
|
20373
|
+
const parsed = Number(rawStepIndex);
|
|
20374
|
+
if (Number.isSafeInteger(parsed)) lastStepIndex = parsed;
|
|
20375
|
+
}
|
|
20376
|
+
}
|
|
19476
20377
|
}
|
|
19477
|
-
}
|
|
19478
20378
|
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
|
|
19482
|
-
|
|
19483
|
-
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19487
|
-
|
|
20379
|
+
let uncachedInput;
|
|
20380
|
+
let cachedInput;
|
|
20381
|
+
let outputTokens;
|
|
20382
|
+
let textOutput;
|
|
20383
|
+
let reasoningOutput;
|
|
20384
|
+
let hasUsageMetadata = false;
|
|
20385
|
+
|
|
20386
|
+
const f4 = inner.find((f) => f.num === 4)?.val;
|
|
20387
|
+
if (f4) {
|
|
20388
|
+
// Antigravity internal usage message layout (Field 4):
|
|
20389
|
+
// - f4.1: system / tool instruction prefix tokens (per-model fixed prefix)
|
|
20390
|
+
// - f4.2: user / prompt tokens for this turn
|
|
20391
|
+
// - f4.3: total output tokens checksum (f4.9 text + f4.10 reasoning)
|
|
20392
|
+
// - f4.5: cached prompt tokens (cache read)
|
|
20393
|
+
// - f4.9: text output tokens
|
|
20394
|
+
// - f4.10: reasoning / thought tokens
|
|
20395
|
+
const f4fields = findAntigravityProtoFields(f4);
|
|
20396
|
+
const sTok = f4fields.find((f) => f.num === 1)?.val;
|
|
20397
|
+
const pTok = f4fields.find((f) => f.num === 2)?.val;
|
|
20398
|
+
const cTok = f4fields.find((f) => f.num === 5)?.val;
|
|
20399
|
+
const oTok = f4fields.find((f) => f.num === 3)?.val;
|
|
20400
|
+
const tTok = f4fields.find((f) => f.num === 9)?.val;
|
|
20401
|
+
const rTok = f4fields.find((f) => f.num === 10)?.val;
|
|
20402
|
+
if (
|
|
20403
|
+
Number.isSafeInteger(sTok) ||
|
|
20404
|
+
Number.isSafeInteger(pTok) ||
|
|
20405
|
+
Number.isSafeInteger(cTok) ||
|
|
20406
|
+
Number.isSafeInteger(oTok) ||
|
|
20407
|
+
Number.isSafeInteger(tTok) ||
|
|
20408
|
+
Number.isSafeInteger(rTok)
|
|
20409
|
+
) {
|
|
20410
|
+
hasUsageMetadata = true;
|
|
20411
|
+
const sysTokens = Number.isSafeInteger(sTok) ? sTok : 0;
|
|
20412
|
+
const promptTokens = Number.isSafeInteger(pTok) ? pTok : 0;
|
|
20413
|
+
uncachedInput = sysTokens + promptTokens;
|
|
20414
|
+
cachedInput = Number.isSafeInteger(cTok) ? cTok : 0;
|
|
20415
|
+
outputTokens = Number.isSafeInteger(oTok) ? oTok : 0;
|
|
20416
|
+
textOutput = Number.isSafeInteger(tTok) ? tTok : 0;
|
|
20417
|
+
reasoningOutput = Number.isSafeInteger(rTok) ? rTok : 0;
|
|
20418
|
+
}
|
|
19488
20419
|
}
|
|
19489
|
-
}
|
|
19490
20420
|
|
|
19491
|
-
|
|
20421
|
+
return {
|
|
20422
|
+
model,
|
|
20423
|
+
contextTokens,
|
|
20424
|
+
lastStepIndex,
|
|
20425
|
+
...(hasUsageMetadata
|
|
20426
|
+
? {
|
|
20427
|
+
uncachedInput,
|
|
20428
|
+
cachedInput,
|
|
20429
|
+
outputTokens,
|
|
20430
|
+
textOutput,
|
|
20431
|
+
reasoningOutput,
|
|
20432
|
+
hasUsageMetadata: true,
|
|
20433
|
+
}
|
|
20434
|
+
: {}),
|
|
20435
|
+
};
|
|
20436
|
+
} catch (_error) {
|
|
20437
|
+
return null;
|
|
20438
|
+
}
|
|
19492
20439
|
}
|
|
19493
20440
|
|
|
19494
20441
|
function resolveAntigravityDbPath(transcriptPath) {
|
|
@@ -19500,32 +20447,67 @@ function resolveAntigravityDbPath(transcriptPath) {
|
|
|
19500
20447
|
|
|
19501
20448
|
function readAntigravityConversationDb(dbPath) {
|
|
19502
20449
|
if (!dbPath) return null;
|
|
20450
|
+
let snapshot = null;
|
|
20451
|
+
let effectiveDbPath = dbPath;
|
|
20452
|
+
// Antigravity keeps active generation metadata in SQLite WAL files. Reading
|
|
20453
|
+
// a UNC/WSL database directly is both lock-prone and can miss rows that have
|
|
20454
|
+
// not checkpointed into the main file, so copy the database and sidecars as
|
|
20455
|
+
// one local read view first.
|
|
20456
|
+
if (isUncPath(dbPath)) {
|
|
20457
|
+
try {
|
|
20458
|
+
snapshot = snapshotSqliteDb(dbPath);
|
|
20459
|
+
effectiveDbPath = snapshot.path;
|
|
20460
|
+
} catch (_error) {
|
|
20461
|
+
// Preserve the existing direct-read fallback for transient snapshot or
|
|
20462
|
+
// permission failures.
|
|
20463
|
+
}
|
|
20464
|
+
}
|
|
19503
20465
|
try {
|
|
20466
|
+
if (!fssync.existsSync(effectiveDbPath)) return null;
|
|
19504
20467
|
const rows = readSqliteJsonRows(
|
|
19505
|
-
|
|
20468
|
+
effectiveDbPath,
|
|
19506
20469
|
"SELECT idx, quote(data) as hex FROM gen_metadata ORDER BY idx",
|
|
19507
|
-
{
|
|
20470
|
+
{
|
|
20471
|
+
readOnly: true,
|
|
20472
|
+
throwOnReadFailure: true,
|
|
20473
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
20474
|
+
timeout: 15_000,
|
|
20475
|
+
},
|
|
19508
20476
|
);
|
|
19509
|
-
if (!Array.isArray(rows)
|
|
20477
|
+
if (!Array.isArray(rows)) return null;
|
|
19510
20478
|
|
|
19511
20479
|
const stepMap = new Map();
|
|
19512
20480
|
for (const r of rows) {
|
|
19513
20481
|
if (!r || typeof r.hex !== "string" || !r.hex.startsWith("X'")) continue;
|
|
19514
20482
|
const buf = Buffer.from(r.hex.slice(2, -1), "hex");
|
|
19515
20483
|
const info = extractAntigravityGenInfo(buf);
|
|
19516
|
-
if (
|
|
20484
|
+
if (
|
|
20485
|
+
info &&
|
|
20486
|
+
info.lastStepIndex != null &&
|
|
20487
|
+
(info.hasUsageMetadata || info.contextTokens > 0)
|
|
20488
|
+
) {
|
|
19517
20489
|
stepMap.set(info.lastStepIndex + 1, info);
|
|
19518
20490
|
}
|
|
19519
20491
|
}
|
|
19520
|
-
|
|
20492
|
+
// An empty map means the database was read successfully but has no usable
|
|
20493
|
+
// metadata yet. Keep it distinct from null, which signals a transient read
|
|
20494
|
+
// failure and must be retried on the next sync.
|
|
20495
|
+
return stepMap;
|
|
19521
20496
|
} catch (_) {
|
|
19522
20497
|
return null;
|
|
20498
|
+
} finally {
|
|
20499
|
+
if (snapshot) snapshot.cleanup();
|
|
19523
20500
|
}
|
|
19524
20501
|
}
|
|
19525
20502
|
|
|
19526
20503
|
async function parseAntigravityFile({
|
|
19527
20504
|
filePath,
|
|
19528
|
-
|
|
20505
|
+
rawBuffer = null,
|
|
20506
|
+
lastLine = 0,
|
|
20507
|
+
maxLine = null,
|
|
20508
|
+
baselineLine = 0,
|
|
20509
|
+
watermarkLine = 0,
|
|
20510
|
+
applyBuckets = true,
|
|
19529
20511
|
initialContextTokens,
|
|
19530
20512
|
initialPrevContext,
|
|
19531
20513
|
initialModel,
|
|
@@ -19539,7 +20521,12 @@ async function parseAntigravityFile({
|
|
|
19539
20521
|
projectRef,
|
|
19540
20522
|
projectKey,
|
|
19541
20523
|
}) {
|
|
19542
|
-
const raw =
|
|
20524
|
+
const raw = Buffer.isBuffer(rawBuffer)
|
|
20525
|
+
? rawBuffer.toString("utf8")
|
|
20526
|
+
: await fs.readFile(filePath, "utf8").catch(() => null);
|
|
20527
|
+
if (raw === null) {
|
|
20528
|
+
throw new Error(`Antigravity transcript could not be read: ${filePath}`);
|
|
20529
|
+
}
|
|
19543
20530
|
if (!raw.trim()) {
|
|
19544
20531
|
return {
|
|
19545
20532
|
lastLine: 0,
|
|
@@ -19549,14 +20536,22 @@ async function parseAntigravityFile({
|
|
|
19549
20536
|
currentModel: null,
|
|
19550
20537
|
lastPlannerModel: null,
|
|
19551
20538
|
usageSource: "estimated",
|
|
20539
|
+
dbReadOk: true,
|
|
20540
|
+
contributions: {},
|
|
19552
20541
|
};
|
|
19553
20542
|
}
|
|
19554
20543
|
|
|
19555
|
-
const
|
|
20544
|
+
const allLines = raw
|
|
19556
20545
|
.split(/\r?\n/)
|
|
19557
20546
|
.map((line) => line.trim())
|
|
19558
20547
|
.filter(Boolean);
|
|
20548
|
+
const endLimit =
|
|
20549
|
+
Number.isFinite(maxLine) && maxLine >= 0
|
|
20550
|
+
? Math.min(allLines.length, maxLine)
|
|
20551
|
+
: allLines.length;
|
|
20552
|
+
const lines = allLines.slice(0, endLimit);
|
|
19559
20553
|
let eventsAggregated = 0;
|
|
20554
|
+
const contributions = {};
|
|
19560
20555
|
const dbPath = resolveAntigravityDbPath(filePath);
|
|
19561
20556
|
const stepMap = dbPath ? readAntigravityConversationDb(dbPath) : null;
|
|
19562
20557
|
// Resume cached context-token total + model so historical lines (i < lastLine)
|
|
@@ -19567,11 +20562,30 @@ async function parseAntigravityFile({
|
|
|
19567
20562
|
const cachedTokens = Number.isFinite(initialContextTokens) ? initialContextTokens : 0;
|
|
19568
20563
|
const cachedPrev = Number.isFinite(initialPrevContext) ? initialPrevContext : 0;
|
|
19569
20564
|
const cachedModel = typeof initialModel === "string" ? initialModel : null;
|
|
20565
|
+
const dbReadFailed = Boolean(dbPath) && stepMap === null;
|
|
20566
|
+
if (dbReadFailed && initialUsageSource === "sqlite") {
|
|
20567
|
+
return {
|
|
20568
|
+
lastLine: Math.min(Number.isFinite(lastLine) ? lastLine : 0, lines.length),
|
|
20569
|
+
eventsAggregated: 0,
|
|
20570
|
+
contextTokens: cachedTokens,
|
|
20571
|
+
previousContextTokens: cachedPrev,
|
|
20572
|
+
currentModel: cachedModel,
|
|
20573
|
+
lastPlannerModel:
|
|
20574
|
+
typeof initialLastPlannerModel === "string" ? initialLastPlannerModel : cachedModel,
|
|
20575
|
+
usageSource: "sqlite",
|
|
20576
|
+
dbReadOk: false,
|
|
20577
|
+
deferred: true,
|
|
20578
|
+
contributions: {},
|
|
20579
|
+
};
|
|
20580
|
+
}
|
|
19570
20581
|
const sqliteCursor = initialUsageSource === "sqlite";
|
|
20582
|
+
const hasSqliteMetadata = Boolean(stepMap && stepMap.size > 0);
|
|
19571
20583
|
const resumed =
|
|
19572
|
-
canResume &&
|
|
20584
|
+
canResume &&
|
|
20585
|
+
(cachedTokens > 0 || cachedModel !== null) &&
|
|
20586
|
+
(!hasSqliteMetadata || sqliteCursor);
|
|
19573
20587
|
const scanStart = resumed ? lastLine : 0;
|
|
19574
|
-
let currentModel = resumed ? cachedModel : null;
|
|
20588
|
+
let currentModel = resumed ? cachedModel : typeof initialModel === "string" ? initialModel : null;
|
|
19575
20589
|
if (!currentModel) {
|
|
19576
20590
|
currentModel = await readAntigravityDefaultModel(filePath);
|
|
19577
20591
|
}
|
|
@@ -19619,7 +20633,12 @@ async function parseAntigravityFile({
|
|
|
19619
20633
|
|
|
19620
20634
|
if (!isNewEvent) {
|
|
19621
20635
|
if (parsed.type === "PLANNER_RESPONSE") {
|
|
19622
|
-
if (
|
|
20636
|
+
if (dbTurn?.hasUsageMetadata) {
|
|
20637
|
+
contextTokens =
|
|
20638
|
+
dbContextTokens > 0
|
|
20639
|
+
? dbContextTokens
|
|
20640
|
+
: (dbTurn.uncachedInput || 0) + (dbTurn.cachedInput || 0);
|
|
20641
|
+
} else if (dbContextTokens > 0) {
|
|
19623
20642
|
contextTokens = dbContextTokens;
|
|
19624
20643
|
}
|
|
19625
20644
|
previousContextTokens = contextTokens;
|
|
@@ -19651,52 +20670,97 @@ async function parseAntigravityFile({
|
|
|
19651
20670
|
let billedPlanner = false;
|
|
19652
20671
|
|
|
19653
20672
|
if (parsed.type === "PLANNER_RESPONSE") {
|
|
19654
|
-
|
|
19655
|
-
|
|
20673
|
+
if (dbTurn?.hasUsageMetadata) {
|
|
20674
|
+
const uncached = dbTurn.uncachedInput || 0;
|
|
20675
|
+
const cached = dbTurn.cachedInput || 0;
|
|
20676
|
+
const reasoning = dbTurn.reasoningOutput || 0;
|
|
20677
|
+
let output = 0;
|
|
20678
|
+
if (Number.isFinite(dbTurn.textOutput) && dbTurn.textOutput > 0) {
|
|
20679
|
+
output = dbTurn.textOutput;
|
|
20680
|
+
} else if (Number.isFinite(dbTurn.outputTokens)) {
|
|
20681
|
+
output = Math.max(0, dbTurn.outputTokens - reasoning);
|
|
20682
|
+
}
|
|
19656
20683
|
|
|
19657
|
-
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
20684
|
+
delta.input_tokens = uncached;
|
|
20685
|
+
delta.cached_input_tokens = cached;
|
|
20686
|
+
delta.output_tokens = output;
|
|
20687
|
+
delta.reasoning_output_tokens = reasoning;
|
|
20688
|
+
delta.total_tokens = uncached + cached + output + reasoning;
|
|
20689
|
+
delta.billable_total_tokens = delta.total_tokens;
|
|
20690
|
+
delta.conversation_count = 1;
|
|
20691
|
+
billedPlanner = delta.total_tokens > 0;
|
|
19664
20692
|
|
|
19665
|
-
|
|
19666
|
-
|
|
19667
|
-
|
|
20693
|
+
contextTokens = dbContextTokens > 0 ? dbContextTokens : uncached + cached;
|
|
20694
|
+
} else {
|
|
20695
|
+
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
20696
|
+
const thinking = typeof parsed.thinking === "string" ? parsed.thinking : "";
|
|
19668
20697
|
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
|
|
19672
|
-
|
|
19673
|
-
|
|
19674
|
-
|
|
19675
|
-
|
|
20698
|
+
if (dbContextTokens > 0) {
|
|
20699
|
+
contextTokens = dbContextTokens;
|
|
20700
|
+
}
|
|
20701
|
+
if (lastPlannerModel && model !== lastPlannerModel) {
|
|
20702
|
+
previousContextTokens = 0;
|
|
20703
|
+
}
|
|
20704
|
+
const inputDelta = Math.max(0, contextTokens - previousContextTokens);
|
|
20705
|
+
|
|
20706
|
+
const outputTokens =
|
|
20707
|
+
antigravityValueTokens(content) + antigravityValueTokens(parsed.tool_calls);
|
|
20708
|
+
const reasoningTokens = antigravityValueTokens(thinking);
|
|
20709
|
+
|
|
20710
|
+
delta.input_tokens = inputDelta;
|
|
20711
|
+
delta.output_tokens = outputTokens;
|
|
20712
|
+
delta.reasoning_output_tokens = reasoningTokens;
|
|
20713
|
+
delta.total_tokens = inputDelta + outputTokens + reasoningTokens;
|
|
20714
|
+
delta.billable_total_tokens = delta.total_tokens;
|
|
20715
|
+
delta.conversation_count = 1;
|
|
20716
|
+
billedPlanner = delta.total_tokens > 0;
|
|
20717
|
+
}
|
|
19676
20718
|
}
|
|
19677
20719
|
|
|
19678
|
-
if (!billedPlanner) {
|
|
20720
|
+
if (!billedPlanner || i < baselineLine) {
|
|
20721
|
+
if (billedPlanner) {
|
|
20722
|
+
previousContextTokens = contextTokens;
|
|
20723
|
+
lastPlannerModel = model;
|
|
20724
|
+
}
|
|
19679
20725
|
contextTokens += eventContextTokens;
|
|
19680
20726
|
lastCompletedLine = i + 1;
|
|
19681
20727
|
continue;
|
|
19682
20728
|
}
|
|
19683
20729
|
|
|
19684
|
-
const
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
if (projectKey && projectState && projectTouchedBuckets) {
|
|
19689
|
-
const projectBucket = getProjectBucket(
|
|
19690
|
-
projectState,
|
|
19691
|
-
projectKey,
|
|
20730
|
+
const cKey = `${source}|${model}|${bucketStart}|${projectKey || ""}`;
|
|
20731
|
+
if (!contributions[cKey]) {
|
|
20732
|
+
contributions[cKey] = {
|
|
19692
20733
|
source,
|
|
20734
|
+
model,
|
|
19693
20735
|
bucketStart,
|
|
19694
|
-
|
|
19695
|
-
|
|
19696
|
-
|
|
19697
|
-
|
|
20736
|
+
projectKey: projectKey || null,
|
|
20737
|
+
projectRef: projectRef || null,
|
|
20738
|
+
totals: initTotals(),
|
|
20739
|
+
};
|
|
20740
|
+
}
|
|
20741
|
+
addTotals(contributions[cKey].totals, delta);
|
|
20742
|
+
|
|
20743
|
+
if (applyBuckets !== false && hourlyState && touchedBuckets) {
|
|
20744
|
+
const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
|
|
20745
|
+
addTotals(bucket.totals, delta);
|
|
20746
|
+
touchedBuckets.add(bucketKey(source, model, bucketStart));
|
|
20747
|
+
|
|
20748
|
+
if (projectKey && projectState && projectTouchedBuckets) {
|
|
20749
|
+
const projectBucket = getProjectBucket(
|
|
20750
|
+
projectState,
|
|
20751
|
+
projectKey,
|
|
20752
|
+
source,
|
|
20753
|
+
bucketStart,
|
|
20754
|
+
projectRef,
|
|
20755
|
+
);
|
|
20756
|
+
addTotals(projectBucket.totals, delta);
|
|
20757
|
+
projectTouchedBuckets.add(projectBucketKey(projectKey, source, bucketStart));
|
|
20758
|
+
}
|
|
20759
|
+
}
|
|
20760
|
+
const isNewForCounter = watermarkLine > 0 ? i >= watermarkLine : true;
|
|
20761
|
+
if (isNewForCounter) {
|
|
20762
|
+
eventsAggregated += 1;
|
|
19698
20763
|
}
|
|
19699
|
-
eventsAggregated += 1;
|
|
19700
20764
|
// Snapshot the pre-planner context first. The planner's own content+tool_calls
|
|
19701
20765
|
// (eventContextTokens, added below) become part of the next turn's history,
|
|
19702
20766
|
// so they MUST be billed as input on the next planner — don't fold them into
|
|
@@ -19714,7 +20778,9 @@ async function parseAntigravityFile({
|
|
|
19714
20778
|
previousContextTokens,
|
|
19715
20779
|
currentModel,
|
|
19716
20780
|
lastPlannerModel,
|
|
19717
|
-
usageSource:
|
|
20781
|
+
usageSource: hasSqliteMetadata ? "sqlite" : "estimated",
|
|
20782
|
+
dbReadOk: !dbPath || stepMap !== null,
|
|
20783
|
+
contributions,
|
|
19718
20784
|
};
|
|
19719
20785
|
}
|
|
19720
20786
|
|
|
@@ -21773,6 +22839,12 @@ module.exports = {
|
|
|
21773
22839
|
resolveOmpSubagentFiles,
|
|
21774
22840
|
resolveOmpDefaultModel,
|
|
21775
22841
|
parseOmpIncremental,
|
|
22842
|
+
resolveOmoHome,
|
|
22843
|
+
resolveOmoAgentDir,
|
|
22844
|
+
resolveOmoSessionFiles,
|
|
22845
|
+
resolveOmoSubagentFiles,
|
|
22846
|
+
resolveOmoDefaultModel,
|
|
22847
|
+
parseOmoIncremental,
|
|
21776
22848
|
resolveKilocodeRoots,
|
|
21777
22849
|
resolveKilocodeTaskFiles,
|
|
21778
22850
|
normalizeKilocodeProviderToModel,
|
|
@@ -21826,6 +22898,7 @@ module.exports = {
|
|
|
21826
22898
|
resolvePiDefaultModel,
|
|
21827
22899
|
parsePiIncremental,
|
|
21828
22900
|
piAgentDirCollidesWithOmp,
|
|
22901
|
+
omoAgentDirCollidesWithOmp,
|
|
21829
22902
|
resolvePrimeAgentHome,
|
|
21830
22903
|
resolvePrimeAgentDir,
|
|
21831
22904
|
resolvePrimeAgentSessionFiles,
|
|
@@ -21865,9 +22938,12 @@ module.exports = {
|
|
|
21865
22938
|
parseGrokBuildIncremental,
|
|
21866
22939
|
|
|
21867
22940
|
// Antigravity (Google Gemini) - Session logs parser
|
|
22941
|
+
ANTIGRAVITY_CURSOR_VERSION,
|
|
22942
|
+
ANTIGRAVITY_EXTRACTOR_REVISION,
|
|
21868
22943
|
resolveAntigravityBrainDirs,
|
|
21869
22944
|
listAntigravitySessionFiles,
|
|
21870
22945
|
listAntigravityTranscripts,
|
|
22946
|
+
listAntigravityTranscriptsWithStatus,
|
|
21871
22947
|
parseAntigravityIncremental,
|
|
21872
22948
|
estimateAntigravityTokens,
|
|
21873
22949
|
isCjkCodePoint,
|