Skip to content

Commit 4e9927c

Browse files
committed
Load templates in all subdirs of .agents
1 parent 73895a8 commit 4e9927c

File tree

5 files changed

+99
-71
lines changed

5 files changed

+99
-71
lines changed

codebuff.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
{
22
"description": "Template configuration for this project. See https://www.codebuff.com/config for all options.",
3-
"baseAgent": "base_lite",
4-
"subagents": [
5-
"researcher",
6-
"thinker",
7-
"reviewer"
8-
],
93
"startupProcesses": [
104
{
115
"name": "drizzle",

common/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const FIND_FILES_MARKER = '[' + 'FIND_FILES_PLEASE]'
33
export const EXISTING_CODE_MARKER = '[[**REPLACE_WITH_EXISTING_CODE**]]'
44

55
// Directory where agent template override files are stored
6-
export const AGENT_TEMPLATES_DIR = '.agents/templates/'
6+
export const AGENT_TEMPLATES_DIR = '.agents/'
77

88
// Enable async agents to run tool calls even when main user input is cancelled
99
export const ASYNC_AGENTS_ENABLED = true

npm-app/src/agents/load-agents.ts

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,34 @@ import { CodebuffConfig } from '@codebuff/common/json-config/constants'
1212

1313
export let loadedAgents: Record<string, DynamicAgentTemplate> = {}
1414

15-
const agentTemplatesSubdir = ['.agents', 'templates'] as const
15+
const agentTemplatesSubdir = ['.agents'] as const
16+
17+
function getAllTsFiles(dir: string): string[] {
18+
const files: string[] = []
19+
20+
try {
21+
const entries = fs.readdirSync(dir, { withFileTypes: true })
22+
23+
for (const entry of entries) {
24+
const fullPath = path.join(dir, entry.name)
25+
26+
if (entry.isDirectory()) {
27+
// Recursively scan subdirectories
28+
files.push(...getAllTsFiles(fullPath))
29+
} else if (
30+
entry.isFile() &&
31+
entry.name.endsWith('.ts') &&
32+
!entry.name.endsWith('.d.ts')
33+
) {
34+
files.push(fullPath)
35+
}
36+
}
37+
} catch (error) {
38+
// Ignore errors reading directories
39+
}
40+
41+
return files
42+
}
1643

1744
export async function loadLocalAgents({
1845
verbose = false,
@@ -28,52 +55,54 @@ export async function loadLocalAgents({
2855
}
2956

3057
try {
31-
const files = fs.readdirSync(agentsDir)
58+
const tsFiles = getAllTsFiles(agentsDir)
59+
60+
for (const fullPath of tsFiles) {
61+
const relativePath = path.relative(agentsDir, fullPath)
62+
const fileName = relativePath.replace(/\.ts$/, '').replace(/[/\\]/g, '-')
3263

33-
for (const file of files) {
34-
const fullPath = path.join(agentsDir, file)
3564
let agentConfig: any
36-
if (file.endsWith('.ts')) {
37-
let agentModule: any
38-
try {
39-
agentModule = await import(fullPath)
40-
} catch (error: any) {
41-
if (verbose) {
42-
console.error('Error importing agent:', error)
43-
}
44-
continue
45-
}
46-
try {
47-
agentConfig = agentModule.default
48-
} catch (error: any) {
49-
console.error('Error loading agent from file:', fullPath, error)
50-
continue
65+
let agentModule: any
66+
try {
67+
agentModule = await import(fullPath)
68+
} catch (error: any) {
69+
if (verbose) {
70+
console.error('Error importing agent:', error)
5171
}
72+
continue
73+
}
74+
try {
75+
agentConfig = agentModule.default
76+
} catch (error: any) {
77+
console.error('Error loading agent from file:', fullPath, error)
78+
continue
79+
}
5280

53-
let typedAgentConfig: DynamicAgentConfigParsed
54-
try {
55-
typedAgentConfig = DynamicAgentConfigSchema.parse(agentConfig)
56-
} catch (error: any) {
57-
console.error('Invalid agent format:', fullPath, error)
58-
continue
59-
}
81+
if (!agentConfig) continue
6082

61-
// Convert handleSteps function to string if present
62-
let handleStepsString: string | undefined
63-
if (agentConfig.handleSteps) {
64-
handleStepsString = agentConfig.handleSteps.toString()
65-
}
83+
let typedAgentConfig: DynamicAgentConfigParsed
84+
try {
85+
typedAgentConfig = DynamicAgentConfigSchema.parse(agentConfig)
86+
} catch (error: any) {
87+
console.error('Invalid agent format:', fullPath, error)
88+
continue
89+
}
90+
91+
// Convert handleSteps function to string if present
92+
let handleStepsString: string | undefined
93+
if (agentConfig.handleSteps) {
94+
handleStepsString = agentConfig.handleSteps.toString()
95+
}
6696

67-
loadedAgents[file.slice(0, -'.ts'.length)] = {
68-
...typedAgentConfig,
69-
systemPrompt: loadFileContents(typedAgentConfig.systemPrompt),
70-
instructionsPrompt: loadFileContents(
71-
typedAgentConfig.instructionsPrompt
72-
),
73-
stepPrompt: loadFileContents(typedAgentConfig.stepPrompt),
97+
loadedAgents[fileName] = {
98+
...typedAgentConfig,
99+
systemPrompt: loadFileContents(typedAgentConfig.systemPrompt),
100+
instructionsPrompt: loadFileContents(
101+
typedAgentConfig.instructionsPrompt
102+
),
103+
stepPrompt: loadFileContents(typedAgentConfig.stepPrompt),
74104

75-
handleSteps: handleStepsString,
76-
}
105+
handleSteps: handleStepsString,
77106
}
78107
}
79108
} catch (error) {}

npm-app/src/cli.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ import {
6868
disableSquashNewlines,
6969
enableSquashNewlines,
7070
} from './display/squash-newlines'
71-
import { loadCodebuffConfig } from './json-config/parser'
7271
import {
7372
displayGreeting,
7473
displayMenu,
@@ -97,44 +96,50 @@ import { Spinner } from './utils/spinner'
9796
import { withHangDetection } from './utils/with-hang-detection'
9897

9998
/**
100-
* Get local agent names from the .agents/templates directory
99+
* Get local agent names from the .agents directory and all subdirectories
101100
* @returns Record of agent type to agent name
102101
*/
103102
function getLocalAgentNames(): Record<string, string> {
104-
const agentsDir = path.join(getProjectRoot(), '.agents', 'templates')
103+
const agentsDir = path.join(getProjectRoot(), '.agents')
105104

106105
if (!fs.existsSync(agentsDir)) {
107106
return {}
108107
}
109108

110109
const agentNames: Record<string, string> = {}
111110

112-
try {
113-
const files = fs.readdirSync(agentsDir)
114-
115-
for (const file of files) {
116-
if (file.endsWith('.json')) {
117-
const agentType = file.replace('.json', '')
118-
const filePath = path.join(agentsDir, file)
119-
120-
try {
121-
const content = fs.readFileSync(filePath, 'utf8')
122-
const agentConfig = JSON.parse(content)
123-
124-
// Use the name from the config, or fall back to the filename
125-
const agentName = agentConfig.name || agentType
126-
agentNames[agentType] = agentName
127-
} catch (error) {
128-
// Skip invalid JSON files
129-
continue
111+
function scanDirectory(dir: string): void {
112+
try {
113+
const entries = fs.readdirSync(dir, { withFileTypes: true })
114+
115+
for (const entry of entries) {
116+
const fullPath = path.join(dir, entry.name)
117+
118+
if (entry.isDirectory()) {
119+
// Recursively scan subdirectories
120+
scanDirectory(fullPath)
121+
} else if (
122+
entry.isFile() &&
123+
entry.name.endsWith('.ts') &&
124+
!entry.name.endsWith('.d.ts')
125+
) {
126+
// Handle TypeScript agent files
127+
const relativePath = path.relative(agentsDir, fullPath)
128+
const agentType = relativePath
129+
.replace(/\.ts$/, '')
130+
.replace(/[/\\]/g, '-')
131+
132+
// For .ts files, use the filename as the display name
133+
// The actual agent loading will be handled by loadLocalAgents
134+
agentNames[agentType] = agentType
130135
}
131136
}
137+
} catch (error) {
138+
// Ignore errors reading directories
132139
}
133-
} catch (error) {
134-
// Return empty object if directory can't be read
135-
return {}
136140
}
137141

142+
scanDirectory(agentsDir)
138143
return agentNames
139144
}
140145

0 commit comments

Comments
 (0)