archicore 0.2.4 → 0.2.6
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/cli/commands/interactive.js +336 -89
- package/dist/cli/utils/conversation-history.d.ts +84 -0
- package/dist/cli/utils/conversation-history.js +301 -0
- package/dist/cli/utils/index.d.ts +2 -0
- package/dist/cli/utils/index.js +2 -0
- package/dist/cli/utils/upload-utils.d.ts +66 -0
- package/dist/cli/utils/upload-utils.js +521 -0
- package/dist/server/routes/api.js +140 -1
- package/package.json +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchiCore CLI - Conversation History Management
|
|
3
|
+
*
|
|
4
|
+
* Сохранение и загрузка истории диалогов между сессиями
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, writeFile, mkdir, readdir, unlink, stat } from 'fs/promises';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
const HISTORY_DIR = join(homedir(), '.archicore', 'history');
|
|
11
|
+
const MAX_HISTORY_FILES = 50; // Максимум файлов истории
|
|
12
|
+
const MAX_MESSAGES_PER_SESSION = 1000; // Максимум сообщений в сессии
|
|
13
|
+
/**
|
|
14
|
+
* Генерация уникального ID сессии
|
|
15
|
+
*/
|
|
16
|
+
function generateSessionId() {
|
|
17
|
+
const now = new Date();
|
|
18
|
+
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
19
|
+
const timeStr = now.toISOString().slice(11, 19).replace(/:/g, '');
|
|
20
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
21
|
+
return `session_${dateStr}_${timeStr}_${random}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Получить путь к файлу сессии
|
|
25
|
+
*/
|
|
26
|
+
function getSessionPath(sessionId) {
|
|
27
|
+
return join(HISTORY_DIR, `${sessionId}.json`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Инициализация директории истории
|
|
31
|
+
*/
|
|
32
|
+
async function ensureHistoryDir() {
|
|
33
|
+
if (!existsSync(HISTORY_DIR)) {
|
|
34
|
+
await mkdir(HISTORY_DIR, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Очистка старых файлов истории
|
|
39
|
+
*/
|
|
40
|
+
async function cleanupOldSessions() {
|
|
41
|
+
try {
|
|
42
|
+
await ensureHistoryDir();
|
|
43
|
+
const files = await readdir(HISTORY_DIR);
|
|
44
|
+
const sessionFiles = files.filter(f => f.startsWith('session_') && f.endsWith('.json'));
|
|
45
|
+
if (sessionFiles.length <= MAX_HISTORY_FILES)
|
|
46
|
+
return;
|
|
47
|
+
// Получаем даты файлов и сортируем
|
|
48
|
+
const fileStats = await Promise.all(sessionFiles.map(async (f) => {
|
|
49
|
+
const filePath = join(HISTORY_DIR, f);
|
|
50
|
+
const stats = await stat(filePath);
|
|
51
|
+
return { file: f, mtime: stats.mtime.getTime() };
|
|
52
|
+
}));
|
|
53
|
+
fileStats.sort((a, b) => a.mtime - b.mtime);
|
|
54
|
+
// Удаляем самые старые
|
|
55
|
+
const toDelete = fileStats.slice(0, fileStats.length - MAX_HISTORY_FILES);
|
|
56
|
+
for (const item of toDelete) {
|
|
57
|
+
await unlink(join(HISTORY_DIR, item.file));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Игнорируем ошибки очистки
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Создать новую сессию разговора
|
|
66
|
+
*/
|
|
67
|
+
export async function createSession(projectId, projectName) {
|
|
68
|
+
await ensureHistoryDir();
|
|
69
|
+
await cleanupOldSessions();
|
|
70
|
+
const session = {
|
|
71
|
+
id: generateSessionId(),
|
|
72
|
+
projectId,
|
|
73
|
+
projectName,
|
|
74
|
+
startedAt: new Date().toISOString(),
|
|
75
|
+
lastActivityAt: new Date().toISOString(),
|
|
76
|
+
messages: [],
|
|
77
|
+
metadata: {
|
|
78
|
+
totalTokens: 0,
|
|
79
|
+
commandsUsed: [],
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
await saveSession(session);
|
|
83
|
+
return session;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Сохранить сессию на диск
|
|
87
|
+
*/
|
|
88
|
+
export async function saveSession(session) {
|
|
89
|
+
await ensureHistoryDir();
|
|
90
|
+
const sessionPath = getSessionPath(session.id);
|
|
91
|
+
await writeFile(sessionPath, JSON.stringify(session, null, 2));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Загрузить сессию с диска
|
|
95
|
+
*/
|
|
96
|
+
export async function loadSession(sessionId) {
|
|
97
|
+
try {
|
|
98
|
+
const sessionPath = getSessionPath(sessionId);
|
|
99
|
+
if (!existsSync(sessionPath))
|
|
100
|
+
return null;
|
|
101
|
+
const data = await readFile(sessionPath, 'utf-8');
|
|
102
|
+
return JSON.parse(data);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Загрузить последнюю сессию для проекта
|
|
110
|
+
*/
|
|
111
|
+
export async function loadLastSession(projectId) {
|
|
112
|
+
try {
|
|
113
|
+
await ensureHistoryDir();
|
|
114
|
+
const files = await readdir(HISTORY_DIR);
|
|
115
|
+
const sessionFiles = files.filter(f => f.startsWith('session_') && f.endsWith('.json'));
|
|
116
|
+
if (sessionFiles.length === 0)
|
|
117
|
+
return null;
|
|
118
|
+
// Сортируем по имени (содержит дату) в обратном порядке
|
|
119
|
+
sessionFiles.sort().reverse();
|
|
120
|
+
// Ищем последнюю сессию для проекта
|
|
121
|
+
for (const file of sessionFiles) {
|
|
122
|
+
const session = await loadSession(file.replace('.json', ''));
|
|
123
|
+
if (session) {
|
|
124
|
+
if (!projectId || session.projectId === projectId) {
|
|
125
|
+
return session;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Добавить сообщение в сессию
|
|
137
|
+
*/
|
|
138
|
+
export async function addMessage(session, role, content, options) {
|
|
139
|
+
// Ограничение на количество сообщений
|
|
140
|
+
if (session.messages.length >= MAX_MESSAGES_PER_SESSION) {
|
|
141
|
+
// Удаляем самые старые сообщения, оставляя последние 80%
|
|
142
|
+
const keepCount = Math.floor(MAX_MESSAGES_PER_SESSION * 0.8);
|
|
143
|
+
session.messages = session.messages.slice(-keepCount);
|
|
144
|
+
}
|
|
145
|
+
const message = {
|
|
146
|
+
role,
|
|
147
|
+
content,
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
command: options?.command,
|
|
150
|
+
tokens: options?.tokens,
|
|
151
|
+
};
|
|
152
|
+
session.messages.push(message);
|
|
153
|
+
session.lastActivityAt = new Date().toISOString();
|
|
154
|
+
// Обновляем метаданные
|
|
155
|
+
if (session.metadata) {
|
|
156
|
+
if (options?.tokens) {
|
|
157
|
+
session.metadata.totalTokens = (session.metadata.totalTokens || 0) + options.tokens;
|
|
158
|
+
}
|
|
159
|
+
if (options?.command && !session.metadata.commandsUsed?.includes(options.command)) {
|
|
160
|
+
session.metadata.commandsUsed = session.metadata.commandsUsed || [];
|
|
161
|
+
session.metadata.commandsUsed.push(options.command);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
await saveSession(session);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Получить контекст для AI (последние N сообщений)
|
|
168
|
+
*/
|
|
169
|
+
export function getContextMessages(session, maxMessages = 20, maxTokens = 4000) {
|
|
170
|
+
const messages = session.messages.slice(-maxMessages * 2); // Берём с запасом
|
|
171
|
+
const result = [];
|
|
172
|
+
let totalTokens = 0;
|
|
173
|
+
// Идём с конца, собирая сообщения пока не превысим лимит токенов
|
|
174
|
+
for (let i = messages.length - 1; i >= 0 && result.length < maxMessages; i--) {
|
|
175
|
+
const msg = messages[i];
|
|
176
|
+
const msgTokens = msg.tokens || Math.ceil(msg.content.length / 4);
|
|
177
|
+
if (totalTokens + msgTokens > maxTokens && result.length > 0)
|
|
178
|
+
break;
|
|
179
|
+
result.unshift(msg);
|
|
180
|
+
totalTokens += msgTokens;
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Получить список всех сессий
|
|
186
|
+
*/
|
|
187
|
+
export async function listSessions(options) {
|
|
188
|
+
try {
|
|
189
|
+
await ensureHistoryDir();
|
|
190
|
+
const files = await readdir(HISTORY_DIR);
|
|
191
|
+
const sessionFiles = files.filter(f => f.startsWith('session_') && f.endsWith('.json'));
|
|
192
|
+
// Сортируем по имени (содержит дату) в обратном порядке
|
|
193
|
+
sessionFiles.sort().reverse();
|
|
194
|
+
const sessions = [];
|
|
195
|
+
const limit = options?.limit || 20;
|
|
196
|
+
for (const file of sessionFiles) {
|
|
197
|
+
if (sessions.length >= limit)
|
|
198
|
+
break;
|
|
199
|
+
const session = await loadSession(file.replace('.json', ''));
|
|
200
|
+
if (!session)
|
|
201
|
+
continue;
|
|
202
|
+
// Фильтр по проекту
|
|
203
|
+
if (options?.projectId && session.projectId !== options.projectId)
|
|
204
|
+
continue;
|
|
205
|
+
// Фильтр по поиску
|
|
206
|
+
if (options?.search) {
|
|
207
|
+
const searchLower = options.search.toLowerCase();
|
|
208
|
+
const hasMatch = session.messages.some(m => m.content.toLowerCase().includes(searchLower));
|
|
209
|
+
if (!hasMatch)
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
sessions.push(session);
|
|
213
|
+
}
|
|
214
|
+
return sessions;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Поиск по истории сообщений
|
|
222
|
+
*/
|
|
223
|
+
export async function searchHistory(query, options) {
|
|
224
|
+
const sessions = await listSessions({ projectId: options?.projectId, limit: 100 });
|
|
225
|
+
const results = [];
|
|
226
|
+
const queryLower = query.toLowerCase();
|
|
227
|
+
const limit = options?.limit || 20;
|
|
228
|
+
for (const session of sessions) {
|
|
229
|
+
for (let i = 0; i < session.messages.length; i++) {
|
|
230
|
+
if (results.length >= limit)
|
|
231
|
+
break;
|
|
232
|
+
const message = session.messages[i];
|
|
233
|
+
// Фильтр по роли
|
|
234
|
+
if (options?.role && message.role !== options.role)
|
|
235
|
+
continue;
|
|
236
|
+
// Поиск по содержимому
|
|
237
|
+
if (message.content.toLowerCase().includes(queryLower)) {
|
|
238
|
+
results.push({ session, message, index: i });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Удалить сессию
|
|
246
|
+
*/
|
|
247
|
+
export async function deleteSession(sessionId) {
|
|
248
|
+
try {
|
|
249
|
+
const sessionPath = getSessionPath(sessionId);
|
|
250
|
+
if (existsSync(sessionPath)) {
|
|
251
|
+
await unlink(sessionPath);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Очистить всю историю
|
|
262
|
+
*/
|
|
263
|
+
export async function clearAllHistory() {
|
|
264
|
+
try {
|
|
265
|
+
await ensureHistoryDir();
|
|
266
|
+
const files = await readdir(HISTORY_DIR);
|
|
267
|
+
const sessionFiles = files.filter(f => f.startsWith('session_') && f.endsWith('.json'));
|
|
268
|
+
for (const file of sessionFiles) {
|
|
269
|
+
await unlink(join(HISTORY_DIR, file));
|
|
270
|
+
}
|
|
271
|
+
return sessionFiles.length;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Экспорт сессии в текстовый формат
|
|
279
|
+
*/
|
|
280
|
+
export function exportSessionAsText(session) {
|
|
281
|
+
const lines = [
|
|
282
|
+
`=== ArchiCore Conversation ===`,
|
|
283
|
+
`Session: ${session.id}`,
|
|
284
|
+
`Project: ${session.projectName || 'N/A'}`,
|
|
285
|
+
`Started: ${new Date(session.startedAt).toLocaleString()}`,
|
|
286
|
+
`Messages: ${session.messages.length}`,
|
|
287
|
+
``,
|
|
288
|
+
`--- Conversation ---`,
|
|
289
|
+
``,
|
|
290
|
+
];
|
|
291
|
+
for (const msg of session.messages) {
|
|
292
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
293
|
+
const role = msg.role === 'user' ? 'You' : msg.role === 'assistant' ? 'ArchiCore' : 'System';
|
|
294
|
+
const command = msg.command ? ` [${msg.command}]` : '';
|
|
295
|
+
lines.push(`[${time}] ${role}${command}:`);
|
|
296
|
+
lines.push(msg.content);
|
|
297
|
+
lines.push('');
|
|
298
|
+
}
|
|
299
|
+
return lines.join('\n');
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=conversation-history.js.map
|
|
@@ -5,4 +5,6 @@ export * from './config.js';
|
|
|
5
5
|
export * from './session.js';
|
|
6
6
|
export { fetchUserProjects, selectProject as selectProjectInteractive, quickProjectSwitch, displayProjectInfo, searchProjects, type ProjectInfo as ProjectSelectorInfo, } from './project-selector.js';
|
|
7
7
|
export * from './error-handler.js';
|
|
8
|
+
export * from './conversation-history.js';
|
|
9
|
+
export * from './upload-utils.js';
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cli/utils/index.js
CHANGED
|
@@ -6,4 +6,6 @@ export * from './session.js';
|
|
|
6
6
|
// project-selector has renamed exports to avoid conflicts with session.js
|
|
7
7
|
export { fetchUserProjects, selectProject as selectProjectInteractive, quickProjectSwitch, displayProjectInfo, searchProjects, } from './project-selector.js';
|
|
8
8
|
export * from './error-handler.js';
|
|
9
|
+
export * from './conversation-history.js';
|
|
10
|
+
export * from './upload-utils.js';
|
|
9
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchiCore CLI - Upload Utilities
|
|
3
|
+
*
|
|
4
|
+
* Утилиты для загрузки больших проектов:
|
|
5
|
+
* - Chunked uploads для больших файлов
|
|
6
|
+
* - Retry логика
|
|
7
|
+
* - Детальная обработка ошибок
|
|
8
|
+
*/
|
|
9
|
+
export interface UploadProgress {
|
|
10
|
+
phase: 'preparing' | 'uploading' | 'processing' | 'done' | 'error';
|
|
11
|
+
current: number;
|
|
12
|
+
total: number;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
export interface UploadResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
statistics?: {
|
|
18
|
+
filesCount: number;
|
|
19
|
+
symbolsCount: number;
|
|
20
|
+
nodesCount?: number;
|
|
21
|
+
edgesCount?: number;
|
|
22
|
+
};
|
|
23
|
+
error?: string;
|
|
24
|
+
errorDetails?: {
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
suggestion: string;
|
|
28
|
+
technicalDetails?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface IndexData {
|
|
32
|
+
asts: Array<[string, unknown]>;
|
|
33
|
+
symbols: Array<[string, unknown]>;
|
|
34
|
+
graph: {
|
|
35
|
+
nodes: Array<[string, unknown]>;
|
|
36
|
+
edges: Array<[string, unknown[]]>;
|
|
37
|
+
};
|
|
38
|
+
fileContents: Array<[string, string]>;
|
|
39
|
+
statistics: {
|
|
40
|
+
totalFiles: number;
|
|
41
|
+
totalSymbols: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Детальный анализ ошибки fetch
|
|
46
|
+
*/
|
|
47
|
+
export declare function analyzeNetworkError(error: unknown): {
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
suggestion: string;
|
|
51
|
+
technicalDetails: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Детальный анализ HTTP ошибки
|
|
55
|
+
*/
|
|
56
|
+
export declare function analyzeHttpError(status: number, responseBody: unknown): {
|
|
57
|
+
code: string;
|
|
58
|
+
message: string;
|
|
59
|
+
suggestion: string;
|
|
60
|
+
technicalDetails: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Загрузка индекса с автоматическим chunked upload для больших проектов
|
|
64
|
+
*/
|
|
65
|
+
export declare function uploadIndexData(projectId: string, data: IndexData, onProgress?: (progress: UploadProgress) => void): Promise<UploadResult>;
|
|
66
|
+
//# sourceMappingURL=upload-utils.d.ts.map
|