Skip to content

Commit 9b8b4e4

Browse files
committed
feat: Make AICODE agent robust with improved error handling
- Update to Claude Sonnet 4.5 model (claude-sonnet-4-5) - Improve prompt to generate valid unified diffs without git hashes - Add multi-strategy patch application with 4 fallback methods: 1. Standard git apply 2. git apply --unidiff-zero 3. git apply --ignore-whitespace 4. patch -p1 command - Add comprehensive documentation to agent code - Add aicode command to admin help text - Fix corrupt patch errors by avoiding placeholder hashes This makes the AICODE agent much more reliable when applying AI-generated code changes from Slack commands.
1 parent 85f35b0 commit 9b8b4e4

2 files changed

Lines changed: 102 additions & 14 deletions

File tree

.github/agent/agent.mjs

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
import { execSync } from "child_process";
22
import fs from "fs";
33

4+
/**
5+
* AICODE Agent - Autonomous code modification agent for SlackONOS
6+
*
7+
* This agent uses AI (Claude, OpenAI, or Gemini) to generate code changes
8+
* based on natural language task descriptions from Slack admins.
9+
*
10+
* Key Features:
11+
* - Multi-provider AI support (Claude Sonnet 4.5 default)
12+
* - Safety checks (forbidden files, line limits, security patterns)
13+
* - Robust patch application with multiple fallback strategies
14+
* - Slack notifications for success/failure
15+
* - Automatic PR creation when tests pass
16+
*
17+
* Patch Application Strategies (tried in order):
18+
* 1. Standard git apply
19+
* 2. git apply --unidiff-zero (for exact line matching)
20+
* 3. git apply --ignore-whitespace (for whitespace variations)
21+
* 4. patch -p1 command (traditional Unix patch)
22+
*/
23+
424
// Support multiple AI providers
525
const provider = process.env.AI_PROVIDER || "claude"; // claude, openai, or gemini
626
const task = process.env.TASK || "Improve code quality";
@@ -21,7 +41,7 @@ if (provider === "claude") {
2141
process.exit(1);
2242
}
2343
aiClient = new Anthropic({ apiKey });
24-
aiModel = process.env.CLAUDE_MODEL || "claude-3-5-sonnet-latest";
44+
aiModel = process.env.CLAUDE_MODEL || "claude-sonnet-4-5";
2545
console.log(`[AGENT] Using Claude (Anthropic) with model: ${aiModel}`);
2646
} else if (provider === "openai") {
2747
// OpenAI (original)
@@ -154,7 +174,7 @@ try {
154174
const prompt = `You are an autonomous coding agent for SlackONOS, a democratic music bot for Discord and Slack that controls Sonos speakers.
155175
156176
CRITICAL SAFETY RULES:
157-
- Output ONLY a valid unified git diff (starting with "diff --git")
177+
- Output ONLY a valid unified git diff format
158178
- DO NOT modify authentication files (webauthn-handler.js, auth-handler.js)
159179
- DO NOT modify config handling (config/*)
160180
- DO NOT modify security-critical code
@@ -177,9 +197,25 @@ ${recentCommits}
177197
TASK FROM ADMIN (${requester}):
178198
${task}
179199
180-
Generate a safe, focused code change as a unified git diff. The diff will be applied with "git apply" so ensure it's properly formatted.
200+
CRITICAL: Generate ONLY the file changes in this EXACT format (no "diff --git" headers, no index lines, no hashes):
201+
202+
--- a/path/to/file.js
203+
+++ b/path/to/file.js
204+
@@ -10,6 +10,7 @@
205+
existing line
206+
another existing line
207+
+new line to add
208+
existing line
209+
210+
Rules for the diff format:
211+
1. Start each file with "--- a/filepath" and "+++ b/filepath"
212+
2. NO "diff --git" line, NO "index" line with hashes
213+
3. Include enough context lines (unchanged lines) around changes
214+
4. Use @@ -startLine,numLines +startLine,numLines @@ for hunks
215+
5. Prefix added lines with "+", removed lines with "-", context lines with " " (space)
216+
6. Include at least 3 lines of context before and after changes
181217
182-
Remember: Output ONLY the git diff, no explanations, no markdown code blocks, just the raw diff.`;
218+
Output ONLY the diff content, no explanations, no markdown code blocks.`;
183219

184220
// Call AI provider with unified interface
185221
async function callAI(promptText) {
@@ -217,18 +253,19 @@ try {
217253
}
218254

219255
// Extract diff from potential markdown code blocks
220-
let diff = output;
256+
let diff = output.trim();
221257
if (output.includes("```")) {
222258
// Extract content between code fences
223259
const match = output.match(/```(?:diff)?\n([\s\S]*?)```/);
224260
if (match) {
225-
diff = match[1];
261+
diff = match[1].trim();
226262
}
227263
}
228264

229-
// Validate diff format
230-
if (!diff.includes("diff --git")) {
231-
const errorMsg = `Model did not return a valid diff. Output was:\n\`\`\`\n${output.substring(0, 500)}\n\`\`\``;
265+
// Validate diff format - accept either unified diff format
266+
const hasValidDiffFormat = diff.includes("--- a/") && diff.includes("+++ b/");
267+
if (!hasValidDiffFormat) {
268+
const errorMsg = `Model did not return a valid diff format. Expected "--- a/" and "+++ b/" lines. Output was:\n\`\`\`\n${output.substring(0, 500)}\n\`\`\``;
232269
await handleError(new Error(errorMsg), "Invalid Diff Format");
233270
// handleError calls process.exit(1), so we never reach here
234271
}
@@ -264,14 +301,64 @@ if (linesChanged > 300) {
264301

265302
console.log(`[AGENT] Generated diff with ${linesChanged} lines changed`);
266303

267-
// Apply patch
304+
// Apply patch with multiple strategies
268305
fs.writeFileSync("/tmp/aicode.patch", diff);
306+
307+
let patchApplied = false;
308+
let lastError = null;
309+
310+
// Strategy 1: Try with standard git apply
269311
try {
270-
sh("git apply --check /tmp/aicode.patch");
271-
sh("git apply /tmp/aicode.patch");
272-
console.log("[AGENT] Patch applied successfully");
312+
sh("git apply --check /tmp/aicode.patch 2>&1");
313+
sh("git apply /tmp/aicode.patch 2>&1");
314+
console.log("[AGENT] Patch applied successfully with 'git apply'");
315+
patchApplied = true;
273316
} catch (err) {
274-
const errorMsg = `Failed to apply patch: ${err.message}\n\nDiff preview:\n\`\`\`\n${diff.substring(0, 500)}\n\`\`\``;
317+
console.log(`[AGENT] Standard git apply failed: ${err.message}`);
318+
lastError = err;
319+
}
320+
321+
// Strategy 2: Try with --unidiff-zero for exact line matching
322+
if (!patchApplied) {
323+
try {
324+
sh("git apply --unidiff-zero --check /tmp/aicode.patch 2>&1");
325+
sh("git apply --unidiff-zero /tmp/aicode.patch 2>&1");
326+
console.log("[AGENT] Patch applied successfully with '--unidiff-zero'");
327+
patchApplied = true;
328+
} catch (err) {
329+
console.log(`[AGENT] git apply --unidiff-zero failed: ${err.message}`);
330+
lastError = err;
331+
}
332+
}
333+
334+
// Strategy 3: Try with more lenient whitespace handling
335+
if (!patchApplied) {
336+
try {
337+
sh("git apply --ignore-whitespace --check /tmp/aicode.patch 2>&1");
338+
sh("git apply --ignore-whitespace /tmp/aicode.patch 2>&1");
339+
console.log("[AGENT] Patch applied successfully with '--ignore-whitespace'");
340+
patchApplied = true;
341+
} catch (err) {
342+
console.log(`[AGENT] git apply --ignore-whitespace failed: ${err.message}`);
343+
lastError = err;
344+
}
345+
}
346+
347+
// Strategy 4: Try patch command as fallback
348+
if (!patchApplied) {
349+
try {
350+
sh("patch -p1 --dry-run < /tmp/aicode.patch 2>&1");
351+
sh("patch -p1 < /tmp/aicode.patch 2>&1");
352+
console.log("[AGENT] Patch applied successfully with 'patch' command");
353+
patchApplied = true;
354+
} catch (err) {
355+
console.log(`[AGENT] patch command failed: ${err.message}`);
356+
lastError = err;
357+
}
358+
}
359+
360+
if (!patchApplied) {
361+
const errorMsg = `Failed to apply patch after trying multiple strategies.\n\nLast error: ${lastError.message}\n\nDiff preview:\n\`\`\`\n${diff.substring(0, 1000)}\n\`\`\``;
275362
await handleError(new Error(errorMsg), "Patch Application Failed");
276363
// handleError calls process.exit(1), so we never reach here
277364
}

templates/help/helpTextAdmin.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
> `telemetry` - Show telemetry status and privacy information.
2727
> `stats` - Show usage statistics for all users.
2828
> `stats [user]` - Show usage statistics for a specific user.
29+
> `aicode <task description>` - Request AI agent to make code changes via GitHub Actions.
2930

3031
*🤖 AI Settings:*
3132
> `setconfig aiPrompt <text>` - Set the DJ-style summary prompt used by AI.

0 commit comments

Comments
 (0)