-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcoding.js
More file actions
95 lines (85 loc) · 3.13 KB
/
coding.js
File metadata and controls
95 lines (85 loc) · 3.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { existsSync } from 'fs';
import { resolve } from 'path';
import { ClaudeCodeSpawner } from '../coder.js';
import { getLogger } from '../utils/logger.js';
let spawner = null;
export function getSpawner(config) {
if (!spawner) spawner = new ClaudeCodeSpawner(config);
return spawner;
}
export function resetClaudeCodeSpawner() {
spawner = null;
}
export const definitions = [
{
name: 'spawn_claude_code',
description:
'Spawn Claude Code CLI to perform coding tasks in a directory. Use for writing code, fixing bugs, reviewing diffs, and scaffolding projects. Claude Code has full access to the filesystem within the given directory.',
input_schema: {
type: 'object',
properties: {
working_directory: {
type: 'string',
description: 'The directory to run Claude Code in (should be a cloned repo)',
},
prompt: {
type: 'string',
description: 'The coding task to perform — be specific about what to do',
},
max_turns: {
type: 'number',
description: 'Max turns for Claude Code (optional, default from config)',
},
timeout_seconds: {
type: 'number',
description: 'Override timeout in seconds for this invocation (optional, default from config)',
},
},
required: ['working_directory', 'prompt'],
},
},
];
export const handlers = {
spawn_claude_code: async (params, context) => {
const logger = getLogger();
const onUpdate = context.onUpdate || null;
const dir = resolve(params.working_directory);
// Validate working_directory against blocked paths
const blockedPaths = context.config?.security?.blocked_paths || [];
for (const bp of blockedPaths) {
const expandedBp = resolve(bp.startsWith('~') ? bp.replace('~', process.env.HOME || '') : bp);
if (dir.startsWith(expandedBp) || dir === expandedBp) {
const msg = `Blocked: working directory is within restricted path ${bp}`;
logger.warn(`spawn_claude_code: ${msg}`);
return { error: msg };
}
}
// Validate directory exists
if (!existsSync(dir)) {
const msg = `Directory not found: ${dir}`;
logger.error(`spawn_claude_code: ${msg}`);
if (onUpdate) onUpdate(`❌ ${msg}`).catch(() => {});
return { error: msg };
}
try {
const coder = getSpawner(context.config);
const result = await coder.run({
workingDirectory: dir,
prompt: params.prompt,
maxTurns: params.max_turns,
timeoutMs: params.timeout_seconds ? params.timeout_seconds * 1000 : undefined,
onOutput: onUpdate,
signal: context.signal || null,
});
// Show stderr if any
if (result.stderr && onUpdate) {
onUpdate(`⚠️ Claude Code stderr:\n\`\`\`\n${result.stderr.slice(0, 500)}\n\`\`\``).catch(() => {});
}
return { success: true, output: result.output };
} catch (err) {
logger.error(`spawn_claude_code failed: ${err.message}`);
if (onUpdate) onUpdate(`❌ Claude Code error: ${err.message}`).catch(() => {});
return { error: err.message };
}
},
};