tarsk 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.js +92 -32
- package/dist/lib/response-builder.d.ts +50 -0
- package/dist/lib/response-builder.js +56 -0
- package/dist/lib/stream-helper.d.ts +39 -0
- package/dist/lib/stream-helper.js +43 -0
- package/dist/managers/ConversationManager.d.ts +83 -0
- package/dist/managers/ConversationManager.js +129 -0
- package/dist/managers/GitManager.d.ts +133 -0
- package/dist/managers/GitManager.js +330 -0
- package/dist/managers/MetadataManager.d.ts +139 -0
- package/dist/managers/MetadataManager.js +309 -0
- package/dist/managers/ModelManager.d.ts +57 -0
- package/dist/managers/ModelManager.js +129 -0
- package/dist/managers/NeovateExecutor.d.ts +40 -0
- package/dist/managers/NeovateExecutor.js +138 -0
- package/dist/managers/ProjectManager.d.ts +162 -0
- package/dist/managers/ProjectManager.js +353 -0
- package/dist/managers/ThreadManager.d.ts +181 -0
- package/dist/managers/ThreadManager.js +325 -0
- package/dist/managers/conversation-manager.d.ts +83 -0
- package/dist/managers/conversation-manager.js +129 -0
- package/dist/managers/git-manager.d.ts +133 -0
- package/dist/managers/git-manager.js +330 -0
- package/dist/managers/metadata-manager.d.ts +139 -0
- package/dist/managers/metadata-manager.js +305 -0
- package/dist/managers/model-manager.d.ts +59 -0
- package/dist/managers/model-manager.js +144 -0
- package/dist/managers/neovate-executor.d.ts +43 -0
- package/dist/managers/neovate-executor.js +205 -0
- package/dist/managers/processing-state-manager.d.ts +40 -0
- package/dist/managers/processing-state-manager.js +27 -0
- package/dist/managers/project-manager.d.ts +199 -0
- package/dist/managers/project-manager.js +465 -0
- package/dist/managers/thread-manager.d.ts +193 -0
- package/dist/managers/thread-manager.js +368 -0
- package/dist/model-info-aihubmix.d.ts +25 -0
- package/dist/model-info-aihubmix.js +117 -0
- package/dist/model-info-openai.d.ts +17 -0
- package/dist/model-info-openai.js +59 -0
- package/dist/model-info-openrouter.d.ts +25 -0
- package/dist/model-info-openrouter.js +101 -0
- package/dist/model-info.d.ts +37 -0
- package/dist/model-info.js +39 -0
- package/dist/provider-data.d.ts +101 -0
- package/dist/provider-data.js +471 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.js +192 -0
- package/dist/public/android-chrome-192x192.png +0 -0
- package/dist/public/android-chrome-512x512.png +0 -0
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/assets/index-B443aj9k.js +8506 -0
- package/dist/public/assets/index-CjXGVbI7.css +1 -0
- package/dist/public/assets/index-DJC-p914.js +8506 -0
- package/dist/public/favicon-16x16.png +0 -0
- package/dist/public/favicon-32x32.png +0 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/index.html +28 -0
- package/dist/public/manifest.json +82 -0
- package/dist/public/placeholder-logo.svg +1 -0
- package/dist/public/placeholder.svg +1 -0
- package/dist/public/snpro.woff2 +0 -0
- package/dist/public/tarsk-color.svg +12 -0
- package/dist/public/tarsk.png +0 -0
- package/dist/public/tarsk.svg +12 -0
- package/dist/public/zalando-sans.woff2 +0 -0
- package/dist/routes/chat-old.d.ts +21 -0
- package/dist/routes/chat-old.js +251 -0
- package/dist/routes/chat.d.ts +21 -0
- package/dist/routes/chat.js +217 -0
- package/dist/routes/git.d.ts +4 -0
- package/dist/routes/git.js +668 -0
- package/dist/routes/models.d.ts +18 -0
- package/dist/routes/models.js +128 -0
- package/dist/routes/projects-old.d.ts +20 -0
- package/dist/routes/projects-old.js +297 -0
- package/dist/routes/projects.d.ts +20 -0
- package/dist/routes/projects.js +365 -0
- package/dist/routes/providers.d.ts +15 -0
- package/dist/routes/providers.js +130 -0
- package/dist/routes/threads-old.d.ts +14 -0
- package/dist/routes/threads-old.js +393 -0
- package/dist/routes/threads.d.ts +14 -0
- package/dist/routes/threads.js +352 -0
- package/dist/types/models.d.ts +315 -0
- package/dist/types/models.js +11 -0
- package/dist/utils/env-manager.d.ts +3 -0
- package/dist/utils/env-manager.js +60 -0
- package/dist/utils/open-router-models.d.ts +45 -0
- package/dist/utils/open-router-models.js +103 -0
- package/dist/utils/openai-models.d.ts +63 -0
- package/dist/utils/openai-models.js +152 -0
- package/dist/utils/openai-pricing-scraper.d.ts +17 -0
- package/dist/utils/openai-pricing-scraper.js +185 -0
- package/dist/utils/validation.d.ts +10 -0
- package/dist/utils/validation.js +20 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +12 -0
- package/package.json +36 -22
- package/LICENSE.md +0 -7
- package/dist/agent/agent.js +0 -131
- package/dist/agent/interfaces.js +0 -1
- package/dist/api/encryption.js +0 -41
- package/dist/api/models.js +0 -169
- package/dist/api/prompt.js +0 -12
- package/dist/api/settings.js +0 -43
- package/dist/api/test.js +0 -29
- package/dist/api/tools.js +0 -287
- package/dist/api/utils.js +0 -18
- package/dist/interfaces/meta.js +0 -1
- package/dist/interfaces/model.js +0 -1
- package/dist/interfaces/settings.js +0 -1
- package/dist/log/log.js +0 -33
- package/dist/prompt.js +0 -49
- package/dist/tools.js +0 -84
- package/dist/utils/files.js +0 -14
- package/dist/utils/json-file.js +0 -28
- package/dist/utils/strip-markdown.js +0 -5
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThreadManager handles thread lifecycle and metadata management
|
|
3
|
+
*
|
|
4
|
+
* This manager is responsible for:
|
|
5
|
+
* - Creating new threads for existing projects
|
|
6
|
+
* - Managing thread metadata
|
|
7
|
+
* - Coordinating with GitManager for repository cloning
|
|
8
|
+
* - Providing CRUD operations for threads
|
|
9
|
+
* - Ensuring thread path uniqueness
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
/**
|
|
14
|
+
* ThreadManagerImpl provides the implementation for thread operations
|
|
15
|
+
*/
|
|
16
|
+
export class ThreadManagerImpl {
|
|
17
|
+
metadataManager;
|
|
18
|
+
gitManager;
|
|
19
|
+
/**
|
|
20
|
+
* Create a new ThreadManager
|
|
21
|
+
* @param metadataManager - Manager for persisting metadata
|
|
22
|
+
* @param gitManager - Manager for git operations
|
|
23
|
+
*
|
|
24
|
+
* Requirements:
|
|
25
|
+
* - 2.1 - WHEN a user creates a new Thread for a Project, THE CLI SHALL create a new clone of the Project's git repository
|
|
26
|
+
* - 2.4 - THE System SHALL maintain a title for each Thread
|
|
27
|
+
*/
|
|
28
|
+
constructor(metadataManager, gitManager) {
|
|
29
|
+
this.metadataManager = metadataManager;
|
|
30
|
+
this.gitManager = gitManager;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new thread for an existing project
|
|
34
|
+
*
|
|
35
|
+
* This method:
|
|
36
|
+
* 1. Validates that the project exists
|
|
37
|
+
* 2. Generates a unique thread ID
|
|
38
|
+
* 3. Gets the project's git URL
|
|
39
|
+
* 4. Generates a unique thread path
|
|
40
|
+
* 5. Delegates to GitManager for cloning
|
|
41
|
+
* 6. Saves thread metadata
|
|
42
|
+
* 7. Updates the project's thread list
|
|
43
|
+
* 8. Yields progress events during the operation
|
|
44
|
+
*
|
|
45
|
+
* @param projectId - The parent project ID
|
|
46
|
+
* @param title - Optional thread title (auto-generated if not provided)
|
|
47
|
+
* @yields ThreadEvent objects during the creation process
|
|
48
|
+
*
|
|
49
|
+
* Requirements:
|
|
50
|
+
* - 2.1 - WHEN a user creates a new Thread for a Project, THE CLI SHALL create a new clone of the Project's git repository
|
|
51
|
+
* - 2.2 - WHEN a Thread is created, THE App SHALL display it under its parent Project in the Side_Panel
|
|
52
|
+
* - 2.4 - THE System SHALL maintain a title for each Thread
|
|
53
|
+
* - 7.4 - THE CLI SHALL ensure each Thread clone is stored in a unique directory path
|
|
54
|
+
*/
|
|
55
|
+
async *createThread(projectId, title) {
|
|
56
|
+
try {
|
|
57
|
+
// Load project to verify it exists and get git URL
|
|
58
|
+
const projects = await this.metadataManager.loadProjects();
|
|
59
|
+
const project = projects.find(p => p.id === projectId);
|
|
60
|
+
if (!project) {
|
|
61
|
+
yield {
|
|
62
|
+
type: 'error',
|
|
63
|
+
message: `Project not found: ${projectId}`
|
|
64
|
+
};
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Generate unique thread ID
|
|
68
|
+
const threadId = randomUUID();
|
|
69
|
+
// Generate thread path: {projectPath}/{threadId}
|
|
70
|
+
const threadPath = this.generateThreadPath(project.path, threadId);
|
|
71
|
+
// Generate thread title if not provided
|
|
72
|
+
const existingThreads = await this.metadataManager.loadThreads();
|
|
73
|
+
const existingTitles = new Set(existingThreads
|
|
74
|
+
.filter(t => t.projectId === projectId)
|
|
75
|
+
.map(t => t.title));
|
|
76
|
+
const threadTitle = title || this.generateThreadTitle(existingTitles, threadId, project.name);
|
|
77
|
+
yield {
|
|
78
|
+
type: 'progress',
|
|
79
|
+
message: `Creating thread "${threadTitle}" for project "${project.name}"...`
|
|
80
|
+
};
|
|
81
|
+
yield {
|
|
82
|
+
type: 'progress',
|
|
83
|
+
message: `Cloning repository to ${threadPath}...`
|
|
84
|
+
};
|
|
85
|
+
// Stream git clone events
|
|
86
|
+
for await (const gitEvent of this.gitManager.cloneRepository(project.gitUrl, threadPath)) {
|
|
87
|
+
if (gitEvent.type === 'error') {
|
|
88
|
+
yield {
|
|
89
|
+
type: 'error',
|
|
90
|
+
message: `Git clone failed: ${gitEvent.message}`
|
|
91
|
+
};
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
else if (gitEvent.type === 'stdout' || gitEvent.type === 'stderr') {
|
|
95
|
+
yield {
|
|
96
|
+
type: 'progress',
|
|
97
|
+
message: gitEvent.data
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Sanitize the thread title to create a valid branch name
|
|
102
|
+
let branchName = this.gitManager.sanitizeBranchName(threadTitle);
|
|
103
|
+
yield {
|
|
104
|
+
type: 'progress',
|
|
105
|
+
message: `Creating branch "${branchName}"...`
|
|
106
|
+
};
|
|
107
|
+
// Check if branch exists and find a unique name if needed
|
|
108
|
+
let branchExists = await this.gitManager.checkBranchExists(threadPath, branchName);
|
|
109
|
+
let counter = 2;
|
|
110
|
+
const baseBranchName = branchName;
|
|
111
|
+
while (branchExists) {
|
|
112
|
+
branchName = `${baseBranchName}-${counter}`;
|
|
113
|
+
branchExists = await this.gitManager.checkBranchExists(threadPath, branchName);
|
|
114
|
+
counter++;
|
|
115
|
+
}
|
|
116
|
+
if (branchName !== baseBranchName) {
|
|
117
|
+
yield {
|
|
118
|
+
type: 'progress',
|
|
119
|
+
message: `Branch "${baseBranchName}" exists, using "${branchName}" instead`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Create and checkout the new branch
|
|
123
|
+
for await (const gitEvent of this.gitManager.createAndCheckoutBranch(threadPath, branchName)) {
|
|
124
|
+
if (gitEvent.type === 'error') {
|
|
125
|
+
yield {
|
|
126
|
+
type: 'error',
|
|
127
|
+
message: `Failed to create branch: ${gitEvent.message}`
|
|
128
|
+
};
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
else if (gitEvent.type === 'stdout' || gitEvent.type === 'stderr') {
|
|
132
|
+
yield {
|
|
133
|
+
type: 'progress',
|
|
134
|
+
message: gitEvent.data
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
yield {
|
|
139
|
+
type: 'progress',
|
|
140
|
+
message: `Branch "${branchName}" created and checked out`
|
|
141
|
+
};
|
|
142
|
+
// Create thread metadata
|
|
143
|
+
const thread = {
|
|
144
|
+
id: threadId,
|
|
145
|
+
projectId,
|
|
146
|
+
title: threadTitle,
|
|
147
|
+
path: threadPath,
|
|
148
|
+
currentBranch: branchName,
|
|
149
|
+
createdAt: new Date()
|
|
150
|
+
};
|
|
151
|
+
// Save thread metadata
|
|
152
|
+
const threads = await this.metadataManager.loadThreads();
|
|
153
|
+
threads.push(thread);
|
|
154
|
+
await this.metadataManager.saveThreads(threads);
|
|
155
|
+
// Update project's thread list
|
|
156
|
+
project.threads.push(threadId);
|
|
157
|
+
const updatedProjects = projects.map(p => p.id === projectId ? project : p);
|
|
158
|
+
await this.metadataManager.saveProjects(updatedProjects);
|
|
159
|
+
yield {
|
|
160
|
+
type: 'progress',
|
|
161
|
+
message: `Thread "${threadTitle}" created successfully`
|
|
162
|
+
};
|
|
163
|
+
// Yield complete event with thread data
|
|
164
|
+
yield {
|
|
165
|
+
type: 'complete',
|
|
166
|
+
message: 'Thread creation complete',
|
|
167
|
+
thread
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
yield {
|
|
172
|
+
type: 'error',
|
|
173
|
+
message: `Failed to create thread: ${error instanceof Error ? error.message : String(error)}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Gets a thread by ID
|
|
179
|
+
* @param threadId - The thread ID
|
|
180
|
+
* @returns The thread or null if not found
|
|
181
|
+
*/
|
|
182
|
+
async getThread(threadId) {
|
|
183
|
+
const threads = await this.metadataManager.loadThreads();
|
|
184
|
+
return threads.find(t => t.id === threadId) || null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Lists all threads for a specific project
|
|
188
|
+
* @param projectId - The project ID to filter threads
|
|
189
|
+
* @returns Array of threads for the project
|
|
190
|
+
*
|
|
191
|
+
* Requirements:
|
|
192
|
+
* - 2.1 - WHEN a Thread is created, THE App SHALL display it under its parent Project in the Side_Panel
|
|
193
|
+
*/
|
|
194
|
+
async listThreads(projectId) {
|
|
195
|
+
const threads = await this.metadataManager.loadThreads();
|
|
196
|
+
return threads.filter(t => t.projectId === projectId);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Deletes a thread and removes its directory
|
|
200
|
+
*
|
|
201
|
+
* This method:
|
|
202
|
+
* 1. Finds the thread by ID
|
|
203
|
+
* 2. Removes the thread directory from filesystem
|
|
204
|
+
* 3. Removes the thread from metadata
|
|
205
|
+
* 4. Updates the parent project's thread list
|
|
206
|
+
*
|
|
207
|
+
* @param threadId - The thread ID to delete
|
|
208
|
+
* @throws Error if thread not found
|
|
209
|
+
*
|
|
210
|
+
* Requirements:
|
|
211
|
+
* - 2.3 - WHEN a user deletes a Thread, THE System SHALL remove the Thread's folder and update the Side_Panel display
|
|
212
|
+
* - 7.5 - WHEN a Thread is deleted, THE CLI SHALL remove the associated git repository clone from the filesystem
|
|
213
|
+
*/
|
|
214
|
+
async deleteThread(threadId) {
|
|
215
|
+
const threads = await this.metadataManager.loadThreads();
|
|
216
|
+
const threadIndex = threads.findIndex(t => t.id === threadId);
|
|
217
|
+
if (threadIndex === -1) {
|
|
218
|
+
throw new Error(`Thread not found: ${threadId}`);
|
|
219
|
+
}
|
|
220
|
+
const thread = threads[threadIndex];
|
|
221
|
+
// Remove thread directory from filesystem
|
|
222
|
+
try {
|
|
223
|
+
const { rm } = await import('fs/promises');
|
|
224
|
+
await rm(thread.path, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw new Error(`Failed to remove thread directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
228
|
+
}
|
|
229
|
+
// Remove thread from metadata
|
|
230
|
+
threads.splice(threadIndex, 1);
|
|
231
|
+
await this.metadataManager.saveThreads(threads);
|
|
232
|
+
// Update parent project's thread list
|
|
233
|
+
const projects = await this.metadataManager.loadProjects();
|
|
234
|
+
const project = projects.find(p => p.id === thread.projectId);
|
|
235
|
+
if (project) {
|
|
236
|
+
project.threads = project.threads.filter(id => id !== threadId);
|
|
237
|
+
await this.metadataManager.saveProjects(projects);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Updates a thread's metadata with partial data
|
|
242
|
+
*
|
|
243
|
+
* This method loads all threads, finds the target, merges the updates,
|
|
244
|
+
* and persists the result.
|
|
245
|
+
*
|
|
246
|
+
* @param threadId - The thread ID to update
|
|
247
|
+
* @param updates - Partial thread data to merge
|
|
248
|
+
* @throws Error if thread not found
|
|
249
|
+
*/
|
|
250
|
+
async updateThread(threadId, updates) {
|
|
251
|
+
const threads = await this.metadataManager.loadThreads();
|
|
252
|
+
const threadIndex = threads.findIndex(t => t.id === threadId);
|
|
253
|
+
if (threadIndex === -1) {
|
|
254
|
+
throw new Error(`Thread not found: ${threadId}`);
|
|
255
|
+
}
|
|
256
|
+
threads[threadIndex] = { ...threads[threadIndex], ...updates };
|
|
257
|
+
await this.metadataManager.saveThreads(threads);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Selects a thread as the active thread
|
|
261
|
+
*
|
|
262
|
+
* @param threadId - The thread ID to select
|
|
263
|
+
*/
|
|
264
|
+
async selectThread(threadId) {
|
|
265
|
+
await this.metadataManager.setSelectedThread(threadId);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Gets the currently selected thread ID
|
|
269
|
+
*
|
|
270
|
+
* @returns The selected thread ID or null
|
|
271
|
+
*/
|
|
272
|
+
async getSelectedThreadId() {
|
|
273
|
+
return await this.metadataManager.getSelectedThread();
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Generates a unique thread path
|
|
277
|
+
*
|
|
278
|
+
* Path format: {projectPath}/{threadId}
|
|
279
|
+
*
|
|
280
|
+
* This ensures thread paths are unique across all projects and threads.
|
|
281
|
+
*
|
|
282
|
+
* @param projectPath - The parent project path
|
|
283
|
+
* @param threadId - The thread ID
|
|
284
|
+
* @returns The absolute path to the thread folder
|
|
285
|
+
*
|
|
286
|
+
* Requirements:
|
|
287
|
+
* - 7.4 - THE CLI SHALL ensure each Thread clone is stored in a unique directory path
|
|
288
|
+
*/
|
|
289
|
+
generateThreadPath(projectPath, threadId) {
|
|
290
|
+
return join(projectPath, threadId);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Generates a thread title if not provided
|
|
294
|
+
*
|
|
295
|
+
* Format: project-name Thread word1-word2
|
|
296
|
+
*
|
|
297
|
+
* @param existingTitles - Set of titles already in use
|
|
298
|
+
* @param threadId - Thread ID used as a fallback
|
|
299
|
+
* @param projectName - Optional project name to include in title
|
|
300
|
+
* @returns The generated thread title
|
|
301
|
+
*/
|
|
302
|
+
generateThreadTitle(existingTitles, threadId, projectName) {
|
|
303
|
+
const wordsA = [
|
|
304
|
+
'amber', 'brisk', 'calm', 'drift', 'ember', 'frost', 'glow', 'hollow',
|
|
305
|
+
'ivory', 'jolly', 'keen', 'lucid', 'mellow', 'noble', 'opal', 'prism',
|
|
306
|
+
'quiet', 'ripple', 'sunny', 'timber', 'ultra', 'vivid', 'wisp', 'zen'
|
|
307
|
+
];
|
|
308
|
+
const wordsB = [
|
|
309
|
+
'arch', 'bay', 'crest', 'dune', 'echo', 'fjord', 'glen', 'harbor',
|
|
310
|
+
'isle', 'jewel', 'knoll', 'lagoon', 'mesa', 'nest', 'oasis', 'peak',
|
|
311
|
+
'quarry', 'reef', 'strand', 'trail', 'vale', 'wharf', 'yard', 'zephyr'
|
|
312
|
+
];
|
|
313
|
+
const prefix = projectName ? `${projectName} Thread ` : 'Thread ';
|
|
314
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
315
|
+
const word1 = wordsA[Math.floor(Math.random() * wordsA.length)];
|
|
316
|
+
const word2 = wordsB[Math.floor(Math.random() * wordsB.length)];
|
|
317
|
+
const candidate = `${prefix}${word1}-${word2}`;
|
|
318
|
+
if (!existingTitles.has(candidate)) {
|
|
319
|
+
return candidate;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return `${prefix}${threadId.slice(0, 8)}`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=ThreadManager.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConversationManager handles conversation history storage and retrieval
|
|
3
|
+
*
|
|
4
|
+
* This manager is responsible for:
|
|
5
|
+
* - Storing request/response pairs in JSON format
|
|
6
|
+
* - Retrieving conversation history for a thread
|
|
7
|
+
* - Managing conversation persistence
|
|
8
|
+
*/
|
|
9
|
+
import { ConversationHistory, ConversationMessage, NeovateEvent, FileData } from '../types/models.js';
|
|
10
|
+
/**
|
|
11
|
+
* ConversationManager interface defines the contract for conversation operations
|
|
12
|
+
*/
|
|
13
|
+
export interface ConversationManager {
|
|
14
|
+
/**
|
|
15
|
+
* Starts a new conversation message (request captured)
|
|
16
|
+
* @param threadId - The thread ID
|
|
17
|
+
* @param threadPath - The absolute path to the thread directory
|
|
18
|
+
* @param message - The user's message
|
|
19
|
+
* @param model - The model being used
|
|
20
|
+
* @param attachments - Optional file attachments
|
|
21
|
+
* @param planMode - Optional plan mode flag
|
|
22
|
+
* @returns The message ID for this conversation
|
|
23
|
+
*/
|
|
24
|
+
startMessage(threadId: string, threadPath: string, message: string, model: string, attachments?: FileData[], planMode?: boolean): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Completes a conversation message (response captured)
|
|
27
|
+
* @param threadPath - The absolute path to the thread directory
|
|
28
|
+
* @param messageId - The message ID to complete
|
|
29
|
+
* @param content - The complete response content
|
|
30
|
+
* @param events - All events captured during execution
|
|
31
|
+
*/
|
|
32
|
+
completeMessage(threadPath: string, messageId: string, content: string, events: NeovateEvent[]): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Gets the conversation history for a thread
|
|
35
|
+
* @param threadPath - The absolute path to the thread directory
|
|
36
|
+
* @returns The conversation history or null if not found
|
|
37
|
+
*/
|
|
38
|
+
getConversationHistory(threadPath: string): Promise<ConversationHistory | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Gets the last N messages from conversation history
|
|
41
|
+
* @param threadPath - The absolute path to the thread directory
|
|
42
|
+
* @param count - Number of messages to retrieve (default: 1)
|
|
43
|
+
* @returns Array of conversation messages
|
|
44
|
+
*/
|
|
45
|
+
getLastMessages(threadPath: string, count?: number): Promise<ConversationMessage[]>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* ConversationManagerImpl provides the implementation for conversation operations
|
|
49
|
+
*/
|
|
50
|
+
export declare class ConversationManagerImpl implements ConversationManager {
|
|
51
|
+
private metadataDir;
|
|
52
|
+
/**
|
|
53
|
+
* Create a new ConversationManagerImpl
|
|
54
|
+
* @param rootFolder - Base directory where metadata will be stored
|
|
55
|
+
*/
|
|
56
|
+
constructor(rootFolder?: string);
|
|
57
|
+
/**
|
|
58
|
+
* Starts a new conversation message
|
|
59
|
+
*/
|
|
60
|
+
startMessage(threadId: string, threadPath: string, message: string, model: string, attachments?: FileData[], planMode?: boolean): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Completes a conversation message
|
|
63
|
+
*/
|
|
64
|
+
completeMessage(threadPath: string, messageId: string, content: string, events: NeovateEvent[]): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Gets the conversation history for a thread
|
|
67
|
+
*/
|
|
68
|
+
getConversationHistory(threadPath: string): Promise<ConversationHistory | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Gets the last N messages from conversation history
|
|
71
|
+
*/
|
|
72
|
+
getLastMessages(threadPath: string, count?: number): Promise<ConversationMessage[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Saves conversation history to file
|
|
75
|
+
*/
|
|
76
|
+
private saveConversationHistory;
|
|
77
|
+
/**
|
|
78
|
+
* Gets the file path for a thread's conversation history in the metadata folder
|
|
79
|
+
* Extracts threadId from threadPath and stores in .metadata/conversations/<threadId>/history.json
|
|
80
|
+
*/
|
|
81
|
+
private getConversationFilePath;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=conversation-manager.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConversationManager handles conversation history storage and retrieval
|
|
3
|
+
*
|
|
4
|
+
* This manager is responsible for:
|
|
5
|
+
* - Storing request/response pairs in JSON format
|
|
6
|
+
* - Retrieving conversation history for a thread
|
|
7
|
+
* - Managing conversation persistence
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
const CONVERSATION_FILE = 'conversation-history.json';
|
|
13
|
+
const METADATA_DIR = '.metadata';
|
|
14
|
+
/**
|
|
15
|
+
* ConversationManagerImpl provides the implementation for conversation operations
|
|
16
|
+
*/
|
|
17
|
+
export class ConversationManagerImpl {
|
|
18
|
+
metadataDir;
|
|
19
|
+
/**
|
|
20
|
+
* Create a new ConversationManagerImpl
|
|
21
|
+
* @param rootFolder - Base directory where metadata will be stored
|
|
22
|
+
*/
|
|
23
|
+
constructor(rootFolder = './projects') {
|
|
24
|
+
this.metadataDir = join(rootFolder, METADATA_DIR);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Starts a new conversation message
|
|
28
|
+
*/
|
|
29
|
+
async startMessage(threadId, threadPath, message, model, attachments, planMode) {
|
|
30
|
+
const messageId = randomUUID();
|
|
31
|
+
const timestamp = new Date().toISOString();
|
|
32
|
+
// Load existing history or create new
|
|
33
|
+
let history = await this.getConversationHistory(threadPath);
|
|
34
|
+
if (!history) {
|
|
35
|
+
history = {
|
|
36
|
+
threadId,
|
|
37
|
+
messages: [],
|
|
38
|
+
lastUpdated: timestamp,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Create incomplete message (will be completed later)
|
|
42
|
+
const incompleteMessage = {
|
|
43
|
+
id: messageId,
|
|
44
|
+
timestamp,
|
|
45
|
+
request: {
|
|
46
|
+
message,
|
|
47
|
+
model,
|
|
48
|
+
...(attachments && { attachments }),
|
|
49
|
+
...(planMode && { planMode }),
|
|
50
|
+
},
|
|
51
|
+
response: null,
|
|
52
|
+
};
|
|
53
|
+
history.messages.push(incompleteMessage);
|
|
54
|
+
history.lastUpdated = timestamp;
|
|
55
|
+
// Save to file
|
|
56
|
+
await this.saveConversationHistory(threadPath, history);
|
|
57
|
+
return messageId;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Completes a conversation message
|
|
61
|
+
*/
|
|
62
|
+
async completeMessage(threadPath, messageId, content, events) {
|
|
63
|
+
const history = await this.getConversationHistory(threadPath);
|
|
64
|
+
if (!history) {
|
|
65
|
+
throw new Error('Conversation history not found');
|
|
66
|
+
}
|
|
67
|
+
// Find the message to complete
|
|
68
|
+
const message = history.messages.find((m) => m.id === messageId);
|
|
69
|
+
if (!message) {
|
|
70
|
+
throw new Error(`Message ${messageId} not found in conversation history`);
|
|
71
|
+
}
|
|
72
|
+
// Complete the message with response data
|
|
73
|
+
message.response = {
|
|
74
|
+
content,
|
|
75
|
+
events,
|
|
76
|
+
completedAt: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
history.lastUpdated = new Date().toISOString();
|
|
79
|
+
// Save to file
|
|
80
|
+
await this.saveConversationHistory(threadPath, history);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Gets the conversation history for a thread
|
|
84
|
+
*/
|
|
85
|
+
async getConversationHistory(threadPath) {
|
|
86
|
+
try {
|
|
87
|
+
const filePath = this.getConversationFilePath(threadPath);
|
|
88
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
89
|
+
return JSON.parse(content);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error instanceof Error && error.code === 'ENOENT') {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Gets the last N messages from conversation history
|
|
100
|
+
*/
|
|
101
|
+
async getLastMessages(threadPath, count = 1) {
|
|
102
|
+
const history = await this.getConversationHistory(threadPath);
|
|
103
|
+
if (!history || history.messages.length === 0) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
// Return last N messages
|
|
107
|
+
return history.messages.slice(-count);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Saves conversation history to file
|
|
111
|
+
*/
|
|
112
|
+
async saveConversationHistory(threadPath, history) {
|
|
113
|
+
const filePath = this.getConversationFilePath(threadPath);
|
|
114
|
+
// Ensure all parent directories exist
|
|
115
|
+
await fs.mkdir(dirname(filePath), { recursive: true });
|
|
116
|
+
await fs.writeFile(filePath, JSON.stringify(history, null, 2), 'utf-8');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Gets the file path for a thread's conversation history in the metadata folder
|
|
120
|
+
* Extracts threadId from threadPath and stores in .metadata/conversations/<threadId>/history.json
|
|
121
|
+
*/
|
|
122
|
+
getConversationFilePath(threadPath) {
|
|
123
|
+
// Extract thread ID from path (last part of the path)
|
|
124
|
+
const threadId = threadPath.split(/[\\/]/).pop() || 'unknown';
|
|
125
|
+
const conversationsDir = join(this.metadataDir, 'conversations');
|
|
126
|
+
return join(conversationsDir, threadId, CONVERSATION_FILE);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=conversation-manager.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitManager handles git operations including URL validation and repository cloning
|
|
3
|
+
*
|
|
4
|
+
* This manager is responsible for:
|
|
5
|
+
* - Validating git URLs in various formats
|
|
6
|
+
* - Cloning repositories with streaming output
|
|
7
|
+
* - Handling git operation errors
|
|
8
|
+
*/
|
|
9
|
+
import { GitEvent } from '../types/models.js';
|
|
10
|
+
/**
|
|
11
|
+
* GitManager interface defines the contract for git operations
|
|
12
|
+
*/
|
|
13
|
+
export interface GitManager {
|
|
14
|
+
/**
|
|
15
|
+
* Validates a git URL format
|
|
16
|
+
* @param url - The git URL to validate
|
|
17
|
+
* @returns true if the URL is a valid git URL, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
validateGitUrl(url: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Clones a git repository to the specified target path
|
|
22
|
+
* @param gitUrl - The git repository URL to clone
|
|
23
|
+
* @param targetPath - The local path where the repository should be cloned
|
|
24
|
+
* @yields GitEvent objects for stdout, stderr, complete, and error events
|
|
25
|
+
*/
|
|
26
|
+
cloneRepository(gitUrl: string, targetPath: string): AsyncGenerator<GitEvent>;
|
|
27
|
+
/**
|
|
28
|
+
* Sanitizes a branch name to ensure it only contains valid git branch characters
|
|
29
|
+
* Replaces spaces with dashes and removes invalid characters
|
|
30
|
+
* @param name - The branch name to sanitize
|
|
31
|
+
* @returns The sanitized branch name
|
|
32
|
+
*/
|
|
33
|
+
sanitizeBranchName(name: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Checks if a branch exists in a git repository
|
|
36
|
+
* @param repoPath - The path to the git repository
|
|
37
|
+
* @param branchName - The branch name to check
|
|
38
|
+
* @returns true if the branch exists, false otherwise
|
|
39
|
+
*/
|
|
40
|
+
checkBranchExists(repoPath: string, branchName: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates and checks out a new branch in a git repository
|
|
43
|
+
* @param repoPath - The path to the git repository
|
|
44
|
+
* @param branchName - The branch name to create
|
|
45
|
+
* @yields GitEvent objects for stdout, stderr, complete, and error events
|
|
46
|
+
*/
|
|
47
|
+
createAndCheckoutBranch(repoPath: string, branchName: string): AsyncGenerator<GitEvent>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* GitManagerImpl provides the implementation for git operations
|
|
51
|
+
*/
|
|
52
|
+
export declare class GitManagerImpl implements GitManager {
|
|
53
|
+
/**
|
|
54
|
+
* Regular expressions for validating different git URL formats
|
|
55
|
+
*/
|
|
56
|
+
private readonly gitUrlPatterns;
|
|
57
|
+
/**
|
|
58
|
+
* Validates a git URL against common git URL formats
|
|
59
|
+
*
|
|
60
|
+
* Supports the following formats:
|
|
61
|
+
* - HTTPS: https://github.com/user/repo.git
|
|
62
|
+
* - Git protocol: git://github.com/user/repo.git
|
|
63
|
+
* - SSH (git@): git@github.com:user/repo.git
|
|
64
|
+
* - SSH (ssh://): ssh://git@github.com/user/repo.git
|
|
65
|
+
*
|
|
66
|
+
* @param url - The URL to validate
|
|
67
|
+
* @returns true if the URL matches a valid git URL format, false otherwise
|
|
68
|
+
*
|
|
69
|
+
* Requirements: 7.1 - WHEN cloning a git repository, THE CLI SHALL validate the git URL format
|
|
70
|
+
*/
|
|
71
|
+
validateGitUrl(url: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Clones a git repository to the specified target path with streaming output
|
|
74
|
+
*
|
|
75
|
+
* This method:
|
|
76
|
+
* 1. Validates the git URL
|
|
77
|
+
* 2. Creates the target directory if needed
|
|
78
|
+
* 3. Spawns a git clone process
|
|
79
|
+
* 4. Streams stdout and stderr output
|
|
80
|
+
* 5. Handles completion and errors
|
|
81
|
+
*
|
|
82
|
+
* @param gitUrl - The git repository URL to clone
|
|
83
|
+
* @param targetPath - The local path where the repository should be cloned
|
|
84
|
+
* @yields GitEvent objects during the clone operation
|
|
85
|
+
*
|
|
86
|
+
* Requirements:
|
|
87
|
+
* - 1.1 - WHEN a user provides a git URL to create a new Project, THE CLI SHALL clone the repository
|
|
88
|
+
* - 1.3 - WHEN the clone operation executes, THE CLI SHALL stream the git clone output back to the App
|
|
89
|
+
* - 7.1 - WHEN cloning a git repository, THE CLI SHALL validate the git URL format
|
|
90
|
+
* - 7.2 - IF a git clone operation fails, THEN THE CLI SHALL return a descriptive error message
|
|
91
|
+
* - 7.3 - WHEN a clone operation is in progress, THE CLI SHALL stream git output to provide progress feedback
|
|
92
|
+
*/
|
|
93
|
+
cloneRepository(gitUrl: string, targetPath: string): AsyncGenerator<GitEvent>;
|
|
94
|
+
/**
|
|
95
|
+
* Sanitizes a branch name to ensure it only contains valid git branch characters
|
|
96
|
+
*
|
|
97
|
+
* This method:
|
|
98
|
+
* 1. Trims whitespace
|
|
99
|
+
* 2. Converts to lowercase
|
|
100
|
+
* 3. Replaces spaces with dashes
|
|
101
|
+
* 4. Removes invalid characters (only allows alphanumeric, dash, underscore, slash, dot)
|
|
102
|
+
* 5. Removes leading/trailing slashes and dots
|
|
103
|
+
* 6. Collapses multiple consecutive dashes into one
|
|
104
|
+
*
|
|
105
|
+
* @param name - The branch name to sanitize
|
|
106
|
+
* @returns The sanitized branch name (all lowercase)
|
|
107
|
+
*/
|
|
108
|
+
sanitizeBranchName(name: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Checks if a branch exists in a git repository
|
|
111
|
+
*
|
|
112
|
+
* Uses `git show-ref` to check for the branch without checking out
|
|
113
|
+
*
|
|
114
|
+
* @param repoPath - The path to the git repository
|
|
115
|
+
* @param branchName - The branch name to check
|
|
116
|
+
* @returns true if the branch exists, false otherwise
|
|
117
|
+
*/
|
|
118
|
+
checkBranchExists(repoPath: string, branchName: string): Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* Creates and checks out a new branch in a git repository with streaming output
|
|
121
|
+
*
|
|
122
|
+
* This method:
|
|
123
|
+
* 1. Creates a new branch from the current HEAD
|
|
124
|
+
* 2. Checks out the new branch
|
|
125
|
+
* 3. Streams output during the operation
|
|
126
|
+
*
|
|
127
|
+
* @param repoPath - The path to the git repository
|
|
128
|
+
* @param branchName - The branch name to create
|
|
129
|
+
* @yields GitEvent objects during the branch creation
|
|
130
|
+
*/
|
|
131
|
+
createAndCheckoutBranch(repoPath: string, branchName: string): AsyncGenerator<GitEvent>;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=git-manager.d.ts.map
|