tokentracker-cli 0.77.1 → 0.78.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 +24 -3
- package/README.ja.md +3 -3
- package/README.ko.md +3 -3
- package/README.md +24 -3
- package/README.zh-CN.md +3 -3
- package/dashboard/dist/assets/ActivityHeatmap-5352XxLR.js +42 -0
- package/dashboard/dist/assets/{Card-DP2HT77t.js → Card-Ca_hYHhx.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-ScYRJSG0.js → ClawdAnimated-DSgOS5ND.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-CU46JMkg.js → CommunityStatsModal-o8uQVQxu.js} +1 -1
- package/dashboard/dist/assets/ConfirmModal-D1hi9XUf.js +1 -0
- package/dashboard/dist/assets/Controls-CwnKP56V.js +1 -0
- package/dashboard/dist/assets/DashboardPage-CDv0TIOL.js +19 -0
- package/dashboard/dist/assets/{DevicePage-BL9ew9-m.js → DevicePage-BlfIHcZX.js} +1 -1
- package/dashboard/dist/assets/DialogClose-CismfYwe.js +1 -0
- package/dashboard/dist/assets/DialogDescription-DWKp1yez.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-Oqnn_dIL.js → DialogTitle-DuuTVbUb.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CFLwnHqt.js → FadeIn-DpnitRKo.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-B397ELF4.js → HeaderGithubStar-D6zy-ADl.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-CamsHe6o.js → IpCheckPage-yUNp0VNe.js} +1 -1
- package/dashboard/dist/assets/LandingPage-BvmpAaVn.js +4314 -0
- package/dashboard/dist/assets/LeaderboardAvatar-2WTIeXye.js +1 -0
- package/dashboard/dist/assets/LeaderboardPage-Bq3-VO7w.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-BIe6gBNu.js → LeaderboardProfileModal-DH0EEK6q.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-DeVBEYtg.js → LeaderboardProfilePage-uJAng2JE.js} +3 -3
- package/dashboard/dist/assets/{LimitsPage-6I5EU7qe.js → LimitsPage-D8UohgJm.js} +2 -2
- package/dashboard/dist/assets/{LocalOnlyNotice-AyYKZloH.js → LocalOnlyNotice-Dv_BnDVV.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-D8smF1gU.js → LoginPage-D6b6DZU3.js} +1 -1
- package/dashboard/dist/assets/PetPage-DfpBkuCa.js +1 -0
- package/dashboard/dist/assets/{PopoverPopup-DRWhz-bY.js → PopoverPopup-CjzdZn-9.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-CzSqJfuA.js → ResetPasswordPage-BnOCFkwe.js} +1 -1
- package/dashboard/dist/assets/Select-CHtQAiLD.js +1 -0
- package/dashboard/dist/assets/{SelectItemText-DEYadcIK.js → SelectItemText-Vjqvf8wO.js} +1 -1
- package/dashboard/dist/assets/SettingsPage-B-iHGL4q.js +1 -0
- package/dashboard/dist/assets/SkillsPage-DtG2aV-q.js +1 -0
- package/dashboard/dist/assets/WidgetsPage-4kqTtWfK.js +1 -0
- package/dashboard/dist/assets/{WrappedPage-BuP9KXqF.js → WrappedPage-DMMRpani.js} +1 -1
- package/dashboard/dist/assets/agent-logos-D0vYkEpX.js +1 -0
- package/dashboard/dist/assets/{arrow-up-right-cFMBPEVS.js → arrow-up-right-BAQYGZkR.js} +1 -1
- package/dashboard/dist/assets/check-C3RPBZiS.js +1 -0
- package/dashboard/dist/assets/{copy-DYx8X3ZO.js → copy-gG_BLBs8.js} +1 -1
- package/dashboard/dist/assets/{download-B-hXZWKj.js → download-BQOcg3gT.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CngV51wN.js → format-tokens-CGK6lQAf.js} +1 -1
- package/dashboard/dist/assets/{info-qVF2hV50.js → info-C_BrDrcq.js} +1 -1
- package/dashboard/dist/assets/{link-2-DR-yGZFS.js → link-2-DFwCBbIZ.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-0k9uueb5.js → loader-circle-BMFI_BJ8.js} +1 -1
- package/dashboard/dist/assets/{main-B8lT0e78.css → main-Dy8Y_qst.css} +1 -1
- package/dashboard/dist/assets/main-w0hAaCsy.js +1190 -0
- package/dashboard/dist/assets/{trash-2-DpxBaV_1.js → trash-2-GeZ6IzgV.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-Cbij-V8j.js → use-community-stats-pC8cmVof.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-q0TuryM0.js → use-limits-display-prefs-DnFpPcA3.js} +1 -1
- package/dashboard/dist/assets/use-native-settings-CvcOSUhn.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-CH-J7c04.js → use-ordered-list-CbMroVZ8.js} +2 -2
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-DKLmkwxe.js → use-quality-per-dollar-pref-Bn3Al6yX.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DrmF6I_t.js → use-usage-limits-D08q_Dr8.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DwygZqrs.js → useCurrency-h3kqF52v.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DVCKuErg.js → useScrollLock-9au2731u.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/status.js +293 -30
- package/src/commands/sync.js +494 -104
- package/src/lib/install-resolver.js +1 -0
- package/src/lib/local-api.js +202 -56
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1329 -40
- package/dashboard/dist/assets/ActivityHeatmap-DTZh551Z.js +0 -42
- package/dashboard/dist/assets/ConfirmModal-BYvz0dzg.js +0 -1
- package/dashboard/dist/assets/Controls-DPElSq0S.js +0 -1
- package/dashboard/dist/assets/DashboardPage-CN0buIso.js +0 -19
- package/dashboard/dist/assets/DialogClose-cqXrG903.js +0 -1
- package/dashboard/dist/assets/DialogDescription-Bo0iWtYz.js +0 -1
- package/dashboard/dist/assets/LandingPage-D4ygTHwO.js +0 -4312
- package/dashboard/dist/assets/LeaderboardAvatar-B39rNGCV.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-BFUOxX6P.js +0 -2
- package/dashboard/dist/assets/PetPage-BlMYvJn2.js +0 -1
- package/dashboard/dist/assets/Select-Cb8n7kQO.js +0 -1
- package/dashboard/dist/assets/SettingsPage-DlHXcFdJ.js +0 -1
- package/dashboard/dist/assets/SkillsPage--Kcv1pxG.js +0 -1
- package/dashboard/dist/assets/WidgetsPage-DwtJ9CKU.js +0 -1
- package/dashboard/dist/assets/agent-logos-DbwDXgg1.js +0 -1
- package/dashboard/dist/assets/check-t_ltRp2A.js +0 -1
- package/dashboard/dist/assets/main-CU04bNYH.js +0 -1172
- package/dashboard/dist/assets/use-native-settings-BCzogs1L.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -3729,7 +3729,11 @@ async function parseKiroIncremental({ dbPath, jsonlPath, cursors, queuePath, onP
|
|
|
3729
3729
|
jsonl: { lastLine: jsonlResult.lineCount, updatedAt: new Date().toISOString() },
|
|
3730
3730
|
updatedAt: new Date().toISOString(),
|
|
3731
3731
|
};
|
|
3732
|
-
return {
|
|
3732
|
+
return {
|
|
3733
|
+
recordsProcessed: 0,
|
|
3734
|
+
eventsAggregated: 0,
|
|
3735
|
+
bucketsQueued: 0,
|
|
3736
|
+
};
|
|
3733
3737
|
}
|
|
3734
3738
|
} else {
|
|
3735
3739
|
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -4212,7 +4216,11 @@ async function parseHermesIncremental({ hermesPath, dbPath, cursors, queuePath,
|
|
|
4212
4216
|
updatedAt, // Update the overall profile state timestamp even if the DB doesn't exist for the fast-path check
|
|
4213
4217
|
};
|
|
4214
4218
|
|
|
4215
|
-
return {
|
|
4219
|
+
return {
|
|
4220
|
+
recordsProcessed,
|
|
4221
|
+
eventsAggregated,
|
|
4222
|
+
bucketsQueued,
|
|
4223
|
+
};
|
|
4216
4224
|
}
|
|
4217
4225
|
|
|
4218
4226
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -4993,7 +5001,11 @@ async function parseKiroCliFromSessionFiles({
|
|
|
4993
5001
|
fileOffsets,
|
|
4994
5002
|
updatedAt: new Date().toISOString(),
|
|
4995
5003
|
};
|
|
4996
|
-
return {
|
|
5004
|
+
return {
|
|
5005
|
+
recordsProcessed: 0,
|
|
5006
|
+
eventsAggregated: 0,
|
|
5007
|
+
bucketsQueued: 0,
|
|
5008
|
+
};
|
|
4997
5009
|
}
|
|
4998
5010
|
|
|
4999
5011
|
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
@@ -5124,14 +5136,32 @@ async function parseKiroCliFromSessionFiles({
|
|
|
5124
5136
|
cursors.hourly = hourlyState;
|
|
5125
5137
|
cursors.kiroCli = { ...kiroCliState, seenIds: cappedSeen, fileOffsets, updatedAt };
|
|
5126
5138
|
|
|
5127
|
-
return {
|
|
5139
|
+
return {
|
|
5140
|
+
recordsProcessed,
|
|
5141
|
+
eventsAggregated,
|
|
5142
|
+
bucketsQueued,
|
|
5143
|
+
};
|
|
5128
5144
|
}
|
|
5129
5145
|
|
|
5130
5146
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
5131
5147
|
|
|
5132
|
-
function
|
|
5148
|
+
function resolveKimiHome(env = process.env) {
|
|
5133
5149
|
const home = require("node:os").homedir();
|
|
5134
|
-
const
|
|
5150
|
+
const explicit = typeof env?.KIMI_HOME === "string" ? env.KIMI_HOME.trim() : "";
|
|
5151
|
+
if (explicit) return path.resolve(explicit);
|
|
5152
|
+
if (process.platform === "win32") {
|
|
5153
|
+
return pickWin32ProviderPath({
|
|
5154
|
+
env,
|
|
5155
|
+
nativeValue: path.join(home, ".kimi"),
|
|
5156
|
+
wslProviderDir: ".kimi",
|
|
5157
|
+
});
|
|
5158
|
+
}
|
|
5159
|
+
return path.join(home, ".kimi");
|
|
5160
|
+
}
|
|
5161
|
+
|
|
5162
|
+
function resolveKimiWireFiles(env = process.env) {
|
|
5163
|
+
const kimiHome = resolveKimiHome(env);
|
|
5164
|
+
if (!kimiHome) return [];
|
|
5135
5165
|
const sessionsDir = path.join(kimiHome, "sessions");
|
|
5136
5166
|
if (!fssync.existsSync(sessionsDir)) return [];
|
|
5137
5167
|
const files = [];
|
|
@@ -9264,6 +9294,46 @@ function pickCopilotModel(attrs) {
|
|
|
9264
9294
|
}
|
|
9265
9295
|
|
|
9266
9296
|
const COPILOT_PARSER_VERSION = 2;
|
|
9297
|
+
const COPILOT_USAGE_CLAIM_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
9298
|
+
const COPILOT_USAGE_CLAIM_MAX_ENTRIES = 10_000;
|
|
9299
|
+
|
|
9300
|
+
function pruneCopilotUsageClaims(
|
|
9301
|
+
events,
|
|
9302
|
+
nowMs = Date.now(),
|
|
9303
|
+
maxEntries = COPILOT_USAGE_CLAIM_MAX_ENTRIES,
|
|
9304
|
+
) {
|
|
9305
|
+
const cutoffMs = nowMs - COPILOT_USAGE_CLAIM_RETENTION_MS;
|
|
9306
|
+
const retained = [];
|
|
9307
|
+
for (const event of Array.isArray(events) ? events : []) {
|
|
9308
|
+
if (!event || typeof event !== "object" || event.consumed === true) {
|
|
9309
|
+
continue;
|
|
9310
|
+
}
|
|
9311
|
+
const storedFirstSeenAtMs = Number(event.firstSeenAtMs);
|
|
9312
|
+
const firstSeenAtMs =
|
|
9313
|
+
Number.isFinite(storedFirstSeenAtMs) && storedFirstSeenAtMs > 0
|
|
9314
|
+
? storedFirstSeenAtMs
|
|
9315
|
+
: nowMs;
|
|
9316
|
+
if (firstSeenAtMs < cutoffMs) continue;
|
|
9317
|
+
retained.push(
|
|
9318
|
+
firstSeenAtMs === storedFirstSeenAtMs
|
|
9319
|
+
? event
|
|
9320
|
+
: { ...event, firstSeenAtMs },
|
|
9321
|
+
);
|
|
9322
|
+
}
|
|
9323
|
+
return retained.length > maxEntries
|
|
9324
|
+
? retained.slice(retained.length - maxEntries)
|
|
9325
|
+
: retained;
|
|
9326
|
+
}
|
|
9327
|
+
|
|
9328
|
+
function replaceCopilotUsageClaims(
|
|
9329
|
+
target,
|
|
9330
|
+
nowMs = Date.now(),
|
|
9331
|
+
maxEntries = COPILOT_USAGE_CLAIM_MAX_ENTRIES,
|
|
9332
|
+
) {
|
|
9333
|
+
if (!Array.isArray(target)) return;
|
|
9334
|
+
const retained = pruneCopilotUsageClaims(target, nowMs, maxEntries);
|
|
9335
|
+
target.splice(0, target.length, ...retained);
|
|
9336
|
+
}
|
|
9267
9337
|
|
|
9268
9338
|
function isCopilotV1ChatSpan(record) {
|
|
9269
9339
|
if (!record || record.type !== "span") return false;
|
|
@@ -9288,6 +9358,65 @@ function getCopilotDedupKey(record, attrs = record?.attributes || {}) {
|
|
|
9288
9358
|
return traceId && spanId ? `${traceId}:${spanId}` : responseId ? `resp:${responseId}` : null;
|
|
9289
9359
|
}
|
|
9290
9360
|
|
|
9361
|
+
function copilotUsageMatchKey({
|
|
9362
|
+
sessionId,
|
|
9363
|
+
model,
|
|
9364
|
+
input,
|
|
9365
|
+
output,
|
|
9366
|
+
cacheRead,
|
|
9367
|
+
cacheWrite,
|
|
9368
|
+
reasoning,
|
|
9369
|
+
}) {
|
|
9370
|
+
return [
|
|
9371
|
+
sessionId || "",
|
|
9372
|
+
normalizeCopilotAppModel(model) || COPILOT_APP_DEFAULT_MODEL,
|
|
9373
|
+
toNonNegativeInt(input),
|
|
9374
|
+
toNonNegativeInt(output),
|
|
9375
|
+
toNonNegativeInt(cacheRead),
|
|
9376
|
+
toNonNegativeInt(cacheWrite),
|
|
9377
|
+
toNonNegativeInt(reasoning),
|
|
9378
|
+
].join("|");
|
|
9379
|
+
}
|
|
9380
|
+
|
|
9381
|
+
function createCopilotStoreUsageMatcher(events) {
|
|
9382
|
+
const candidates = new Map();
|
|
9383
|
+
for (const event of Array.isArray(events) ? events : []) {
|
|
9384
|
+
if (!event || typeof event !== "object") continue;
|
|
9385
|
+
if (event.consumed === true) continue;
|
|
9386
|
+
const tsMs = Number(event.tsMs);
|
|
9387
|
+
if (!Number.isFinite(tsMs) || tsMs <= 0) continue;
|
|
9388
|
+
const key = copilotUsageMatchKey(event);
|
|
9389
|
+
const list = candidates.get(key) || [];
|
|
9390
|
+
list.push({ tsMs, event });
|
|
9391
|
+
candidates.set(key, list);
|
|
9392
|
+
}
|
|
9393
|
+
for (const list of candidates.values()) {
|
|
9394
|
+
list.sort((a, b) => a.tsMs - b.tsMs);
|
|
9395
|
+
}
|
|
9396
|
+
return {
|
|
9397
|
+
consume(event, toleranceMs = 2_000) {
|
|
9398
|
+
const tsMs = Number(event?.tsMs);
|
|
9399
|
+
if (!Number.isFinite(tsMs) || tsMs <= 0) return false;
|
|
9400
|
+
const key = copilotUsageMatchKey(event || {});
|
|
9401
|
+
const list = candidates.get(key);
|
|
9402
|
+
if (!list || list.length === 0) return false;
|
|
9403
|
+
let bestIndex = -1;
|
|
9404
|
+
let bestDistance = Infinity;
|
|
9405
|
+
for (let i = 0; i < list.length; i++) {
|
|
9406
|
+
const distance = Math.abs(list[i].tsMs - tsMs);
|
|
9407
|
+
if (distance <= toleranceMs && distance < bestDistance) {
|
|
9408
|
+
bestIndex = i;
|
|
9409
|
+
bestDistance = distance;
|
|
9410
|
+
}
|
|
9411
|
+
}
|
|
9412
|
+
if (bestIndex < 0) return false;
|
|
9413
|
+
const [matched] = list.splice(bestIndex, 1);
|
|
9414
|
+
matched.event.consumed = true;
|
|
9415
|
+
return true;
|
|
9416
|
+
},
|
|
9417
|
+
};
|
|
9418
|
+
}
|
|
9419
|
+
|
|
9291
9420
|
// Migration helper: stream the bytes v1 already saw (0 -> prevSize), classify
|
|
9292
9421
|
// whether the file contains old CLI spans v1 processed, and whether it also
|
|
9293
9422
|
// contains v2-only chat records v1 skipped. Mixed files must be replayed, but
|
|
@@ -9334,7 +9463,54 @@ async function scanCopilotV1MigrationFile(filePath, maxBytes) {
|
|
|
9334
9463
|
return result;
|
|
9335
9464
|
}
|
|
9336
9465
|
|
|
9337
|
-
async function
|
|
9466
|
+
async function copilotOtelCursorHasLegacyCliUsage(cursors) {
|
|
9467
|
+
const state =
|
|
9468
|
+
cursors?.copilot && typeof cursors.copilot === "object"
|
|
9469
|
+
? cursors.copilot
|
|
9470
|
+
: {};
|
|
9471
|
+
if (state.legacyCliHistory === true) return true;
|
|
9472
|
+
if (state.usageClaimsComplete === true) return false;
|
|
9473
|
+
const offsets =
|
|
9474
|
+
state.fileOffsets && typeof state.fileOffsets === "object"
|
|
9475
|
+
? state.fileOffsets
|
|
9476
|
+
: {};
|
|
9477
|
+
const entries = Object.entries(offsets).filter(
|
|
9478
|
+
([, entry]) => Number(entry?.size) > 0,
|
|
9479
|
+
);
|
|
9480
|
+
const hasSeenIds =
|
|
9481
|
+
Array.isArray(state.seenIds) && state.seenIds.length > 0;
|
|
9482
|
+
if (entries.length === 0) return hasSeenIds;
|
|
9483
|
+
for (const [filePath, entry] of entries) {
|
|
9484
|
+
let stat;
|
|
9485
|
+
try {
|
|
9486
|
+
stat = fssync.statSync(filePath);
|
|
9487
|
+
} catch (_e) {
|
|
9488
|
+
return true;
|
|
9489
|
+
}
|
|
9490
|
+
if (
|
|
9491
|
+
stat.size < Number(entry?.size) ||
|
|
9492
|
+
(typeof entry?.ino === "number" && stat.ino !== entry.ino)
|
|
9493
|
+
) {
|
|
9494
|
+
return true;
|
|
9495
|
+
}
|
|
9496
|
+
const scan = await scanCopilotV1MigrationFile(
|
|
9497
|
+
filePath,
|
|
9498
|
+
Number(entry?.size) || 0,
|
|
9499
|
+
);
|
|
9500
|
+
if (scan.v1Processed) return true;
|
|
9501
|
+
}
|
|
9502
|
+
return false;
|
|
9503
|
+
}
|
|
9504
|
+
|
|
9505
|
+
async function parseCopilotIncremental({
|
|
9506
|
+
otelPaths,
|
|
9507
|
+
cursors,
|
|
9508
|
+
queuePath,
|
|
9509
|
+
onProgress,
|
|
9510
|
+
env,
|
|
9511
|
+
skipCliSpans = false,
|
|
9512
|
+
storeUsageEvents,
|
|
9513
|
+
} = {}) {
|
|
9338
9514
|
await ensureDir(path.dirname(queuePath));
|
|
9339
9515
|
const copilotState = cursors.copilot && typeof cursors.copilot === "object" ? cursors.copilot : {};
|
|
9340
9516
|
const seenIds = new Set(Array.isArray(copilotState.seenIds) ? copilotState.seenIds : []);
|
|
@@ -9344,6 +9520,16 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9344
9520
|
? copilotState.fileOffsets
|
|
9345
9521
|
: {};
|
|
9346
9522
|
const fileOffsets = { ...fileOffsetsRaw };
|
|
9523
|
+
const claimNowMs = Date.now();
|
|
9524
|
+
const recentOtelUsageEvents = pruneCopilotUsageClaims(
|
|
9525
|
+
copilotState.recentUsageEvents,
|
|
9526
|
+
claimNowMs,
|
|
9527
|
+
);
|
|
9528
|
+
replaceCopilotUsageClaims(storeUsageEvents, claimNowMs, Infinity);
|
|
9529
|
+
const hadPriorUsageHistory =
|
|
9530
|
+
seenIds.size > 0 || Object.keys(fileOffsetsRaw).length > 0;
|
|
9531
|
+
let usageClaimsComplete =
|
|
9532
|
+
copilotState.usageClaimsComplete === true || !hadPriorUsageHistory;
|
|
9347
9533
|
const migrationSkipLineHashes = new Map();
|
|
9348
9534
|
// One-shot v1->v2 migration:
|
|
9349
9535
|
// - pure v2-only files: clear offset and re-read all skipped Chat records
|
|
@@ -9371,9 +9557,16 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9371
9557
|
version: COPILOT_PARSER_VERSION,
|
|
9372
9558
|
seenIds: Array.from(seenIds),
|
|
9373
9559
|
fileOffsets,
|
|
9560
|
+
recentUsageEvents: recentOtelUsageEvents,
|
|
9561
|
+
usageClaimsComplete,
|
|
9374
9562
|
updatedAt: new Date().toISOString(),
|
|
9375
9563
|
};
|
|
9376
|
-
return {
|
|
9564
|
+
return {
|
|
9565
|
+
recordsProcessed: 0,
|
|
9566
|
+
eventsAggregated: 0,
|
|
9567
|
+
bucketsQueued: 0,
|
|
9568
|
+
usageClaims: recentOtelUsageEvents,
|
|
9569
|
+
};
|
|
9377
9570
|
}
|
|
9378
9571
|
|
|
9379
9572
|
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
@@ -9381,6 +9574,7 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9381
9574
|
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
9382
9575
|
let recordsProcessed = 0;
|
|
9383
9576
|
let eventsAggregated = 0;
|
|
9577
|
+
const storeUsageMatcher = createCopilotStoreUsageMatcher(storeUsageEvents);
|
|
9384
9578
|
|
|
9385
9579
|
for (let fileIdx = 0; fileIdx < files.length; fileIdx++) {
|
|
9386
9580
|
const filePath = files[fileIdx];
|
|
@@ -9436,6 +9630,10 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9436
9630
|
// gen_ai.response.id is per-LLM-call unique.
|
|
9437
9631
|
const dedupKey = getCopilotDedupKey(record, attrs);
|
|
9438
9632
|
if (dedupKey && seenIds.has(dedupKey)) continue;
|
|
9633
|
+
if (skipCliSpans && isCopilotV1ChatSpan(record)) {
|
|
9634
|
+
if (dedupKey) seenIds.add(dedupKey);
|
|
9635
|
+
continue;
|
|
9636
|
+
}
|
|
9439
9637
|
|
|
9440
9638
|
const inputRaw = toNonNegativeInt(attrs["gen_ai.usage.input_tokens"]);
|
|
9441
9639
|
const output = toNonNegativeInt(attrs["gen_ai.usage.output_tokens"]);
|
|
@@ -9495,6 +9693,30 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9495
9693
|
const model =
|
|
9496
9694
|
normalizeCopilotAppModel(pickCopilotModel(attrs)) ||
|
|
9497
9695
|
COPILOT_APP_DEFAULT_MODEL;
|
|
9696
|
+
const cliSessionId =
|
|
9697
|
+
typeof attrs["gen_ai.conversation.id"] === "string"
|
|
9698
|
+
? attrs["gen_ai.conversation.id"].trim()
|
|
9699
|
+
: "";
|
|
9700
|
+
if (cliSpan && !cliSessionId) usageClaimsComplete = false;
|
|
9701
|
+
const matchBase = {
|
|
9702
|
+
sessionId: cliSessionId,
|
|
9703
|
+
model,
|
|
9704
|
+
output: outputWithoutReasoning,
|
|
9705
|
+
cacheRead: cacheReadClamped,
|
|
9706
|
+
cacheWrite: cacheWriteClamped,
|
|
9707
|
+
reasoning: reasoningClamped,
|
|
9708
|
+
tsMs,
|
|
9709
|
+
};
|
|
9710
|
+
const matchedStoreUsage =
|
|
9711
|
+
isCopilotV1ChatSpan(record) &&
|
|
9712
|
+
storeUsageMatcher.consume({
|
|
9713
|
+
...matchBase,
|
|
9714
|
+
input,
|
|
9715
|
+
});
|
|
9716
|
+
if (matchedStoreUsage) {
|
|
9717
|
+
if (dedupKey) seenIds.add(dedupKey);
|
|
9718
|
+
continue;
|
|
9719
|
+
}
|
|
9498
9720
|
|
|
9499
9721
|
const delta = {
|
|
9500
9722
|
input_tokens: input,
|
|
@@ -9515,6 +9737,13 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9515
9737
|
addTotals(bucket.totals, delta);
|
|
9516
9738
|
touchedBuckets.add(bucketKey("copilot", model, bucketStart));
|
|
9517
9739
|
eventsAggregated++;
|
|
9740
|
+
if (cliSpan && cliSessionId) {
|
|
9741
|
+
recentOtelUsageEvents.push({
|
|
9742
|
+
...matchBase,
|
|
9743
|
+
input,
|
|
9744
|
+
firstSeenAtMs: claimNowMs,
|
|
9745
|
+
});
|
|
9746
|
+
}
|
|
9518
9747
|
if (dedupKey) seenIds.add(dedupKey);
|
|
9519
9748
|
|
|
9520
9749
|
if (cb) {
|
|
@@ -9542,6 +9771,10 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9542
9771
|
// Cap dedup set to last 10k IDs to bound state size
|
|
9543
9772
|
const seenArr = Array.from(seenIds);
|
|
9544
9773
|
const cappedSeen = seenArr.length > 10_000 ? seenArr.slice(seenArr.length - 10_000) : seenArr;
|
|
9774
|
+
const retainedRecentOtelUsageEvents = pruneCopilotUsageClaims(
|
|
9775
|
+
recentOtelUsageEvents,
|
|
9776
|
+
);
|
|
9777
|
+
replaceCopilotUsageClaims(storeUsageEvents, Date.now(), Infinity);
|
|
9545
9778
|
|
|
9546
9779
|
const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
|
|
9547
9780
|
const updatedAt = new Date().toISOString();
|
|
@@ -9552,10 +9785,914 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9552
9785
|
version: COPILOT_PARSER_VERSION,
|
|
9553
9786
|
seenIds: cappedSeen,
|
|
9554
9787
|
fileOffsets,
|
|
9788
|
+
recentUsageEvents: retainedRecentOtelUsageEvents,
|
|
9789
|
+
usageClaimsComplete,
|
|
9555
9790
|
updatedAt,
|
|
9556
9791
|
};
|
|
9557
9792
|
|
|
9558
|
-
return {
|
|
9793
|
+
return {
|
|
9794
|
+
recordsProcessed,
|
|
9795
|
+
eventsAggregated,
|
|
9796
|
+
bucketsQueued,
|
|
9797
|
+
usageClaims: recentOtelUsageEvents,
|
|
9798
|
+
};
|
|
9799
|
+
}
|
|
9800
|
+
|
|
9801
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9802
|
+
// GitHub Copilot local runtime — session-store.db assistant_usage_events
|
|
9803
|
+
//
|
|
9804
|
+
// Copilot CLI 1.0.70+ persists one row per LLM request here. The Copilot App
|
|
9805
|
+
// uses the same runtime, so this is the only local source that remains complete
|
|
9806
|
+
// when one session moves App -> CLI -> App (or the reverse). data.db only keeps
|
|
9807
|
+
// the App-owned portion of those sessions.
|
|
9808
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9809
|
+
|
|
9810
|
+
const COPILOT_STORE_CURSOR_VERSION = 2;
|
|
9811
|
+
const COPILOT_STORE_LEGACY_MODEL = "github-copilot-legacy";
|
|
9812
|
+
|
|
9813
|
+
function resolveCopilotDbPaths({ fileName, overrideEnvKey }, env = process.env) {
|
|
9814
|
+
const home =
|
|
9815
|
+
process.platform === "win32"
|
|
9816
|
+
? env.USERPROFILE || env.HOME || require("node:os").homedir()
|
|
9817
|
+
: env.HOME || require("node:os").homedir();
|
|
9818
|
+
const paths = new Set();
|
|
9819
|
+
const addDbPath = (dbPath) => {
|
|
9820
|
+
const normalized = normalizeCopilotDbPath(dbPath, env);
|
|
9821
|
+
if (normalized) paths.add(normalized);
|
|
9822
|
+
};
|
|
9823
|
+
const addHome = (homePath) => {
|
|
9824
|
+
const normalizedHome = normalizeCopilotDbPath(homePath, env);
|
|
9825
|
+
if (normalizedHome) addDbPath(path.join(normalizedHome, fileName));
|
|
9826
|
+
};
|
|
9827
|
+
|
|
9828
|
+
if (typeof env[overrideEnvKey] === "string" && env[overrideEnvKey].trim()) {
|
|
9829
|
+
addDbPath(env[overrideEnvKey]);
|
|
9830
|
+
}
|
|
9831
|
+
if (typeof env.COPILOT_HOME === "string" && env.COPILOT_HOME.trim()) {
|
|
9832
|
+
addHome(env.COPILOT_HOME);
|
|
9833
|
+
}
|
|
9834
|
+
|
|
9835
|
+
addDbPath(path.join(home, ".copilot", fileName));
|
|
9836
|
+
|
|
9837
|
+
return Array.from(paths).sort();
|
|
9838
|
+
}
|
|
9839
|
+
|
|
9840
|
+
function resolveCopilotSessionStorePaths(env = process.env) {
|
|
9841
|
+
return resolveCopilotDbPaths(
|
|
9842
|
+
{
|
|
9843
|
+
fileName: "session-store.db",
|
|
9844
|
+
overrideEnvKey: "TOKENTRACKER_COPILOT_SESSION_STORE_DB",
|
|
9845
|
+
},
|
|
9846
|
+
env,
|
|
9847
|
+
);
|
|
9848
|
+
}
|
|
9849
|
+
|
|
9850
|
+
function copilotStoreRowKey(row) {
|
|
9851
|
+
if (!row) return null;
|
|
9852
|
+
return crypto
|
|
9853
|
+
.createHash("sha256")
|
|
9854
|
+
.update(
|
|
9855
|
+
JSON.stringify([
|
|
9856
|
+
row.max_id ?? row.id,
|
|
9857
|
+
row.session_id,
|
|
9858
|
+
row.model,
|
|
9859
|
+
row.input_tokens,
|
|
9860
|
+
row.output_tokens,
|
|
9861
|
+
row.cache_read_tokens,
|
|
9862
|
+
row.cache_write_tokens,
|
|
9863
|
+
row.reasoning_tokens,
|
|
9864
|
+
row.created_at,
|
|
9865
|
+
]),
|
|
9866
|
+
)
|
|
9867
|
+
.digest("hex");
|
|
9868
|
+
}
|
|
9869
|
+
|
|
9870
|
+
function copilotStoreEventFingerprint(row) {
|
|
9871
|
+
const sessionId =
|
|
9872
|
+
typeof row?.session_id === "string" ? row.session_id.trim() : "";
|
|
9873
|
+
if (!sessionId) return null;
|
|
9874
|
+
const normalized = normalizeCopilotSessionStoreUsage(row);
|
|
9875
|
+
const timestamp =
|
|
9876
|
+
parseCopilotAppTimestamp(row?.created_at) ||
|
|
9877
|
+
`invalid:${String(row?.created_at ?? "")}`;
|
|
9878
|
+
return crypto
|
|
9879
|
+
.createHash("sha256")
|
|
9880
|
+
.update(
|
|
9881
|
+
JSON.stringify([
|
|
9882
|
+
sessionId,
|
|
9883
|
+
normalizeCopilotAppModel(row?.model) || COPILOT_APP_DEFAULT_MODEL,
|
|
9884
|
+
normalized.input_tokens,
|
|
9885
|
+
normalized.output_tokens,
|
|
9886
|
+
normalized.cached_input_tokens,
|
|
9887
|
+
normalized.cache_creation_input_tokens,
|
|
9888
|
+
normalized.reasoning_output_tokens,
|
|
9889
|
+
timestamp,
|
|
9890
|
+
]),
|
|
9891
|
+
)
|
|
9892
|
+
.digest("hex");
|
|
9893
|
+
}
|
|
9894
|
+
|
|
9895
|
+
function normalizeCopilotFingerprintCounts(value) {
|
|
9896
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
9897
|
+
const result = {};
|
|
9898
|
+
for (const [fingerprint, count] of Object.entries(value)) {
|
|
9899
|
+
const normalized = toNonNegativeInt(count);
|
|
9900
|
+
if (/^[a-f0-9]{64}$/.test(fingerprint) && normalized > 0) {
|
|
9901
|
+
result[fingerprint] = normalized;
|
|
9902
|
+
}
|
|
9903
|
+
}
|
|
9904
|
+
return result;
|
|
9905
|
+
}
|
|
9906
|
+
|
|
9907
|
+
function normalizeCopilotPendingMalformedEvents(value) {
|
|
9908
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
9909
|
+
const result = {};
|
|
9910
|
+
for (const [id, fingerprint] of Object.entries(value)) {
|
|
9911
|
+
const normalizedId = toNonNegativeInt(id);
|
|
9912
|
+
if (normalizedId > 0) {
|
|
9913
|
+
result[normalizedId] =
|
|
9914
|
+
typeof fingerprint === "string" ? fingerprint : "";
|
|
9915
|
+
}
|
|
9916
|
+
}
|
|
9917
|
+
return result;
|
|
9918
|
+
}
|
|
9919
|
+
|
|
9920
|
+
function incrementCopilotFingerprintCount(counts, fingerprint) {
|
|
9921
|
+
if (!fingerprint) return;
|
|
9922
|
+
counts[fingerprint] = toNonNegativeInt(counts[fingerprint]) + 1;
|
|
9923
|
+
}
|
|
9924
|
+
|
|
9925
|
+
function latestCopilotTimestamp(first, second) {
|
|
9926
|
+
const values = [first, second]
|
|
9927
|
+
.map((value) => parseCopilotAppTimestamp(value))
|
|
9928
|
+
.filter(Boolean);
|
|
9929
|
+
if (values.length === 0) return null;
|
|
9930
|
+
return values.reduce((latest, value) => (value > latest ? value : latest));
|
|
9931
|
+
}
|
|
9932
|
+
|
|
9933
|
+
function omitCopilotLegacyCatchupState(state) {
|
|
9934
|
+
const clean = { ...(state || {}) };
|
|
9935
|
+
delete clean.pendingCatchupMaxId;
|
|
9936
|
+
delete clean.pendingCatchupFingerprint;
|
|
9937
|
+
delete clean.pendingLegacyCatchup;
|
|
9938
|
+
return clean;
|
|
9939
|
+
}
|
|
9940
|
+
|
|
9941
|
+
function readCopilotSessionStoreMetadata(
|
|
9942
|
+
dbPath,
|
|
9943
|
+
sqliteOptions = {},
|
|
9944
|
+
cursorId = 0,
|
|
9945
|
+
) {
|
|
9946
|
+
const tables = readSqliteJsonRows(
|
|
9947
|
+
dbPath,
|
|
9948
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('assistant_usage_events', 'schema_version')",
|
|
9949
|
+
{
|
|
9950
|
+
label: "GitHub Copilot session store",
|
|
9951
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
9952
|
+
timeout: 10_000,
|
|
9953
|
+
readOnly: true,
|
|
9954
|
+
throwOnReadFailure: true,
|
|
9955
|
+
...sqliteOptions,
|
|
9956
|
+
},
|
|
9957
|
+
);
|
|
9958
|
+
const names = new Set(tables.map((row) => row?.name).filter(Boolean));
|
|
9959
|
+
if (!names.has("assistant_usage_events")) {
|
|
9960
|
+
return { active: false, schemaVersion: null, maxId: 0 };
|
|
9961
|
+
}
|
|
9962
|
+
const maxRows = readSqliteJsonRows(
|
|
9963
|
+
dbPath,
|
|
9964
|
+
`
|
|
9965
|
+
SELECT
|
|
9966
|
+
id AS max_id,
|
|
9967
|
+
session_id,
|
|
9968
|
+
model,
|
|
9969
|
+
input_tokens,
|
|
9970
|
+
output_tokens,
|
|
9971
|
+
cache_read_tokens,
|
|
9972
|
+
cache_write_tokens,
|
|
9973
|
+
reasoning_tokens,
|
|
9974
|
+
created_at
|
|
9975
|
+
FROM assistant_usage_events
|
|
9976
|
+
ORDER BY id DESC
|
|
9977
|
+
LIMIT 1
|
|
9978
|
+
`.trim(),
|
|
9979
|
+
{
|
|
9980
|
+
label: "GitHub Copilot session store",
|
|
9981
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
9982
|
+
timeout: 10_000,
|
|
9983
|
+
readOnly: true,
|
|
9984
|
+
throwOnReadFailure: true,
|
|
9985
|
+
...sqliteOptions,
|
|
9986
|
+
},
|
|
9987
|
+
);
|
|
9988
|
+
let schemaVersion = null;
|
|
9989
|
+
if (names.has("schema_version")) {
|
|
9990
|
+
const versionRows = readSqliteJsonRows(
|
|
9991
|
+
dbPath,
|
|
9992
|
+
"SELECT version FROM schema_version LIMIT 1",
|
|
9993
|
+
{
|
|
9994
|
+
label: "GitHub Copilot session store",
|
|
9995
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
9996
|
+
timeout: 10_000,
|
|
9997
|
+
readOnly: true,
|
|
9998
|
+
throwOnReadFailure: true,
|
|
9999
|
+
...sqliteOptions,
|
|
10000
|
+
},
|
|
10001
|
+
);
|
|
10002
|
+
schemaVersion = toNonNegativeInt(versionRows[0]?.version);
|
|
10003
|
+
}
|
|
10004
|
+
let cursorRowKey = null;
|
|
10005
|
+
const normalizedCursorId = toNonNegativeInt(cursorId);
|
|
10006
|
+
if (normalizedCursorId > 0) {
|
|
10007
|
+
if (normalizedCursorId === toNonNegativeInt(maxRows[0]?.max_id)) {
|
|
10008
|
+
cursorRowKey = copilotStoreRowKey(maxRows[0]);
|
|
10009
|
+
} else {
|
|
10010
|
+
const cursorRows = readSqliteJsonRows(
|
|
10011
|
+
dbPath,
|
|
10012
|
+
`
|
|
10013
|
+
SELECT
|
|
10014
|
+
id,
|
|
10015
|
+
session_id,
|
|
10016
|
+
model,
|
|
10017
|
+
input_tokens,
|
|
10018
|
+
output_tokens,
|
|
10019
|
+
cache_read_tokens,
|
|
10020
|
+
cache_write_tokens,
|
|
10021
|
+
reasoning_tokens,
|
|
10022
|
+
created_at
|
|
10023
|
+
FROM assistant_usage_events
|
|
10024
|
+
WHERE id = ${normalizedCursorId}
|
|
10025
|
+
LIMIT 1
|
|
10026
|
+
`.trim(),
|
|
10027
|
+
{
|
|
10028
|
+
label: "GitHub Copilot session store",
|
|
10029
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
10030
|
+
timeout: 10_000,
|
|
10031
|
+
readOnly: true,
|
|
10032
|
+
throwOnReadFailure: true,
|
|
10033
|
+
...sqliteOptions,
|
|
10034
|
+
},
|
|
10035
|
+
);
|
|
10036
|
+
cursorRowKey = copilotStoreRowKey(cursorRows[0]);
|
|
10037
|
+
}
|
|
10038
|
+
}
|
|
10039
|
+
return {
|
|
10040
|
+
active: true,
|
|
10041
|
+
schemaVersion,
|
|
10042
|
+
maxId: toNonNegativeInt(maxRows[0]?.max_id),
|
|
10043
|
+
lastRowKey: copilotStoreRowKey(maxRows[0]),
|
|
10044
|
+
lastEventAt: parseCopilotAppTimestamp(maxRows[0]?.created_at),
|
|
10045
|
+
cursorRowKey,
|
|
10046
|
+
};
|
|
10047
|
+
}
|
|
10048
|
+
|
|
10049
|
+
function describeCopilotSessionStoreDb(dbPath, sqliteOptions = {}) {
|
|
10050
|
+
let snap = null;
|
|
10051
|
+
try {
|
|
10052
|
+
snap = snapshotSqliteDb(dbPath);
|
|
10053
|
+
const metadata = readCopilotSessionStoreMetadata(snap.path, sqliteOptions);
|
|
10054
|
+
if (!metadata.active) {
|
|
10055
|
+
return {
|
|
10056
|
+
path: dbPath,
|
|
10057
|
+
active: false,
|
|
10058
|
+
schemaVersion: metadata.schemaVersion,
|
|
10059
|
+
eventCount: 0,
|
|
10060
|
+
lastEventId: 0,
|
|
10061
|
+
lastEventAt: null,
|
|
10062
|
+
};
|
|
10063
|
+
}
|
|
10064
|
+
const countRows = readSqliteJsonRows(
|
|
10065
|
+
snap.path,
|
|
10066
|
+
"SELECT COUNT(*) AS event_count FROM assistant_usage_events",
|
|
10067
|
+
{
|
|
10068
|
+
label: "GitHub Copilot session store",
|
|
10069
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
10070
|
+
timeout: 10_000,
|
|
10071
|
+
readOnly: true,
|
|
10072
|
+
throwOnReadFailure: true,
|
|
10073
|
+
...sqliteOptions,
|
|
10074
|
+
},
|
|
10075
|
+
);
|
|
10076
|
+
return {
|
|
10077
|
+
path: dbPath,
|
|
10078
|
+
active: true,
|
|
10079
|
+
schemaVersion: metadata.schemaVersion,
|
|
10080
|
+
eventCount: toNonNegativeInt(countRows[0]?.event_count),
|
|
10081
|
+
lastEventId: metadata.maxId,
|
|
10082
|
+
lastEventAt: metadata.lastEventAt,
|
|
10083
|
+
};
|
|
10084
|
+
} finally {
|
|
10085
|
+
if (snap) snap.cleanup();
|
|
10086
|
+
}
|
|
10087
|
+
}
|
|
10088
|
+
|
|
10089
|
+
function readCopilotSessionStoreUsageRowsWhere(
|
|
10090
|
+
dbPath,
|
|
10091
|
+
whereClause,
|
|
10092
|
+
sqliteOptions = {},
|
|
10093
|
+
) {
|
|
10094
|
+
return readSqliteJsonRows(
|
|
10095
|
+
dbPath,
|
|
10096
|
+
`
|
|
10097
|
+
SELECT
|
|
10098
|
+
id,
|
|
10099
|
+
session_id,
|
|
10100
|
+
model,
|
|
10101
|
+
input_tokens,
|
|
10102
|
+
output_tokens,
|
|
10103
|
+
cache_read_tokens,
|
|
10104
|
+
cache_write_tokens,
|
|
10105
|
+
reasoning_tokens,
|
|
10106
|
+
token_details_json,
|
|
10107
|
+
created_at
|
|
10108
|
+
FROM assistant_usage_events
|
|
10109
|
+
WHERE ${whereClause}
|
|
10110
|
+
ORDER BY id ASC
|
|
10111
|
+
`.trim(),
|
|
10112
|
+
{
|
|
10113
|
+
label: "GitHub Copilot session store",
|
|
10114
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
10115
|
+
timeout: 60_000,
|
|
10116
|
+
readOnly: true,
|
|
10117
|
+
throwOnReadFailure: true,
|
|
10118
|
+
...sqliteOptions,
|
|
10119
|
+
},
|
|
10120
|
+
);
|
|
10121
|
+
}
|
|
10122
|
+
|
|
10123
|
+
function readCopilotSessionStoreUsageRows(dbPath, lastId, sqliteOptions = {}) {
|
|
10124
|
+
return readCopilotSessionStoreUsageRowsWhere(
|
|
10125
|
+
dbPath,
|
|
10126
|
+
`id > ${toNonNegativeInt(lastId)}`,
|
|
10127
|
+
sqliteOptions,
|
|
10128
|
+
);
|
|
10129
|
+
}
|
|
10130
|
+
|
|
10131
|
+
function readCopilotSessionStoreUsageRowsByIds(
|
|
10132
|
+
dbPath,
|
|
10133
|
+
ids,
|
|
10134
|
+
sqliteOptions = {},
|
|
10135
|
+
) {
|
|
10136
|
+
const normalizedIds = [
|
|
10137
|
+
...new Set(
|
|
10138
|
+
(Array.isArray(ids) ? ids : [])
|
|
10139
|
+
.map(toNonNegativeInt)
|
|
10140
|
+
.filter((id) => id > 0),
|
|
10141
|
+
),
|
|
10142
|
+
];
|
|
10143
|
+
const rows = [];
|
|
10144
|
+
for (let start = 0; start < normalizedIds.length; start += 500) {
|
|
10145
|
+
const chunk = normalizedIds.slice(start, start + 500);
|
|
10146
|
+
rows.push(
|
|
10147
|
+
...readCopilotSessionStoreUsageRowsWhere(
|
|
10148
|
+
dbPath,
|
|
10149
|
+
`id IN (${chunk.join(",")})`,
|
|
10150
|
+
sqliteOptions,
|
|
10151
|
+
),
|
|
10152
|
+
);
|
|
10153
|
+
}
|
|
10154
|
+
return rows.sort((a, b) => toNonNegativeInt(a?.id) - toNonNegativeInt(b?.id));
|
|
10155
|
+
}
|
|
10156
|
+
|
|
10157
|
+
function parseCopilotStoreTokenDetails(raw) {
|
|
10158
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
10159
|
+
let entries;
|
|
10160
|
+
try {
|
|
10161
|
+
entries = JSON.parse(raw);
|
|
10162
|
+
} catch (_e) {
|
|
10163
|
+
return null;
|
|
10164
|
+
}
|
|
10165
|
+
if (!Array.isArray(entries)) return null;
|
|
10166
|
+
const totals = { input: 0, cacheRead: 0, cacheWrite: 0, output: 0 };
|
|
10167
|
+
let recognized = 0;
|
|
10168
|
+
for (const entry of entries) {
|
|
10169
|
+
if (!entry || typeof entry !== "object") continue;
|
|
10170
|
+
const count = toNonNegativeInt(entry.tokenCount);
|
|
10171
|
+
switch (entry.tokenType) {
|
|
10172
|
+
case "input":
|
|
10173
|
+
totals.input += count;
|
|
10174
|
+
recognized++;
|
|
10175
|
+
break;
|
|
10176
|
+
case "cache_read":
|
|
10177
|
+
totals.cacheRead += count;
|
|
10178
|
+
recognized++;
|
|
10179
|
+
break;
|
|
10180
|
+
case "cache_write":
|
|
10181
|
+
totals.cacheWrite += count;
|
|
10182
|
+
recognized++;
|
|
10183
|
+
break;
|
|
10184
|
+
case "output":
|
|
10185
|
+
totals.output += count;
|
|
10186
|
+
recognized++;
|
|
10187
|
+
break;
|
|
10188
|
+
default:
|
|
10189
|
+
break;
|
|
10190
|
+
}
|
|
10191
|
+
}
|
|
10192
|
+
return recognized > 0 ? totals : null;
|
|
10193
|
+
}
|
|
10194
|
+
|
|
10195
|
+
function copilotStoreRecentEvent(row, firstSeenAtMs = Date.now()) {
|
|
10196
|
+
const tsIso = parseCopilotAppTimestamp(row?.created_at);
|
|
10197
|
+
const tsMs = tsIso ? Date.parse(tsIso) : NaN;
|
|
10198
|
+
const sessionId =
|
|
10199
|
+
typeof row?.session_id === "string" ? row.session_id.trim() : "";
|
|
10200
|
+
if (!sessionId || !Number.isFinite(tsMs) || tsMs <= 0) return null;
|
|
10201
|
+
const normalized = normalizeCopilotSessionStoreUsage(row);
|
|
10202
|
+
return {
|
|
10203
|
+
id: toNonNegativeInt(row?.id),
|
|
10204
|
+
sessionId,
|
|
10205
|
+
model: normalizeCopilotAppModel(row?.model) || COPILOT_APP_DEFAULT_MODEL,
|
|
10206
|
+
input: normalized.input_tokens,
|
|
10207
|
+
output: normalized.output_tokens,
|
|
10208
|
+
cacheRead: normalized.cached_input_tokens,
|
|
10209
|
+
cacheWrite: normalized.cache_creation_input_tokens,
|
|
10210
|
+
reasoning: normalized.reasoning_output_tokens,
|
|
10211
|
+
tsMs,
|
|
10212
|
+
firstSeenAtMs,
|
|
10213
|
+
};
|
|
10214
|
+
}
|
|
10215
|
+
|
|
10216
|
+
function normalizeCopilotSessionStoreUsage(row) {
|
|
10217
|
+
const inputRaw = toNonNegativeInt(row?.input_tokens);
|
|
10218
|
+
const outputRaw = toNonNegativeInt(row?.output_tokens);
|
|
10219
|
+
const reasoning = toNonNegativeInt(row?.reasoning_tokens);
|
|
10220
|
+
const details = parseCopilotStoreTokenDetails(row?.token_details_json);
|
|
10221
|
+
let input;
|
|
10222
|
+
let cacheRead;
|
|
10223
|
+
let cacheWrite;
|
|
10224
|
+
let output;
|
|
10225
|
+
let precision = "fallback";
|
|
10226
|
+
|
|
10227
|
+
if (
|
|
10228
|
+
details &&
|
|
10229
|
+
details.input + details.cacheRead + details.cacheWrite === inputRaw &&
|
|
10230
|
+
details.output === outputRaw
|
|
10231
|
+
) {
|
|
10232
|
+
input = details.input;
|
|
10233
|
+
cacheRead = details.cacheRead;
|
|
10234
|
+
cacheWrite = details.cacheWrite;
|
|
10235
|
+
output = details.output;
|
|
10236
|
+
precision = "exact";
|
|
10237
|
+
} else {
|
|
10238
|
+
cacheRead = Math.min(toNonNegativeInt(row?.cache_read_tokens), inputRaw);
|
|
10239
|
+
cacheWrite = Math.min(
|
|
10240
|
+
toNonNegativeInt(row?.cache_write_tokens),
|
|
10241
|
+
Math.max(0, inputRaw - cacheRead),
|
|
10242
|
+
);
|
|
10243
|
+
input = Math.max(0, inputRaw - cacheRead - cacheWrite);
|
|
10244
|
+
output = outputRaw;
|
|
10245
|
+
}
|
|
10246
|
+
|
|
10247
|
+
const reasoningClamped = Math.min(reasoning, output);
|
|
10248
|
+
const outputWithoutReasoning = Math.max(0, output - reasoningClamped);
|
|
10249
|
+
return {
|
|
10250
|
+
input_tokens: input,
|
|
10251
|
+
cached_input_tokens: cacheRead,
|
|
10252
|
+
cache_creation_input_tokens: cacheWrite,
|
|
10253
|
+
output_tokens: outputWithoutReasoning,
|
|
10254
|
+
reasoning_output_tokens: reasoningClamped,
|
|
10255
|
+
total_tokens:
|
|
10256
|
+
input + cacheRead + cacheWrite + outputWithoutReasoning + reasoningClamped,
|
|
10257
|
+
precision,
|
|
10258
|
+
};
|
|
10259
|
+
}
|
|
10260
|
+
|
|
10261
|
+
async function parseCopilotSessionStoreIncremental({
|
|
10262
|
+
dbPath,
|
|
10263
|
+
dbPaths,
|
|
10264
|
+
cursors,
|
|
10265
|
+
queuePath,
|
|
10266
|
+
onProgress,
|
|
10267
|
+
env,
|
|
10268
|
+
sqliteOptions,
|
|
10269
|
+
backfillOnFirstRun = false,
|
|
10270
|
+
excludeSessionIdsOnFirstRun,
|
|
10271
|
+
excludeSessionTotalsOnFirstRun,
|
|
10272
|
+
expectedFingerprints,
|
|
10273
|
+
otelUsageEvents,
|
|
10274
|
+
} = {}) {
|
|
10275
|
+
await ensureDir(path.dirname(queuePath));
|
|
10276
|
+
const paths = Array.isArray(dbPaths)
|
|
10277
|
+
? dbPaths
|
|
10278
|
+
: dbPath
|
|
10279
|
+
? [dbPath]
|
|
10280
|
+
: resolveCopilotSessionStorePaths(env || process.env);
|
|
10281
|
+
const storeState =
|
|
10282
|
+
cursors.copilotStore && typeof cursors.copilotStore === "object"
|
|
10283
|
+
? cursors.copilotStore
|
|
10284
|
+
: {};
|
|
10285
|
+
const dbStates =
|
|
10286
|
+
storeState.dbs && typeof storeState.dbs === "object" ? { ...storeState.dbs } : {};
|
|
10287
|
+
let uniquePaths = uniqueCopilotDbPaths(
|
|
10288
|
+
paths,
|
|
10289
|
+
env || process.env,
|
|
10290
|
+
Object.keys(dbStates),
|
|
10291
|
+
);
|
|
10292
|
+
const migratedAliasPaths = coalesceCopilotDbStatesByIdentity(
|
|
10293
|
+
dbStates,
|
|
10294
|
+
uniquePaths,
|
|
10295
|
+
mergeCopilotStoreAliasStates,
|
|
10296
|
+
);
|
|
10297
|
+
uniquePaths = uniquePaths.filter(
|
|
10298
|
+
(dbPath) => !migratedAliasPaths.has(dbPath),
|
|
10299
|
+
);
|
|
10300
|
+
const seenSessions = new Set(
|
|
10301
|
+
Array.isArray(storeState.seenSessions) ? storeState.seenSessions : [],
|
|
10302
|
+
);
|
|
10303
|
+
const excludedFirstRunSessions = new Set(
|
|
10304
|
+
Array.isArray(excludeSessionIdsOnFirstRun)
|
|
10305
|
+
? excludeSessionIdsOnFirstRun.filter((value) => typeof value === "string" && value)
|
|
10306
|
+
: [],
|
|
10307
|
+
);
|
|
10308
|
+
const excludedFirstRunTotals =
|
|
10309
|
+
excludeSessionTotalsOnFirstRun &&
|
|
10310
|
+
typeof excludeSessionTotalsOnFirstRun === "object" &&
|
|
10311
|
+
!Array.isArray(excludeSessionTotalsOnFirstRun)
|
|
10312
|
+
? excludeSessionTotalsOnFirstRun
|
|
10313
|
+
: {};
|
|
10314
|
+
const legacyResiduals = new Map();
|
|
10315
|
+
const claimNowMs = Date.now();
|
|
10316
|
+
const recentEvents = pruneCopilotUsageClaims(
|
|
10317
|
+
storeState.recentEvents,
|
|
10318
|
+
claimNowMs,
|
|
10319
|
+
);
|
|
10320
|
+
replaceCopilotUsageClaims(otelUsageEvents, claimNowMs, Infinity);
|
|
10321
|
+
const otelUsageMatcher = createCopilotStoreUsageMatcher(otelUsageEvents);
|
|
10322
|
+
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
10323
|
+
const touchedBuckets = new Set();
|
|
10324
|
+
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
10325
|
+
const updatedAt = new Date(claimNowMs).toISOString();
|
|
10326
|
+
let activeDbCount = 0;
|
|
10327
|
+
let adoptedThisRun = false;
|
|
10328
|
+
let recordsProcessed = 0;
|
|
10329
|
+
let eventsAggregated = 0;
|
|
10330
|
+
let dbErrors = 0;
|
|
10331
|
+
const canonicalDbPaths = [];
|
|
10332
|
+
const globalAdoptionPending = storeState.active !== true;
|
|
10333
|
+
|
|
10334
|
+
for (const resolvedDb of uniquePaths) {
|
|
10335
|
+
const dbState =
|
|
10336
|
+
dbStates[resolvedDb] && typeof dbStates[resolvedDb] === "object"
|
|
10337
|
+
? dbStates[resolvedDb]
|
|
10338
|
+
: {};
|
|
10339
|
+
const seenEventCounts = normalizeCopilotFingerprintCounts(
|
|
10340
|
+
dbState.seenEventCounts,
|
|
10341
|
+
);
|
|
10342
|
+
let pendingMalformedEvents = normalizeCopilotPendingMalformedEvents(
|
|
10343
|
+
dbState.pendingMalformedEvents,
|
|
10344
|
+
);
|
|
10345
|
+
let fingerprint = null;
|
|
10346
|
+
let snap = null;
|
|
10347
|
+
try {
|
|
10348
|
+
fingerprint = sqliteSidecarFingerprint(resolvedDb);
|
|
10349
|
+
const expectedFingerprint = expectedFingerprints?.[resolvedDb];
|
|
10350
|
+
if (
|
|
10351
|
+
expectedFingerprint &&
|
|
10352
|
+
!sameSqliteFingerprint(fingerprint, expectedFingerprint)
|
|
10353
|
+
) {
|
|
10354
|
+
dbStates[resolvedDb] = {
|
|
10355
|
+
...omitCopilotLegacyCatchupState(dbState),
|
|
10356
|
+
adoptionDeferredAt: updatedAt,
|
|
10357
|
+
updatedAt,
|
|
10358
|
+
};
|
|
10359
|
+
continue;
|
|
10360
|
+
}
|
|
10361
|
+
const currentIno = fingerprint?.db?.ino;
|
|
10362
|
+
const priorLastId = toNonNegativeInt(dbState.lastId);
|
|
10363
|
+
snap = snapshotSqliteDb(resolvedDb);
|
|
10364
|
+
const metadata = readCopilotSessionStoreMetadata(
|
|
10365
|
+
snap.path,
|
|
10366
|
+
sqliteOptions,
|
|
10367
|
+
priorLastId,
|
|
10368
|
+
);
|
|
10369
|
+
if (!metadata.active) {
|
|
10370
|
+
dbStates[resolvedDb] = {
|
|
10371
|
+
...omitCopilotLegacyCatchupState(dbState),
|
|
10372
|
+
schemaVersion: metadata.schemaVersion,
|
|
10373
|
+
lastError: null,
|
|
10374
|
+
lastErrorAt: null,
|
|
10375
|
+
updatedAt,
|
|
10376
|
+
};
|
|
10377
|
+
continue;
|
|
10378
|
+
}
|
|
10379
|
+
|
|
10380
|
+
const inodeChanged =
|
|
10381
|
+
typeof dbState.dbIno === "number" &&
|
|
10382
|
+
typeof currentIno === "number" &&
|
|
10383
|
+
dbState.dbIno !== currentIno;
|
|
10384
|
+
const cursorRowChanged =
|
|
10385
|
+
priorLastId > 0 &&
|
|
10386
|
+
typeof dbState.lastRowKey === "string" &&
|
|
10387
|
+
dbState.lastRowKey !== metadata.cursorRowKey;
|
|
10388
|
+
const idRegressed = metadata.maxId < priorLastId;
|
|
10389
|
+
const resetDetected =
|
|
10390
|
+
Boolean(dbState.adoptedAt) && (idRegressed || cursorRowChanged);
|
|
10391
|
+
const needsFingerprintSeed =
|
|
10392
|
+
Boolean(dbState.adoptedAt) &&
|
|
10393
|
+
priorLastId > 0 &&
|
|
10394
|
+
Object.keys(seenEventCounts).length === 0;
|
|
10395
|
+
const resetWithoutFingerprintHistory =
|
|
10396
|
+
resetDetected && needsFingerprintSeed;
|
|
10397
|
+
const reconcileAllRows =
|
|
10398
|
+
(resetDetected || inodeChanged) && !resetWithoutFingerprintHistory;
|
|
10399
|
+
const needsAdoption =
|
|
10400
|
+
globalAdoptionPending ||
|
|
10401
|
+
!dbState.adoptedAt ||
|
|
10402
|
+
resetDetected;
|
|
10403
|
+
const baselineOnly =
|
|
10404
|
+
resetWithoutFingerprintHistory ||
|
|
10405
|
+
(needsAdoption && !resetDetected && !backfillOnFirstRun);
|
|
10406
|
+
const incrementalRows = readCopilotSessionStoreUsageRows(
|
|
10407
|
+
snap.path,
|
|
10408
|
+
reconcileAllRows || baselineOnly || needsFingerprintSeed ? 0 : priorLastId,
|
|
10409
|
+
sqliteOptions,
|
|
10410
|
+
);
|
|
10411
|
+
const pendingRetryRows = resetDetected || reconcileAllRows
|
|
10412
|
+
? []
|
|
10413
|
+
: readCopilotSessionStoreUsageRowsByIds(
|
|
10414
|
+
snap.path,
|
|
10415
|
+
Object.keys(pendingMalformedEvents),
|
|
10416
|
+
sqliteOptions,
|
|
10417
|
+
);
|
|
10418
|
+
const pendingRetryIds = new Set(
|
|
10419
|
+
pendingRetryRows.map((row) => toNonNegativeInt(row?.id)),
|
|
10420
|
+
);
|
|
10421
|
+
if (resetDetected || reconcileAllRows) {
|
|
10422
|
+
pendingMalformedEvents = {};
|
|
10423
|
+
} else {
|
|
10424
|
+
for (const pendingId of Object.keys(pendingMalformedEvents)) {
|
|
10425
|
+
if (!pendingRetryIds.has(toNonNegativeInt(pendingId))) {
|
|
10426
|
+
delete pendingMalformedEvents[pendingId];
|
|
10427
|
+
}
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
const rowsById = new Map();
|
|
10431
|
+
for (const row of [...pendingRetryRows, ...incrementalRows]) {
|
|
10432
|
+
rowsById.set(toNonNegativeInt(row?.id), row);
|
|
10433
|
+
}
|
|
10434
|
+
const rows = Array.from(rowsById.values()).sort(
|
|
10435
|
+
(a, b) => toNonNegativeInt(a?.id) - toNonNegativeInt(b?.id),
|
|
10436
|
+
);
|
|
10437
|
+
// Reindex/reset can rewrite every SQLite id. Subtract the exact metadata
|
|
10438
|
+
// multiset already observed so only genuinely new requests are emitted.
|
|
10439
|
+
const historicalFingerprintCounts = reconcileAllRows
|
|
10440
|
+
? new Map(
|
|
10441
|
+
Object.entries(seenEventCounts).map(([eventFingerprint, count]) => [
|
|
10442
|
+
eventFingerprint,
|
|
10443
|
+
toNonNegativeInt(count),
|
|
10444
|
+
]),
|
|
10445
|
+
)
|
|
10446
|
+
: null;
|
|
10447
|
+
if (needsAdoption) adoptedThisRun = true;
|
|
10448
|
+
if (expectedFingerprint) {
|
|
10449
|
+
const finalFingerprint = sqliteSidecarFingerprint(resolvedDb);
|
|
10450
|
+
if (!sameSqliteFingerprint(finalFingerprint, expectedFingerprint)) {
|
|
10451
|
+
dbStates[resolvedDb] = {
|
|
10452
|
+
...omitCopilotLegacyCatchupState(dbState),
|
|
10453
|
+
adoptionDeferredAt: updatedAt,
|
|
10454
|
+
updatedAt,
|
|
10455
|
+
};
|
|
10456
|
+
continue;
|
|
10457
|
+
}
|
|
10458
|
+
fingerprint = finalFingerprint;
|
|
10459
|
+
}
|
|
10460
|
+
let lastId =
|
|
10461
|
+
reconcileAllRows || baselineOnly ? metadata.maxId : priorLastId;
|
|
10462
|
+
for (const row of rows) {
|
|
10463
|
+
recordsProcessed++;
|
|
10464
|
+
const rowId = toNonNegativeInt(row?.id);
|
|
10465
|
+
lastId = Math.max(lastId, rowId);
|
|
10466
|
+
const sessionId =
|
|
10467
|
+
typeof row?.session_id === "string" ? row.session_id.trim() : "";
|
|
10468
|
+
if (!sessionId) {
|
|
10469
|
+
if (rowId > 0) pendingMalformedEvents[rowId] = "";
|
|
10470
|
+
continue;
|
|
10471
|
+
}
|
|
10472
|
+
const eventFingerprint = copilotStoreEventFingerprint(row);
|
|
10473
|
+
if (historicalFingerprintCounts && eventFingerprint) {
|
|
10474
|
+
const historicalCount = toNonNegativeInt(
|
|
10475
|
+
historicalFingerprintCounts.get(eventFingerprint),
|
|
10476
|
+
);
|
|
10477
|
+
if (historicalCount > 0) {
|
|
10478
|
+
historicalFingerprintCounts.set(
|
|
10479
|
+
eventFingerprint,
|
|
10480
|
+
historicalCount - 1,
|
|
10481
|
+
);
|
|
10482
|
+
if (!parseCopilotAppTimestamp(row?.created_at) && rowId > 0) {
|
|
10483
|
+
pendingMalformedEvents[rowId] = eventFingerprint;
|
|
10484
|
+
}
|
|
10485
|
+
seenSessions.add(sessionId);
|
|
10486
|
+
continue;
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
const seedOnly =
|
|
10490
|
+
needsFingerprintSeed && rowId <= priorLastId;
|
|
10491
|
+
const recentEvent = copilotStoreRecentEvent(row, claimNowMs);
|
|
10492
|
+
if (pendingRetryIds.has(rowId) && !recentEvent) {
|
|
10493
|
+
if (seedOnly) {
|
|
10494
|
+
incrementCopilotFingerprintCount(
|
|
10495
|
+
seenEventCounts,
|
|
10496
|
+
eventFingerprint,
|
|
10497
|
+
);
|
|
10498
|
+
}
|
|
10499
|
+
pendingMalformedEvents[rowId] = eventFingerprint || "";
|
|
10500
|
+
seenSessions.add(sessionId);
|
|
10501
|
+
continue;
|
|
10502
|
+
}
|
|
10503
|
+
if (rowId > 0) delete pendingMalformedEvents[rowId];
|
|
10504
|
+
const matchedOtelUsage =
|
|
10505
|
+
recentEvent && otelUsageMatcher.consume(recentEvent);
|
|
10506
|
+
if (matchedOtelUsage) recentEvent.consumed = true;
|
|
10507
|
+
if (baselineOnly || seedOnly) {
|
|
10508
|
+
incrementCopilotFingerprintCount(seenEventCounts, eventFingerprint);
|
|
10509
|
+
if (!recentEvent && rowId > 0) {
|
|
10510
|
+
pendingMalformedEvents[rowId] = eventFingerprint || "";
|
|
10511
|
+
}
|
|
10512
|
+
seenSessions.add(sessionId);
|
|
10513
|
+
continue;
|
|
10514
|
+
}
|
|
10515
|
+
incrementCopilotFingerprintCount(seenEventCounts, eventFingerprint);
|
|
10516
|
+
if (needsAdoption && backfillOnFirstRun && excludedFirstRunSessions.has(sessionId)) {
|
|
10517
|
+
if (
|
|
10518
|
+
!matchedOtelUsage &&
|
|
10519
|
+
Object.prototype.hasOwnProperty.call(
|
|
10520
|
+
excludedFirstRunTotals,
|
|
10521
|
+
sessionId,
|
|
10522
|
+
)
|
|
10523
|
+
) {
|
|
10524
|
+
const normalized = normalizeCopilotSessionStoreUsage(row);
|
|
10525
|
+
const priorResidual = legacyResiduals.get(sessionId) || {
|
|
10526
|
+
storeTokens: 0,
|
|
10527
|
+
lastEventAt: null,
|
|
10528
|
+
};
|
|
10529
|
+
priorResidual.storeTokens += normalized.total_tokens;
|
|
10530
|
+
priorResidual.lastEventAt = latestCopilotTimestamp(
|
|
10531
|
+
priorResidual.lastEventAt,
|
|
10532
|
+
row?.created_at,
|
|
10533
|
+
);
|
|
10534
|
+
legacyResiduals.set(sessionId, priorResidual);
|
|
10535
|
+
}
|
|
10536
|
+
if (recentEvent && !matchedOtelUsage) recentEvents.push(recentEvent);
|
|
10537
|
+
seenSessions.add(sessionId);
|
|
10538
|
+
continue;
|
|
10539
|
+
}
|
|
10540
|
+
if (matchedOtelUsage) {
|
|
10541
|
+
seenSessions.add(sessionId);
|
|
10542
|
+
continue;
|
|
10543
|
+
}
|
|
10544
|
+
if (recentEvent) recentEvents.push(recentEvent);
|
|
10545
|
+
const normalized = normalizeCopilotSessionStoreUsage(row);
|
|
10546
|
+
if (normalized.total_tokens <= 0) {
|
|
10547
|
+
seenSessions.add(sessionId);
|
|
10548
|
+
continue;
|
|
10549
|
+
}
|
|
10550
|
+
const tsIso = parseCopilotAppTimestamp(row.created_at);
|
|
10551
|
+
const bucketStart = tsIso ? toUtcHalfHourStart(tsIso) : null;
|
|
10552
|
+
if (!bucketStart) {
|
|
10553
|
+
if (rowId > 0) {
|
|
10554
|
+
pendingMalformedEvents[rowId] = eventFingerprint || "";
|
|
10555
|
+
}
|
|
10556
|
+
seenSessions.add(sessionId);
|
|
10557
|
+
continue;
|
|
10558
|
+
}
|
|
10559
|
+
const model =
|
|
10560
|
+
normalizeCopilotAppModel(row.model) || COPILOT_APP_DEFAULT_MODEL;
|
|
10561
|
+
const delta = {
|
|
10562
|
+
input_tokens: normalized.input_tokens,
|
|
10563
|
+
cached_input_tokens: normalized.cached_input_tokens,
|
|
10564
|
+
cache_creation_input_tokens: normalized.cache_creation_input_tokens,
|
|
10565
|
+
output_tokens: normalized.output_tokens,
|
|
10566
|
+
reasoning_output_tokens: normalized.reasoning_output_tokens,
|
|
10567
|
+
total_tokens: normalized.total_tokens,
|
|
10568
|
+
conversation_count: seenSessions.has(sessionId) ? 0 : 1,
|
|
10569
|
+
};
|
|
10570
|
+
const bucket = getHourlyBucket(hourlyState, "copilot", model, bucketStart);
|
|
10571
|
+
addTotals(bucket.totals, delta);
|
|
10572
|
+
touchedBuckets.add(bucketKey("copilot", model, bucketStart));
|
|
10573
|
+
seenSessions.add(sessionId);
|
|
10574
|
+
eventsAggregated++;
|
|
10575
|
+
if (cb) {
|
|
10576
|
+
cb({
|
|
10577
|
+
index: recordsProcessed,
|
|
10578
|
+
total: rows.length,
|
|
10579
|
+
recordsProcessed,
|
|
10580
|
+
eventsAggregated,
|
|
10581
|
+
bucketsQueued: touchedBuckets.size,
|
|
10582
|
+
});
|
|
10583
|
+
}
|
|
10584
|
+
}
|
|
10585
|
+
|
|
10586
|
+
dbStates[resolvedDb] = {
|
|
10587
|
+
...omitCopilotLegacyCatchupState(dbState),
|
|
10588
|
+
adoptedAt: dbState.adoptedAt || updatedAt,
|
|
10589
|
+
resetAt: resetDetected ? updatedAt : dbState.resetAt || null,
|
|
10590
|
+
schemaVersion: metadata.schemaVersion,
|
|
10591
|
+
lastRowKey: metadata.lastRowKey,
|
|
10592
|
+
lastId: Math.max(lastId, metadata.maxId),
|
|
10593
|
+
lastEventAt: latestCopilotTimestamp(
|
|
10594
|
+
dbState.lastEventAt,
|
|
10595
|
+
metadata.lastEventAt,
|
|
10596
|
+
),
|
|
10597
|
+
seenEventCounts,
|
|
10598
|
+
pendingMalformedEvents,
|
|
10599
|
+
malformedEventCount: Object.keys(pendingMalformedEvents).length,
|
|
10600
|
+
resetGapEventCount:
|
|
10601
|
+
toNonNegativeInt(dbState.resetGapEventCount) +
|
|
10602
|
+
(resetWithoutFingerprintHistory ? rows.length : 0),
|
|
10603
|
+
dbIno: typeof currentIno === "number" ? currentIno : null,
|
|
10604
|
+
lastDbFingerprint: fingerprint,
|
|
10605
|
+
lastError: null,
|
|
10606
|
+
lastErrorAt: null,
|
|
10607
|
+
updatedAt,
|
|
10608
|
+
};
|
|
10609
|
+
canonicalDbPaths.push(resolvedDb);
|
|
10610
|
+
activeDbCount++;
|
|
10611
|
+
} catch (err) {
|
|
10612
|
+
if (err?.code !== "ENOENT") dbErrors++;
|
|
10613
|
+
dbStates[resolvedDb] = {
|
|
10614
|
+
...omitCopilotLegacyCatchupState(dbState),
|
|
10615
|
+
lastError: err && err.message ? err.message : String(err),
|
|
10616
|
+
lastErrorAt: updatedAt,
|
|
10617
|
+
updatedAt,
|
|
10618
|
+
};
|
|
10619
|
+
} finally {
|
|
10620
|
+
if (snap) snap.cleanup();
|
|
10621
|
+
}
|
|
10622
|
+
}
|
|
10623
|
+
|
|
10624
|
+
for (const [sessionId, residual] of legacyResiduals) {
|
|
10625
|
+
const baseline = excludedFirstRunTotals[sessionId] || {};
|
|
10626
|
+
const appTotals = normalizeCopilotAppTokenDelta(baseline, {
|
|
10627
|
+
input: 0,
|
|
10628
|
+
output: 0,
|
|
10629
|
+
cached: 0,
|
|
10630
|
+
reasoning: 0,
|
|
10631
|
+
});
|
|
10632
|
+
const residualTokens = Math.max(
|
|
10633
|
+
0,
|
|
10634
|
+
toNonNegativeInt(residual.storeTokens) - appTotals.total_tokens,
|
|
10635
|
+
);
|
|
10636
|
+
if (residualTokens <= 0) continue;
|
|
10637
|
+
const timestamp = latestCopilotTimestamp(
|
|
10638
|
+
residual.lastEventAt,
|
|
10639
|
+
baseline.updatedAt,
|
|
10640
|
+
);
|
|
10641
|
+
const bucketStart = timestamp ? toUtcHalfHourStart(timestamp) : null;
|
|
10642
|
+
if (!bucketStart) continue;
|
|
10643
|
+
const bucket = getHourlyBucket(
|
|
10644
|
+
hourlyState,
|
|
10645
|
+
"copilot",
|
|
10646
|
+
COPILOT_STORE_LEGACY_MODEL,
|
|
10647
|
+
bucketStart,
|
|
10648
|
+
);
|
|
10649
|
+
addTotals(bucket.totals, {
|
|
10650
|
+
input_tokens: residualTokens,
|
|
10651
|
+
cached_input_tokens: 0,
|
|
10652
|
+
cache_creation_input_tokens: 0,
|
|
10653
|
+
output_tokens: 0,
|
|
10654
|
+
reasoning_output_tokens: 0,
|
|
10655
|
+
total_tokens: residualTokens,
|
|
10656
|
+
conversation_count: 0,
|
|
10657
|
+
});
|
|
10658
|
+
touchedBuckets.add(
|
|
10659
|
+
bucketKey("copilot", COPILOT_STORE_LEGACY_MODEL, bucketStart),
|
|
10660
|
+
);
|
|
10661
|
+
eventsAggregated++;
|
|
10662
|
+
}
|
|
10663
|
+
|
|
10664
|
+
const bucketsQueued = await enqueueTouchedBuckets({
|
|
10665
|
+
queuePath,
|
|
10666
|
+
hourlyState,
|
|
10667
|
+
touchedBuckets,
|
|
10668
|
+
});
|
|
10669
|
+
const allCurrentPathsHealthy =
|
|
10670
|
+
uniquePaths.length > 0 &&
|
|
10671
|
+
activeDbCount === uniquePaths.length &&
|
|
10672
|
+
dbErrors === 0;
|
|
10673
|
+
const canonicalActive = storeState.active === true || allCurrentPathsHealthy;
|
|
10674
|
+
hourlyState.updatedAt = updatedAt;
|
|
10675
|
+
cursors.hourly = hourlyState;
|
|
10676
|
+
cursors.copilotStore = {
|
|
10677
|
+
...omitCopilotLegacyCatchupState(storeState),
|
|
10678
|
+
version: COPILOT_STORE_CURSOR_VERSION,
|
|
10679
|
+
active: canonicalActive,
|
|
10680
|
+
dbs: dbStates,
|
|
10681
|
+
seenSessions: Array.from(seenSessions),
|
|
10682
|
+
recentEvents: pruneCopilotUsageClaims(recentEvents),
|
|
10683
|
+
updatedAt,
|
|
10684
|
+
};
|
|
10685
|
+
return {
|
|
10686
|
+
active: canonicalActive,
|
|
10687
|
+
healthy: allCurrentPathsHealthy,
|
|
10688
|
+
adoptedThisRun,
|
|
10689
|
+
canonicalDbPaths,
|
|
10690
|
+
recordsProcessed,
|
|
10691
|
+
eventsAggregated,
|
|
10692
|
+
bucketsQueued,
|
|
10693
|
+
dbErrors,
|
|
10694
|
+
usageClaims: recentEvents,
|
|
10695
|
+
};
|
|
9559
10696
|
}
|
|
9560
10697
|
|
|
9561
10698
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -9567,9 +10704,9 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9567
10704
|
// there before the child has produced any tokens, so parsing event history would
|
|
9568
10705
|
// double-count parent usage. The only accounting source here is positive deltas
|
|
9569
10706
|
// from the `sessions.total_*` counters. Those deltas feed the same aggregate
|
|
9570
|
-
// `source="copilot"` pipeline
|
|
9571
|
-
//
|
|
9572
|
-
//
|
|
10707
|
+
// `source="copilot"` pipeline during legacy adoption. Once session-store is
|
|
10708
|
+
// canonical, this parser runs observe-only so a temporary store outage cannot
|
|
10709
|
+
// create duplicate fallback writes when the store returns.
|
|
9573
10710
|
// Project attribution and per-conversation detail are intentionally absent:
|
|
9574
10711
|
// data.db's summary columns do not expose privacy-safe project/message metadata.
|
|
9575
10712
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -9577,44 +10714,169 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9577
10714
|
const COPILOT_APP_CURSOR_VERSION = 1;
|
|
9578
10715
|
const COPILOT_APP_DEFAULT_MODEL = "github-copilot";
|
|
9579
10716
|
|
|
9580
|
-
function
|
|
10717
|
+
function normalizeCopilotDbPath(dbPath, env = process.env) {
|
|
9581
10718
|
if (typeof dbPath !== "string" || !dbPath.trim()) return null;
|
|
9582
|
-
const
|
|
9583
|
-
|
|
10719
|
+
const raw = dbPath.trim();
|
|
10720
|
+
const home =
|
|
10721
|
+
process.platform === "win32"
|
|
10722
|
+
? env.USERPROFILE || env.HOME || require("node:os").homedir()
|
|
10723
|
+
: env.HOME || require("node:os").homedir();
|
|
10724
|
+
const expanded =
|
|
10725
|
+
raw === "~"
|
|
10726
|
+
? home
|
|
10727
|
+
: raw.startsWith("~/") || raw.startsWith("~\\")
|
|
10728
|
+
? path.join(home, raw.slice(2))
|
|
10729
|
+
: raw;
|
|
10730
|
+
const normalized =
|
|
10731
|
+
path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(expanded);
|
|
10732
|
+
return normalized;
|
|
9584
10733
|
}
|
|
9585
10734
|
|
|
9586
|
-
function
|
|
9587
|
-
const
|
|
9588
|
-
const
|
|
9589
|
-
const
|
|
9590
|
-
|
|
9591
|
-
|
|
10735
|
+
function mergeCopilotStoreAliasStates(primary = {}, alias = {}) {
|
|
10736
|
+
const primaryLastId = toNonNegativeInt(primary?.lastId);
|
|
10737
|
+
const aliasLastId = toNonNegativeInt(alias?.lastId);
|
|
10738
|
+
const primaryUpdatedAt = Date.parse(primary?.updatedAt || "") || 0;
|
|
10739
|
+
const aliasUpdatedAt = Date.parse(alias?.updatedAt || "") || 0;
|
|
10740
|
+
const newer =
|
|
10741
|
+
aliasLastId > primaryLastId ||
|
|
10742
|
+
(aliasLastId === primaryLastId && aliasUpdatedAt > primaryUpdatedAt)
|
|
10743
|
+
? alias
|
|
10744
|
+
: primary;
|
|
10745
|
+
const seenEventCounts = normalizeCopilotFingerprintCounts(
|
|
10746
|
+
primary?.seenEventCounts,
|
|
10747
|
+
);
|
|
10748
|
+
for (const [fingerprint, count] of Object.entries(
|
|
10749
|
+
normalizeCopilotFingerprintCounts(alias?.seenEventCounts),
|
|
10750
|
+
)) {
|
|
10751
|
+
seenEventCounts[fingerprint] = Math.max(
|
|
10752
|
+
toNonNegativeInt(seenEventCounts[fingerprint]),
|
|
10753
|
+
count,
|
|
10754
|
+
);
|
|
10755
|
+
}
|
|
10756
|
+
const pendingMalformedEvents = {
|
|
10757
|
+
...normalizeCopilotPendingMalformedEvents(
|
|
10758
|
+
primary?.pendingMalformedEvents,
|
|
10759
|
+
),
|
|
10760
|
+
...normalizeCopilotPendingMalformedEvents(
|
|
10761
|
+
alias?.pendingMalformedEvents,
|
|
10762
|
+
),
|
|
9592
10763
|
};
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
10764
|
+
return {
|
|
10765
|
+
...primary,
|
|
10766
|
+
...newer,
|
|
10767
|
+
seenEventCounts,
|
|
10768
|
+
pendingMalformedEvents,
|
|
10769
|
+
malformedEventCount: Object.keys(pendingMalformedEvents).length,
|
|
10770
|
+
resetGapEventCount: Math.max(
|
|
10771
|
+
toNonNegativeInt(primary?.resetGapEventCount),
|
|
10772
|
+
toNonNegativeInt(alias?.resetGapEventCount),
|
|
10773
|
+
),
|
|
9596
10774
|
};
|
|
10775
|
+
}
|
|
9597
10776
|
|
|
9598
|
-
|
|
9599
|
-
|
|
10777
|
+
function coalesceCopilotDbStatesByIdentity(
|
|
10778
|
+
dbStates,
|
|
10779
|
+
selectedPaths,
|
|
10780
|
+
mergeStates,
|
|
10781
|
+
) {
|
|
10782
|
+
if (!dbStates || typeof dbStates !== "object") return new Set();
|
|
10783
|
+
const selectedByIdentity = new Map();
|
|
10784
|
+
const selectedByInode = new Map();
|
|
10785
|
+
for (const selectedPath of selectedPaths) {
|
|
10786
|
+
try {
|
|
10787
|
+
const stat = fssync.statSync(selectedPath);
|
|
10788
|
+
selectedByIdentity.set(`${stat.dev}:${stat.ino}`, selectedPath);
|
|
10789
|
+
if (selectedByInode.has(stat.ino)) selectedByInode.set(stat.ino, null);
|
|
10790
|
+
else selectedByInode.set(stat.ino, selectedPath);
|
|
10791
|
+
} catch (_e) {}
|
|
9600
10792
|
}
|
|
9601
|
-
|
|
9602
|
-
|
|
10793
|
+
const migratedPaths = new Set();
|
|
10794
|
+
for (const [statePath, state] of Object.entries({ ...dbStates })) {
|
|
10795
|
+
let dev = null;
|
|
10796
|
+
let ino = null;
|
|
10797
|
+
try {
|
|
10798
|
+
const stat = fssync.statSync(statePath);
|
|
10799
|
+
dev = stat.dev;
|
|
10800
|
+
ino = stat.ino;
|
|
10801
|
+
} catch (_e) {
|
|
10802
|
+
dev = state?.lastDbFingerprint?.db?.dev;
|
|
10803
|
+
ino = state?.lastDbFingerprint?.db?.ino;
|
|
10804
|
+
}
|
|
10805
|
+
const selectedPath =
|
|
10806
|
+
Number.isFinite(dev) && Number.isFinite(ino)
|
|
10807
|
+
? selectedByIdentity.get(`${dev}:${ino}`)
|
|
10808
|
+
: Number.isFinite(ino)
|
|
10809
|
+
? selectedByInode.get(ino)
|
|
10810
|
+
: null;
|
|
10811
|
+
if (!selectedPath || selectedPath === statePath) continue;
|
|
10812
|
+
dbStates[selectedPath] = mergeStates(
|
|
10813
|
+
dbStates[selectedPath],
|
|
10814
|
+
state,
|
|
10815
|
+
);
|
|
10816
|
+
delete dbStates[statePath];
|
|
10817
|
+
migratedPaths.add(statePath);
|
|
9603
10818
|
}
|
|
10819
|
+
return migratedPaths;
|
|
10820
|
+
}
|
|
9604
10821
|
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
10822
|
+
function uniqueCopilotDbPaths(
|
|
10823
|
+
paths,
|
|
10824
|
+
env = process.env,
|
|
10825
|
+
preferredPaths = [],
|
|
10826
|
+
) {
|
|
10827
|
+
const pathKey = (dbPath) =>
|
|
10828
|
+
process.platform === "win32" ? dbPath.toLowerCase() : dbPath;
|
|
10829
|
+
const preferredKeys = new Set(
|
|
10830
|
+
(Array.isArray(preferredPaths) ? preferredPaths : [])
|
|
10831
|
+
.map((candidate) => normalizeCopilotDbPath(candidate, env))
|
|
10832
|
+
.filter(Boolean)
|
|
10833
|
+
.map(pathKey),
|
|
10834
|
+
);
|
|
10835
|
+
const candidates = (Array.isArray(paths) ? paths : [])
|
|
10836
|
+
.map((candidate, index) => ({
|
|
10837
|
+
index,
|
|
10838
|
+
path: normalizeCopilotDbPath(candidate, env),
|
|
10839
|
+
}))
|
|
10840
|
+
.filter((candidate) => candidate.path)
|
|
10841
|
+
.sort((a, b) => {
|
|
10842
|
+
const preferredDiff =
|
|
10843
|
+
Number(preferredKeys.has(pathKey(b.path))) -
|
|
10844
|
+
Number(preferredKeys.has(pathKey(a.path)));
|
|
10845
|
+
return preferredDiff || a.index - b.index;
|
|
10846
|
+
});
|
|
10847
|
+
const result = [];
|
|
10848
|
+
const identities = new Set();
|
|
10849
|
+
for (const candidate of candidates) {
|
|
10850
|
+
const normalized = candidate.path;
|
|
10851
|
+
let identity =
|
|
10852
|
+
process.platform === "win32"
|
|
10853
|
+
? `path:${normalized.toLowerCase()}`
|
|
10854
|
+
: `path:${normalized}`;
|
|
10855
|
+
try {
|
|
10856
|
+
const stat = fssync.statSync(normalized);
|
|
10857
|
+
if (
|
|
10858
|
+
Number.isFinite(stat.dev) &&
|
|
10859
|
+
Number.isFinite(stat.ino) &&
|
|
10860
|
+
stat.ino > 0
|
|
10861
|
+
) {
|
|
10862
|
+
identity = `inode:${stat.dev}:${stat.ino}`;
|
|
10863
|
+
}
|
|
10864
|
+
} catch (_e) {}
|
|
10865
|
+
if (identities.has(identity)) continue;
|
|
10866
|
+
identities.add(identity);
|
|
10867
|
+
result.push(normalized);
|
|
9615
10868
|
}
|
|
10869
|
+
return result;
|
|
10870
|
+
}
|
|
9616
10871
|
|
|
9617
|
-
|
|
10872
|
+
function resolveCopilotAppDbPaths(env = process.env) {
|
|
10873
|
+
return resolveCopilotDbPaths(
|
|
10874
|
+
{
|
|
10875
|
+
fileName: "data.db",
|
|
10876
|
+
overrideEnvKey: "TOKENTRACKER_COPILOT_APP_DB",
|
|
10877
|
+
},
|
|
10878
|
+
env,
|
|
10879
|
+
);
|
|
9618
10880
|
}
|
|
9619
10881
|
|
|
9620
10882
|
function resolveCopilotAppDbPath(env = process.env) {
|
|
@@ -9723,6 +10985,7 @@ function sqliteSidecarFingerprint(dbPath) {
|
|
|
9723
10985
|
out[suffix || "db"] = {
|
|
9724
10986
|
size: stat.size,
|
|
9725
10987
|
mtimeMs: stat.mtimeMs,
|
|
10988
|
+
dev: stat.dev,
|
|
9726
10989
|
ino: stat.ino,
|
|
9727
10990
|
};
|
|
9728
10991
|
} catch (e) {
|
|
@@ -9736,6 +10999,10 @@ function sameSqliteFingerprint(a, b) {
|
|
|
9736
10999
|
return JSON.stringify(a || {}) === JSON.stringify(b || {});
|
|
9737
11000
|
}
|
|
9738
11001
|
|
|
11002
|
+
function getCopilotSqliteFingerprint(dbPath) {
|
|
11003
|
+
return sqliteSidecarFingerprint(dbPath);
|
|
11004
|
+
}
|
|
11005
|
+
|
|
9739
11006
|
function copilotAppBaselineTotal(value) {
|
|
9740
11007
|
if (!value || typeof value !== "object") return 0;
|
|
9741
11008
|
return (
|
|
@@ -9801,6 +11068,7 @@ async function parseCopilotAppDbIncremental({
|
|
|
9801
11068
|
onProgress,
|
|
9802
11069
|
env,
|
|
9803
11070
|
sqliteOptions,
|
|
11071
|
+
observeOnly = false,
|
|
9804
11072
|
} = {}) {
|
|
9805
11073
|
await ensureDir(path.dirname(queuePath));
|
|
9806
11074
|
const paths = Array.isArray(dbPaths) && dbPaths.length > 0
|
|
@@ -9808,11 +11076,15 @@ async function parseCopilotAppDbIncremental({
|
|
|
9808
11076
|
: dbPath
|
|
9809
11077
|
? [dbPath]
|
|
9810
11078
|
: resolveCopilotAppDbPaths(env || process.env);
|
|
9811
|
-
const uniquePaths = [...new Set(paths.map((p) => normalizeCopilotAppDbPath(p, env || process.env)).filter(Boolean))];
|
|
9812
11079
|
const appState =
|
|
9813
11080
|
cursors.copilotApp && typeof cursors.copilotApp === "object" ? cursors.copilotApp : {};
|
|
9814
11081
|
const dbStates =
|
|
9815
11082
|
appState.dbs && typeof appState.dbs === "object" ? { ...appState.dbs } : {};
|
|
11083
|
+
const uniquePaths = uniqueCopilotDbPaths(
|
|
11084
|
+
paths,
|
|
11085
|
+
env || process.env,
|
|
11086
|
+
Object.keys(dbStates),
|
|
11087
|
+
);
|
|
9816
11088
|
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
9817
11089
|
const touchedBuckets = new Set();
|
|
9818
11090
|
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
@@ -9895,6 +11167,10 @@ async function parseCopilotAppDbIncremental({
|
|
|
9895
11167
|
sessionTotals[sessionId] = { ...curr, model, updatedAt: rowUpdatedAt };
|
|
9896
11168
|
continue;
|
|
9897
11169
|
}
|
|
11170
|
+
if (observeOnly) {
|
|
11171
|
+
sessionTotals[sessionId] = { ...curr, model, updatedAt: rowUpdatedAt };
|
|
11172
|
+
continue;
|
|
11173
|
+
}
|
|
9898
11174
|
|
|
9899
11175
|
const bucketStart = toUtcHalfHourStart(rowUpdatedAt);
|
|
9900
11176
|
if (!bucketStart) {
|
|
@@ -10859,8 +12135,16 @@ module.exports = {
|
|
|
10859
12135
|
probeWslDistros,
|
|
10860
12136
|
discoverWslHermesHome,
|
|
10861
12137
|
resolveCopilotOtelPaths,
|
|
12138
|
+
normalizeCopilotDbPath,
|
|
12139
|
+
uniqueCopilotDbPaths,
|
|
12140
|
+
coalesceCopilotDbStatesByIdentity,
|
|
12141
|
+
resolveCopilotSessionStorePaths,
|
|
12142
|
+
getCopilotSqliteFingerprint,
|
|
12143
|
+
describeCopilotSessionStoreDb,
|
|
10862
12144
|
resolveCopilotAppDbPath,
|
|
10863
12145
|
resolveCopilotAppDbPaths,
|
|
12146
|
+
readCopilotSessionStoreUsageRows,
|
|
12147
|
+
normalizeCopilotSessionStoreUsage,
|
|
10864
12148
|
readCopilotAppSessionsFromSqlite,
|
|
10865
12149
|
parseRolloutIncremental,
|
|
10866
12150
|
parseClaudeIncremental,
|
|
@@ -10874,8 +12158,12 @@ module.exports = {
|
|
|
10874
12158
|
gooseInstallOwnsCursor,
|
|
10875
12159
|
zedInstallOwnsCursor,
|
|
10876
12160
|
hermesInstallOwnsCursor,
|
|
12161
|
+
copilotOtelCursorHasLegacyCliUsage,
|
|
12162
|
+
pruneCopilotUsageClaims,
|
|
10877
12163
|
parseCopilotIncremental,
|
|
12164
|
+
parseCopilotSessionStoreIncremental,
|
|
10878
12165
|
parseCopilotAppDbIncremental,
|
|
12166
|
+
resolveKimiHome,
|
|
10879
12167
|
resolveKimiWireFiles,
|
|
10880
12168
|
resolveKimiDefaultModel,
|
|
10881
12169
|
parseKimiIncremental,
|
|
@@ -10957,6 +12245,7 @@ module.exports = {
|
|
|
10957
12245
|
// Exposed for regression tests covering nested-group remote URLs.
|
|
10958
12246
|
canonicalizeProjectRef,
|
|
10959
12247
|
deriveProjectKeyFromRef,
|
|
12248
|
+
CLAUDE_MEM_OBSERVER_PROJECT_REF,
|
|
10960
12249
|
|
|
10961
12250
|
// Grok Build (xAI) — SessionEnd hook + passive updates.jsonl/signals.json reader
|
|
10962
12251
|
resolveGrokBuildHome,
|