Skip to content

Commit 0073cdf

Browse files
committed
feat(init): add .agents/types directory scaffolding and schema error handling
- Add .agents/ and .agents/types/ directory creation in init command - Copy agent-definition.ts, tools.ts, util-types.ts type files - Add ensureJsonSchemaCompatible fallback for invalid Zod schemas - Add toJsonSchemaSafe error handling in lookup-agent-info - Always include local agents in spawnableAgents (remove agentId check) - Add comprehensive tests for init command (19 tests) - Add schema handling error recovery tests (13 tests)
1 parent 8d33c52 commit 0073cdf

File tree

8 files changed

+1031
-34
lines changed

8 files changed

+1031
-34
lines changed

cli/src/commands/__tests__/init.test.ts

Lines changed: 455 additions & 0 deletions
Large diffs are not rendered by default.

cli/src/commands/init.ts

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { existsSync, writeFileSync } from 'fs'
1+
import { existsSync, mkdirSync, writeFileSync } from 'fs'
22
import path from 'path'
33

4+
import agentDefinitionSource from '../../../common/src/templates/initial-agents-dir/types/agent-definition' with { type: 'text' }
5+
import toolsSource from '../../../common/src/templates/initial-agents-dir/types/tools' with { type: 'text' }
6+
import utilTypesSource from '../../../common/src/templates/initial-agents-dir/types/util-types' with { type: 'text' }
7+
48
import { getProjectRoot } from '../project-files'
59
import { getSystemMessage } from '../utils/message-history'
610

@@ -27,25 +31,77 @@ This file gives Codebuff context about your project: goals, commands, convention
2731
- Things to avoid:
2832
`
2933

34+
const COMMON_TYPE_FILES = [
35+
{
36+
fileName: 'agent-definition.ts',
37+
source: agentDefinitionSource,
38+
},
39+
{
40+
fileName: 'tools.ts',
41+
source: toolsSource,
42+
},
43+
{
44+
fileName: 'util-types.ts',
45+
source: utilTypesSource,
46+
},
47+
]
48+
3049
export function handleInitializationFlowLocally(): {
3150
postUserMessage: PostUserMessageFn
3251
} {
3352
const projectRoot = getProjectRoot()
3453
const knowledgePath = path.join(projectRoot, KNOWLEDGE_FILE_NAME)
54+
const messages: string[] = []
3555

3656
if (existsSync(knowledgePath)) {
37-
const postUserMessage: PostUserMessageFn = (prev) => [
38-
...prev,
39-
getSystemMessage(`📋 \`${KNOWLEDGE_FILE_NAME}\` already exists.`),
40-
]
41-
return { postUserMessage }
57+
messages.push(`📋 \`${KNOWLEDGE_FILE_NAME}\` already exists.`)
58+
} else {
59+
writeFileSync(knowledgePath, INITIAL_KNOWLEDGE_FILE)
60+
messages.push(`✅ Created \`${KNOWLEDGE_FILE_NAME}\``)
61+
}
62+
63+
const agentsDir = path.join(projectRoot, '.agents')
64+
const agentsTypesDir = path.join(agentsDir, 'types')
65+
66+
if (existsSync(agentsDir)) {
67+
messages.push('📋 `.agents/` already exists.')
68+
} else {
69+
mkdirSync(agentsDir, { recursive: true })
70+
messages.push('✅ Created `.agents/`')
4271
}
4372

44-
writeFileSync(knowledgePath, INITIAL_KNOWLEDGE_FILE)
73+
if (existsSync(agentsTypesDir)) {
74+
messages.push('📋 `.agents/types/` already exists.')
75+
} else {
76+
mkdirSync(agentsTypesDir, { recursive: true })
77+
messages.push('✅ Created `.agents/types/`')
78+
}
79+
80+
for (const { fileName, source } of COMMON_TYPE_FILES) {
81+
const targetPath = path.join(agentsTypesDir, fileName)
82+
if (existsSync(targetPath)) {
83+
messages.push(`📋 \`.agents/types/${fileName}\` already exists.`)
84+
continue
85+
}
86+
87+
try {
88+
if (!source || source.trim().length === 0) {
89+
throw new Error('Source content is empty')
90+
}
91+
writeFileSync(targetPath, source)
92+
messages.push(`✅ Copied \`.agents/types/${fileName}\``)
93+
} catch (error) {
94+
messages.push(
95+
`⚠️ Failed to copy \`.agents/types/${fileName}\`: ${
96+
error instanceof Error ? error.message : String(error ?? 'Unknown')
97+
}`,
98+
)
99+
}
100+
}
45101

46102
const postUserMessage: PostUserMessageFn = (prev) => [
47103
...prev,
48-
getSystemMessage(`✅ Created \`${KNOWLEDGE_FILE_NAME}\``),
104+
...messages.map((message) => getSystemMessage(message)),
49105
]
50106
return { postUserMessage }
51107
}

packages/agent-runtime/src/__tests__/main-prompt.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,68 @@ describe('mainPrompt', () => {
200200
},
201201
}
202202

203+
it('includes local agents in spawnableAgents when agentId is provided', async () => {
204+
const sessionState = getInitialSessionState(mockFileContext)
205+
const mainAgentId = 'test-main-agent'
206+
const localAgentId = 'test-local-agent'
207+
208+
const localAgentTemplates: Record<string, AgentTemplate> = {
209+
[mainAgentId]: {
210+
id: mainAgentId,
211+
displayName: 'Test Main Agent',
212+
outputMode: 'last_message',
213+
inputSchema: {},
214+
spawnerPrompt: '',
215+
model: 'gpt-4o-mini',
216+
includeMessageHistory: true,
217+
inheritParentSystemPrompt: false,
218+
mcpServers: {},
219+
toolNames: ['write_file', 'run_terminal_command', 'end_turn'],
220+
spawnableAgents: [],
221+
systemPrompt: '',
222+
instructionsPrompt: '',
223+
stepPrompt: '',
224+
},
225+
[localAgentId]: {
226+
id: localAgentId,
227+
displayName: 'Test Local Agent',
228+
outputMode: 'last_message',
229+
inputSchema: {},
230+
spawnerPrompt: '',
231+
model: 'gpt-4o-mini',
232+
includeMessageHistory: false,
233+
inheritParentSystemPrompt: false,
234+
mcpServers: {},
235+
toolNames: ['write_file', 'run_terminal_command', 'end_turn'],
236+
spawnableAgents: [],
237+
systemPrompt: '',
238+
instructionsPrompt: '',
239+
stepPrompt: '',
240+
},
241+
}
242+
243+
const action = {
244+
type: 'prompt' as const,
245+
prompt: 'Hello',
246+
sessionState,
247+
fingerprintId: 'test',
248+
costMode: 'normal' as const,
249+
promptId: 'test',
250+
toolResults: [],
251+
agentId: mainAgentId,
252+
}
253+
254+
await mainPrompt({
255+
...mainPromptBaseParams,
256+
action,
257+
localAgentTemplates,
258+
})
259+
260+
expect(localAgentTemplates[mainAgentId].spawnableAgents).toContain(
261+
localAgentId,
262+
)
263+
})
264+
203265
it('should handle write_file tool call', async () => {
204266
// Mock LLM to return a write_file tool call using native tool call chunks
205267
mockAgentStream([

0 commit comments

Comments
 (0)