@fastino-ai/pioneer-cli 0.1.0 → 0.2.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 +161 -22
- package/bun.lock +82 -0
- package/cache/cache.db +0 -0
- package/cache/cache.db-shm +0 -0
- package/cache/cache.db-wal +0 -0
- package/fastino-ai-pioneer-cli-0.2.0.tgz +0 -0
- package/package.json +6 -3
- package/src/agent/Agent.ts +342 -0
- package/src/agent/BudgetManager.ts +167 -0
- package/src/agent/FileResolver.ts +321 -0
- package/src/agent/LLMClient.ts +435 -0
- package/src/agent/ToolRegistry.ts +97 -0
- package/src/agent/index.ts +15 -0
- package/src/agent/types.ts +84 -0
- package/src/chat/ChatApp.tsx +701 -0
- package/src/chat/index.ts +7 -0
- package/src/config.ts +185 -3
- package/src/evolution/EvalRunner.ts +301 -0
- package/src/evolution/EvolutionEngine.ts +319 -0
- package/src/evolution/FeedbackCollector.ts +197 -0
- package/src/evolution/ModelTrainer.ts +371 -0
- package/src/evolution/index.ts +18 -0
- package/src/evolution/types.ts +110 -0
- package/src/index.tsx +101 -2
- package/src/tools/bash.ts +184 -0
- package/src/tools/filesystem.ts +444 -0
- package/src/tools/index.ts +29 -0
- package/src/tools/modal.ts +269 -0
- package/src/tools/sandbox.ts +310 -0
- package/src/tools/training.ts +443 -0
- package/src/tools/wandb.ts +348 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileResolver - Parse and resolve @file references in messages
|
|
3
|
+
* Similar to Claude Code's @ mention functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
|
|
9
|
+
export interface FileReference {
|
|
10
|
+
original: string; // The original @path text
|
|
11
|
+
path: string; // Resolved absolute path
|
|
12
|
+
relativePath: string; // Path as typed by user
|
|
13
|
+
exists: boolean;
|
|
14
|
+
isDirectory: boolean;
|
|
15
|
+
content?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ResolvedMessage {
|
|
20
|
+
originalMessage: string;
|
|
21
|
+
cleanMessage: string; // Message with @refs replaced by placeholders
|
|
22
|
+
references: FileReference[];
|
|
23
|
+
contextBlock: string; // Formatted context to prepend to message
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const MAX_FILE_SIZE = 1024 * 1024; // 1MB max per file
|
|
27
|
+
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB total max
|
|
28
|
+
const MAX_DIRECTORY_FILES = 50; // Max files to include from a directory
|
|
29
|
+
|
|
30
|
+
export class FileResolver {
|
|
31
|
+
private basePath: string;
|
|
32
|
+
|
|
33
|
+
constructor(basePath: string = process.cwd()) {
|
|
34
|
+
this.basePath = basePath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setBasePath(basePath: string): void {
|
|
38
|
+
this.basePath = basePath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parse a message and resolve all @file references
|
|
43
|
+
*/
|
|
44
|
+
resolve(message: string): ResolvedMessage {
|
|
45
|
+
const references: FileReference[] = [];
|
|
46
|
+
|
|
47
|
+
// Match @path patterns - supports paths with extensions, slashes, etc.
|
|
48
|
+
// Matches: @file.ts, @src/file.ts, @./relative/path, @../parent/path
|
|
49
|
+
// Stops at whitespace, quotes, or common punctuation
|
|
50
|
+
const atPattern = /@((?:\.\.?\/)?[\w\-./]+(?:\.\w+)?)/g;
|
|
51
|
+
|
|
52
|
+
let match;
|
|
53
|
+
const foundPaths = new Set<string>();
|
|
54
|
+
|
|
55
|
+
while ((match = atPattern.exec(message)) !== null) {
|
|
56
|
+
const relativePath = match[1];
|
|
57
|
+
|
|
58
|
+
// Skip if we've already processed this path
|
|
59
|
+
if (foundPaths.has(relativePath)) continue;
|
|
60
|
+
foundPaths.add(relativePath);
|
|
61
|
+
|
|
62
|
+
const ref = this.resolveReference(relativePath, match[0]);
|
|
63
|
+
references.push(ref);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Build context block from resolved files
|
|
67
|
+
const contextBlock = this.buildContextBlock(references);
|
|
68
|
+
|
|
69
|
+
// Create clean message (optionally replace @refs with indicators)
|
|
70
|
+
let cleanMessage = message;
|
|
71
|
+
for (const ref of references) {
|
|
72
|
+
if (ref.exists) {
|
|
73
|
+
// Keep the @reference in the message so the LLM knows what was referenced
|
|
74
|
+
// but the actual content is in the context block
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
originalMessage: message,
|
|
80
|
+
cleanMessage,
|
|
81
|
+
references,
|
|
82
|
+
contextBlock,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private resolveReference(relativePath: string, original: string): FileReference {
|
|
87
|
+
// Resolve to absolute path
|
|
88
|
+
const absolutePath = path.isAbsolute(relativePath)
|
|
89
|
+
? relativePath
|
|
90
|
+
: path.resolve(this.basePath, relativePath);
|
|
91
|
+
|
|
92
|
+
const ref: FileReference = {
|
|
93
|
+
original,
|
|
94
|
+
path: absolutePath,
|
|
95
|
+
relativePath,
|
|
96
|
+
exists: false,
|
|
97
|
+
isDirectory: false,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
if (!fs.existsSync(absolutePath)) {
|
|
102
|
+
ref.error = "File or directory not found";
|
|
103
|
+
return ref;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const stats = fs.statSync(absolutePath);
|
|
107
|
+
ref.exists = true;
|
|
108
|
+
ref.isDirectory = stats.isDirectory();
|
|
109
|
+
|
|
110
|
+
if (ref.isDirectory) {
|
|
111
|
+
ref.content = this.readDirectory(absolutePath);
|
|
112
|
+
} else {
|
|
113
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
114
|
+
ref.error = `File too large (${(stats.size / 1024).toFixed(1)}KB > ${MAX_FILE_SIZE / 1024}KB limit)`;
|
|
115
|
+
return ref;
|
|
116
|
+
}
|
|
117
|
+
ref.content = this.readFile(absolutePath);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
ref.error = err instanceof Error ? err.message : String(err);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return ref;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private readFile(filePath: string): string {
|
|
127
|
+
try {
|
|
128
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
129
|
+
} catch (err) {
|
|
130
|
+
throw new Error(`Failed to read file: ${err instanceof Error ? err.message : err}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private readDirectory(dirPath: string): string {
|
|
135
|
+
const entries: string[] = [];
|
|
136
|
+
const files = this.listDirectoryRecursive(dirPath, "", 0, 3);
|
|
137
|
+
|
|
138
|
+
// Build a tree-like structure
|
|
139
|
+
let output = `Directory: ${dirPath}\n`;
|
|
140
|
+
output += "```\n";
|
|
141
|
+
output += files.join("\n");
|
|
142
|
+
output += "\n```";
|
|
143
|
+
|
|
144
|
+
return output;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private listDirectoryRecursive(
|
|
148
|
+
basePath: string,
|
|
149
|
+
relativePath: string,
|
|
150
|
+
depth: number,
|
|
151
|
+
maxDepth: number
|
|
152
|
+
): string[] {
|
|
153
|
+
const results: string[] = [];
|
|
154
|
+
const fullPath = path.join(basePath, relativePath);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
158
|
+
|
|
159
|
+
// Sort: directories first, then files
|
|
160
|
+
entries.sort((a, b) => {
|
|
161
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
162
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
163
|
+
return a.name.localeCompare(b.name);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
// Skip hidden files and common ignored directories
|
|
168
|
+
if (entry.name.startsWith(".")) continue;
|
|
169
|
+
if (["node_modules", "__pycache__", "dist", "build", ".git"].includes(entry.name)) continue;
|
|
170
|
+
|
|
171
|
+
const indent = " ".repeat(depth);
|
|
172
|
+
const entryRelPath = path.join(relativePath, entry.name);
|
|
173
|
+
|
|
174
|
+
if (entry.isDirectory()) {
|
|
175
|
+
results.push(`${indent}${entry.name}/`);
|
|
176
|
+
if (depth < maxDepth) {
|
|
177
|
+
results.push(...this.listDirectoryRecursive(basePath, entryRelPath, depth + 1, maxDepth));
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
results.push(`${indent}${entry.name}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (results.length >= MAX_DIRECTORY_FILES) {
|
|
184
|
+
results.push(`${indent}... (truncated)`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
// Skip inaccessible directories
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private buildContextBlock(references: FileReference[]): string {
|
|
196
|
+
if (references.length === 0) return "";
|
|
197
|
+
|
|
198
|
+
const successfulRefs = references.filter((r) => r.exists && r.content);
|
|
199
|
+
if (successfulRefs.length === 0) return "";
|
|
200
|
+
|
|
201
|
+
let totalSize = 0;
|
|
202
|
+
const blocks: string[] = [];
|
|
203
|
+
|
|
204
|
+
blocks.push("<referenced_files>");
|
|
205
|
+
|
|
206
|
+
for (const ref of successfulRefs) {
|
|
207
|
+
if (!ref.content) continue;
|
|
208
|
+
|
|
209
|
+
// Check total size limit
|
|
210
|
+
if (totalSize + ref.content.length > MAX_TOTAL_SIZE) {
|
|
211
|
+
blocks.push(`\n<!-- Skipped ${ref.relativePath}: would exceed total size limit -->`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
totalSize += ref.content.length;
|
|
216
|
+
|
|
217
|
+
if (ref.isDirectory) {
|
|
218
|
+
blocks.push(`\n<directory path="${ref.relativePath}">`);
|
|
219
|
+
blocks.push(ref.content);
|
|
220
|
+
blocks.push("</directory>");
|
|
221
|
+
} else {
|
|
222
|
+
const ext = path.extname(ref.relativePath).slice(1) || "txt";
|
|
223
|
+
blocks.push(`\n<file path="${ref.relativePath}">`);
|
|
224
|
+
blocks.push("```" + ext);
|
|
225
|
+
blocks.push(ref.content);
|
|
226
|
+
blocks.push("```");
|
|
227
|
+
blocks.push("</file>");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
blocks.push("\n</referenced_files>\n");
|
|
232
|
+
|
|
233
|
+
// Add any errors
|
|
234
|
+
const errors = references.filter((r) => r.error);
|
|
235
|
+
if (errors.length > 0) {
|
|
236
|
+
blocks.push("\n<!-- File reference errors:");
|
|
237
|
+
for (const err of errors) {
|
|
238
|
+
blocks.push(` ${err.original}: ${err.error}`);
|
|
239
|
+
}
|
|
240
|
+
blocks.push("-->\n");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return blocks.join("\n");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get file suggestions for autocomplete
|
|
248
|
+
* Handles: "" (list cwd), "src/" (list src dir), "src/a" (filter by prefix)
|
|
249
|
+
*/
|
|
250
|
+
getSuggestions(partial: string, limit = 15): string[] {
|
|
251
|
+
const suggestions: string[] = [];
|
|
252
|
+
|
|
253
|
+
let searchDir: string;
|
|
254
|
+
let prefix: string;
|
|
255
|
+
let dirPrefix: string;
|
|
256
|
+
|
|
257
|
+
if (!partial || partial === "") {
|
|
258
|
+
// Empty: list current directory
|
|
259
|
+
searchDir = this.basePath;
|
|
260
|
+
prefix = "";
|
|
261
|
+
dirPrefix = "";
|
|
262
|
+
} else if (partial.endsWith("/")) {
|
|
263
|
+
// Ends with /: list that directory
|
|
264
|
+
searchDir = path.resolve(this.basePath, partial);
|
|
265
|
+
prefix = "";
|
|
266
|
+
dirPrefix = partial;
|
|
267
|
+
} else if (partial.includes("/")) {
|
|
268
|
+
// Has / but doesn't end with it: filter in parent dir
|
|
269
|
+
searchDir = path.resolve(this.basePath, path.dirname(partial));
|
|
270
|
+
prefix = path.basename(partial).toLowerCase();
|
|
271
|
+
dirPrefix = path.dirname(partial) + "/";
|
|
272
|
+
} else {
|
|
273
|
+
// No slash: filter in current dir
|
|
274
|
+
searchDir = this.basePath;
|
|
275
|
+
prefix = partial.toLowerCase();
|
|
276
|
+
dirPrefix = "";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
if (!fs.existsSync(searchDir) || !fs.statSync(searchDir).isDirectory()) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const entries = fs.readdirSync(searchDir, { withFileTypes: true });
|
|
285
|
+
|
|
286
|
+
// Sort: directories first, then alphabetically
|
|
287
|
+
entries.sort((a, b) => {
|
|
288
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
289
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
290
|
+
return a.name.localeCompare(b.name);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
for (const entry of entries) {
|
|
294
|
+
if (entry.name.startsWith(".")) continue;
|
|
295
|
+
if (["node_modules", "__pycache__", "dist", "build", ".git", "venv"].includes(entry.name)) continue;
|
|
296
|
+
|
|
297
|
+
if (!prefix || entry.name.toLowerCase().startsWith(prefix)) {
|
|
298
|
+
const relativePath = dirPrefix + entry.name;
|
|
299
|
+
suggestions.push(entry.isDirectory() ? `${relativePath}/` : relativePath);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (suggestions.length >= limit) break;
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
// Ignore errors
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return suggestions;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Singleton for easy access
|
|
313
|
+
let defaultResolver: FileResolver | null = null;
|
|
314
|
+
|
|
315
|
+
export function getFileResolver(basePath?: string): FileResolver {
|
|
316
|
+
if (!defaultResolver || basePath) {
|
|
317
|
+
defaultResolver = new FileResolver(basePath);
|
|
318
|
+
}
|
|
319
|
+
return defaultResolver;
|
|
320
|
+
}
|
|
321
|
+
|