| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- /**
- * Claude Agent SDK Client for Issue Automation
- */
- import { query } from '@anthropic-ai/claude-agent-sdk';
- import { writeFileSync } from 'fs';
- import { join } from 'path';
- import { ClaudeClientConfig, IssueData, IssueHandlerResult } from './types.js';
- import { Config } from './config.js';
- export class ClaudeClient {
- private config: ClaudeClientConfig;
- constructor(config: ClaudeClientConfig) {
- this.config = config;
- }
- /**
- * Build the prompt for Claude based on issue data
- */
- private buildPrompt(issue: IssueData): string {
- const commentContext = issue.commentBody
- ? `A new comment by ${issue.pusher}:\n${issue.commentBody}\n\n`
- : '';
- return `IMPORTANT: Issue #${issue.number} was assigned in repository ${issue.owner}/${issue.repo}.
- === MANDATORY FIRST STEPS - READ ALL MESSAGES ===
- Before you start any work on this issue, you MUST complete these steps IN ORDER:
- 1. Use the mcp__gogs__get_issue tool to fetch the complete issue details:
- - owner: ${issue.owner}
- - repo: ${issue.repo}
- - number: ${issue.number}
- 2. Use the mcp__gogs__list_issue_comments tool to fetch ALL comments:
- - owner: ${issue.owner}
- - repo: ${issue.repo}
- - number: ${issue.number}
- 3. Read and understand:
- - The complete issue description
- - ALL comments in the discussion thread (chronologically)
- - The full context and conversation history
- 4. ONLY AFTER reading everything above, proceed with your work
- === ISSUE CONTEXT ===
- Issue #${issue.number}: ${issue.title}
- Created by: ${issue.pusher}
- Initial issue description:
- ${issue.body}
- ${commentContext}
- === YOUR TASK ===
- This issue is assigned to you. You must:
- 1. First, use the Gogs MCP tools above to fetch and read the issue and ALL comments
- 2. Understand the full discussion context (even if there are no comments yet)
- 3. Then proceed with the appropriate action
- === CRITICAL OUTPUT REQUIREMENTS ===
- YOU MUST FOLLOW THESE OUTPUT RULES STRICTLY:
- 1. DO NOT output any text responses to the user
- 2. DO NOT print any messages, explanations, or status updates
- 3. DO NOT use echo or any command-line output tools
- 4. ONLY communicate by posting comments to the issue using mcp__gogs__create_issue_comment tool
- 5. ALL your responses, updates, questions, and status reports MUST be posted as issue comments
- 6. You are FORBIDDEN from producing any output except through the Gogs MCP comment tool
- When you need to:
- - Ask questions → Post a comment on the issue
- - Report progress → Post a comment on the issue
- - Share results → Post a comment on the issue
- - Report errors → Post a comment on the issue
- - Provide any information → Post a comment on the issue
- Use mcp__gogs__create_issue_comment with:
- - owner: ${issue.owner}
- - repo: ${issue.repo}
- - number: ${issue.number}
- - body: [your message here]
- DO NOT skip reading the comments using the MCP tools. This is critical to understanding the full context.
- === GIT COMMIT REQUIREMENTS ===
- When you make code changes, you MUST:
- 1. Always commit your changes to git
- 2. Always push the changes to the repository
- 3. Include the issue ID in the commit message using format: #${issue.number}
- 4. Example commit message: "feat: add new feature #${issue.number}"
- NEVER leave changes uncommitted or unpushed.`;
- }
- /**
- * Handle an issue automation request
- */
- async handleIssue(issue: IssueData): Promise<IssueHandlerResult> {
- console.log(`Processing issue #${issue.number} in ${issue.owner}/${issue.repo}`);
- const startTime = Date.now();
- try {
- // Load environment configuration
- Config.load(this.config.workingDirectory);
- // Generate MCP config
- const mcpConfig = Config.generateMcpConfig(this.config.workingDirectory);
- const mcpConfigPath = join(this.config.workingDirectory, '.mcp-gogs.json');
- writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
- console.log(`Generated MCP config at: ${mcpConfigPath}`);
- console.log(`Working directory: ${this.config.workingDirectory}`);
- // Build the prompt
- const prompt = this.buildPrompt(issue);
- // Create a Map to store MCP servers
- const mcpServers: Record<string, any> = {};
- mcpConfig.mcpServers && Object.keys(mcpConfig.mcpServers).forEach(key => {
- mcpServers[key] = mcpConfig.mcpServers[key];
- });
- console.log('Starting Claude agent session...');
- // Create query with options
- const queryInstance = query({
- prompt,
- options: {
- cwd: this.config.workingDirectory,
- mcpServers,
- allowedTools: this.config.allowedTools,
- allowDangerouslySkipPermissions: this.config.dangerouslySkipPermissions || true
- }
- });
- // Iterate through messages
- let sessionId: string | undefined;
- let totalCost = 0;
- let messageCount = 0;
- for await (const message of queryInstance) {
- messageCount++;
- // Extract session ID and cost information
- if ('sessionId' in message) {
- sessionId = message.sessionId as string;
- }
- if ('cost' in message && typeof message.cost === 'number') {
- totalCost = message.cost;
- }
- // Log progress
- if (message.type === 'assistant' || message.type === 'result') {
- console.log(`Message ${messageCount}: ${message.type}`);
- }
- }
- const duration = Date.now() - startTime;
- console.log('Claude agent session completed');
- console.log(`Total messages: ${messageCount}`);
- console.log(`Total cost: $${totalCost}`);
- console.log(`Duration: ${duration}ms`);
- return {
- success: true,
- sessionId,
- cost: totalCost,
- duration,
- output: `Processed ${messageCount} messages`
- };
- } catch (error) {
- console.error('Error handling issue:', error);
- return {
- success: false,
- error: error instanceof Error ? error.message : String(error),
- duration: Date.now() - startTime
- };
- }
- }
- }
|