lonny-agent 0.2.3 → 0.2.4
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/.claude/settings.local.json +14 -0
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/project.d.ts +26 -0
- package/dist/agent/project.d.ts.map +1 -0
- package/dist/agent/project.js +303 -0
- package/dist/agent/project.js.map +1 -0
- package/dist/agent/prompt-builder.d.ts +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +10 -5
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/anthropic.d.ts.map +1 -1
- package/dist/agent/providers/anthropic.js +20 -2
- package/dist/agent/providers/anthropic.js.map +1 -1
- package/dist/agent/providers/google.js +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/ollama.d.ts.map +1 -1
- package/dist/agent/providers/ollama.js +2 -1
- package/dist/agent/providers/ollama.js.map +1 -1
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +23 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts +4 -2
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +206 -147
- package/dist/agent/session.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +14 -14
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/edit.d.ts +17 -0
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +81 -34
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/read.d.ts +6 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +42 -15
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +0 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/write_plan.d.ts +1 -1
- package/dist/tools/write_plan.d.ts.map +1 -1
- package/dist/tools/write_plan.js +14 -4
- package/dist/tools/write_plan.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +5 -4
- package/dist/tui/index.js.map +1 -1
- package/dist/web/index.js +5 -5
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/index.ts +1 -1
- package/src/agent/project.ts +366 -0
- package/src/agent/prompt-builder.ts +11 -5
- package/src/agent/providers/anthropic.ts +9 -2
- package/src/agent/providers/google.ts +1 -1
- package/src/agent/providers/ollama.ts +5 -1
- package/src/agent/providers/openai.ts +15 -3
- package/src/agent/session.ts +216 -150
- package/src/tools/__tests__/edit.test.ts +14 -14
- package/src/tools/edit.ts +103 -36
- package/src/tools/read.ts +61 -25
- package/src/tools/registry.ts +260 -265
- package/src/tools/write_plan.ts +21 -5
- package/src/tui/index.ts +5 -4
- package/src/web/index.ts +5 -5
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
|
|
4
|
+
// ── Project type detection ─────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export type ProjectType = 'node' | 'python' | 'rust' | 'go' | 'java' | 'cpp' | 'unknown'
|
|
7
|
+
|
|
8
|
+
export interface ProjectInfo {
|
|
9
|
+
type: ProjectType
|
|
10
|
+
entryPoints: string[]
|
|
11
|
+
dependencies: Record<string, string>
|
|
12
|
+
devDependencies: Record<string, string>
|
|
13
|
+
testFiles: string[]
|
|
14
|
+
configFiles: string[]
|
|
15
|
+
srcDirs: string[]
|
|
16
|
+
hasTests: boolean
|
|
17
|
+
packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun' | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PackageJson {
|
|
21
|
+
name?: string
|
|
22
|
+
version?: string
|
|
23
|
+
main?: string
|
|
24
|
+
module?: string
|
|
25
|
+
exports?: string | Record<string, string>
|
|
26
|
+
dependencies?: Record<string, string>
|
|
27
|
+
devDependencies?: Record<string, string>
|
|
28
|
+
scripts?: Record<string, string>
|
|
29
|
+
type?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Common entry point patterns
|
|
33
|
+
const ENTRY_PATTERNS: Record<ProjectType, string[]> = {
|
|
34
|
+
node: [
|
|
35
|
+
'src/index.ts',
|
|
36
|
+
'src/main.ts',
|
|
37
|
+
'src/app.ts',
|
|
38
|
+
'src/server.ts',
|
|
39
|
+
'index.ts',
|
|
40
|
+
'app.ts',
|
|
41
|
+
'server.ts',
|
|
42
|
+
'main.ts',
|
|
43
|
+
],
|
|
44
|
+
python: ['main.py', 'app.py', 'src/main.py', 'src/app.py'],
|
|
45
|
+
rust: ['src/main.rs', 'src/lib.rs'],
|
|
46
|
+
go: ['main.go', 'cmd/main.go'],
|
|
47
|
+
java: ['src/main/java/Main.java'],
|
|
48
|
+
cpp: ['src/main.cpp', 'main.cpp'],
|
|
49
|
+
unknown: [],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const TEST_PATTERNS: Record<ProjectType, string[]> = {
|
|
53
|
+
node: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**', 'tests/**/*.ts'],
|
|
54
|
+
python: ['test_*.py', '*_test.py', 'tests/**/*.py'],
|
|
55
|
+
rust: ['tests/**/*.rs', 'src/**/*.rs'],
|
|
56
|
+
go: ['*_test.go'],
|
|
57
|
+
java: ['**/*Test.java', '**/*Tests.java'],
|
|
58
|
+
cpp: ['**/*test*.cpp', 'tests/**/*.cpp'],
|
|
59
|
+
unknown: [],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CONFIG_FILES: Record<ProjectType, string[]> = {
|
|
63
|
+
node: [
|
|
64
|
+
'package.json',
|
|
65
|
+
'tsconfig.json',
|
|
66
|
+
'biome.json',
|
|
67
|
+
'.biomerc',
|
|
68
|
+
'jest.config.*',
|
|
69
|
+
'vitest.config.*',
|
|
70
|
+
'.eslintrc*',
|
|
71
|
+
'.prettierrc*',
|
|
72
|
+
'.npmrc',
|
|
73
|
+
],
|
|
74
|
+
python: [
|
|
75
|
+
'pyproject.toml',
|
|
76
|
+
'setup.py',
|
|
77
|
+
'setup.cfg',
|
|
78
|
+
'requirements.txt',
|
|
79
|
+
'Pipfile',
|
|
80
|
+
'poetry.lock',
|
|
81
|
+
'pytest.ini',
|
|
82
|
+
'pyproject.toml',
|
|
83
|
+
],
|
|
84
|
+
rust: ['Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml'],
|
|
85
|
+
go: ['go.mod', 'go.sum'],
|
|
86
|
+
java: ['pom.xml', 'build.gradle', 'build.gradle.kts'],
|
|
87
|
+
cpp: ['CMakeLists.txt', 'Makefile', '*.cmake'],
|
|
88
|
+
unknown: [],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const SRC_DIRS: Record<ProjectType, string[]> = {
|
|
92
|
+
node: ['src', 'lib', 'app', 'packages'],
|
|
93
|
+
python: ['src', 'lib', 'app'],
|
|
94
|
+
rust: ['src'],
|
|
95
|
+
go: ['cmd', 'internal', 'pkg'],
|
|
96
|
+
java: ['src/main/java', 'src'],
|
|
97
|
+
cpp: ['src', 'lib', 'include'],
|
|
98
|
+
unknown: [],
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Detection functions ───────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/** Detect project type from root directory */
|
|
104
|
+
async function detectProjectType(cwd: string): Promise<ProjectType> {
|
|
105
|
+
const files = await fs.readdir(cwd, { withFileTypes: true })
|
|
106
|
+
const fileNames = files.map(f => f.name.toLowerCase())
|
|
107
|
+
|
|
108
|
+
// Priority order matters
|
|
109
|
+
if (fileNames.includes('cargo.toml')) return 'rust'
|
|
110
|
+
if (fileNames.includes('go.mod')) return 'go'
|
|
111
|
+
if (fileNames.includes('pom.xml') || fileNames.includes('build.gradle')) return 'java'
|
|
112
|
+
if (fileNames.includes('package.json')) return 'node'
|
|
113
|
+
if (
|
|
114
|
+
fileNames.includes('pyproject.toml') ||
|
|
115
|
+
fileNames.includes('setup.py') ||
|
|
116
|
+
fileNames.includes('requirements.txt')
|
|
117
|
+
)
|
|
118
|
+
return 'python'
|
|
119
|
+
if (fileNames.includes('cmakelists.txt')) return 'cpp'
|
|
120
|
+
|
|
121
|
+
return 'unknown'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Find entry points for the project */
|
|
125
|
+
async function findEntryPoints(cwd: string, type: ProjectType): Promise<string[]> {
|
|
126
|
+
if (type === 'unknown') return []
|
|
127
|
+
|
|
128
|
+
const entryPoints: string[] = []
|
|
129
|
+
const patterns = ENTRY_PATTERNS[type]
|
|
130
|
+
|
|
131
|
+
for (const pattern of patterns) {
|
|
132
|
+
const fullPath = path.join(cwd, pattern)
|
|
133
|
+
try {
|
|
134
|
+
const stat = await fs.stat(fullPath)
|
|
135
|
+
if (stat.isFile()) {
|
|
136
|
+
entryPoints.push(pattern)
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// File doesn't exist, skip
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// For Node.js, also check package.json main/module
|
|
144
|
+
if (type === 'node') {
|
|
145
|
+
try {
|
|
146
|
+
const pkgPath = path.join(cwd, 'package.json')
|
|
147
|
+
const content = await fs.readFile(pkgPath, 'utf-8')
|
|
148
|
+
const pkg: PackageJson = JSON.parse(content)
|
|
149
|
+
if (pkg.main && !entryPoints.includes(pkg.main)) {
|
|
150
|
+
entryPoints.push(pkg.main)
|
|
151
|
+
}
|
|
152
|
+
if (pkg.module && !entryPoints.includes(pkg.module)) {
|
|
153
|
+
entryPoints.push(pkg.module)
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// No package.json
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return entryPoints
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Find test files */
|
|
164
|
+
async function findTestFiles(cwd: string, type: ProjectType): Promise<string[]> {
|
|
165
|
+
if (type === 'unknown') return []
|
|
166
|
+
|
|
167
|
+
const testFiles: string[] = []
|
|
168
|
+
const patterns = TEST_PATTERNS[type]
|
|
169
|
+
|
|
170
|
+
// Simple glob simulation - just check common locations
|
|
171
|
+
const dirs =
|
|
172
|
+
type === 'node'
|
|
173
|
+
? ['src', 'tests', '__tests__', '']
|
|
174
|
+
: type === 'python'
|
|
175
|
+
? ['tests', '']
|
|
176
|
+
: type === 'rust'
|
|
177
|
+
? ['tests', 'src']
|
|
178
|
+
: ['']
|
|
179
|
+
|
|
180
|
+
for (const dir of dirs) {
|
|
181
|
+
const searchDir = dir ? path.join(cwd, dir) : cwd
|
|
182
|
+
try {
|
|
183
|
+
const files = await fs.readdir(searchDir, { withFileTypes: true })
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
if (file.isFile()) {
|
|
186
|
+
const name = file.name.toLowerCase()
|
|
187
|
+
if (name.includes('test') || name.includes('spec')) {
|
|
188
|
+
const relPath = dir ? `${dir}/${file.name}` : file.name
|
|
189
|
+
testFiles.push(relPath)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// Directory doesn't exist
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return testFiles.slice(0, 10) // Limit to 10
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Find config files */
|
|
202
|
+
async function findConfigFiles(cwd: string, type: ProjectType): Promise<string[]> {
|
|
203
|
+
const configs: string[] = []
|
|
204
|
+
const patterns = CONFIG_FILES[type] || []
|
|
205
|
+
|
|
206
|
+
for (const pattern of patterns) {
|
|
207
|
+
const fullPath = path.join(cwd, pattern)
|
|
208
|
+
try {
|
|
209
|
+
const stat = await fs.stat(fullPath)
|
|
210
|
+
if (stat.isFile()) {
|
|
211
|
+
configs.push(pattern)
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// File doesn't exist
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return configs
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Find source directories */
|
|
222
|
+
async function findSrcDirs(cwd: string, type: ProjectType): Promise<string[]> {
|
|
223
|
+
const srcDirs: string[] = []
|
|
224
|
+
const patterns = SRC_DIRS[type] || []
|
|
225
|
+
|
|
226
|
+
for (const pattern of patterns) {
|
|
227
|
+
const fullPath = path.join(cwd, pattern)
|
|
228
|
+
try {
|
|
229
|
+
const stat = await fs.stat(fullPath)
|
|
230
|
+
if (stat.isDirectory()) {
|
|
231
|
+
srcDirs.push(pattern)
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
// Directory doesn't exist
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return srcDirs
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Detect package manager */
|
|
242
|
+
async function detectPackageManager(cwd: string): Promise<ProjectInfo['packageManager']> {
|
|
243
|
+
const files = await fs.readdir(cwd, { withFileTypes: true })
|
|
244
|
+
const fileNames = files.map(f => f.name)
|
|
245
|
+
|
|
246
|
+
if (fileNames.includes('pnpm-lock.yaml')) return 'pnpm'
|
|
247
|
+
if (fileNames.includes('yarn.lock')) return 'yarn'
|
|
248
|
+
if (fileNames.includes('bun.lockb')) return 'bun'
|
|
249
|
+
if (fileNames.includes('package-lock.json')) return 'npm'
|
|
250
|
+
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Load dependencies from package.json */
|
|
255
|
+
async function loadDependencies(
|
|
256
|
+
cwd: string,
|
|
257
|
+
): Promise<{ deps: Record<string, string>; devDeps: Record<string, string> }> {
|
|
258
|
+
try {
|
|
259
|
+
const pkgPath = path.join(cwd, 'package.json')
|
|
260
|
+
const content = await fs.readFile(pkgPath, 'utf-8')
|
|
261
|
+
const pkg: PackageJson = JSON.parse(content)
|
|
262
|
+
return {
|
|
263
|
+
deps: pkg.dependencies || {},
|
|
264
|
+
devDeps: pkg.devDependencies || {},
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
return { deps: {}, devDeps: {} }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Main discovery function ───────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
// Cache for project info
|
|
274
|
+
const projectCache = new Map<string, { info: ProjectInfo; timestamp: number }>()
|
|
275
|
+
const CACHE_TTL = 60_000 // 1 minute
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Discover project information for a given directory.
|
|
279
|
+
* Results are cached for 1 minute to avoid repeated disk I/O.
|
|
280
|
+
*/
|
|
281
|
+
export async function discoverProject(cwd: string): Promise<ProjectInfo> {
|
|
282
|
+
// Check cache first
|
|
283
|
+
const cached = projectCache.get(cwd)
|
|
284
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
285
|
+
return cached.info
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const type = await detectProjectType(cwd)
|
|
289
|
+
const [entryPoints, testFiles, configFiles, srcDirs, packageManager, deps] = await Promise.all([
|
|
290
|
+
findEntryPoints(cwd, type),
|
|
291
|
+
findTestFiles(cwd, type),
|
|
292
|
+
findConfigFiles(cwd, type),
|
|
293
|
+
findSrcDirs(cwd, type),
|
|
294
|
+
detectPackageManager(cwd),
|
|
295
|
+
type === 'node' ? loadDependencies(cwd) : Promise.resolve({ deps: {}, devDeps: {} }),
|
|
296
|
+
])
|
|
297
|
+
|
|
298
|
+
const info: ProjectInfo = {
|
|
299
|
+
type,
|
|
300
|
+
entryPoints,
|
|
301
|
+
dependencies: deps.deps,
|
|
302
|
+
devDependencies: deps.devDeps,
|
|
303
|
+
testFiles,
|
|
304
|
+
configFiles,
|
|
305
|
+
srcDirs,
|
|
306
|
+
hasTests: testFiles.length > 0,
|
|
307
|
+
packageManager,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Cache the result
|
|
311
|
+
projectCache.set(cwd, { info, timestamp: Date.now() })
|
|
312
|
+
|
|
313
|
+
return info
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Clear project cache */
|
|
317
|
+
export function clearProjectCache(): void {
|
|
318
|
+
projectCache.clear()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Invalidate cache for a specific directory */
|
|
322
|
+
export function invalidateProjectCache(cwd: string): void {
|
|
323
|
+
projectCache.delete(cwd)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Format project info as a string for inclusion in prompts.
|
|
328
|
+
*/
|
|
329
|
+
export function formatProjectContext(info: ProjectInfo): string {
|
|
330
|
+
const lines: string[] = ['## Project Context']
|
|
331
|
+
|
|
332
|
+
if (info.type === 'unknown') {
|
|
333
|
+
lines.push('- Project type: Unknown')
|
|
334
|
+
return lines.join('\n')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
lines.push(`- Project type: ${info.type}`)
|
|
338
|
+
lines.push(`- Package manager: ${info.packageManager || 'unknown'}`)
|
|
339
|
+
|
|
340
|
+
if (info.entryPoints.length > 0) {
|
|
341
|
+
lines.push(`- Entry point(s): ${info.entryPoints.join(', ')}`)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (info.srcDirs.length > 0) {
|
|
345
|
+
lines.push(`- Source directories: ${info.srcDirs.join(', ')}`)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (info.configFiles.length > 0) {
|
|
349
|
+
lines.push(`- Config files: ${info.configFiles.join(', ')}`)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (info.hasTests) {
|
|
353
|
+
const testCount = info.testFiles.length
|
|
354
|
+
lines.push(`- Test files: ${testCount} found`)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Show main dependencies
|
|
358
|
+
const mainDeps = Object.keys(info.dependencies).slice(0, 10)
|
|
359
|
+
if (mainDeps.length > 0) {
|
|
360
|
+
lines.push(
|
|
361
|
+
`- Dependencies: ${mainDeps.join(', ')}${Object.keys(info.dependencies).length > 10 ? '...' : ''}`,
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return lines.join('\n')
|
|
366
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as os from 'node:os'
|
|
2
2
|
import type { Config } from '../config/index.js'
|
|
3
|
+
import { discoverProject, formatProjectContext } from './project.js'
|
|
3
4
|
import { formatSkillsForPrompt, loadSkills } from './skills.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Build the system prompt for the current configuration.
|
|
7
8
|
* Extracted from session.ts to keep module size manageable (<500 LoC target).
|
|
8
9
|
*/
|
|
9
|
-
export function buildSystemPrompt(config: Config): string {
|
|
10
|
+
export async function buildSystemPrompt(config: Config): Promise<string> {
|
|
10
11
|
const platform = os.platform()
|
|
11
12
|
const release = os.release()
|
|
12
13
|
const shell = process.env.SHELL || process.env.ComSpec || 'unknown'
|
|
@@ -18,6 +19,10 @@ export function buildSystemPrompt(config: Config): string {
|
|
|
18
19
|
const skills = loadSkills(cwd)
|
|
19
20
|
const skillsSection = formatSkillsForPrompt(skills)
|
|
20
21
|
|
|
22
|
+
// ── Load project context ─────────────────────────────────────────────────
|
|
23
|
+
const projectInfo = await discoverProject(cwd)
|
|
24
|
+
const projectSection = formatProjectContext(projectInfo)
|
|
25
|
+
|
|
21
26
|
// ── Mode-specific tool list ───────────────────────────────────────────
|
|
22
27
|
function getToolListForMode(mode: string): string {
|
|
23
28
|
if (mode === 'ask') {
|
|
@@ -36,7 +41,7 @@ export function buildSystemPrompt(config: Config): string {
|
|
|
36
41
|
- \`find\`: Find files by name pattern (pattern: string, path?: string, maxResults?: number)
|
|
37
42
|
- \`git\`: Run read-only git commands (command: string)
|
|
38
43
|
- \`search\`: Search the web using Tavily (query: string, search_depth?: string, include_answer?: boolean, max_results?: number, topic?: string, days?: number)
|
|
39
|
-
- \`write_plan\`: Save plan/todo markdown into .lonny/ folder
|
|
44
|
+
- \`write_plan\`: Save plan/todo markdown into .lonny/ folder (use descriptive names like backend-api.md, frontend-ui.md if splitting into multiple files)
|
|
40
45
|
`
|
|
41
46
|
}
|
|
42
47
|
return `Available tools:
|
|
@@ -45,7 +50,7 @@ export function buildSystemPrompt(config: Config): string {
|
|
|
45
50
|
- \`grep\`: Search file content by regex (pattern: string, include?: string, path?: string)
|
|
46
51
|
- \`ls\`: List directory (path?: string)
|
|
47
52
|
- \`bash\`: Execute a shell command
|
|
48
|
-
- \`edit\`: Replace text in files — call with {
|
|
53
|
+
- \`edit\`: Replace text in files — call with {edits: [{file_path: string, old_string: string, new_string: string}]} (array required)
|
|
49
54
|
- \`install_skill\`: Install an npm package as a skill — fetches package info from npm, runs npm install, and creates a .lonny/skills/ file with usage instructions for the AI
|
|
50
55
|
- \`find\`: Find files by name pattern (pattern: string, path?: string, maxResults?: number)
|
|
51
56
|
- \`git\`: Run read-only git commands (command: string)
|
|
@@ -78,7 +83,8 @@ RULES (plan-specific):
|
|
|
78
83
|
3. Use \`bash\` for investigation only — NEVER to modify files, install packages, or run write operations.
|
|
79
84
|
4. Your ONLY output is a plan file saved via \`write_plan\`. You CANNOT modify the codebase directly.
|
|
80
85
|
5. You MUST persist the final plan AND todo list to a file in \`.lonny/\` using \`write_plan\`. The \`write_plan\` content MUST include both ## Plan and ## Todo List sections.
|
|
81
|
-
6.
|
|
86
|
+
6. If the plan is very long (or the todo list has many items), split into multiple files with descriptive names like \`backend-api.md\`, \`frontend-ui.md\`, \`database.md\`, etc.
|
|
87
|
+
7. You MUST also include the todo list in your text response to the user (not just in the file).
|
|
82
88
|
7. If the user asks you to modify files, run write commands, or install packages — refuse and explain they need to switch to code mode (\`/mode code\`).
|
|
83
89
|
|
|
84
90
|
OUTPUT FORMAT (you MUST include both in write_plan AND in your response text):
|
|
@@ -171,5 +177,5 @@ ${isWindows ? ' - Use `type` instead of `cat`, `dir` instead of `ls`, `where` i
|
|
|
171
177
|
${sharedRules}`
|
|
172
178
|
return `${modeInstructions}
|
|
173
179
|
|
|
174
|
-
${envSection}${rulesSection}${methodologySection}${skillsSection}`
|
|
180
|
+
${envSection}${rulesSection}${methodologySection}${projectSection}${skillsSection}`
|
|
175
181
|
}
|
|
@@ -117,9 +117,14 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
117
117
|
} else if (event.type === 'content_block_stop') {
|
|
118
118
|
if (currentToolUse) {
|
|
119
119
|
let input: Record<string, unknown>
|
|
120
|
+
const rawInput = currentToolUse.input || ''
|
|
120
121
|
try {
|
|
121
|
-
input = JSON.parse(
|
|
122
|
+
input = JSON.parse(rawInput || '{}')
|
|
122
123
|
} catch {
|
|
124
|
+
console.error(
|
|
125
|
+
'[anthropic] Failed to parse tool_use input (content_block_stop):',
|
|
126
|
+
rawInput,
|
|
127
|
+
)
|
|
123
128
|
input = {}
|
|
124
129
|
}
|
|
125
130
|
yield {
|
|
@@ -145,9 +150,11 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
145
150
|
if (event.delta.stop_reason === 'end_turn' || event.delta.stop_reason === 'stop_sequence') {
|
|
146
151
|
if (currentToolUse) {
|
|
147
152
|
let input: Record<string, unknown>
|
|
153
|
+
const rawInput = currentToolUse.input || ''
|
|
148
154
|
try {
|
|
149
|
-
input = JSON.parse(
|
|
155
|
+
input = JSON.parse(rawInput || '{}')
|
|
150
156
|
} catch {
|
|
157
|
+
console.error('[anthropic] Failed to parse tool_use input (message_delta):', rawInput)
|
|
151
158
|
input = {}
|
|
152
159
|
}
|
|
153
160
|
yield {
|
|
@@ -150,6 +150,10 @@ export class OllamaProvider implements LLMProvider {
|
|
|
150
150
|
try {
|
|
151
151
|
input = JSON.parse(tc.function.arguments || '{}')
|
|
152
152
|
} catch {
|
|
153
|
+
console.error(
|
|
154
|
+
'[ollama] Failed to parse tool_call arguments:',
|
|
155
|
+
tc.function.arguments,
|
|
156
|
+
)
|
|
153
157
|
input = {}
|
|
154
158
|
}
|
|
155
159
|
yield {
|
|
@@ -174,7 +178,7 @@ export class OllamaProvider implements LLMProvider {
|
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
180
|
} catch {
|
|
177
|
-
|
|
181
|
+
console.error('[ollama] Failed to parse stream line:', line)
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
}
|
|
@@ -201,9 +201,14 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
201
201
|
if (tc.id) {
|
|
202
202
|
if (currentToolCall) {
|
|
203
203
|
let input: Record<string, unknown>
|
|
204
|
+
const rawArgs = currentToolCall.arguments || ''
|
|
204
205
|
try {
|
|
205
|
-
input = JSON.parse(
|
|
206
|
+
input = JSON.parse(rawArgs || '{}')
|
|
206
207
|
} catch {
|
|
208
|
+
console.error(
|
|
209
|
+
'[openai] Failed to parse tool_call arguments (flush on new tool):',
|
|
210
|
+
rawArgs,
|
|
211
|
+
)
|
|
207
212
|
input = {}
|
|
208
213
|
}
|
|
209
214
|
yield {
|
|
@@ -230,17 +235,22 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
230
235
|
|
|
231
236
|
if (chunk.choices?.[0]?.finish_reason) {
|
|
232
237
|
if (currentToolCall) {
|
|
238
|
+
const finalArgs = currentToolCall.arguments || ''
|
|
233
239
|
try {
|
|
234
240
|
yield {
|
|
235
241
|
type: 'tool_use',
|
|
236
242
|
tool_call: {
|
|
237
243
|
id: currentToolCall.id,
|
|
238
244
|
name: currentToolCall.name,
|
|
239
|
-
input: JSON.parse(
|
|
245
|
+
input: JSON.parse(finalArgs || '{}'),
|
|
240
246
|
},
|
|
241
247
|
reasoning_content: reasoningContent,
|
|
242
248
|
}
|
|
243
249
|
} catch {
|
|
250
|
+
console.error(
|
|
251
|
+
'[openai] Failed to parse tool_call arguments (finish_reason):',
|
|
252
|
+
finalArgs,
|
|
253
|
+
)
|
|
244
254
|
yield {
|
|
245
255
|
type: 'tool_use',
|
|
246
256
|
tool_call: {
|
|
@@ -303,9 +313,11 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
303
313
|
}
|
|
304
314
|
: undefined
|
|
305
315
|
let input: Record<string, unknown>
|
|
316
|
+
const rawFinalArgs = currentToolCall.arguments || ''
|
|
306
317
|
try {
|
|
307
|
-
input = JSON.parse(
|
|
318
|
+
input = JSON.parse(rawFinalArgs || '{}')
|
|
308
319
|
} catch {
|
|
320
|
+
console.error('[openai] Failed to parse tool_call arguments (final flush):', rawFinalArgs)
|
|
309
321
|
input = {}
|
|
310
322
|
}
|
|
311
323
|
yield {
|