tokentracker-cli 0.65.0 → 0.65.2
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 +476 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dashboard/dist/assets/{ActivityHeatmap-Ccf0I3uP.js → ActivityHeatmap-CggJI8SR.js} +1 -1
- package/dashboard/dist/assets/{Card-BSF8dYEt.js → Card-5au9t9nC.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-Bjp6eg2f.js → ConfirmModal-DdczuiTm.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BInQkoDe.js → DashboardPage-VAYxz_kf.js} +2 -2
- package/dashboard/dist/assets/{DevicePage-B0yxbDDR.js → DevicePage-DQJlqpU3.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-uw-Oa5Qv.js → DialogTitle-DR64sBQk.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DPIBqXRl.js → FadeIn-CGzcyAaJ.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DDXfJ1uC.js → HeaderGithubStar-DiJi3EYZ.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-KC93tPlt.js → IpCheckPage-CoGzED0h.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Itx2uPWI.js → LandingPage-D_h4MI77.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-Bv44b_Kl.js → LeaderboardAvatar-BuSHrfaq.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-DDq5gzyz.js → LeaderboardPage-aP0oQNBq.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-LQyYrCf1.js → LeaderboardProfileModal-zWSPrjXm.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-3you3mbA.js → LeaderboardProfilePage-BNR__NEp.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-CrLvR8vE.js → LimitsPage-CbwEV4tL.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-Di1gnfIi.js → LocalOnlyNotice-DFxhTdeP.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-f9QHz_uZ.js → LoginPage-6m5w2iay.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-7rjAxAqk.js → PopoverPopup-6bH8WcTu.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DKA7HGpJ.js → ResetPasswordPage-ByOumS6-.js} +1 -1
- package/dashboard/dist/assets/{Select-2gikPo1a.js → Select-DrNJJE2o.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DZx33XF1.js → SelectItemText-BTHh_2vw.js} +1 -1
- package/dashboard/dist/assets/SettingsPage-BIxrrxUF.js +1 -0
- package/dashboard/dist/assets/{SkillsPage-B6BJ9UZp.js → SkillsPage-Cg8iXYaS.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-D8p3Tx7E.js → WidgetsPage-Bi8d2YwV.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-cXEfhti9.js → WrappedPage-Cc2kLDpE.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-D3iFlaWg.js → agent-logos-CzNQ3IaQ.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-D87Aw_ZN.js → arrow-up-right-FgMhJ3Pu.js} +1 -1
- package/dashboard/dist/assets/{download-BtcEs1OB.js → download-Bo9t1yfc.js} +1 -1
- package/dashboard/dist/assets/{main-DqnXui98.js → main-6K89gDOX.js} +21 -20
- package/dashboard/dist/assets/{main-CGx8BJ4O.css → main-BHwig9Um.css} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-DAxlRSu2.js → use-limits-display-prefs-DN1nv1HE.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-CgvB_B6W.js → use-native-settings-6ygLVyJu.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DOfX6aSy.js → use-ordered-list-li19cWJh.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-kEbwQwk2.js → use-quality-per-dollar-pref-rNMT2kff.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-la-KPfJj.js → use-usage-limits-kVHyIhp3.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-LDRg7ST0.js → useCurrency-g0G5pwGx.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-lP1Is9IY.js → useScrollLock-Dg7JyjFK.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/init.js +3 -2
- package/src/commands/serve.js +59 -1
- package/src/commands/status.js +26 -17
- package/src/commands/sync.js +672 -98
- package/src/lib/openclaw-session-plugin.js +2 -2
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +602 -178
- package/src/lib/wsl-probe.js +181 -0
- package/dashboard/dist/assets/SettingsPage-CwHPmuZV.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -6,6 +6,7 @@ const readline = require("node:readline");
|
|
|
6
6
|
const crypto = require("node:crypto");
|
|
7
7
|
const { ensureDir } = require("./fs");
|
|
8
8
|
const { readSqliteJsonRows } = require("./sqlite-reader");
|
|
9
|
+
const wsl = require("./wsl-probe");
|
|
9
10
|
|
|
10
11
|
const DEFAULT_SOURCE = "codex";
|
|
11
12
|
const DEFAULT_MODEL = "unknown";
|
|
@@ -13,9 +14,25 @@ const BUCKET_SEPARATOR = "|";
|
|
|
13
14
|
const CLAUDE_MEM_OBSERVER_PATH_SEGMENT = "--claude-mem-observer-sessions";
|
|
14
15
|
const CLAUDE_MEM_OBSERVER_PROJECT_REF =
|
|
15
16
|
"https://local.tokentracker/claude-mem/observer-sessions";
|
|
17
|
+
const PROJECT_ABSENT_CONTEXT_RESCAN_MS = 24 * 60 * 60 * 1000;
|
|
18
|
+
const DEFAULT_CODEX_COLD_SKIP_RECENT_DAYS = 2;
|
|
16
19
|
|
|
17
|
-
async function listRolloutFiles(sessionsDir) {
|
|
20
|
+
async function listRolloutFiles(sessionsDir, options = {}) {
|
|
18
21
|
const out = [];
|
|
22
|
+
const dayInventoryCache =
|
|
23
|
+
options?.dayInventoryCache && typeof options.dayInventoryCache === "object"
|
|
24
|
+
? options.dayInventoryCache
|
|
25
|
+
: null;
|
|
26
|
+
const stats = options?.stats && typeof options.stats === "object" ? options.stats : null;
|
|
27
|
+
if (dayInventoryCache) {
|
|
28
|
+
if (dayInventoryCache.version !== 1) {
|
|
29
|
+
dayInventoryCache.days = {};
|
|
30
|
+
}
|
|
31
|
+
dayInventoryCache.version = 1;
|
|
32
|
+
if (!dayInventoryCache.days || typeof dayInventoryCache.days !== "object") {
|
|
33
|
+
dayInventoryCache.days = {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
19
36
|
const years = await safeReadDir(sessionsDir);
|
|
20
37
|
for (const y of years) {
|
|
21
38
|
if (!/^[0-9]{4}$/.test(y.name) || !y.isDirectory()) continue;
|
|
@@ -28,12 +45,8 @@ async function listRolloutFiles(sessionsDir) {
|
|
|
28
45
|
for (const d of days) {
|
|
29
46
|
if (!/^[0-9]{2}$/.test(d.name) || !d.isDirectory()) continue;
|
|
30
47
|
const dayDir = path.join(monthDir, d.name);
|
|
31
|
-
const files = await
|
|
32
|
-
|
|
33
|
-
if (!f.isFile()) continue;
|
|
34
|
-
if (!f.name.startsWith("rollout-") || !f.name.endsWith(".jsonl")) continue;
|
|
35
|
-
out.push(path.join(dayDir, f.name));
|
|
36
|
-
}
|
|
48
|
+
const files = await listRolloutDayFiles(dayDir, { dayInventoryCache, stats });
|
|
49
|
+
out.push(...files);
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
}
|
|
@@ -42,6 +55,58 @@ async function listRolloutFiles(sessionsDir) {
|
|
|
42
55
|
return out;
|
|
43
56
|
}
|
|
44
57
|
|
|
58
|
+
async function listRolloutDayFiles(dayDir, { dayInventoryCache, stats } = {}) {
|
|
59
|
+
const cacheDays = dayInventoryCache?.days;
|
|
60
|
+
const dayStat = dayInventoryCache ? await fs.stat(dayDir).catch(() => null) : null;
|
|
61
|
+
const statKey = dayStat && dayStat.isDirectory() ? directoryInventoryStatKey(dayStat) : null;
|
|
62
|
+
const cached = cacheDays && cacheDays[dayDir];
|
|
63
|
+
if (
|
|
64
|
+
statKey &&
|
|
65
|
+
cached &&
|
|
66
|
+
typeof cached === "object" &&
|
|
67
|
+
cached.statKey === statKey &&
|
|
68
|
+
Array.isArray(cached.files) &&
|
|
69
|
+
cached.files.every(isRolloutFileName)
|
|
70
|
+
) {
|
|
71
|
+
if (stats) stats.dayInventoryCacheHits = Number(stats.dayInventoryCacheHits || 0) + 1;
|
|
72
|
+
return cached.files.map((name) => path.join(dayDir, name));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (stats && cached) stats.dayInventoryCacheMisses = Number(stats.dayInventoryCacheMisses || 0) + 1;
|
|
76
|
+
const entries = await safeReadDir(dayDir);
|
|
77
|
+
const files = [];
|
|
78
|
+
for (const f of entries) {
|
|
79
|
+
if (!f.isFile()) continue;
|
|
80
|
+
if (!isRolloutFileName(f.name)) continue;
|
|
81
|
+
files.push(f.name);
|
|
82
|
+
}
|
|
83
|
+
files.sort((a, b) => a.localeCompare(b));
|
|
84
|
+
if (cacheDays && statKey) {
|
|
85
|
+
cacheDays[dayDir] = {
|
|
86
|
+
statKey,
|
|
87
|
+
files,
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return files.map((name) => path.join(dayDir, name));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isRolloutFileName(name) {
|
|
95
|
+
if (typeof name !== "string") return false;
|
|
96
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
97
|
+
if (name !== path.basename(name)) return false;
|
|
98
|
+
return name.startsWith("rollout-") && name.endsWith(".jsonl");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function directoryInventoryStatKey(st) {
|
|
102
|
+
return [
|
|
103
|
+
Number(st.ino || 0),
|
|
104
|
+
Number(st.size || 0),
|
|
105
|
+
Number(st.mtimeMs || 0),
|
|
106
|
+
Number(st.ctimeMs || 0),
|
|
107
|
+
].join(":");
|
|
108
|
+
}
|
|
109
|
+
|
|
45
110
|
// Collect rollout-*.jsonl at ANY depth under dir. listRolloutFiles requires the
|
|
46
111
|
// strict YYYY/MM/DD/ nesting Codex itself writes, but Codex-Manager archives
|
|
47
112
|
// sessions FLAT into ~/.codex/archived_sessions/ (issue #187), so the strict
|
|
@@ -119,6 +184,8 @@ async function parseRolloutIncremental({
|
|
|
119
184
|
const projectTouchedBuckets = projectEnabled ? new Set() : null;
|
|
120
185
|
const projectMetaCache = projectEnabled ? new Map() : null;
|
|
121
186
|
const publicRepoCache = projectEnabled ? new Map() : null;
|
|
187
|
+
const projectFreshnessCache = projectEnabled ? new Map() : null;
|
|
188
|
+
const projectFreshnessNowMs = Date.now();
|
|
122
189
|
const touchedBuckets = new Set();
|
|
123
190
|
const defaultSource = normalizeSourceInput(source) || DEFAULT_SOURCE;
|
|
124
191
|
|
|
@@ -154,7 +221,29 @@ async function parseRolloutIncremental({
|
|
|
154
221
|
const lastTotal = sameInode && !truncated ? prev.lastTotal || null : null;
|
|
155
222
|
const lastModel = sameInode && !truncated ? prev.lastModel || null : null;
|
|
156
223
|
|
|
157
|
-
|
|
224
|
+
const codexProjectFastPath = projectEnabled && fileSource === DEFAULT_SOURCE;
|
|
225
|
+
const projectOffset = sameInode && !truncated ? Number(prev.projectOffset || 0) : 0;
|
|
226
|
+
const projectUpToDate =
|
|
227
|
+
codexProjectFastPath &&
|
|
228
|
+
typeof publicRepoResolver !== "function" &&
|
|
229
|
+
projectOffset >= st.size &&
|
|
230
|
+
(await isProjectFileContextFresh(prev?.projectFileContext, {
|
|
231
|
+
freshnessCache: projectFreshnessCache,
|
|
232
|
+
nowMs: projectFreshnessNowMs,
|
|
233
|
+
}));
|
|
234
|
+
const projectContextOnlyScan =
|
|
235
|
+
codexProjectFastPath &&
|
|
236
|
+
typeof publicRepoResolver !== "function" &&
|
|
237
|
+
sameInode &&
|
|
238
|
+
!truncated &&
|
|
239
|
+
startOffset >= st.size &&
|
|
240
|
+
!projectUpToDate;
|
|
241
|
+
if (
|
|
242
|
+
sameInode &&
|
|
243
|
+
!truncated &&
|
|
244
|
+
startOffset >= st.size &&
|
|
245
|
+
(!projectEnabled || projectUpToDate)
|
|
246
|
+
) {
|
|
158
247
|
if (cb) {
|
|
159
248
|
cb({
|
|
160
249
|
index: idx + 1,
|
|
@@ -180,33 +269,57 @@ async function parseRolloutIncremental({
|
|
|
180
269
|
const projectRef = projectContext?.projectRef || null;
|
|
181
270
|
const projectKey = projectContext?.projectKey || null;
|
|
182
271
|
|
|
183
|
-
const result =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
272
|
+
const result = projectContextOnlyScan
|
|
273
|
+
? await scanRolloutProjectFileContexts({
|
|
274
|
+
filePath,
|
|
275
|
+
fileStat: st,
|
|
276
|
+
lastTotal,
|
|
277
|
+
lastModel,
|
|
278
|
+
projectState,
|
|
279
|
+
projectMetaCache,
|
|
280
|
+
publicRepoCache,
|
|
281
|
+
publicRepoResolver,
|
|
282
|
+
projectContext,
|
|
283
|
+
})
|
|
284
|
+
: await parseRolloutFile({
|
|
285
|
+
filePath,
|
|
286
|
+
fileStat: st,
|
|
287
|
+
startOffset,
|
|
288
|
+
lastTotal,
|
|
289
|
+
lastModel,
|
|
290
|
+
hourlyState,
|
|
291
|
+
touchedBuckets,
|
|
292
|
+
source: fileSource,
|
|
293
|
+
projectState,
|
|
294
|
+
projectTouchedBuckets,
|
|
295
|
+
projectRef,
|
|
296
|
+
projectKey,
|
|
297
|
+
projectMetaCache,
|
|
298
|
+
publicRepoCache,
|
|
299
|
+
publicRepoResolver,
|
|
300
|
+
projectContext,
|
|
301
|
+
seenCodexEvents,
|
|
302
|
+
sessionId: codexSessionIdFromPath(filePath),
|
|
303
|
+
});
|
|
202
304
|
|
|
203
|
-
|
|
305
|
+
const nextCursor = {
|
|
204
306
|
inode,
|
|
205
307
|
offset: result.endOffset,
|
|
206
308
|
lastTotal: result.lastTotal,
|
|
207
309
|
lastModel: result.lastModel,
|
|
208
310
|
updatedAt: new Date().toISOString(),
|
|
209
311
|
};
|
|
312
|
+
if (codexProjectFastPath) {
|
|
313
|
+
nextCursor.projectOffset = result.endOffset;
|
|
314
|
+
nextCursor.projectFileContext = buildProjectFileContext(
|
|
315
|
+
result.projectFileContexts,
|
|
316
|
+
projectFreshnessNowMs,
|
|
317
|
+
);
|
|
318
|
+
} else if (Number.isFinite(prev?.projectOffset)) {
|
|
319
|
+
nextCursor.projectOffset = prev.projectOffset;
|
|
320
|
+
if (prev?.projectFileContext) nextCursor.projectFileContext = prev.projectFileContext;
|
|
321
|
+
}
|
|
322
|
+
cursors.files[key] = nextCursor;
|
|
210
323
|
|
|
211
324
|
filesProcessed += 1;
|
|
212
325
|
eventsAggregated += result.eventsAggregated;
|
|
@@ -238,6 +351,108 @@ async function parseRolloutIncremental({
|
|
|
238
351
|
return { filesProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
|
|
239
352
|
}
|
|
240
353
|
|
|
354
|
+
async function filterColdCodexRolloutFiles({
|
|
355
|
+
rolloutFiles,
|
|
356
|
+
cursors,
|
|
357
|
+
projectEnabled = false,
|
|
358
|
+
auditDue = false,
|
|
359
|
+
nowMs = Date.now(),
|
|
360
|
+
recentDays = DEFAULT_CODEX_COLD_SKIP_RECENT_DAYS,
|
|
361
|
+
} = {}) {
|
|
362
|
+
const files = Array.isArray(rolloutFiles) ? rolloutFiles : [];
|
|
363
|
+
if (auditDue || !cursors?.files || typeof cursors.files !== "object") {
|
|
364
|
+
return { rolloutFiles: files, skipped: 0 };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const activeDates = activeCodexRolloutDates(files, { nowMs, recentDays });
|
|
368
|
+
const freshnessCache = new Map();
|
|
369
|
+
const out = [];
|
|
370
|
+
let skipped = 0;
|
|
371
|
+
|
|
372
|
+
for (const entry of files) {
|
|
373
|
+
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
374
|
+
const source =
|
|
375
|
+
typeof entry === "string"
|
|
376
|
+
? DEFAULT_SOURCE
|
|
377
|
+
: normalizeSourceInput(entry?.source) || DEFAULT_SOURCE;
|
|
378
|
+
if (!filePath || source !== DEFAULT_SOURCE) {
|
|
379
|
+
out.push(entry);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const rolloutDate = rolloutDateFromPath(filePath);
|
|
384
|
+
if (!rolloutDate || activeDates.has(rolloutDate)) {
|
|
385
|
+
out.push(entry);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const prev = cursors.files[filePath];
|
|
390
|
+
const cachedSize = Number(prev?.offset);
|
|
391
|
+
if (!Number.isFinite(cachedSize) || cachedSize <= 0) {
|
|
392
|
+
out.push(entry);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (projectEnabled) {
|
|
397
|
+
const projectOffset = Number(prev?.projectOffset);
|
|
398
|
+
if (!Number.isFinite(projectOffset) || projectOffset < cachedSize) {
|
|
399
|
+
out.push(entry);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (
|
|
403
|
+
!(await isProjectFileContextFresh(prev?.projectFileContext, {
|
|
404
|
+
freshnessCache,
|
|
405
|
+
nowMs,
|
|
406
|
+
}))
|
|
407
|
+
) {
|
|
408
|
+
out.push(entry);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
skipped += 1;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return { rolloutFiles: out, skipped };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function activeCodexRolloutDates(
|
|
420
|
+
rolloutFiles,
|
|
421
|
+
{ nowMs = Date.now(), recentDays = DEFAULT_CODEX_COLD_SKIP_RECENT_DAYS } = {},
|
|
422
|
+
) {
|
|
423
|
+
const active = new Set();
|
|
424
|
+
const days = Math.max(1, Math.floor(Number(recentDays) || 1));
|
|
425
|
+
const now = new Date(nowMs);
|
|
426
|
+
if (Number.isFinite(now.getTime())) {
|
|
427
|
+
for (let i = 0; i < days; i += 1) {
|
|
428
|
+
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);
|
|
429
|
+
active.add(formatLocalDate(d));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let latest = null;
|
|
434
|
+
for (const entry of Array.isArray(rolloutFiles) ? rolloutFiles : []) {
|
|
435
|
+
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
436
|
+
const source =
|
|
437
|
+
typeof entry === "string"
|
|
438
|
+
? DEFAULT_SOURCE
|
|
439
|
+
: normalizeSourceInput(entry?.source) || DEFAULT_SOURCE;
|
|
440
|
+
if (source !== DEFAULT_SOURCE) continue;
|
|
441
|
+
const rolloutDate = rolloutDateFromPath(filePath);
|
|
442
|
+
if (rolloutDate && (!latest || rolloutDate > latest)) latest = rolloutDate;
|
|
443
|
+
}
|
|
444
|
+
if (latest) active.add(latest);
|
|
445
|
+
|
|
446
|
+
return active;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function formatLocalDate(value) {
|
|
450
|
+
const year = value.getFullYear();
|
|
451
|
+
const month = String(value.getMonth() + 1).padStart(2, "0");
|
|
452
|
+
const day = String(value.getDate()).padStart(2, "0");
|
|
453
|
+
return `${year}-${month}-${day}`;
|
|
454
|
+
}
|
|
455
|
+
|
|
241
456
|
async function parseClaudeIncremental({
|
|
242
457
|
projectFiles,
|
|
243
458
|
cursors,
|
|
@@ -352,7 +567,7 @@ async function parseClaudeIncremental({
|
|
|
352
567
|
} else {
|
|
353
568
|
projectRef = null;
|
|
354
569
|
projectKey = null;
|
|
355
|
-
nextProjectFileContext =
|
|
570
|
+
nextProjectFileContext = buildProjectFileContext(null);
|
|
356
571
|
}
|
|
357
572
|
}
|
|
358
573
|
|
|
@@ -928,13 +1143,16 @@ async function parseRolloutFile({
|
|
|
928
1143
|
projectMetaCache,
|
|
929
1144
|
publicRepoCache,
|
|
930
1145
|
publicRepoResolver,
|
|
1146
|
+
projectContext,
|
|
931
1147
|
seenCodexEvents,
|
|
932
1148
|
sessionId,
|
|
933
1149
|
}) {
|
|
934
1150
|
const st = fileStat || (await fs.stat(filePath));
|
|
935
1151
|
const endOffset = st.size;
|
|
1152
|
+
const projectFileContexts = [];
|
|
1153
|
+
addProjectFileContext(projectFileContexts, projectContext);
|
|
936
1154
|
if (startOffset >= endOffset) {
|
|
937
|
-
return { endOffset, lastTotal, lastModel, eventsAggregated: 0 };
|
|
1155
|
+
return { endOffset, lastTotal, lastModel, eventsAggregated: 0, projectFileContexts };
|
|
938
1156
|
}
|
|
939
1157
|
|
|
940
1158
|
const stream = fssync.createReadStream(filePath, { encoding: "utf8", start: startOffset });
|
|
@@ -996,6 +1214,7 @@ async function parseRolloutFile({
|
|
|
996
1214
|
currentCwd = nextCwd;
|
|
997
1215
|
currentProjectRef = context?.projectRef || null;
|
|
998
1216
|
currentProjectKey = context?.projectKey || null;
|
|
1217
|
+
addProjectFileContext(projectFileContexts, context);
|
|
999
1218
|
}
|
|
1000
1219
|
}
|
|
1001
1220
|
continue;
|
|
@@ -1066,7 +1285,73 @@ async function parseRolloutFile({
|
|
|
1066
1285
|
eventsAggregated += 1;
|
|
1067
1286
|
}
|
|
1068
1287
|
|
|
1069
|
-
return { endOffset, lastTotal: totals, lastModel: model, eventsAggregated };
|
|
1288
|
+
return { endOffset, lastTotal: totals, lastModel: model, eventsAggregated, projectFileContexts };
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
async function scanRolloutProjectFileContexts({
|
|
1292
|
+
filePath,
|
|
1293
|
+
fileStat,
|
|
1294
|
+
lastTotal,
|
|
1295
|
+
lastModel,
|
|
1296
|
+
projectState,
|
|
1297
|
+
projectMetaCache,
|
|
1298
|
+
publicRepoCache,
|
|
1299
|
+
publicRepoResolver,
|
|
1300
|
+
projectContext,
|
|
1301
|
+
}) {
|
|
1302
|
+
const st = fileStat || (await fs.stat(filePath));
|
|
1303
|
+
const endOffset = st.size;
|
|
1304
|
+
const projectFileContexts = [];
|
|
1305
|
+
addProjectFileContext(projectFileContexts, projectContext);
|
|
1306
|
+
if (!projectState || endOffset <= 0) {
|
|
1307
|
+
return { endOffset, lastTotal, lastModel, eventsAggregated: 0, projectFileContexts };
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const stream = fssync.createReadStream(filePath, { encoding: "utf8", start: 0 });
|
|
1311
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
1312
|
+
let currentCwd = null;
|
|
1313
|
+
|
|
1314
|
+
for await (const line of rl) {
|
|
1315
|
+
if (
|
|
1316
|
+
!line ||
|
|
1317
|
+
!(
|
|
1318
|
+
line.includes('"turn_context"') ||
|
|
1319
|
+
line.includes('"session_meta"')
|
|
1320
|
+
) ||
|
|
1321
|
+
!line.includes('"cwd"')
|
|
1322
|
+
) {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
let obj;
|
|
1327
|
+
try {
|
|
1328
|
+
obj = JSON.parse(line);
|
|
1329
|
+
} catch (_e) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if (
|
|
1333
|
+
(obj?.type !== "turn_context" && obj?.type !== "session_meta") ||
|
|
1334
|
+
!obj?.payload ||
|
|
1335
|
+
typeof obj.payload !== "object" ||
|
|
1336
|
+
typeof obj.payload.cwd !== "string"
|
|
1337
|
+
) {
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const nextCwd = obj.payload.cwd.trim();
|
|
1342
|
+
if (!nextCwd || nextCwd === currentCwd) continue;
|
|
1343
|
+
const context = await resolveProjectContextForPath({
|
|
1344
|
+
startDir: nextCwd,
|
|
1345
|
+
projectMetaCache,
|
|
1346
|
+
publicRepoCache,
|
|
1347
|
+
publicRepoResolver,
|
|
1348
|
+
projectState,
|
|
1349
|
+
});
|
|
1350
|
+
currentCwd = nextCwd;
|
|
1351
|
+
addProjectFileContext(projectFileContexts, context);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return { endOffset, lastTotal, lastModel, eventsAggregated: 0, projectFileContexts };
|
|
1070
1355
|
}
|
|
1071
1356
|
|
|
1072
1357
|
async function parseClaudeFile({
|
|
@@ -2135,9 +2420,9 @@ async function resolveProjectMetaForPath(startDir, cache) {
|
|
|
2135
2420
|
|
|
2136
2421
|
const configPath = await resolveGitConfigPath(current);
|
|
2137
2422
|
if (configPath) {
|
|
2423
|
+
const configStat = await fs.stat(configPath).catch(() => null);
|
|
2138
2424
|
const remoteUrl = await readGitRemoteUrl(configPath);
|
|
2139
2425
|
const projectRef = canonicalizeProjectRef(remoteUrl);
|
|
2140
|
-
const configStat = await fs.stat(configPath).catch(() => null);
|
|
2141
2426
|
const meta = {
|
|
2142
2427
|
projectRef: projectRef || null,
|
|
2143
2428
|
repoRoot: current,
|
|
@@ -2164,6 +2449,86 @@ async function resolveProjectMetaForPath(startDir, cache) {
|
|
|
2164
2449
|
return null;
|
|
2165
2450
|
}
|
|
2166
2451
|
|
|
2452
|
+
function addProjectFileContext(contexts, projectContext) {
|
|
2453
|
+
if (!Array.isArray(contexts) || !projectContext || typeof projectContext !== "object") return;
|
|
2454
|
+
const configPath =
|
|
2455
|
+
typeof projectContext.configPath === "string" && projectContext.configPath
|
|
2456
|
+
? projectContext.configPath
|
|
2457
|
+
: null;
|
|
2458
|
+
if (!configPath) return;
|
|
2459
|
+
contexts.push(projectContext);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
function buildProjectFileContext(projectContexts, checkedAtMs = Date.now()) {
|
|
2463
|
+
const contexts = Array.isArray(projectContexts)
|
|
2464
|
+
? projectContexts
|
|
2465
|
+
: projectContexts && typeof projectContexts === "object"
|
|
2466
|
+
? [projectContexts]
|
|
2467
|
+
: [];
|
|
2468
|
+
const seen = new Set();
|
|
2469
|
+
const configs = [];
|
|
2470
|
+
for (const context of contexts) {
|
|
2471
|
+
const configPath =
|
|
2472
|
+
typeof context?.configPath === "string" && context.configPath ? context.configPath : null;
|
|
2473
|
+
if (!configPath || seen.has(configPath)) continue;
|
|
2474
|
+
seen.add(configPath);
|
|
2475
|
+
configs.push({
|
|
2476
|
+
configPath,
|
|
2477
|
+
configMtimeMs: Number.isFinite(context.configMtimeMs) ? context.configMtimeMs : null,
|
|
2478
|
+
configSize: Number.isFinite(context.configSize) ? context.configSize : null,
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
if (configs.length === 0) return { absent: true, checkedAtMs };
|
|
2482
|
+
if (configs.length > 1) return { configs };
|
|
2483
|
+
const [config] = configs;
|
|
2484
|
+
return {
|
|
2485
|
+
...config,
|
|
2486
|
+
configs,
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
async function isProjectFileContextFresh(projectFileContext, options = {}) {
|
|
2491
|
+
if (!projectFileContext || typeof projectFileContext !== "object") return false;
|
|
2492
|
+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
2493
|
+
const absentTtlMs = Number.isFinite(options.absentTtlMs)
|
|
2494
|
+
? options.absentTtlMs
|
|
2495
|
+
: PROJECT_ABSENT_CONTEXT_RESCAN_MS;
|
|
2496
|
+
const freshnessCache =
|
|
2497
|
+
options.freshnessCache && typeof options.freshnessCache === "object"
|
|
2498
|
+
? options.freshnessCache
|
|
2499
|
+
: null;
|
|
2500
|
+
if (projectFileContext.absent === true) {
|
|
2501
|
+
const checkedAtMs = Number(projectFileContext.checkedAtMs);
|
|
2502
|
+
return Number.isFinite(checkedAtMs) && checkedAtMs > 0 && nowMs - checkedAtMs < absentTtlMs;
|
|
2503
|
+
}
|
|
2504
|
+
if (Array.isArray(projectFileContext.configs)) {
|
|
2505
|
+
if (projectFileContext.configs.length === 0) return false;
|
|
2506
|
+
for (const config of projectFileContext.configs) {
|
|
2507
|
+
if (!(await isSingleProjectConfigFresh(config, freshnessCache))) return false;
|
|
2508
|
+
}
|
|
2509
|
+
return true;
|
|
2510
|
+
}
|
|
2511
|
+
return isSingleProjectConfigFresh(projectFileContext, freshnessCache);
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
async function isSingleProjectConfigFresh(projectFileContext, freshnessCache = null) {
|
|
2515
|
+
const configPath =
|
|
2516
|
+
typeof projectFileContext.configPath === "string" ? projectFileContext.configPath : null;
|
|
2517
|
+
if (!configPath) return false;
|
|
2518
|
+
let st;
|
|
2519
|
+
if (freshnessCache && freshnessCache.has(configPath)) {
|
|
2520
|
+
st = freshnessCache.get(configPath);
|
|
2521
|
+
} else {
|
|
2522
|
+
st = await fs.stat(configPath).catch(() => null);
|
|
2523
|
+
if (freshnessCache) freshnessCache.set(configPath, st);
|
|
2524
|
+
}
|
|
2525
|
+
if (!st || !st.isFile()) return false;
|
|
2526
|
+
return (
|
|
2527
|
+
st.mtimeMs === projectFileContext.configMtimeMs &&
|
|
2528
|
+
st.size === projectFileContext.configSize
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2167
2532
|
function hashRepoRoot(repoRoot) {
|
|
2168
2533
|
return crypto.createHash("sha256").update(String(repoRoot)).digest("hex");
|
|
2169
2534
|
}
|
|
@@ -2283,6 +2648,9 @@ async function resolveProjectContextForPath({
|
|
|
2283
2648
|
projectKey: meta?.projectKey || null,
|
|
2284
2649
|
status: meta?.status || "blocked",
|
|
2285
2650
|
repoRootHash: meta?.repoRootHash || repoRootHash,
|
|
2651
|
+
configPath: projectMeta.configPath || null,
|
|
2652
|
+
configMtimeMs: projectMeta.configMtimeMs ?? null,
|
|
2653
|
+
configSize: projectMeta.configSize ?? null,
|
|
2286
2654
|
};
|
|
2287
2655
|
recordProjectMeta(projectState, normalized);
|
|
2288
2656
|
const configFields = {
|
|
@@ -2306,30 +2674,6 @@ async function resolveProjectContextForPath({
|
|
|
2306
2674
|
};
|
|
2307
2675
|
}
|
|
2308
2676
|
|
|
2309
|
-
// Builds a small, cursor-persistable freshness fingerprint for a resolved
|
|
2310
|
-
// project context's git config file, so a later sync can tell whether the
|
|
2311
|
-
// resolution is still valid without re-walking the filesystem.
|
|
2312
|
-
function buildProjectFileContext(context) {
|
|
2313
|
-
const configPath = typeof context?.configPath === "string" ? context.configPath : null;
|
|
2314
|
-
if (!configPath) return { absent: true };
|
|
2315
|
-
return {
|
|
2316
|
-
configPath,
|
|
2317
|
-
configMtimeMs: Number.isFinite(context.configMtimeMs) ? context.configMtimeMs : null,
|
|
2318
|
-
configSize: Number.isFinite(context.configSize) ? context.configSize : null,
|
|
2319
|
-
};
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
async function isProjectFileContextFresh(projectFileContext) {
|
|
2323
|
-
if (!projectFileContext || typeof projectFileContext !== "object") return false;
|
|
2324
|
-
if (projectFileContext.absent === true) return false;
|
|
2325
|
-
const configPath =
|
|
2326
|
-
typeof projectFileContext.configPath === "string" ? projectFileContext.configPath : null;
|
|
2327
|
-
if (!configPath) return false;
|
|
2328
|
-
const st = await fs.stat(configPath).catch(() => null);
|
|
2329
|
-
if (!st || !st.isFile()) return false;
|
|
2330
|
-
return st.mtimeMs === projectFileContext.configMtimeMs && st.size === projectFileContext.configSize;
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
2677
|
// Claude Code session files log the launch cwd on message lines (top-level
|
|
2334
2678
|
// "cwd" field) — unlike the file's on-disk location under
|
|
2335
2679
|
// ~/.claude/projects/<dash-encoded-cwd>/, which is not itself inside the
|
|
@@ -3446,115 +3790,75 @@ function resolveHermesPath(env = process.env, deps = {}) {
|
|
|
3446
3790
|
// On Windows, scan BOTH the native install (%LOCALAPPDATA%\hermes) and
|
|
3447
3791
|
// any WSL distros running Hermes Agent. Each install has its own independent
|
|
3448
3792
|
// state.db with different sessions, so there is no double-counting risk.
|
|
3449
|
-
//
|
|
3793
|
+
// TOKENTRACKER_WSL_MODE controls which source takes priority.
|
|
3450
3794
|
if (process.platform === "win32") {
|
|
3451
3795
|
let nativePath = null;
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3796
|
+
if (wsl.shouldProbeNative(env)) {
|
|
3797
|
+
const localAppData = typeof env.LOCALAPPDATA === "string" ? env.LOCALAPPDATA.trim() : "";
|
|
3798
|
+
if (localAppData.length > 0) {
|
|
3799
|
+
const candidate = path.join(localAppData, "hermes");
|
|
3800
|
+
try {
|
|
3801
|
+
if (fssync.existsSync(candidate)) nativePath = candidate;
|
|
3802
|
+
} catch (_e) { }
|
|
3803
|
+
}
|
|
3458
3804
|
}
|
|
3459
|
-
const wslPath = discoverWslHermesHome(deps);
|
|
3460
|
-
|
|
3461
|
-
if (
|
|
3805
|
+
const wslPath = wsl.shouldProbeWsl(env) ? discoverWslHermesHome(deps) : null;
|
|
3806
|
+
const picked = wsl.pickWin32Path({ wslValue: wslPath, nativeValue: nativePath, env, platform: "win32" });
|
|
3807
|
+
if (picked) return picked;
|
|
3808
|
+
const mode = wsl.getWslMode(env);
|
|
3809
|
+
if (mode === "wsl-only" || mode === "native-only") return null;
|
|
3462
3810
|
}
|
|
3463
3811
|
return defaultPath;
|
|
3464
3812
|
}
|
|
3465
3813
|
|
|
3466
|
-
// ── WSL auto-discovery (Windows host,
|
|
3814
|
+
// ── WSL auto-discovery (Windows host, tools inside a distro) ──────────────────
|
|
3467
3815
|
// `wsl.exe -l -v` prints UTF-16LE; the per-distro `whoami` runs a Linux process
|
|
3468
3816
|
// so its stdout is UTF-8. We capture buffers and decode per-call.
|
|
3469
3817
|
function defaultRunWsl(args, { utf16 = false } = {}) {
|
|
3470
|
-
|
|
3471
|
-
const buf = execFileSync("wsl.exe", args, {
|
|
3472
|
-
timeout: 5000,
|
|
3473
|
-
windowsHide: true,
|
|
3474
|
-
maxBuffer: 1024 * 1024,
|
|
3475
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
3476
|
-
});
|
|
3477
|
-
return utf16 ? buf.toString("utf16le") : buf.toString("utf8");
|
|
3818
|
+
return wsl.defaultRunWsl(args, { utf16 });
|
|
3478
3819
|
}
|
|
3479
3820
|
|
|
3480
3821
|
// Parse `wsl.exe -l -v` output into [{ name, version, isDefault }]. The default
|
|
3481
3822
|
// distro is prefixed with `*`; the VERSION column (1 or 2) decides which UNC
|
|
3482
3823
|
// alias to try first (simonlpaige, #87).
|
|
3483
3824
|
function parseWslListVerbose(raw) {
|
|
3484
|
-
|
|
3485
|
-
const distros = [];
|
|
3486
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
3487
|
-
const clean = line.replace(//g, "").replace(//g, "").trim();
|
|
3488
|
-
if (!clean) continue;
|
|
3489
|
-
const cells = clean.split(/\s+/);
|
|
3490
|
-
let isDefault = false;
|
|
3491
|
-
let idx = 0;
|
|
3492
|
-
if (cells[0] === "*") {
|
|
3493
|
-
isDefault = true;
|
|
3494
|
-
idx = 1;
|
|
3495
|
-
}
|
|
3496
|
-
const name = cells[idx];
|
|
3497
|
-
if (!name || name === "NAME") continue; // skip header row
|
|
3498
|
-
const version = parseInt(cells[cells.length - 1], 10);
|
|
3499
|
-
distros.push({
|
|
3500
|
-
name,
|
|
3501
|
-
version: Number.isFinite(version) ? version : null,
|
|
3502
|
-
isDefault,
|
|
3503
|
-
});
|
|
3504
|
-
}
|
|
3505
|
-
return distros;
|
|
3825
|
+
return wsl.parseWslListVerbose(raw);
|
|
3506
3826
|
}
|
|
3507
3827
|
|
|
3508
3828
|
// Default distro first, then listed order — matches what a user expects when
|
|
3509
3829
|
// they have one primary distro plus extras.
|
|
3510
3830
|
function probeWslDistros(deps = {}) {
|
|
3511
|
-
|
|
3512
|
-
let raw;
|
|
3513
|
-
try {
|
|
3514
|
-
raw = runWsl(["-l", "-v"], { utf16: true });
|
|
3515
|
-
} catch (_e) {
|
|
3516
|
-
return [];
|
|
3517
|
-
}
|
|
3518
|
-
const distros = parseWslListVerbose(raw);
|
|
3519
|
-
return distros.sort((a, b) => (b.isDefault ? 1 : 0) - (a.isDefault ? 1 : 0));
|
|
3831
|
+
return wsl.probeWslDistros(deps);
|
|
3520
3832
|
}
|
|
3521
3833
|
|
|
3522
3834
|
// Probe each WSL distro for ~/.hermes via UNC. The Linux username rarely equals
|
|
3523
3835
|
// %USERNAME%, so we ask the distro directly with `whoami` rather than guessing
|
|
3524
3836
|
// (simonlpaige, #87).
|
|
3525
3837
|
function discoverWslHermesHome(deps = {}) {
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
? ["\\\\wsl.localhost\\", "\\\\wsl$\\"]
|
|
3541
|
-
: ["\\\\wsl$\\", "\\\\wsl.localhost\\"];
|
|
3542
|
-
for (const root of roots) {
|
|
3543
|
-
const candidate = `${root}${distro.name}\\home\\${user}\\.hermes`;
|
|
3544
|
-
try {
|
|
3545
|
-
if (existsSync(candidate)) return candidate;
|
|
3546
|
-
} catch (_e) { }
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3549
|
-
return null;
|
|
3838
|
+
return wsl.discoverWslHome(".hermes", deps);
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3841
|
+
function pickWin32ProviderPath({ env = process.env, nativeValue, wslProviderDir, wslValue }) {
|
|
3842
|
+
const nativeCandidate = wsl.shouldProbeNative(env) ? nativeValue : null;
|
|
3843
|
+
const wslCandidate = wslValue !== undefined
|
|
3844
|
+
? wslValue
|
|
3845
|
+
: (wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(wslProviderDir) : null);
|
|
3846
|
+
return wsl.pickWin32Path({
|
|
3847
|
+
wslValue: wslCandidate,
|
|
3848
|
+
nativeValue: nativeCandidate,
|
|
3849
|
+
env,
|
|
3850
|
+
platform: "win32",
|
|
3851
|
+
});
|
|
3550
3852
|
}
|
|
3551
3853
|
|
|
3552
3854
|
function resolveHermesDbPath(env = process.env) {
|
|
3553
|
-
|
|
3855
|
+
const hermesPath = resolveHermesPath(env);
|
|
3856
|
+
return hermesPath ? path.join(hermesPath, "state.db") : null;
|
|
3554
3857
|
}
|
|
3555
3858
|
|
|
3556
3859
|
function resolveAllHermesDBPaths({ hermesPath, dbPath } = {}) {
|
|
3557
3860
|
const hermesDir = hermesPath ?? (dbPath ? path.dirname(dbPath) : resolveHermesPath());
|
|
3861
|
+
if (!hermesDir) return { default: null, profiles: {} };
|
|
3558
3862
|
const defaultDbPath = dbPath ?? path.join(hermesDir, "state.db");
|
|
3559
3863
|
const profilePaths = {};
|
|
3560
3864
|
try {
|
|
@@ -3586,28 +3890,11 @@ function sqliteStringLiteral(value) {
|
|
|
3586
3890
|
// / SMB bridge can't grant the locks SQLite asks for — even after `wsl
|
|
3587
3891
|
// --shutdown`. Detect those paths so we can snapshot the DB locally first.
|
|
3588
3892
|
function isUncPath(p) {
|
|
3589
|
-
return
|
|
3893
|
+
return wsl.isUncPath(p);
|
|
3590
3894
|
}
|
|
3591
3895
|
|
|
3592
3896
|
function snapshotSqliteDb(dbPath) {
|
|
3593
|
-
|
|
3594
|
-
path.join(require("node:os").tmpdir(), "tokentracker-hermes-snap-"),
|
|
3595
|
-
);
|
|
3596
|
-
const target = path.join(tmpRoot, path.basename(dbPath));
|
|
3597
|
-
fssync.copyFileSync(dbPath, target);
|
|
3598
|
-
// Best-effort copy of SQLite sidecars; missing -wal/-shm/-journal is fine.
|
|
3599
|
-
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
3600
|
-
const src = dbPath + suffix;
|
|
3601
|
-
try {
|
|
3602
|
-
if (fssync.existsSync(src)) fssync.copyFileSync(src, target + suffix);
|
|
3603
|
-
} catch (_e) { }
|
|
3604
|
-
}
|
|
3605
|
-
return {
|
|
3606
|
-
path: target,
|
|
3607
|
-
cleanup() {
|
|
3608
|
-
try { fssync.rmSync(tmpRoot, { recursive: true, force: true }); } catch (_e) { }
|
|
3609
|
-
},
|
|
3610
|
-
};
|
|
3897
|
+
return wsl.snapshotSqliteDb(dbPath);
|
|
3611
3898
|
}
|
|
3612
3899
|
|
|
3613
3900
|
function readHermesSessions(dbPath, lastCompletedEpoch, unfinishedSessionIds = [], sqliteOptions = {}) {
|
|
@@ -4908,11 +5195,21 @@ async function parseKimiIncremental({ wireFiles, cursors, queuePath, onProgress,
|
|
|
4908
5195
|
function resolveKimiCodeHome(env = process.env) {
|
|
4909
5196
|
const home = require("node:os").homedir();
|
|
4910
5197
|
const explicit = typeof env?.KIMI_CODE_HOME === "string" ? env.KIMI_CODE_HOME.trim() : "";
|
|
4911
|
-
|
|
5198
|
+
if (explicit) return path.resolve(explicit);
|
|
5199
|
+
if (process.platform === "win32") {
|
|
5200
|
+
return pickWin32ProviderPath({
|
|
5201
|
+
env,
|
|
5202
|
+
nativeValue: path.join(home, ".kimi-code"),
|
|
5203
|
+
wslProviderDir: ".kimi-code",
|
|
5204
|
+
});
|
|
5205
|
+
}
|
|
5206
|
+
return path.join(home, ".kimi-code");
|
|
4912
5207
|
}
|
|
4913
5208
|
|
|
4914
5209
|
function resolveKimiCodeWireFiles(env = process.env) {
|
|
4915
|
-
const
|
|
5210
|
+
const kimiHome = resolveKimiCodeHome(env);
|
|
5211
|
+
if (!kimiHome) return [];
|
|
5212
|
+
const sessionsDir = path.join(kimiHome, "sessions");
|
|
4916
5213
|
if (!fssync.existsSync(sessionsDir)) return [];
|
|
4917
5214
|
const files = [];
|
|
4918
5215
|
const walk = (dir, depth) => {
|
|
@@ -4932,7 +5229,9 @@ function resolveKimiCodeWireFiles(env = process.env) {
|
|
|
4932
5229
|
function resolveKimiCodeDefaultModel(env = process.env) {
|
|
4933
5230
|
const fallback = "kimi-for-coding";
|
|
4934
5231
|
try {
|
|
4935
|
-
const
|
|
5232
|
+
const kimiHome = resolveKimiCodeHome(env);
|
|
5233
|
+
if (!kimiHome) return fallback;
|
|
5234
|
+
const cfgPath = path.join(kimiHome, "config.toml");
|
|
4936
5235
|
const raw = fssync.readFileSync(cfgPath, "utf8");
|
|
4937
5236
|
const m = raw.match(/^\s*default_model\s*=\s*"([^"]+)"/m);
|
|
4938
5237
|
if (!m) return fallback;
|
|
@@ -5136,13 +5435,23 @@ async function parseKimiCodeIncremental({ wireFiles, cursors, queuePath, onProgr
|
|
|
5136
5435
|
|
|
5137
5436
|
function resolveCodebuddyHome(env = process.env) {
|
|
5138
5437
|
const home = env.HOME || require("node:os").homedir();
|
|
5139
|
-
|
|
5438
|
+
if (env.CODEBUDDY_HOME) return env.CODEBUDDY_HOME;
|
|
5439
|
+
if (process.platform === "win32") {
|
|
5440
|
+
return pickWin32ProviderPath({
|
|
5441
|
+
env,
|
|
5442
|
+
nativeValue: path.join(home, ".codebuddy"),
|
|
5443
|
+
wslProviderDir: ".codebuddy",
|
|
5444
|
+
});
|
|
5445
|
+
}
|
|
5446
|
+
return path.join(home, ".codebuddy");
|
|
5140
5447
|
}
|
|
5141
5448
|
|
|
5142
5449
|
function resolveCodebuddyDefaultModel(env = process.env) {
|
|
5143
5450
|
const fallback = "codebuddy-unknown";
|
|
5144
5451
|
try {
|
|
5145
|
-
const
|
|
5452
|
+
const codebuddyHome = resolveCodebuddyHome(env);
|
|
5453
|
+
if (!codebuddyHome) return fallback;
|
|
5454
|
+
const settingsPath = path.join(codebuddyHome, "settings.json");
|
|
5146
5455
|
const raw = fssync.readFileSync(settingsPath, "utf8");
|
|
5147
5456
|
const parsed = JSON.parse(raw);
|
|
5148
5457
|
if (parsed && typeof parsed === "object" && typeof parsed.model === "string" && parsed.model.trim()) {
|
|
@@ -5155,7 +5464,9 @@ function resolveCodebuddyDefaultModel(env = process.env) {
|
|
|
5155
5464
|
}
|
|
5156
5465
|
|
|
5157
5466
|
function resolveCodebuddyProjectFiles(env = process.env) {
|
|
5158
|
-
const
|
|
5467
|
+
const codebuddyHome = resolveCodebuddyHome(env);
|
|
5468
|
+
if (!codebuddyHome) return [];
|
|
5469
|
+
const projectsDir = path.join(codebuddyHome, "projects");
|
|
5159
5470
|
if (!fssync.existsSync(projectsDir)) return [];
|
|
5160
5471
|
const files = [];
|
|
5161
5472
|
try {
|
|
@@ -5430,13 +5741,23 @@ async function parseCodebuddyIncremental({
|
|
|
5430
5741
|
|
|
5431
5742
|
function resolveWorkbuddyHome(env = process.env) {
|
|
5432
5743
|
const home = env.HOME || require("node:os").homedir();
|
|
5433
|
-
|
|
5744
|
+
if (env.WORKBUDDY_HOME) return env.WORKBUDDY_HOME;
|
|
5745
|
+
if (process.platform === "win32") {
|
|
5746
|
+
return pickWin32ProviderPath({
|
|
5747
|
+
env,
|
|
5748
|
+
nativeValue: path.join(home, ".workbuddy"),
|
|
5749
|
+
wslProviderDir: ".workbuddy",
|
|
5750
|
+
});
|
|
5751
|
+
}
|
|
5752
|
+
return path.join(home, ".workbuddy");
|
|
5434
5753
|
}
|
|
5435
5754
|
|
|
5436
5755
|
function resolveWorkbuddyDefaultModel(env = process.env) {
|
|
5437
5756
|
const fallback = "auto";
|
|
5438
5757
|
try {
|
|
5439
|
-
const
|
|
5758
|
+
const workbuddyHome = resolveWorkbuddyHome(env);
|
|
5759
|
+
if (!workbuddyHome) return fallback;
|
|
5760
|
+
const settingsPath = path.join(workbuddyHome, "settings.json");
|
|
5440
5761
|
const raw = fssync.readFileSync(settingsPath, "utf8");
|
|
5441
5762
|
const parsed = JSON.parse(raw);
|
|
5442
5763
|
if (parsed && typeof parsed === "object" && typeof parsed.model === "string" && parsed.model.trim()) {
|
|
@@ -5452,7 +5773,9 @@ function resolveWorkbuddyDefaultModel(env = process.env) {
|
|
|
5452
5773
|
// per-session conversation logs AND their nested subagents/agent-*.jsonl files
|
|
5453
5774
|
// are both discovered. Non-.jsonl artefacts (tool-results/*.txt) are ignored.
|
|
5454
5775
|
function resolveWorkbuddyProjectFiles(env = process.env) {
|
|
5455
|
-
const
|
|
5776
|
+
const workbuddyHome = resolveWorkbuddyHome(env);
|
|
5777
|
+
if (!workbuddyHome) return [];
|
|
5778
|
+
const projectsDir = path.join(workbuddyHome, "projects");
|
|
5456
5779
|
if (!fssync.existsSync(projectsDir)) return [];
|
|
5457
5780
|
const files = [];
|
|
5458
5781
|
const walk = (dir) => {
|
|
@@ -5726,6 +6049,13 @@ function resolveOmpHome(env = process.env) {
|
|
|
5726
6049
|
// Honor TokenTracker override first, then oh-my-pi upstream env vars.
|
|
5727
6050
|
if (env.OMP_HOME) return env.OMP_HOME;
|
|
5728
6051
|
if (env.PI_CONFIG_DIR) return path.join(home, env.PI_CONFIG_DIR);
|
|
6052
|
+
if (process.platform === "win32") {
|
|
6053
|
+
return pickWin32ProviderPath({
|
|
6054
|
+
env,
|
|
6055
|
+
nativeValue: path.join(home, ".omp"),
|
|
6056
|
+
wslProviderDir: ".omp",
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
5729
6059
|
return path.join(home, ".omp");
|
|
5730
6060
|
}
|
|
5731
6061
|
|
|
@@ -5763,11 +6093,14 @@ function resolveOmpAgentDir(env = process.env) {
|
|
|
5763
6093
|
if (env.PI_CODING_AGENT_DIR && decidePiCodingAgentDirOwner(env) === "omp") {
|
|
5764
6094
|
return expandHomePath(env.PI_CODING_AGENT_DIR, env);
|
|
5765
6095
|
}
|
|
5766
|
-
|
|
6096
|
+
const ompHome = resolveOmpHome(env);
|
|
6097
|
+
return ompHome ? path.join(ompHome, "agent") : null;
|
|
5767
6098
|
}
|
|
5768
6099
|
|
|
5769
6100
|
function resolveOmpSessionFiles(env = process.env) {
|
|
5770
|
-
const
|
|
6101
|
+
const agentDir = resolveOmpAgentDir(env);
|
|
6102
|
+
if (!agentDir) return [];
|
|
6103
|
+
const sessionsDir = path.join(agentDir, "sessions");
|
|
5771
6104
|
if (!fssync.existsSync(sessionsDir)) return [];
|
|
5772
6105
|
const files = [];
|
|
5773
6106
|
try {
|
|
@@ -5843,14 +6176,28 @@ function resolveKilocodeRoots(env = process.env) {
|
|
|
5843
6176
|
);
|
|
5844
6177
|
} else if (process.platform === "win32") {
|
|
5845
6178
|
const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
5846
|
-
|
|
6179
|
+
const nativeRoots = [
|
|
5847
6180
|
path.join(appData, "Code"),
|
|
5848
6181
|
path.join(appData, "Code - Insiders"),
|
|
5849
6182
|
path.join(appData, "Cursor"),
|
|
5850
6183
|
path.join(appData, "CodeBuddy"),
|
|
5851
6184
|
path.join(appData, "Windsurf"),
|
|
5852
6185
|
path.join(appData, "VSCodium"),
|
|
5853
|
-
|
|
6186
|
+
];
|
|
6187
|
+
const wslRoots = [];
|
|
6188
|
+
if (wsl.shouldProbeWsl(env)) {
|
|
6189
|
+
for (const ide of ["Code", "Code - Insiders", "Cursor", "CodeBuddy", "Windsurf", "VSCodium"]) {
|
|
6190
|
+
const wslDir = wsl.discoverWslHome(`.config/${ide}`, { env });
|
|
6191
|
+
if (wslDir) wslRoots.push(wslDir);
|
|
6192
|
+
}
|
|
6193
|
+
}
|
|
6194
|
+
const nativeCandidates = wsl.shouldProbeNative(env) ? nativeRoots : [];
|
|
6195
|
+
const mode = wsl.getWslMode(env);
|
|
6196
|
+
if (mode === "native-first" || mode === "native-only") {
|
|
6197
|
+
candidates.push(...nativeCandidates, ...wslRoots);
|
|
6198
|
+
} else {
|
|
6199
|
+
candidates.push(...wslRoots, ...nativeCandidates);
|
|
6200
|
+
}
|
|
5854
6201
|
} else {
|
|
5855
6202
|
const xdg = env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
5856
6203
|
candidates.push(
|
|
@@ -6178,7 +6525,17 @@ function resolveZedDbPath(env = process.env) {
|
|
|
6178
6525
|
}
|
|
6179
6526
|
if (process.platform === "win32") {
|
|
6180
6527
|
const local = env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
6181
|
-
|
|
6528
|
+
const native = path.join(local, "Zed", "threads", "threads.db");
|
|
6529
|
+
let nativeExists = null;
|
|
6530
|
+
if (wsl.shouldProbeNative(env)) {
|
|
6531
|
+
try { if (fssync.existsSync(native)) nativeExists = native; } catch (_e) { }
|
|
6532
|
+
}
|
|
6533
|
+
const wslDir = wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(".local/share/zed/threads", { env }) : null;
|
|
6534
|
+
const wslValue = wslDir ? path.join(wslDir, "threads.db") : null;
|
|
6535
|
+
const picked = wsl.pickWin32Path({ wslValue, nativeValue: nativeExists, env, platform: "win32" });
|
|
6536
|
+
if (picked) return picked;
|
|
6537
|
+
const mode = wsl.getWslMode(env);
|
|
6538
|
+
return mode === "wsl-only" || mode === "native-only" ? null : native;
|
|
6182
6539
|
}
|
|
6183
6540
|
const xdg = env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
6184
6541
|
return path.join(xdg, "zed", "threads", "threads.db");
|
|
@@ -6330,6 +6687,10 @@ async function parseZedIncremental({
|
|
|
6330
6687
|
} = {}) {
|
|
6331
6688
|
await ensureDir(path.dirname(queuePath));
|
|
6332
6689
|
const resolvedDb = dbPath || resolveZedDbPath(env || process.env);
|
|
6690
|
+
if (!resolvedDb) {
|
|
6691
|
+
cursors.zed = { ...cursors.zed, updatedAt: new Date().toISOString() };
|
|
6692
|
+
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
6693
|
+
}
|
|
6333
6694
|
const zedState =
|
|
6334
6695
|
cursors.zed && typeof cursors.zed === "object" ? cursors.zed : {};
|
|
6335
6696
|
const threadTotals =
|
|
@@ -6547,7 +6908,20 @@ function resolveGooseDbPath(env = process.env) {
|
|
|
6547
6908
|
);
|
|
6548
6909
|
} else if (process.platform === "win32") {
|
|
6549
6910
|
const appData = env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
6550
|
-
|
|
6911
|
+
const native = path.join(appData, "goose", "sessions", "sessions.db");
|
|
6912
|
+
let nativeExists = null;
|
|
6913
|
+
if (wsl.shouldProbeNative(env)) {
|
|
6914
|
+
try { if (fssync.existsSync(native)) nativeExists = native; } catch (_e) { }
|
|
6915
|
+
}
|
|
6916
|
+
const wslDir = wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(".local/share/goose/sessions", { env }) : null;
|
|
6917
|
+
const wslValue = wslDir ? path.join(wslDir, "sessions.db") : null;
|
|
6918
|
+
const picked = wsl.pickWin32Path({ wslValue, nativeValue: nativeExists, env, platform: "win32" });
|
|
6919
|
+
if (picked) candidates.push(picked);
|
|
6920
|
+
for (const c of candidates) {
|
|
6921
|
+
if (fssync.existsSync(c)) return c;
|
|
6922
|
+
}
|
|
6923
|
+
const mode = wsl.getWslMode(env);
|
|
6924
|
+
return mode === "wsl-only" || mode === "native-only" ? null : native;
|
|
6551
6925
|
}
|
|
6552
6926
|
const xdg = env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
6553
6927
|
candidates.push(
|
|
@@ -6559,7 +6933,7 @@ function resolveGooseDbPath(env = process.env) {
|
|
|
6559
6933
|
for (const c of candidates) {
|
|
6560
6934
|
if (fssync.existsSync(c)) return c;
|
|
6561
6935
|
}
|
|
6562
|
-
return candidates[0];
|
|
6936
|
+
return candidates[0] || null;
|
|
6563
6937
|
}
|
|
6564
6938
|
|
|
6565
6939
|
function parseGooseModelName(modelConfigJson) {
|
|
@@ -6654,6 +7028,11 @@ async function parseGooseIncremental({
|
|
|
6654
7028
|
? { ...gooseState.sessionTotals }
|
|
6655
7029
|
: {};
|
|
6656
7030
|
|
|
7031
|
+
if (!resolvedDb) {
|
|
7032
|
+
cursors.goose = { ...gooseState, sessionTotals, updatedAt: new Date().toISOString() };
|
|
7033
|
+
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
7034
|
+
}
|
|
7035
|
+
|
|
6657
7036
|
const cursorDbMtime = Number.isFinite(gooseState.lastDbMtimeMs) ? gooseState.lastDbMtimeMs : 0;
|
|
6658
7037
|
let currentMtime = 0;
|
|
6659
7038
|
try {
|
|
@@ -6835,6 +7214,14 @@ function resolveDroidSessionsDirs(env = process.env) {
|
|
|
6835
7214
|
return [path.join(expandHomePath(env.FACTORY_DIR.trim(), env), "sessions")];
|
|
6836
7215
|
}
|
|
6837
7216
|
const home = env.HOME || require("node:os").homedir();
|
|
7217
|
+
if (process.platform === "win32") {
|
|
7218
|
+
const picked = pickWin32ProviderPath({
|
|
7219
|
+
env,
|
|
7220
|
+
nativeValue: path.join(home, ".factory", "sessions"),
|
|
7221
|
+
wslProviderDir: ".factory/sessions",
|
|
7222
|
+
});
|
|
7223
|
+
return picked ? [picked] : [];
|
|
7224
|
+
}
|
|
6838
7225
|
return [path.join(home, ".factory", "sessions")];
|
|
6839
7226
|
}
|
|
6840
7227
|
|
|
@@ -7692,6 +8079,13 @@ async function parseOmpIncremental({
|
|
|
7692
8079
|
|
|
7693
8080
|
function resolvePiHome(env = process.env) {
|
|
7694
8081
|
const home = env.HOME || require("node:os").homedir();
|
|
8082
|
+
if (process.platform === "win32") {
|
|
8083
|
+
return pickWin32ProviderPath({
|
|
8084
|
+
env,
|
|
8085
|
+
nativeValue: path.join(home, ".pi"),
|
|
8086
|
+
wslProviderDir: ".pi",
|
|
8087
|
+
});
|
|
8088
|
+
}
|
|
7695
8089
|
return path.join(home, ".pi");
|
|
7696
8090
|
}
|
|
7697
8091
|
|
|
@@ -7702,7 +8096,8 @@ function resolvePiAgentDir(env = process.env) {
|
|
|
7702
8096
|
if (env.PI_CODING_AGENT_DIR && decidePiCodingAgentDirOwner(env) === "pi") {
|
|
7703
8097
|
return expandHomePath(env.PI_CODING_AGENT_DIR, env);
|
|
7704
8098
|
}
|
|
7705
|
-
|
|
8099
|
+
const piHome = resolvePiHome(env);
|
|
8100
|
+
return piHome ? path.join(piHome, "agent") : null;
|
|
7706
8101
|
}
|
|
7707
8102
|
|
|
7708
8103
|
// Defense in depth for invariant 2 (no double-count). Two explicit overrides
|
|
@@ -7711,11 +8106,16 @@ function resolvePiAgentDir(env = process.env) {
|
|
|
7711
8106
|
// the install-signal disambiguator and would otherwise have both providers scan
|
|
7712
8107
|
// the same sessions directory under different `source` tags.
|
|
7713
8108
|
function piAgentDirCollidesWithOmp(env = process.env) {
|
|
7714
|
-
|
|
8109
|
+
const piAgentDir = resolvePiAgentDir(env);
|
|
8110
|
+
const ompAgentDir = resolveOmpAgentDir(env);
|
|
8111
|
+
if (!piAgentDir || !ompAgentDir) return false;
|
|
8112
|
+
return path.resolve(piAgentDir) === path.resolve(ompAgentDir);
|
|
7715
8113
|
}
|
|
7716
8114
|
|
|
7717
8115
|
function resolvePiSessionFiles(env = process.env) {
|
|
7718
|
-
const
|
|
8116
|
+
const agentDir = resolvePiAgentDir(env);
|
|
8117
|
+
if (!agentDir) return [];
|
|
8118
|
+
const sessionsDir = path.join(agentDir, "sessions");
|
|
7719
8119
|
if (!fssync.existsSync(sessionsDir)) return [];
|
|
7720
8120
|
const files = [];
|
|
7721
8121
|
try {
|
|
@@ -7963,11 +8363,19 @@ async function parsePiIncremental({
|
|
|
7963
8363
|
function resolveCraftConfigDir(env = process.env) {
|
|
7964
8364
|
if (env.CRAFT_CONFIG_DIR) return env.CRAFT_CONFIG_DIR;
|
|
7965
8365
|
const home = env.HOME || require("node:os").homedir();
|
|
8366
|
+
if (process.platform === "win32") {
|
|
8367
|
+
return pickWin32ProviderPath({
|
|
8368
|
+
env,
|
|
8369
|
+
nativeValue: path.join(home, ".craft-agent"),
|
|
8370
|
+
wslProviderDir: ".craft-agent",
|
|
8371
|
+
});
|
|
8372
|
+
}
|
|
7966
8373
|
return path.join(home, ".craft-agent");
|
|
7967
8374
|
}
|
|
7968
8375
|
|
|
7969
8376
|
function resolveCraftWorkspaceRoots(env = process.env) {
|
|
7970
8377
|
const configDir = resolveCraftConfigDir(env);
|
|
8378
|
+
if (!configDir) return [];
|
|
7971
8379
|
const roots = new Set();
|
|
7972
8380
|
// Always include the default workspaces directory so a fresh install (no
|
|
7973
8381
|
// config.json yet) still gets discovered.
|
|
@@ -8261,13 +8669,22 @@ async function parseCraftIncremental({
|
|
|
8261
8669
|
function resolveCopilotOtelPaths(env = process.env) {
|
|
8262
8670
|
const home = env.HOME || require("node:os").homedir();
|
|
8263
8671
|
const paths = new Set();
|
|
8264
|
-
const
|
|
8265
|
-
|
|
8672
|
+
const scanDir = (dir) => {
|
|
8673
|
+
if (!fssync.existsSync(dir)) return;
|
|
8266
8674
|
try {
|
|
8267
|
-
for (const entry of fssync.readdirSync(
|
|
8268
|
-
if (entry.endsWith(".jsonl")) paths.add(path.join(
|
|
8675
|
+
for (const entry of fssync.readdirSync(dir)) {
|
|
8676
|
+
if (entry.endsWith(".jsonl")) paths.add(path.join(dir, entry));
|
|
8269
8677
|
}
|
|
8270
8678
|
} catch (_e) {}
|
|
8679
|
+
};
|
|
8680
|
+
if (process.platform !== "win32" || wsl.shouldProbeNative(env)) {
|
|
8681
|
+
scanDir(path.join(home, ".copilot", "otel"));
|
|
8682
|
+
}
|
|
8683
|
+
if (process.platform === "win32") {
|
|
8684
|
+
if (wsl.shouldProbeWsl(env)) {
|
|
8685
|
+
const wslDir = wsl.discoverWslHome(".copilot/otel", { env });
|
|
8686
|
+
if (wslDir) scanDir(wslDir);
|
|
8687
|
+
}
|
|
8271
8688
|
}
|
|
8272
8689
|
const explicit = env.COPILOT_OTEL_FILE_EXPORTER_PATH;
|
|
8273
8690
|
if (typeof explicit === "string" && explicit.trim() && fssync.existsSync(explicit)) {
|
|
@@ -8590,15 +9007,21 @@ const GROK_ESTIMATED_INPUT_RATIO = 0.8;
|
|
|
8590
9007
|
const GROK_CURSOR_VERSION = 3;
|
|
8591
9008
|
|
|
8592
9009
|
function resolveGrokBuildHome(env = process.env) {
|
|
8593
|
-
return
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
9010
|
+
if (env.TOKENTRACKER_GROK_HOME) return env.TOKENTRACKER_GROK_HOME;
|
|
9011
|
+
if (env.GROK_HOME) return env.GROK_HOME;
|
|
9012
|
+
if (process.platform === "win32") {
|
|
9013
|
+
return pickWin32ProviderPath({
|
|
9014
|
+
env,
|
|
9015
|
+
nativeValue: path.join(require("node:os").homedir(), ".grok"),
|
|
9016
|
+
wslProviderDir: ".grok",
|
|
9017
|
+
});
|
|
9018
|
+
}
|
|
9019
|
+
return path.join(require("node:os").homedir(), ".grok");
|
|
8598
9020
|
}
|
|
8599
9021
|
|
|
8600
9022
|
function resolveGrokBuildSessions(env = process.env) {
|
|
8601
9023
|
const home = resolveGrokBuildHome(env);
|
|
9024
|
+
if (!home) return [];
|
|
8602
9025
|
const sessionsRoot = path.join(home, "sessions");
|
|
8603
9026
|
if (!fssync.existsSync(sessionsRoot)) return [];
|
|
8604
9027
|
|
|
@@ -9472,6 +9895,7 @@ module.exports = {
|
|
|
9472
9895
|
listRolloutFiles,
|
|
9473
9896
|
listRolloutFilesDeep,
|
|
9474
9897
|
codexSessionIdFromPath,
|
|
9898
|
+
filterColdCodexRolloutFiles,
|
|
9475
9899
|
listClaudeProjectFiles,
|
|
9476
9900
|
listGeminiSessionFiles,
|
|
9477
9901
|
listOpencodeMessageFiles,
|