@pixelbyte-software/pixcode 1.53.17 → 1.53.19
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-ByaiDQ05.js → index-CvIHrHIG.js} +195 -184
- package/dist/assets/index-DvudRU5X.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +246 -99
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/projects.js +96 -78
- package/dist-server/server/projects.js.map +1 -1
- package/dist-server/server/routes/git.js +43 -29
- package/dist-server/server/routes/git.js.map +1 -1
- package/package.json +2 -1
- package/scripts/smoke/backend-resource-bounds.mjs +91 -0
- package/scripts/smoke/mobile-ux.mjs +28 -2
- package/server/index.js +262 -99
- package/server/projects.js +100 -76
- package/server/routes/git.js +42 -30
- package/dist/assets/index-nJQ4Q6hZ.css +0 -32
|
@@ -71,6 +71,7 @@ const FILE_COUNT_SCAN_MAX_MS = 2500;
|
|
|
71
71
|
const CODEX_SESSION_INDEX_TTL_MS = 60 * 1000;
|
|
72
72
|
const CODEX_SESSION_INDEX_MAX_FILES = 1200;
|
|
73
73
|
const CODEX_SESSION_INDEX_MAX_DIRS = 2500;
|
|
74
|
+
const CLAUDE_JSONL_LINE_MAX_CHARS = 1024 * 1024;
|
|
74
75
|
const GEMINI_QWEN_PROJECT_ENTRY_CACHE_TTL_MS = 60 * 1000;
|
|
75
76
|
const CLI_CHAT_FILE_READ_MAX_BYTES = 2 * 1024 * 1024;
|
|
76
77
|
const CLI_CHAT_FILES_PER_PROJECT_LIMIT = 150;
|
|
@@ -838,10 +839,11 @@ async function getSessions(projectName, limit = 5, offset = 0) {
|
|
|
838
839
|
}));
|
|
839
840
|
filesWithStats.sort((a, b) => b.mtime - a.mtime);
|
|
840
841
|
const allSessions = new Map();
|
|
841
|
-
const
|
|
842
|
-
const uuidToSessionMap = new Map();
|
|
842
|
+
const sessionFirstUserMessageIds = new Map();
|
|
843
843
|
// Collect all sessions and entries from all files
|
|
844
|
+
let filesScanned = 0;
|
|
844
845
|
for (const { file } of filesWithStats) {
|
|
846
|
+
filesScanned++;
|
|
845
847
|
const jsonlFile = path.join(projectDir, file);
|
|
846
848
|
const result = await parseJsonlSessions(jsonlFile);
|
|
847
849
|
result.sessions.forEach(session => {
|
|
@@ -849,29 +851,25 @@ async function getSessions(projectName, limit = 5, offset = 0) {
|
|
|
849
851
|
allSessions.set(session.id, session);
|
|
850
852
|
}
|
|
851
853
|
});
|
|
852
|
-
|
|
854
|
+
result.firstUserMessageIds.forEach((firstUserMsgId, sessionId) => {
|
|
855
|
+
if (!sessionFirstUserMessageIds.has(sessionId)) {
|
|
856
|
+
sessionFirstUserMessageIds.set(sessionId, firstUserMsgId);
|
|
857
|
+
}
|
|
858
|
+
});
|
|
853
859
|
// Early exit optimization for large projects
|
|
854
|
-
if (allSessions.size >= (limit + offset) * 2 &&
|
|
860
|
+
if (allSessions.size >= (limit + offset) * 2 && filesScanned >= Math.min(3, filesWithStats.length)) {
|
|
855
861
|
break;
|
|
856
862
|
}
|
|
857
863
|
}
|
|
858
|
-
// Build UUID-to-session mapping for timeline detection
|
|
859
|
-
allEntries.forEach(entry => {
|
|
860
|
-
if (entry.uuid && entry.sessionId) {
|
|
861
|
-
uuidToSessionMap.set(entry.uuid, entry.sessionId);
|
|
862
|
-
}
|
|
863
|
-
});
|
|
864
864
|
// Group sessions by first user message ID
|
|
865
865
|
const sessionGroups = new Map(); // firstUserMsgId -> { latestSession, allSessions[] }
|
|
866
866
|
const sessionToFirstUserMsgId = new Map(); // sessionId -> firstUserMsgId
|
|
867
867
|
// Find the first user message for each session
|
|
868
|
-
|
|
869
|
-
if (
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
sessionToFirstUserMsgId.set(entry.sessionId, firstUserMsgId);
|
|
874
|
-
const session = allSessions.get(entry.sessionId);
|
|
868
|
+
sessionFirstUserMessageIds.forEach((firstUserMsgId, sessionId) => {
|
|
869
|
+
if (sessionId && firstUserMsgId) {
|
|
870
|
+
if (!sessionToFirstUserMsgId.has(sessionId)) {
|
|
871
|
+
sessionToFirstUserMsgId.set(sessionId, firstUserMsgId);
|
|
872
|
+
const session = allSessions.get(sessionId);
|
|
875
873
|
if (session) {
|
|
876
874
|
if (!sessionGroups.has(firstUserMsgId)) {
|
|
877
875
|
sessionGroups.set(firstUserMsgId, {
|
|
@@ -930,7 +928,7 @@ async function getSessions(projectName, limit = 5, offset = 0) {
|
|
|
930
928
|
}
|
|
931
929
|
async function parseJsonlSessions(filePath) {
|
|
932
930
|
const sessions = new Map();
|
|
933
|
-
const
|
|
931
|
+
const firstUserMessageIds = new Map();
|
|
934
932
|
const pendingSummaries = new Map(); // leafUuid -> summary for entries without sessionId
|
|
935
933
|
try {
|
|
936
934
|
const fileStream = fsSync.createReadStream(filePath);
|
|
@@ -940,9 +938,11 @@ async function parseJsonlSessions(filePath) {
|
|
|
940
938
|
});
|
|
941
939
|
for await (const line of rl) {
|
|
942
940
|
if (line.trim()) {
|
|
941
|
+
if (line.length > CLAUDE_JSONL_LINE_MAX_CHARS) {
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
943
944
|
try {
|
|
944
945
|
const entry = JSON.parse(line);
|
|
945
|
-
entries.push(entry);
|
|
946
946
|
// Handle summary entries that don't have sessionId yet
|
|
947
947
|
if (entry.type === 'summary' && entry.summary && !entry.sessionId && entry.leafUuid) {
|
|
948
948
|
pendingSummaries.set(entry.leafUuid, entry.summary);
|
|
@@ -960,6 +960,12 @@ async function parseJsonlSessions(filePath) {
|
|
|
960
960
|
});
|
|
961
961
|
}
|
|
962
962
|
const session = sessions.get(entry.sessionId);
|
|
963
|
+
if (entry.type === 'user'
|
|
964
|
+
&& entry.parentUuid === null
|
|
965
|
+
&& entry.uuid
|
|
966
|
+
&& !firstUserMessageIds.has(entry.sessionId)) {
|
|
967
|
+
firstUserMessageIds.set(entry.sessionId, entry.uuid);
|
|
968
|
+
}
|
|
963
969
|
// Apply pending summary if this entry has a parentUuid that matches a pending summary
|
|
964
970
|
if (session.summary === 'New Session' && entry.parentUuid && pendingSummaries.has(entry.parentUuid)) {
|
|
965
971
|
session.summary = pendingSummaries.get(entry.parentUuid);
|
|
@@ -1053,12 +1059,12 @@ async function parseJsonlSessions(filePath) {
|
|
|
1053
1059
|
});
|
|
1054
1060
|
return {
|
|
1055
1061
|
sessions: filteredSessions,
|
|
1056
|
-
|
|
1062
|
+
firstUserMessageIds,
|
|
1057
1063
|
};
|
|
1058
1064
|
}
|
|
1059
1065
|
catch (error) {
|
|
1060
1066
|
console.error('Error reading JSONL file:', error);
|
|
1061
|
-
return { sessions: [],
|
|
1067
|
+
return { sessions: [], firstUserMessageIds: new Map() };
|
|
1062
1068
|
}
|
|
1063
1069
|
}
|
|
1064
1070
|
// Parse an agent JSONL file and extract tool uses
|
|
@@ -1119,6 +1125,9 @@ async function parseAgentTools(filePath) {
|
|
|
1119
1125
|
// Get messages for a specific session with pagination support
|
|
1120
1126
|
async function getSessionMessages(projectName, sessionId, limit = null, offset = 0) {
|
|
1121
1127
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName);
|
|
1128
|
+
const boundedLimit = limit === null ? null : Math.max(0, Number(limit) || 0);
|
|
1129
|
+
const boundedOffset = Math.max(0, Number(offset) || 0);
|
|
1130
|
+
const maxBufferedMessages = boundedLimit === null ? Infinity : boundedLimit + boundedOffset;
|
|
1122
1131
|
try {
|
|
1123
1132
|
const files = await fs.readdir(projectDir);
|
|
1124
1133
|
// agent-*.jsonl files contain subagent tool history - we'll process them separately
|
|
@@ -1128,6 +1137,7 @@ async function getSessionMessages(projectName, sessionId, limit = null, offset =
|
|
|
1128
1137
|
return { messages: [], total: 0, hasMore: false };
|
|
1129
1138
|
}
|
|
1130
1139
|
const messages = [];
|
|
1140
|
+
let totalMessages = 0;
|
|
1131
1141
|
// Map of agentId -> tools for subagent tool grouping
|
|
1132
1142
|
const agentToolsCache = new Map();
|
|
1133
1143
|
// Process all JSONL files to find messages for this session
|
|
@@ -1141,9 +1151,16 @@ async function getSessionMessages(projectName, sessionId, limit = null, offset =
|
|
|
1141
1151
|
for await (const line of rl) {
|
|
1142
1152
|
if (line.trim()) {
|
|
1143
1153
|
try {
|
|
1154
|
+
if (line.length > CLAUDE_JSONL_LINE_MAX_CHARS) {
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1144
1157
|
const entry = JSON.parse(line);
|
|
1145
1158
|
if (entry.sessionId === sessionId) {
|
|
1159
|
+
totalMessages += 1;
|
|
1146
1160
|
messages.push(entry);
|
|
1161
|
+
if (messages.length > maxBufferedMessages) {
|
|
1162
|
+
messages.shift();
|
|
1163
|
+
}
|
|
1147
1164
|
}
|
|
1148
1165
|
}
|
|
1149
1166
|
catch (parseError) {
|
|
@@ -1180,28 +1197,28 @@ async function getSessionMessages(projectName, sessionId, limit = null, offset =
|
|
|
1180
1197
|
}
|
|
1181
1198
|
// Sort messages by timestamp
|
|
1182
1199
|
const sortedMessages = messages.sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0));
|
|
1183
|
-
const total = sortedMessages.length;
|
|
1200
|
+
const total = boundedLimit === null ? sortedMessages.length : totalMessages;
|
|
1184
1201
|
// If no limit is specified, return all messages (backward compatibility)
|
|
1185
|
-
if (
|
|
1202
|
+
if (boundedLimit === null) {
|
|
1186
1203
|
return sortedMessages;
|
|
1187
1204
|
}
|
|
1188
1205
|
// Apply pagination - for recent messages, we need to slice from the end
|
|
1189
1206
|
// offset 0 should give us the most recent messages
|
|
1190
|
-
const startIndex = Math.max(0,
|
|
1191
|
-
const endIndex =
|
|
1207
|
+
const startIndex = Math.max(0, sortedMessages.length - boundedOffset - boundedLimit);
|
|
1208
|
+
const endIndex = sortedMessages.length - boundedOffset;
|
|
1192
1209
|
const paginatedMessages = sortedMessages.slice(startIndex, endIndex);
|
|
1193
|
-
const hasMore =
|
|
1210
|
+
const hasMore = total > boundedOffset + boundedLimit;
|
|
1194
1211
|
return {
|
|
1195
1212
|
messages: paginatedMessages,
|
|
1196
1213
|
total,
|
|
1197
1214
|
hasMore,
|
|
1198
|
-
offset,
|
|
1199
|
-
limit
|
|
1215
|
+
offset: boundedOffset,
|
|
1216
|
+
limit: boundedLimit
|
|
1200
1217
|
};
|
|
1201
1218
|
}
|
|
1202
1219
|
catch (error) {
|
|
1203
1220
|
console.error(`Error reading messages for session ${sessionId}:`, error);
|
|
1204
|
-
return
|
|
1221
|
+
return boundedLimit === null ? [] : { messages: [], total: 0, hasMore: false };
|
|
1205
1222
|
}
|
|
1206
1223
|
}
|
|
1207
1224
|
// Rename a project's display name
|
|
@@ -1514,20 +1531,24 @@ async function findCodexJsonlFiles(dir, { maxFiles = CODEX_SESSION_INDEX_MAX_FIL
|
|
|
1514
1531
|
const currentDir = pendingDirs.pop();
|
|
1515
1532
|
visitedDirs += 1;
|
|
1516
1533
|
try {
|
|
1517
|
-
const
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1534
|
+
const dirHandle = await fs.opendir(currentDir);
|
|
1535
|
+
try {
|
|
1536
|
+
for await (const entry of dirHandle) {
|
|
1537
|
+
if (files.length >= maxFiles)
|
|
1538
|
+
break;
|
|
1539
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1540
|
+
if (entry.isDirectory()) {
|
|
1541
|
+
if (visitedDirs + pendingDirs.length < maxDirs) {
|
|
1542
|
+
pendingDirs.push(fullPath);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
else if (entry.name.endsWith('.jsonl')) {
|
|
1546
|
+
files.push(fullPath);
|
|
1526
1547
|
}
|
|
1527
1548
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1549
|
+
}
|
|
1550
|
+
finally {
|
|
1551
|
+
await dirHandle.close().catch(() => undefined);
|
|
1531
1552
|
}
|
|
1532
1553
|
}
|
|
1533
1554
|
catch {
|
|
@@ -1686,35 +1707,29 @@ async function parseCodexSessionFile(filePath) {
|
|
|
1686
1707
|
}
|
|
1687
1708
|
// Get messages for a specific Codex session
|
|
1688
1709
|
async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
1710
|
+
const boundedLimit = limit === null ? null : Math.max(0, Number(limit) || 0);
|
|
1711
|
+
const boundedOffset = Math.max(0, Number(offset) || 0);
|
|
1712
|
+
const maxBufferedMessages = boundedLimit === null ? Infinity : boundedLimit + boundedOffset;
|
|
1689
1713
|
try {
|
|
1690
1714
|
const codexSessionsDir = path.join(os.homedir(), '.codex', 'sessions');
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
const fullPath = path.join(dir, entry.name);
|
|
1697
|
-
if (entry.isDirectory()) {
|
|
1698
|
-
const found = await findSessionFile(fullPath);
|
|
1699
|
-
if (found)
|
|
1700
|
-
return found;
|
|
1701
|
-
}
|
|
1702
|
-
else if (entry.name.includes(sessionId) && entry.name.endsWith('.jsonl')) {
|
|
1703
|
-
return fullPath;
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
catch (error) {
|
|
1708
|
-
// Skip directories we can't read
|
|
1709
|
-
}
|
|
1710
|
-
return null;
|
|
1711
|
-
};
|
|
1712
|
-
const sessionFilePath = await findSessionFile(codexSessionsDir);
|
|
1715
|
+
const sessionFiles = await findCodexJsonlFiles(codexSessionsDir, {
|
|
1716
|
+
maxFiles: Math.max(CODEX_SESSION_INDEX_MAX_FILES, 10000),
|
|
1717
|
+
maxDirs: CODEX_SESSION_INDEX_MAX_DIRS,
|
|
1718
|
+
});
|
|
1719
|
+
const sessionFilePath = sessionFiles.find(filePath => path.basename(filePath).includes(sessionId)) || null;
|
|
1713
1720
|
if (!sessionFilePath) {
|
|
1714
1721
|
console.warn(`Codex session file not found for session ${sessionId}`);
|
|
1715
1722
|
return { messages: [], total: 0, hasMore: false };
|
|
1716
1723
|
}
|
|
1717
1724
|
const messages = [];
|
|
1725
|
+
let totalMessages = 0;
|
|
1726
|
+
const pushMessage = (message) => {
|
|
1727
|
+
totalMessages += 1;
|
|
1728
|
+
messages.push(message);
|
|
1729
|
+
if (messages.length > maxBufferedMessages) {
|
|
1730
|
+
messages.shift();
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1718
1733
|
let tokenUsage = null;
|
|
1719
1734
|
const fileStream = fsSync.createReadStream(sessionFilePath);
|
|
1720
1735
|
const rl = readline.createInterface({
|
|
@@ -1741,6 +1756,9 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1741
1756
|
for await (const line of rl) {
|
|
1742
1757
|
if (line.trim()) {
|
|
1743
1758
|
try {
|
|
1759
|
+
if (line.length > CLAUDE_JSONL_LINE_MAX_CHARS) {
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1744
1762
|
const entry = JSON.parse(line);
|
|
1745
1763
|
// Extract token usage from token_count events (keep latest)
|
|
1746
1764
|
if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) {
|
|
@@ -1754,7 +1772,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1754
1772
|
}
|
|
1755
1773
|
// Use event_msg.user_message for user-visible inputs.
|
|
1756
1774
|
if (entry.type === 'event_msg' && isVisibleCodexUserMessage(entry.payload)) {
|
|
1757
|
-
|
|
1775
|
+
pushMessage({
|
|
1758
1776
|
type: 'user',
|
|
1759
1777
|
timestamp: entry.timestamp,
|
|
1760
1778
|
message: {
|
|
@@ -1772,7 +1790,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1772
1790
|
const textContent = extractText(content);
|
|
1773
1791
|
// Only add if there's actual content
|
|
1774
1792
|
if (textContent?.trim()) {
|
|
1775
|
-
|
|
1793
|
+
pushMessage({
|
|
1776
1794
|
type: 'assistant',
|
|
1777
1795
|
timestamp: entry.timestamp,
|
|
1778
1796
|
message: {
|
|
@@ -1788,7 +1806,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1788
1806
|
.filter(Boolean)
|
|
1789
1807
|
.join('\n');
|
|
1790
1808
|
if (summaryText?.trim()) {
|
|
1791
|
-
|
|
1809
|
+
pushMessage({
|
|
1792
1810
|
type: 'thinking',
|
|
1793
1811
|
timestamp: entry.timestamp,
|
|
1794
1812
|
message: {
|
|
@@ -1812,7 +1830,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1812
1830
|
// Keep original if parsing fails
|
|
1813
1831
|
}
|
|
1814
1832
|
}
|
|
1815
|
-
|
|
1833
|
+
pushMessage({
|
|
1816
1834
|
type: 'tool_use',
|
|
1817
1835
|
timestamp: entry.timestamp,
|
|
1818
1836
|
toolName: toolName,
|
|
@@ -1821,7 +1839,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1821
1839
|
});
|
|
1822
1840
|
}
|
|
1823
1841
|
if (entry.type === 'response_item' && entry.payload?.type === 'function_call_output') {
|
|
1824
|
-
|
|
1842
|
+
pushMessage({
|
|
1825
1843
|
type: 'tool_result',
|
|
1826
1844
|
timestamp: entry.timestamp,
|
|
1827
1845
|
toolCallId: entry.payload.call_id,
|
|
@@ -1847,7 +1865,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1847
1865
|
newLines.push(line.substring(1));
|
|
1848
1866
|
}
|
|
1849
1867
|
}
|
|
1850
|
-
|
|
1868
|
+
pushMessage({
|
|
1851
1869
|
type: 'tool_use',
|
|
1852
1870
|
timestamp: entry.timestamp,
|
|
1853
1871
|
toolName: 'Edit',
|
|
@@ -1860,7 +1878,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1860
1878
|
});
|
|
1861
1879
|
}
|
|
1862
1880
|
else {
|
|
1863
|
-
|
|
1881
|
+
pushMessage({
|
|
1864
1882
|
type: 'tool_use',
|
|
1865
1883
|
timestamp: entry.timestamp,
|
|
1866
1884
|
toolName: toolName,
|
|
@@ -1870,7 +1888,7 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1870
1888
|
}
|
|
1871
1889
|
}
|
|
1872
1890
|
if (entry.type === 'response_item' && entry.payload?.type === 'custom_tool_call_output') {
|
|
1873
|
-
|
|
1891
|
+
pushMessage({
|
|
1874
1892
|
type: 'tool_result',
|
|
1875
1893
|
timestamp: entry.timestamp,
|
|
1876
1894
|
toolCallId: entry.payload.call_id,
|
|
@@ -1885,19 +1903,19 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
|
|
|
1885
1903
|
}
|
|
1886
1904
|
// Sort by timestamp
|
|
1887
1905
|
messages.sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0));
|
|
1888
|
-
const total = messages.length;
|
|
1906
|
+
const total = boundedLimit === null ? messages.length : totalMessages;
|
|
1889
1907
|
// Apply pagination if limit is specified
|
|
1890
|
-
if (
|
|
1891
|
-
const startIndex = Math.max(0,
|
|
1892
|
-
const endIndex =
|
|
1908
|
+
if (boundedLimit !== null) {
|
|
1909
|
+
const startIndex = Math.max(0, messages.length - boundedOffset - boundedLimit);
|
|
1910
|
+
const endIndex = messages.length - boundedOffset;
|
|
1893
1911
|
const paginatedMessages = messages.slice(startIndex, endIndex);
|
|
1894
|
-
const hasMore =
|
|
1912
|
+
const hasMore = total > boundedOffset + boundedLimit;
|
|
1895
1913
|
return {
|
|
1896
1914
|
messages: paginatedMessages,
|
|
1897
1915
|
total,
|
|
1898
1916
|
hasMore,
|
|
1899
|
-
offset,
|
|
1900
|
-
limit,
|
|
1917
|
+
offset: boundedOffset,
|
|
1918
|
+
limit: boundedLimit,
|
|
1901
1919
|
tokenUsage
|
|
1902
1920
|
};
|
|
1903
1921
|
}
|