@ottocode/server 0.1.265 → 0.1.267
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/routes/auth/copilot.ts +699 -0
- package/src/routes/auth/oauth.ts +578 -0
- package/src/routes/auth/onboarding.ts +45 -0
- package/src/routes/auth/providers.ts +189 -0
- package/src/routes/auth/service.ts +167 -0
- package/src/routes/auth/state.ts +23 -0
- package/src/routes/auth/status.ts +203 -0
- package/src/routes/auth/wallet.ts +229 -0
- package/src/routes/auth.ts +12 -2080
- package/src/routes/config/models-service.ts +411 -0
- package/src/routes/config/models.ts +6 -426
- package/src/routes/config/providers-service.ts +237 -0
- package/src/routes/config/providers.ts +10 -242
- package/src/routes/files/handlers.ts +297 -0
- package/src/routes/files/service.ts +313 -0
- package/src/routes/files.ts +12 -608
- package/src/routes/git/commit-service.ts +207 -0
- package/src/routes/git/commit.ts +6 -220
- package/src/routes/git/remote-service.ts +116 -0
- package/src/routes/git/remote.ts +8 -115
- package/src/routes/git/staging-service.ts +111 -0
- package/src/routes/git/staging.ts +10 -205
- package/src/routes/mcp/auth.ts +338 -0
- package/src/routes/mcp/lifecycle.ts +263 -0
- package/src/routes/mcp/servers.ts +212 -0
- package/src/routes/mcp/service.ts +664 -0
- package/src/routes/mcp/state.ts +13 -0
- package/src/routes/mcp.ts +6 -1233
- package/src/routes/ottorouter/billing.ts +593 -0
- package/src/routes/ottorouter/service.ts +92 -0
- package/src/routes/ottorouter/topup.ts +301 -0
- package/src/routes/ottorouter/wallet.ts +370 -0
- package/src/routes/ottorouter.ts +6 -1319
- package/src/routes/research/service.ts +339 -0
- package/src/routes/research.ts +12 -390
- package/src/routes/sessions/crud.ts +563 -0
- package/src/routes/sessions/queue.ts +242 -0
- package/src/routes/sessions/retry.ts +121 -0
- package/src/routes/sessions/service.ts +768 -0
- package/src/routes/sessions/share.ts +434 -0
- package/src/routes/sessions.ts +8 -1977
- package/src/routes/skills/service.ts +221 -0
- package/src/routes/skills/spec.ts +309 -0
- package/src/routes/skills.ts +31 -909
- package/src/routes/terminals/service.ts +326 -0
- package/src/routes/terminals.ts +19 -295
- package/src/routes/tunnel/service.ts +217 -0
- package/src/routes/tunnel.ts +29 -219
- package/src/runtime/agent/registry-prompts.ts +147 -0
- package/src/runtime/agent/registry.ts +6 -124
- package/src/runtime/agent/runner-errors.ts +116 -0
- package/src/runtime/agent/runner-reminders.ts +45 -0
- package/src/runtime/agent/runner-setup-model.ts +75 -0
- package/src/runtime/agent/runner-setup-prompt.ts +185 -0
- package/src/runtime/agent/runner-setup-tools.ts +103 -0
- package/src/runtime/agent/runner-setup-utils.ts +21 -0
- package/src/runtime/agent/runner-setup.ts +54 -288
- package/src/runtime/agent/runner-telemetry.ts +112 -0
- package/src/runtime/agent/runner-text.ts +108 -0
- package/src/runtime/agent/runner-tool-observer.ts +86 -0
- package/src/runtime/agent/runner.ts +79 -378
- package/src/runtime/prompt/builder.ts +5 -1
- package/src/runtime/prompt/capabilities.ts +13 -8
- package/src/runtime/provider/custom.ts +73 -0
- package/src/runtime/provider/index.ts +2 -85
- package/src/runtime/provider/reasoning-builders.ts +280 -0
- package/src/runtime/provider/reasoning.ts +67 -264
- package/src/tools/adapter/events.ts +116 -0
- package/src/tools/adapter/execution.ts +160 -0
- package/src/tools/adapter/pending.ts +37 -0
- package/src/tools/adapter/persistence.ts +166 -0
- package/src/tools/adapter/results.ts +97 -0
- package/src/tools/adapter.ts +124 -451
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, relative, resolve } from 'node:path';
|
|
4
|
+
import { spawn, exec } from 'node:child_process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { logger } from '@ottocode/sdk';
|
|
7
|
+
import { resolveBinary } from '@ottocode/sdk/tools/bin-manager';
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
const EXCLUDED_FILES = new Set(['.DS_Store', 'bun.lockb']);
|
|
12
|
+
|
|
13
|
+
const HOME_SEARCH_MAX_DEPTH = 3;
|
|
14
|
+
const HOME_SEARCH_LIMIT = 500;
|
|
15
|
+
const DEFAULT_SEARCH_MAX_DEPTH = 12;
|
|
16
|
+
const DEFAULT_SEARCH_LIMIT = 10_000;
|
|
17
|
+
export const TREE_ENTRY_LIMIT = 1000;
|
|
18
|
+
|
|
19
|
+
const EXCLUDED_DIRS = new Set([
|
|
20
|
+
'node_modules',
|
|
21
|
+
'.git',
|
|
22
|
+
'dist',
|
|
23
|
+
'build',
|
|
24
|
+
'.next',
|
|
25
|
+
'.nuxt',
|
|
26
|
+
'.turbo',
|
|
27
|
+
'.astro',
|
|
28
|
+
'.svelte-kit',
|
|
29
|
+
'.vercel',
|
|
30
|
+
'.output',
|
|
31
|
+
'coverage',
|
|
32
|
+
'.cache',
|
|
33
|
+
'__pycache__',
|
|
34
|
+
'.tsbuildinfo',
|
|
35
|
+
'target',
|
|
36
|
+
'.cargo',
|
|
37
|
+
'.rustup',
|
|
38
|
+
'vendor',
|
|
39
|
+
'.gradle',
|
|
40
|
+
'.idea',
|
|
41
|
+
'.vscode',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
type SearchPolicy = {
|
|
45
|
+
maxDepth: number;
|
|
46
|
+
limit: number;
|
|
47
|
+
includeIgnored: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function isHomeDirectory(projectRoot: string): boolean {
|
|
51
|
+
return resolve(projectRoot) === resolve(homedir());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function clampNumber(value: number, min: number, max: number): number {
|
|
55
|
+
if (!Number.isFinite(value)) return max;
|
|
56
|
+
return Math.min(Math.max(value, min), max);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getSearchPolicy(projectRoot: string): SearchPolicy {
|
|
60
|
+
if (isHomeDirectory(projectRoot)) {
|
|
61
|
+
return {
|
|
62
|
+
maxDepth: HOME_SEARCH_MAX_DEPTH,
|
|
63
|
+
limit: HOME_SEARCH_LIMIT,
|
|
64
|
+
includeIgnored: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
maxDepth: DEFAULT_SEARCH_MAX_DEPTH,
|
|
69
|
+
limit: DEFAULT_SEARCH_LIMIT,
|
|
70
|
+
includeIgnored: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function shouldExcludeFile(name: string): boolean {
|
|
75
|
+
return EXCLUDED_FILES.has(name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function shouldExcludeDir(name: string): boolean {
|
|
79
|
+
return EXCLUDED_DIRS.has(name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function shouldExcludeSearchDir(name: string): boolean {
|
|
83
|
+
return shouldExcludeDir(name) || name.startsWith('.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function listFilesWithRg(
|
|
87
|
+
projectRoot: string,
|
|
88
|
+
maxDepth: number,
|
|
89
|
+
limit: number,
|
|
90
|
+
includeIgnored = false,
|
|
91
|
+
query = '',
|
|
92
|
+
): Promise<{ files: string[]; truncated: boolean }> {
|
|
93
|
+
const rgBin = await resolveBinary('rg');
|
|
94
|
+
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
const args = ['--files', '--sort', 'path', '--max-depth', String(maxDepth)];
|
|
97
|
+
if (includeIgnored) {
|
|
98
|
+
args.push('--no-ignore');
|
|
99
|
+
}
|
|
100
|
+
for (const dir of EXCLUDED_DIRS) {
|
|
101
|
+
args.push('--glob', `!**/${dir}/**`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const proc = spawn(rgBin, args, { cwd: projectRoot });
|
|
105
|
+
let stdout = '';
|
|
106
|
+
let stderr = '';
|
|
107
|
+
|
|
108
|
+
proc.stdout.on('data', (data) => {
|
|
109
|
+
stdout += data.toString();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
proc.stderr.on('data', (data) => {
|
|
113
|
+
stderr += data.toString();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
proc.on('close', (code) => {
|
|
117
|
+
if (code !== 0 && code !== 1) {
|
|
118
|
+
logger.warn('rg --files failed, falling back', { stderr } as Record<
|
|
119
|
+
string,
|
|
120
|
+
unknown
|
|
121
|
+
>);
|
|
122
|
+
resolve({ files: [], truncated: false });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const allFiles = stdout.split('\n').filter(Boolean);
|
|
127
|
+
|
|
128
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
129
|
+
const filtered = allFiles.filter((f) => {
|
|
130
|
+
const filename = f.split(/[\\/]/).pop() || f;
|
|
131
|
+
if (shouldExcludeFile(filename)) return false;
|
|
132
|
+
if (!normalizedQuery) return true;
|
|
133
|
+
return f.toLowerCase().includes(normalizedQuery);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const truncated = filtered.length > limit;
|
|
137
|
+
resolve({ files: filtered.slice(0, limit), truncated });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
proc.on('error', () => {
|
|
141
|
+
resolve({ files: [], truncated: false });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function parseGitignore(
|
|
147
|
+
projectRoot: string,
|
|
148
|
+
): Promise<Set<string>> {
|
|
149
|
+
const patterns = new Set<string>();
|
|
150
|
+
try {
|
|
151
|
+
const gitignorePath = join(projectRoot, '.gitignore');
|
|
152
|
+
const content = await readFile(gitignorePath, 'utf-8');
|
|
153
|
+
for (const line of content.split('\n')) {
|
|
154
|
+
const trimmed = line.trim();
|
|
155
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
156
|
+
patterns.add(trimmed);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (_err) {}
|
|
160
|
+
return patterns;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function matchesGitignorePattern(
|
|
164
|
+
relativePath: string,
|
|
165
|
+
patterns: Set<string>,
|
|
166
|
+
): boolean {
|
|
167
|
+
for (const pattern of patterns) {
|
|
168
|
+
const cleanPattern = pattern.replace(/^\//, '').replace(/\/$/, '');
|
|
169
|
+
const pathParts = relativePath.split(/[\\/]/);
|
|
170
|
+
|
|
171
|
+
if (pattern.endsWith('/')) {
|
|
172
|
+
if (pathParts[0] === cleanPattern) return true;
|
|
173
|
+
if (relativePath.startsWith(`${cleanPattern}/`)) return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (pattern.includes('*')) {
|
|
177
|
+
const regex = new RegExp(
|
|
178
|
+
`^${cleanPattern.replace(/\*/g, '.*').replace(/\?/g, '.')}$`,
|
|
179
|
+
);
|
|
180
|
+
if (regex.test(relativePath)) return true;
|
|
181
|
+
for (const part of pathParts) {
|
|
182
|
+
if (regex.test(part)) return true;
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
if (relativePath === cleanPattern) return true;
|
|
186
|
+
if (pathParts.includes(cleanPattern)) return true;
|
|
187
|
+
if (relativePath.startsWith(`${cleanPattern}/`)) return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function traverseDirectory(
|
|
194
|
+
dir: string,
|
|
195
|
+
projectRoot: string,
|
|
196
|
+
maxDepth: number,
|
|
197
|
+
currentDepth = 0,
|
|
198
|
+
limit: number,
|
|
199
|
+
collected: string[] = [],
|
|
200
|
+
gitignorePatterns?: Set<string>,
|
|
201
|
+
): Promise<{ files: string[]; truncated: boolean }> {
|
|
202
|
+
if (currentDepth >= maxDepth || collected.length >= limit) {
|
|
203
|
+
return { files: collected, truncated: collected.length >= limit };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
208
|
+
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
if (collected.length >= limit) {
|
|
211
|
+
return { files: collected, truncated: true };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const fullPath = join(dir, entry.name);
|
|
215
|
+
const relativePath = relative(projectRoot, fullPath);
|
|
216
|
+
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
if (shouldExcludeSearchDir(entry.name)) continue;
|
|
219
|
+
if (
|
|
220
|
+
gitignorePatterns &&
|
|
221
|
+
matchesGitignorePattern(relativePath, gitignorePatterns)
|
|
222
|
+
) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const result = await traverseDirectory(
|
|
226
|
+
fullPath,
|
|
227
|
+
projectRoot,
|
|
228
|
+
maxDepth,
|
|
229
|
+
currentDepth + 1,
|
|
230
|
+
limit,
|
|
231
|
+
collected,
|
|
232
|
+
gitignorePatterns,
|
|
233
|
+
);
|
|
234
|
+
if (result.truncated) {
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
} else if (entry.isFile()) {
|
|
238
|
+
if (shouldExcludeFile(entry.name)) continue;
|
|
239
|
+
if (
|
|
240
|
+
gitignorePatterns &&
|
|
241
|
+
matchesGitignorePattern(relativePath, gitignorePatterns)
|
|
242
|
+
) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
collected.push(relativePath);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch (err) {
|
|
249
|
+
logger.warn(
|
|
250
|
+
`Failed to read directory ${dir}:`,
|
|
251
|
+
err as Record<string, unknown>,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { files: collected, truncated: false };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function getChangedFiles(
|
|
259
|
+
projectRoot: string,
|
|
260
|
+
): Promise<Map<string, string>> {
|
|
261
|
+
try {
|
|
262
|
+
const { stdout } = await execAsync('git status --porcelain', {
|
|
263
|
+
cwd: projectRoot,
|
|
264
|
+
});
|
|
265
|
+
const changedFiles = new Map<string, string>();
|
|
266
|
+
for (const line of stdout.split('\n')) {
|
|
267
|
+
if (line.length > 3) {
|
|
268
|
+
const statusCode = line.substring(0, 2).trim();
|
|
269
|
+
const filePath = line.substring(3).trim();
|
|
270
|
+
|
|
271
|
+
let status = 'modified';
|
|
272
|
+
if (statusCode.includes('A')) status = 'added';
|
|
273
|
+
else if (statusCode.includes('M')) status = 'modified';
|
|
274
|
+
else if (statusCode.includes('D')) status = 'deleted';
|
|
275
|
+
else if (statusCode.includes('R')) status = 'renamed';
|
|
276
|
+
else if (statusCode.includes('?')) status = 'untracked';
|
|
277
|
+
|
|
278
|
+
changedFiles.set(filePath, status);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return changedFiles;
|
|
282
|
+
} catch (_err) {
|
|
283
|
+
return new Map();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function getGitIgnoredFiles(
|
|
288
|
+
projectRoot: string,
|
|
289
|
+
files: string[],
|
|
290
|
+
): Promise<Set<string>> {
|
|
291
|
+
if (files.length === 0) return new Set();
|
|
292
|
+
try {
|
|
293
|
+
return new Promise((resolve) => {
|
|
294
|
+
const proc = spawn('git', ['check-ignore', '--stdin'], {
|
|
295
|
+
cwd: projectRoot,
|
|
296
|
+
});
|
|
297
|
+
let stdout = '';
|
|
298
|
+
proc.stdout.on('data', (data) => {
|
|
299
|
+
stdout += data.toString();
|
|
300
|
+
});
|
|
301
|
+
proc.on('close', () => {
|
|
302
|
+
resolve(new Set(stdout.split('\n').filter(Boolean)));
|
|
303
|
+
});
|
|
304
|
+
proc.on('error', () => {
|
|
305
|
+
resolve(new Set());
|
|
306
|
+
});
|
|
307
|
+
proc.stdin.write(files.join('\n'));
|
|
308
|
+
proc.stdin.end();
|
|
309
|
+
});
|
|
310
|
+
} catch (_err) {
|
|
311
|
+
return new Set();
|
|
312
|
+
}
|
|
313
|
+
}
|