@pixelbyte-software/pixcode 1.53.10 → 1.53.12
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/dist/assets/index-BEyC4q_s.css +32 -0
- package/dist/assets/{index-ChssgA1r.js → index-kP_gZ-wL.js} +178 -178
- package/dist/index.html +2 -2
- package/dist-server/server/database/db.js +26 -0
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/index.js +290 -57
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/projects.js +126 -43
- package/dist-server/server/projects.js.map +1 -1
- package/dist-server/server/routes/git.js +16 -0
- package/dist-server/server/routes/git.js.map +1 -1
- package/dist-server/server/routes/plugins.js +364 -0
- package/dist-server/server/routes/plugins.js.map +1 -1
- package/dist-server/server/services/telegram/bot.js +43 -5
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +169 -5
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +28 -2
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +1 -1
- package/server/database/db.js +26 -0
- package/server/index.js +312 -62
- package/server/projects.js +128 -41
- package/server/routes/git.js +17 -0
- package/server/routes/plugins.js +370 -0
- package/server/services/telegram/bot.js +45 -5
- package/server/services/telegram/control-center.js +173 -5
- package/server/services/telegram/translations.js +28 -2
- package/dist/assets/index-FhOsv2Yy.css +0 -32
|
@@ -65,6 +65,15 @@ import Database from 'better-sqlite3';
|
|
|
65
65
|
import sessionManager from './sessionManager.js';
|
|
66
66
|
import { applyCustomSessionNames } from './database/db.js';
|
|
67
67
|
const FILE_COUNT_LIMIT = 20000;
|
|
68
|
+
const FILE_COUNT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
69
|
+
const FILE_COUNT_CACHE_MAX_ENTRIES = 300;
|
|
70
|
+
const FILE_COUNT_SCAN_MAX_MS = 2500;
|
|
71
|
+
const CODEX_SESSION_INDEX_TTL_MS = 60 * 1000;
|
|
72
|
+
const CODEX_SESSION_INDEX_MAX_FILES = 1200;
|
|
73
|
+
const CODEX_SESSION_INDEX_MAX_DIRS = 2500;
|
|
74
|
+
const GEMINI_QWEN_PROJECT_ENTRY_CACHE_TTL_MS = 60 * 1000;
|
|
75
|
+
const CLI_CHAT_FILE_READ_MAX_BYTES = 2 * 1024 * 1024;
|
|
76
|
+
const CLI_CHAT_FILES_PER_PROJECT_LIMIT = 150;
|
|
68
77
|
const FILE_COUNT_IGNORED_DIRECTORIES = new Set([
|
|
69
78
|
'.cache',
|
|
70
79
|
'.git',
|
|
@@ -84,10 +93,43 @@ const FILE_COUNT_IGNORED_DIRECTORIES = new Set([
|
|
|
84
93
|
'target',
|
|
85
94
|
'vendor'
|
|
86
95
|
]);
|
|
96
|
+
const fileCountCache = new Map();
|
|
97
|
+
let codexSessionsIndexCache = null;
|
|
98
|
+
let geminiCliProjectEntriesCache = null;
|
|
99
|
+
let qwenCliProjectEntriesCache = null;
|
|
100
|
+
function pruneMapToMaxEntries(map, maxEntries) {
|
|
101
|
+
while (map.size > maxEntries) {
|
|
102
|
+
const firstKey = map.keys().next().value;
|
|
103
|
+
if (firstKey === undefined)
|
|
104
|
+
break;
|
|
105
|
+
map.delete(firstKey);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function readFreshCacheEntry(cache, key, now = Date.now()) {
|
|
109
|
+
const entry = cache.get(key);
|
|
110
|
+
if (!entry || entry.expiresAt <= now) {
|
|
111
|
+
if (entry)
|
|
112
|
+
cache.delete(key);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return entry.value;
|
|
116
|
+
}
|
|
117
|
+
function writeCacheEntry(cache, key, value, ttlMs, maxEntries) {
|
|
118
|
+
cache.set(key, {
|
|
119
|
+
value,
|
|
120
|
+
expiresAt: Date.now() + ttlMs,
|
|
121
|
+
});
|
|
122
|
+
pruneMapToMaxEntries(cache, maxEntries);
|
|
123
|
+
}
|
|
87
124
|
async function countProjectFiles(projectPath, maxFiles = FILE_COUNT_LIMIT) {
|
|
88
125
|
if (!projectPath || typeof projectPath !== 'string') {
|
|
89
126
|
return undefined;
|
|
90
127
|
}
|
|
128
|
+
const cacheKey = `${path.resolve(projectPath)}:${maxFiles}`;
|
|
129
|
+
const cached = readFreshCacheEntry(fileCountCache, cacheKey);
|
|
130
|
+
if (cached !== undefined) {
|
|
131
|
+
return cached;
|
|
132
|
+
}
|
|
91
133
|
try {
|
|
92
134
|
const stats = await fs.stat(projectPath);
|
|
93
135
|
if (!stats.isDirectory()) {
|
|
@@ -98,8 +140,13 @@ async function countProjectFiles(projectPath, maxFiles = FILE_COUNT_LIMIT) {
|
|
|
98
140
|
return undefined;
|
|
99
141
|
}
|
|
100
142
|
let count = 0;
|
|
143
|
+
const startedAt = Date.now();
|
|
101
144
|
const pendingDirectories = [projectPath];
|
|
102
145
|
while (pendingDirectories.length > 0) {
|
|
146
|
+
if (Date.now() - startedAt > FILE_COUNT_SCAN_MAX_MS) {
|
|
147
|
+
writeCacheEntry(fileCountCache, cacheKey, count, FILE_COUNT_CACHE_TTL_MS, FILE_COUNT_CACHE_MAX_ENTRIES);
|
|
148
|
+
return count;
|
|
149
|
+
}
|
|
103
150
|
const currentDirectory = pendingDirectories.pop();
|
|
104
151
|
let entries = [];
|
|
105
152
|
try {
|
|
@@ -118,11 +165,13 @@ async function countProjectFiles(projectPath, maxFiles = FILE_COUNT_LIMIT) {
|
|
|
118
165
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
119
166
|
count += 1;
|
|
120
167
|
if (count >= maxFiles) {
|
|
168
|
+
writeCacheEntry(fileCountCache, cacheKey, count, FILE_COUNT_CACHE_TTL_MS, FILE_COUNT_CACHE_MAX_ENTRIES);
|
|
121
169
|
return count;
|
|
122
170
|
}
|
|
123
171
|
}
|
|
124
172
|
}
|
|
125
173
|
}
|
|
174
|
+
writeCacheEntry(fileCountCache, cacheKey, count, FILE_COUNT_CACHE_TTL_MS, FILE_COUNT_CACHE_MAX_ENTRIES);
|
|
126
175
|
return count;
|
|
127
176
|
}
|
|
128
177
|
// Cache for extracted project directories
|
|
@@ -186,6 +235,9 @@ function areStringArraysEqual(first, second) {
|
|
|
186
235
|
// Gemini ones rather than sharing a parametric implementation, keeping
|
|
187
236
|
// provider-specific logic discoverable in one place.
|
|
188
237
|
async function listQwenCliProjectEntries() {
|
|
238
|
+
if (qwenCliProjectEntriesCache?.expiresAt > Date.now()) {
|
|
239
|
+
return qwenCliProjectEntriesCache.entries;
|
|
240
|
+
}
|
|
189
241
|
const qwenTmpDir = path.join(os.homedir(), '.qwen', 'tmp');
|
|
190
242
|
try {
|
|
191
243
|
await fs.access(qwenTmpDir);
|
|
@@ -219,9 +271,16 @@ async function listQwenCliProjectEntries() {
|
|
|
219
271
|
normalizedProjectRoot,
|
|
220
272
|
});
|
|
221
273
|
}
|
|
274
|
+
qwenCliProjectEntriesCache = {
|
|
275
|
+
entries,
|
|
276
|
+
expiresAt: Date.now() + GEMINI_QWEN_PROJECT_ENTRY_CACHE_TTL_MS,
|
|
277
|
+
};
|
|
222
278
|
return entries;
|
|
223
279
|
}
|
|
224
280
|
async function listGeminiCliProjectEntries() {
|
|
281
|
+
if (geminiCliProjectEntriesCache?.expiresAt > Date.now()) {
|
|
282
|
+
return geminiCliProjectEntriesCache.entries;
|
|
283
|
+
}
|
|
225
284
|
const geminiTmpDir = path.join(os.homedir(), '.gemini', 'tmp');
|
|
226
285
|
try {
|
|
227
286
|
await fs.access(geminiTmpDir);
|
|
@@ -256,8 +315,19 @@ async function listGeminiCliProjectEntries() {
|
|
|
256
315
|
normalizedProjectRoot,
|
|
257
316
|
});
|
|
258
317
|
}
|
|
318
|
+
geminiCliProjectEntriesCache = {
|
|
319
|
+
entries,
|
|
320
|
+
expiresAt: Date.now() + GEMINI_QWEN_PROJECT_ENTRY_CACHE_TTL_MS,
|
|
321
|
+
};
|
|
259
322
|
return entries;
|
|
260
323
|
}
|
|
324
|
+
async function readJsonTextFileLimited(filePath, maxBytes = CLI_CHAT_FILE_READ_MAX_BYTES) {
|
|
325
|
+
const stat = await fs.stat(filePath);
|
|
326
|
+
if (!stat.isFile() || stat.size > maxBytes) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
return fs.readFile(filePath, 'utf8');
|
|
330
|
+
}
|
|
261
331
|
function addDiscoveredProject(discoveredProjectsByPath, projectPath, provider) {
|
|
262
332
|
if (typeof projectPath !== 'string' || projectPath.trim().length === 0) {
|
|
263
333
|
return;
|
|
@@ -1436,26 +1506,40 @@ function normalizeComparablePath(inputPath) {
|
|
|
1436
1506
|
const resolved = path.resolve(normalized);
|
|
1437
1507
|
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
1438
1508
|
}
|
|
1439
|
-
async function findCodexJsonlFiles(dir) {
|
|
1509
|
+
async function findCodexJsonlFiles(dir, { maxFiles = CODEX_SESSION_INDEX_MAX_FILES, maxDirs = CODEX_SESSION_INDEX_MAX_DIRS, } = {}) {
|
|
1440
1510
|
const files = [];
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1511
|
+
const pendingDirs = [dir];
|
|
1512
|
+
let visitedDirs = 0;
|
|
1513
|
+
while (pendingDirs.length > 0 && files.length < maxFiles && visitedDirs < maxDirs) {
|
|
1514
|
+
const currentDir = pendingDirs.pop();
|
|
1515
|
+
visitedDirs += 1;
|
|
1516
|
+
try {
|
|
1517
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
1518
|
+
entries.sort((a, b) => b.name.localeCompare(a.name));
|
|
1519
|
+
for (const entry of entries) {
|
|
1520
|
+
if (files.length >= maxFiles)
|
|
1521
|
+
break;
|
|
1522
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1523
|
+
if (entry.isDirectory()) {
|
|
1524
|
+
if (visitedDirs + pendingDirs.length < maxDirs) {
|
|
1525
|
+
pendingDirs.push(fullPath);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
else if (entry.name.endsWith('.jsonl')) {
|
|
1529
|
+
files.push(fullPath);
|
|
1530
|
+
}
|
|
1450
1531
|
}
|
|
1451
1532
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1533
|
+
catch {
|
|
1534
|
+
// Skip directories we can't read
|
|
1535
|
+
}
|
|
1455
1536
|
}
|
|
1456
1537
|
return files;
|
|
1457
1538
|
}
|
|
1458
1539
|
async function buildCodexSessionsIndex() {
|
|
1540
|
+
if (codexSessionsIndexCache?.expiresAt > Date.now()) {
|
|
1541
|
+
return codexSessionsIndexCache.sessionsByProject;
|
|
1542
|
+
}
|
|
1459
1543
|
const codexSessionsDir = path.join(os.homedir(), '.codex', 'sessions');
|
|
1460
1544
|
const sessionsByProject = new Map();
|
|
1461
1545
|
try {
|
|
@@ -1497,6 +1581,10 @@ async function buildCodexSessionsIndex() {
|
|
|
1497
1581
|
for (const sessions of sessionsByProject.values()) {
|
|
1498
1582
|
sessions.sort((a, b) => new Date(b.lastActivity) - new Date(a.lastActivity));
|
|
1499
1583
|
}
|
|
1584
|
+
codexSessionsIndexCache = {
|
|
1585
|
+
sessionsByProject,
|
|
1586
|
+
expiresAt: Date.now() + CODEX_SESSION_INDEX_TTL_MS,
|
|
1587
|
+
};
|
|
1500
1588
|
return sessionsByProject;
|
|
1501
1589
|
}
|
|
1502
1590
|
// Fetch Codex sessions for a given project path
|
|
@@ -1823,24 +1911,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1823
1911
|
async function deleteCodexSession(sessionId) {
|
|
1824
1912
|
try {
|
|
1825
1913
|
const codexSessionsDir = path.join(os.homedir(), '.codex', 'sessions');
|
|
1826
|
-
const
|
|
1827
|
-
const files = [];
|
|
1828
|
-
try {
|
|
1829
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1830
|
-
for (const entry of entries) {
|
|
1831
|
-
const fullPath = path.join(dir, entry.name);
|
|
1832
|
-
if (entry.isDirectory()) {
|
|
1833
|
-
files.push(...await findJsonlFiles(fullPath));
|
|
1834
|
-
}
|
|
1835
|
-
else if (entry.name.endsWith('.jsonl')) {
|
|
1836
|
-
files.push(fullPath);
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
catch (error) { }
|
|
1841
|
-
return files;
|
|
1842
|
-
};
|
|
1843
|
-
const jsonlFiles = await findJsonlFiles(codexSessionsDir);
|
|
1914
|
+
const jsonlFiles = await findCodexJsonlFiles(codexSessionsDir);
|
|
1844
1915
|
for (const filePath of jsonlFiles) {
|
|
1845
1916
|
const sessionData = await parseCodexSessionFile(filePath);
|
|
1846
1917
|
if (sessionData && sessionData.id === sessionId) {
|
|
@@ -2305,14 +2376,16 @@ async function searchGeminiSessionsForProject(projectPath, projectResult, allWor
|
|
|
2305
2376
|
catch {
|
|
2306
2377
|
continue;
|
|
2307
2378
|
}
|
|
2308
|
-
for (const chatFile of chatFiles) {
|
|
2379
|
+
for (const chatFile of chatFiles.slice(0, CLI_CHAT_FILES_PER_PROJECT_LIMIT)) {
|
|
2309
2380
|
if (getTotalMatches() >= limit || isAborted())
|
|
2310
2381
|
break;
|
|
2311
2382
|
if (!chatFile.endsWith('.json'))
|
|
2312
2383
|
continue;
|
|
2313
2384
|
try {
|
|
2314
2385
|
const filePath = path.join(chatsDir, chatFile);
|
|
2315
|
-
const data = await
|
|
2386
|
+
const data = await readJsonTextFileLimited(filePath);
|
|
2387
|
+
if (!data)
|
|
2388
|
+
continue;
|
|
2316
2389
|
const session = JSON.parse(data);
|
|
2317
2390
|
if (!session.messages || !Array.isArray(session.messages))
|
|
2318
2391
|
continue;
|
|
@@ -2401,12 +2474,14 @@ async function getQwenCliSessions(projectPath, options = {}) {
|
|
|
2401
2474
|
catch {
|
|
2402
2475
|
continue;
|
|
2403
2476
|
}
|
|
2404
|
-
for (const chatFile of chatFiles) {
|
|
2477
|
+
for (const chatFile of chatFiles.slice(0, CLI_CHAT_FILES_PER_PROJECT_LIMIT)) {
|
|
2405
2478
|
if (!chatFile.endsWith('.json'))
|
|
2406
2479
|
continue;
|
|
2407
2480
|
try {
|
|
2408
2481
|
const filePath = path.join(chatsDir, chatFile);
|
|
2409
|
-
const data = await
|
|
2482
|
+
const data = await readJsonTextFileLimited(filePath);
|
|
2483
|
+
if (!data)
|
|
2484
|
+
continue;
|
|
2410
2485
|
const session = JSON.parse(data);
|
|
2411
2486
|
if (!session.messages || !Array.isArray(session.messages))
|
|
2412
2487
|
continue;
|
|
@@ -2480,11 +2555,13 @@ async function getOpencodeCliSessions(projectPath) {
|
|
|
2480
2555
|
catch {
|
|
2481
2556
|
continue;
|
|
2482
2557
|
}
|
|
2483
|
-
for (const file of sessionFiles) {
|
|
2558
|
+
for (const file of sessionFiles.slice(0, CLI_CHAT_FILES_PER_PROJECT_LIMIT)) {
|
|
2484
2559
|
if (!file.startsWith('ses_') || !file.endsWith('.json'))
|
|
2485
2560
|
continue;
|
|
2486
2561
|
try {
|
|
2487
|
-
const data = await
|
|
2562
|
+
const data = await readJsonTextFileLimited(path.join(dir, file));
|
|
2563
|
+
if (!data)
|
|
2564
|
+
continue;
|
|
2488
2565
|
const sess = JSON.parse(data);
|
|
2489
2566
|
const sessDir = typeof sess?.directory === 'string'
|
|
2490
2567
|
? normalizeComparablePath(sess.directory)
|
|
@@ -2547,12 +2624,14 @@ async function getQwenCliSessionMessages(sessionId) {
|
|
|
2547
2624
|
catch {
|
|
2548
2625
|
continue;
|
|
2549
2626
|
}
|
|
2550
|
-
for (const chatFile of chatFiles) {
|
|
2627
|
+
for (const chatFile of chatFiles.slice(0, CLI_CHAT_FILES_PER_PROJECT_LIMIT)) {
|
|
2551
2628
|
if (!chatFile.endsWith('.json'))
|
|
2552
2629
|
continue;
|
|
2553
2630
|
try {
|
|
2554
2631
|
const filePath = path.join(chatsDir, chatFile);
|
|
2555
|
-
const data = await
|
|
2632
|
+
const data = await readJsonTextFileLimited(filePath);
|
|
2633
|
+
if (!data)
|
|
2634
|
+
continue;
|
|
2556
2635
|
const session = JSON.parse(data);
|
|
2557
2636
|
const fileSessionId = session.sessionId || chatFile.replace('.json', '');
|
|
2558
2637
|
if (fileSessionId !== sessionId)
|
|
@@ -2605,12 +2684,14 @@ async function getGeminiCliSessions(projectPath, options = {}) {
|
|
|
2605
2684
|
catch {
|
|
2606
2685
|
continue;
|
|
2607
2686
|
}
|
|
2608
|
-
for (const chatFile of chatFiles) {
|
|
2687
|
+
for (const chatFile of chatFiles.slice(0, CLI_CHAT_FILES_PER_PROJECT_LIMIT)) {
|
|
2609
2688
|
if (!chatFile.endsWith('.json'))
|
|
2610
2689
|
continue;
|
|
2611
2690
|
try {
|
|
2612
2691
|
const filePath = path.join(chatsDir, chatFile);
|
|
2613
|
-
const data = await
|
|
2692
|
+
const data = await readJsonTextFileLimited(filePath);
|
|
2693
|
+
if (!data)
|
|
2694
|
+
continue;
|
|
2614
2695
|
const session = JSON.parse(data);
|
|
2615
2696
|
if (!session.messages || !Array.isArray(session.messages))
|
|
2616
2697
|
continue;
|
|
@@ -2658,12 +2739,14 @@ async function getGeminiCliSessionMessages(sessionId) {
|
|
|
2658
2739
|
catch {
|
|
2659
2740
|
continue;
|
|
2660
2741
|
}
|
|
2661
|
-
for (const chatFile of chatFiles) {
|
|
2742
|
+
for (const chatFile of chatFiles.slice(0, CLI_CHAT_FILES_PER_PROJECT_LIMIT)) {
|
|
2662
2743
|
if (!chatFile.endsWith('.json'))
|
|
2663
2744
|
continue;
|
|
2664
2745
|
try {
|
|
2665
2746
|
const filePath = path.join(chatsDir, chatFile);
|
|
2666
|
-
const data = await
|
|
2747
|
+
const data = await readJsonTextFileLimited(filePath);
|
|
2748
|
+
if (!data)
|
|
2749
|
+
continue;
|
|
2667
2750
|
const session = JSON.parse(data);
|
|
2668
2751
|
const fileSessionId = session.sessionId || chatFile.replace('.json', '');
|
|
2669
2752
|
if (fileSessionId !== sessionId)
|