btca-server 1.0.961 → 2.0.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/package.json +3 -3
- package/src/agent/agent.test.ts +31 -24
- package/src/agent/index.ts +8 -2
- package/src/agent/loop.ts +303 -346
- package/src/agent/service.ts +252 -233
- package/src/agent/types.ts +2 -2
- package/src/collections/index.ts +2 -1
- package/src/collections/service.ts +352 -345
- package/src/config/config.test.ts +3 -1
- package/src/config/index.ts +615 -727
- package/src/config/remote.ts +214 -369
- package/src/context/index.ts +6 -12
- package/src/context/transaction.ts +23 -30
- package/src/effect/errors.ts +45 -0
- package/src/effect/layers.ts +26 -0
- package/src/effect/runtime.ts +19 -0
- package/src/effect/services.ts +154 -0
- package/src/index.ts +291 -369
- package/src/metrics/index.ts +46 -46
- package/src/pricing/models-dev.ts +104 -106
- package/src/providers/auth.ts +159 -200
- package/src/providers/index.ts +19 -2
- package/src/providers/model.ts +115 -135
- package/src/providers/openai.ts +3 -3
- package/src/resources/impls/git.ts +123 -146
- package/src/resources/impls/npm.test.ts +16 -5
- package/src/resources/impls/npm.ts +66 -75
- package/src/resources/index.ts +6 -1
- package/src/resources/schema.ts +7 -6
- package/src/resources/service.test.ts +13 -12
- package/src/resources/service.ts +153 -112
- package/src/stream/index.ts +1 -1
- package/src/stream/service.test.ts +5 -5
- package/src/stream/service.ts +282 -293
- package/src/tools/glob.ts +126 -141
- package/src/tools/grep.ts +205 -210
- package/src/tools/index.ts +8 -4
- package/src/tools/list.ts +118 -140
- package/src/tools/read.ts +209 -235
- package/src/tools/virtual-sandbox.ts +91 -83
- package/src/validation/index.ts +18 -22
- package/src/vfs/virtual-fs.test.ts +37 -25
- package/src/vfs/virtual-fs.ts +218 -216
package/src/validation/index.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* - Command injection via branch names
|
|
8
8
|
* - DoS via unbounded input sizes
|
|
9
9
|
*/
|
|
10
|
-
import { Result } from 'better-result';
|
|
11
10
|
|
|
12
11
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
12
|
// Regex Patterns
|
|
@@ -73,7 +72,13 @@ const ok = (): ValidationResult => ({ valid: true });
|
|
|
73
72
|
const okWithValue = <T>(value: T): ValidationResultWithValue<T> => ({ valid: true, value });
|
|
74
73
|
const fail = (error: string): ValidationResult => ({ valid: false, error });
|
|
75
74
|
const failWithValue = <T>(error: string): ValidationResultWithValue<T> => ({ valid: false, error });
|
|
76
|
-
const parseUrl = (value: string) =>
|
|
75
|
+
const parseUrl = (value: string) => {
|
|
76
|
+
try {
|
|
77
|
+
return new URL(value);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
77
82
|
const isWsl = () =>
|
|
78
83
|
process.platform === 'linux' &&
|
|
79
84
|
(Boolean(process.env.WSL_DISTRO_NAME) ||
|
|
@@ -173,10 +178,7 @@ export const validateBranchName = (branch: string): ValidationResult => {
|
|
|
173
178
|
* Non-GitHub URLs are returned unchanged.
|
|
174
179
|
*/
|
|
175
180
|
export const normalizeGitHubUrl = (url: string): string => {
|
|
176
|
-
const parsed = parseUrl(url)
|
|
177
|
-
ok: (value) => value,
|
|
178
|
-
err: () => null
|
|
179
|
-
});
|
|
181
|
+
const parsed = parseUrl(url);
|
|
180
182
|
if (!parsed) return url;
|
|
181
183
|
|
|
182
184
|
const hostname = parsed.hostname.toLowerCase();
|
|
@@ -221,10 +223,7 @@ export const validateGitUrl = (url: string): ValidationResultWithValue<string> =
|
|
|
221
223
|
return failWithValue('Git URL cannot be empty');
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
const parsed = parseUrl(url)
|
|
225
|
-
ok: (value) => value,
|
|
226
|
-
err: () => null
|
|
227
|
-
});
|
|
226
|
+
const parsed = parseUrl(url);
|
|
228
227
|
if (!parsed) return failWithValue(`Invalid URL format: "${url}"`);
|
|
229
228
|
|
|
230
229
|
// Only allow HTTPS protocol
|
|
@@ -321,10 +320,13 @@ const toNpmReference = (parsed: { packageName: string; version?: string }): Pars
|
|
|
321
320
|
};
|
|
322
321
|
|
|
323
322
|
const safeDecodeUriComponent = (value: string): string | null =>
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
323
|
+
(() => {
|
|
324
|
+
try {
|
|
325
|
+
return decodeURIComponent(value);
|
|
326
|
+
} catch {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
328
330
|
|
|
329
331
|
const parseNpmSpecReference = (reference: string): ParsedNpmReference | null => {
|
|
330
332
|
if (!reference.startsWith('npm:')) return null;
|
|
@@ -339,10 +341,7 @@ const parseNpmSpecReference = (reference: string): ParsedNpmReference | null =>
|
|
|
339
341
|
};
|
|
340
342
|
|
|
341
343
|
const parseNpmUrlReference = (reference: string): ParsedNpmReference | null => {
|
|
342
|
-
const parsedUrl = parseUrl(reference)
|
|
343
|
-
ok: (value) => value,
|
|
344
|
-
err: () => null
|
|
345
|
-
});
|
|
344
|
+
const parsedUrl = parseUrl(reference);
|
|
346
345
|
if (!parsedUrl) return null;
|
|
347
346
|
if (parsedUrl.protocol !== 'https:') return null;
|
|
348
347
|
|
|
@@ -375,10 +374,7 @@ export const parseNpmReference = (reference: string): ParsedNpmReference | null
|
|
|
375
374
|
parseNpmSpecReference(reference) ?? parseNpmUrlReference(reference);
|
|
376
375
|
|
|
377
376
|
const isNpmPackageUrl = (reference: string): boolean => {
|
|
378
|
-
const parsedUrl = parseUrl(reference)
|
|
379
|
-
ok: (value) => value,
|
|
380
|
-
err: () => null
|
|
381
|
-
});
|
|
377
|
+
const parsedUrl = parseUrl(reference);
|
|
382
378
|
if (!parsedUrl || parsedUrl.protocol !== 'https:') return false;
|
|
383
379
|
const hostname = parsedUrl.hostname.toLowerCase();
|
|
384
380
|
if (hostname !== 'npmjs.com' && hostname !== 'www.npmjs.com') return false;
|
|
@@ -4,11 +4,23 @@ import { promises as fs } from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
7
|
+
import { executeGlobTool } from '../tools/glob.ts';
|
|
8
|
+
import { executeGrepTool } from '../tools/grep.ts';
|
|
9
|
+
import { executeListTool } from '../tools/list.ts';
|
|
10
|
+
import { executeReadTool } from '../tools/read.ts';
|
|
11
|
+
import {
|
|
12
|
+
createVirtualFs,
|
|
13
|
+
disposeVirtualFs,
|
|
14
|
+
existsInVirtualFs,
|
|
15
|
+
importDirectoryIntoVirtualFs,
|
|
16
|
+
listVirtualFsFilesRecursive,
|
|
17
|
+
mkdirVirtualFs,
|
|
18
|
+
readVirtualFsFile,
|
|
19
|
+
readdirVirtualFs,
|
|
20
|
+
rmVirtualFs,
|
|
21
|
+
statVirtualFs,
|
|
22
|
+
writeVirtualFsFile
|
|
23
|
+
} from './virtual-fs.ts';
|
|
12
24
|
|
|
13
25
|
const posix = path.posix;
|
|
14
26
|
|
|
@@ -16,7 +28,7 @@ const createRoot = () => `/vfs-test-${randomUUID()}`;
|
|
|
16
28
|
|
|
17
29
|
const cleanupVirtual = async (root: string, vfsId?: string) => {
|
|
18
30
|
try {
|
|
19
|
-
await
|
|
31
|
+
await rmVirtualFs(root, { recursive: true, force: true }, vfsId);
|
|
20
32
|
} catch {
|
|
21
33
|
// ignore cleanup failures
|
|
22
34
|
}
|
|
@@ -25,37 +37,37 @@ const cleanupVirtual = async (root: string, vfsId?: string) => {
|
|
|
25
37
|
describe('VirtualFs (just-bash)', () => {
|
|
26
38
|
it('supports basic in-memory file operations', async () => {
|
|
27
39
|
const root = createRoot();
|
|
28
|
-
const vfsId =
|
|
40
|
+
const vfsId = createVirtualFs();
|
|
29
41
|
try {
|
|
30
42
|
const dir = posix.join(root, 'dir');
|
|
31
43
|
const file = posix.join(dir, 'hello.txt');
|
|
32
44
|
|
|
33
|
-
await
|
|
34
|
-
await
|
|
45
|
+
await mkdirVirtualFs(dir, { recursive: true }, vfsId);
|
|
46
|
+
await writeVirtualFsFile(file, 'Hello virtual', vfsId);
|
|
35
47
|
|
|
36
|
-
const text = await
|
|
48
|
+
const text = await readVirtualFsFile(file, vfsId);
|
|
37
49
|
expect(text).toBe('Hello virtual');
|
|
38
50
|
|
|
39
|
-
const fileStat = await
|
|
51
|
+
const fileStat = await statVirtualFs(file, vfsId);
|
|
40
52
|
expect(fileStat.isFile).toBe(true);
|
|
41
53
|
|
|
42
|
-
const dirStat = await
|
|
54
|
+
const dirStat = await statVirtualFs(dir, vfsId);
|
|
43
55
|
expect(dirStat.isDirectory).toBe(true);
|
|
44
56
|
|
|
45
|
-
const entries = await
|
|
57
|
+
const entries = await readdirVirtualFs(dir, vfsId);
|
|
46
58
|
expect(entries.some((entry) => entry.name === 'hello.txt')).toBe(true);
|
|
47
59
|
|
|
48
|
-
const files = await
|
|
60
|
+
const files = await listVirtualFsFilesRecursive(root, vfsId);
|
|
49
61
|
expect(files).toContain(file);
|
|
50
62
|
} finally {
|
|
51
63
|
await cleanupVirtual(root, vfsId);
|
|
52
|
-
|
|
64
|
+
disposeVirtualFs(vfsId);
|
|
53
65
|
}
|
|
54
66
|
});
|
|
55
67
|
|
|
56
68
|
it('imports from disk and works with virtual tools', async () => {
|
|
57
69
|
const root = createRoot();
|
|
58
|
-
const vfsId =
|
|
70
|
+
const vfsId = createVirtualFs();
|
|
59
71
|
const sourceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'btca-just-bash-'));
|
|
60
72
|
try {
|
|
61
73
|
const resourceName = `repo-${randomUUID()}`;
|
|
@@ -68,9 +80,9 @@ describe('VirtualFs (just-bash)', () => {
|
|
|
68
80
|
await fs.mkdir(path.join(sourceDir, '.git'), { recursive: true });
|
|
69
81
|
await fs.writeFile(path.join(sourceDir, '.git', 'HEAD'), 'ref: refs/heads/main');
|
|
70
82
|
|
|
71
|
-
await
|
|
83
|
+
await mkdirVirtualFs(collectionPath, { recursive: true }, vfsId);
|
|
72
84
|
|
|
73
|
-
await
|
|
85
|
+
await importDirectoryIntoVirtualFs({
|
|
74
86
|
sourcePath: sourceDir,
|
|
75
87
|
destinationPath: resourcePath,
|
|
76
88
|
vfsId,
|
|
@@ -82,26 +94,26 @@ describe('VirtualFs (just-bash)', () => {
|
|
|
82
94
|
}
|
|
83
95
|
});
|
|
84
96
|
|
|
85
|
-
expect(await
|
|
86
|
-
expect(await
|
|
97
|
+
expect(await existsInVirtualFs(posix.join(resourcePath, 'README.md'), vfsId)).toBe(true);
|
|
98
|
+
expect(await existsInVirtualFs(posix.join(resourcePath, '.git', 'HEAD'), vfsId)).toBe(false);
|
|
87
99
|
|
|
88
100
|
const context = { basePath: collectionPath, vfsId };
|
|
89
101
|
|
|
90
|
-
const listResult = await
|
|
102
|
+
const listResult = await executeListTool({ path: '.' }, context);
|
|
91
103
|
expect(listResult.metadata.entries.some((entry) => entry.name === resourceName)).toBe(true);
|
|
92
104
|
|
|
93
|
-
const readResult = await
|
|
105
|
+
const readResult = await executeReadTool({ path: `${resourceName}/README.md` }, context);
|
|
94
106
|
expect(readResult.output).toContain('needle');
|
|
95
107
|
|
|
96
|
-
const globResult = await
|
|
108
|
+
const globResult = await executeGlobTool({ pattern: '**/*.ts' }, context);
|
|
97
109
|
expect(globResult.output.split('\n')).toContain(`${resourceName}/src/index.ts`);
|
|
98
110
|
|
|
99
|
-
const grepResult = await
|
|
111
|
+
const grepResult = await executeGrepTool({ pattern: 'needle' }, context);
|
|
100
112
|
expect(grepResult.output).toContain(`${resourceName}/README.md`);
|
|
101
113
|
} finally {
|
|
102
114
|
await cleanupVirtual(root, vfsId);
|
|
103
115
|
await fs.rm(sourceDir, { recursive: true, force: true });
|
|
104
|
-
|
|
116
|
+
disposeVirtualFs(vfsId);
|
|
105
117
|
}
|
|
106
118
|
});
|
|
107
119
|
});
|