style: Improve code formatting and add comments for clarity across multiple files

This commit is contained in:
Nawaz Dhandala
2025-12-29 14:13:05 +00:00
parent f96c0404bc
commit 8ab088ace0
15 changed files with 173 additions and 102 deletions

View File

@@ -1,4 +1,8 @@
import { CodeAgent, CodeAgentType, getCodeAgentDisplayName } from "./CodeAgentInterface";
import {
CodeAgent,
CodeAgentType,
getCodeAgentDisplayName,
} from "./CodeAgentInterface";
import OpenCodeAgent from "./OpenCodeAgent";
import logger from "Common/Server/Utils/Logger";
@@ -15,11 +19,13 @@ export default class CodeAgentFactory {
case CodeAgentType.OpenCode:
return new OpenCodeAgent();
// Future agents can be added here:
// case CodeAgentType.Goose:
// return new GooseAgent();
// case CodeAgentType.ClaudeCode:
// return new ClaudeCodeAgent();
/*
* Future agents can be added here:
* case CodeAgentType.Goose:
* return new GooseAgent();
* case CodeAgentType.ClaudeCode:
* return new ClaudeCodeAgent();
*/
default:
throw new Error(`Unknown code agent type: ${type}`);
@@ -68,8 +74,10 @@ export default class CodeAgentFactory {
return null;
}
// Create agent with fallback
// Tries to create the specified type, falls back to first available
/*
* Create agent with fallback
* Tries to create the specified type, falls back to first available
*/
public static async createAgentWithFallback(
preferredType?: CodeAgentType,
): Promise<CodeAgent> {

View File

@@ -40,17 +40,16 @@ export type CodeAgentProgressCallback = (
event: CodeAgentProgressEvent,
) => void | Promise<void>;
// Abstract interface for code agents
// This allows us to support multiple agents (OpenCode, Goose, Claude Code, etc.)
/*
* Abstract interface for code agents
* This allows us to support multiple agents (OpenCode, Goose, Claude Code, etc.)
*/
export interface CodeAgent {
// Name of the agent (e.g., "OpenCode", "Goose", "ClaudeCode")
readonly name: string;
// Initialize the agent with LLM configuration
initialize(
config: CodeAgentLLMConfig,
logger?: TaskLogger,
): Promise<void>;
initialize(config: CodeAgentLLMConfig, logger?: TaskLogger): Promise<void>;
// Execute a task and return the result
executeTask(task: CodeAgentTask): Promise<CodeAgentResult>;
@@ -71,10 +70,12 @@ export interface CodeAgent {
// Enum for supported code agent types
export enum CodeAgentType {
OpenCode = "OpenCode",
// Future agents:
// Goose = "Goose",
// ClaudeCode = "ClaudeCode",
// Aider = "Aider",
/*
* Future agents:
* Goose = "Goose",
* ClaudeCode = "ClaudeCode",
* Aider = "Aider",
*/
}
// Helper function to get display name for agent type

View File

@@ -51,12 +51,15 @@ export default class OpenCodeAgent implements CodeAgent {
public async executeTask(task: CodeAgentTask): Promise<CodeAgentResult> {
if (!this.config) {
return this.createErrorResult("Agent not initialized. Call initialize() first.");
return this.createErrorResult(
"Agent not initialized. Call initialize() first.",
);
}
this.aborted = false;
const logs: Array<string> = [];
const timeoutMs: number = task.timeoutMs || OpenCodeAgent.DEFAULT_TIMEOUT_MS;
const timeoutMs: number =
task.timeoutMs || OpenCodeAgent.DEFAULT_TIMEOUT_MS;
try {
await this.log(`Executing task in directory: ${task.workingDirectory}`);
@@ -83,7 +86,9 @@ export default class OpenCodeAgent implements CodeAgent {
},
);
logs.push(`Output: ${output.substring(0, 1000)}${output.length > 1000 ? "..." : ""}`);
logs.push(
`Output: ${output.substring(0, 1000)}${output.length > 1000 ? "..." : ""}`,
);
if (this.aborted) {
return this.createErrorResult("Task was aborted", logs);
@@ -94,7 +99,9 @@ export default class OpenCodeAgent implements CodeAgent {
task.workingDirectory,
);
await this.log(`OpenCode completed. ${modifiedFiles.length} files modified.`);
await this.log(
`OpenCode completed. ${modifiedFiles.length} files modified.`,
);
return {
success: true,
@@ -308,7 +315,13 @@ export default class OpenCodeAgent implements CodeAgent {
const args: Array<string> = ["run", prompt];
logger.debug(`Running: opencode ${args.map((a: string) => a.includes(" ") ? `"${a.substring(0, 50)}..."` : a).join(" ")}`);
logger.debug(
`Running: opencode ${args
.map((a: string) => {
return a.includes(" ") ? `"${a.substring(0, 50)}..."` : a;
})
.join(" ")}`,
);
const child: ChildProcess = spawn("opencode", args, {
cwd: workingDirectory,
@@ -325,7 +338,11 @@ export default class OpenCodeAgent implements CodeAgent {
const timeout: ReturnType<typeof setTimeout> = setTimeout(() => {
if (child.pid) {
child.kill("SIGTERM");
reject(new Error(`OpenCode execution timed out after ${timeoutMs / 1000} seconds`));
reject(
new Error(
`OpenCode execution timed out after ${timeoutMs / 1000} seconds`,
),
);
}
}, timeoutMs);
@@ -340,9 +357,11 @@ export default class OpenCodeAgent implements CodeAgent {
// Stream to task logger for server-side logging
if (this.taskLogger) {
this.taskLogger.info(`[OpenCode] ${trimmedText}`).catch((err: Error) => {
logger.error(`Failed to log OpenCode output: ${err.message}`);
});
this.taskLogger
.info(`[OpenCode] ${trimmedText}`)
.catch((err: Error) => {
logger.error(`Failed to log OpenCode output: ${err.message}`);
});
}
}
@@ -364,9 +383,11 @@ export default class OpenCodeAgent implements CodeAgent {
// Stream to task logger for server-side logging
if (this.taskLogger) {
this.taskLogger.warning(`[OpenCode stderr] ${trimmedText}`).catch((err: Error) => {
logger.error(`Failed to log OpenCode stderr: ${err.message}`);
});
this.taskLogger
.warning(`[OpenCode stderr] ${trimmedText}`)
.catch((err: Error) => {
logger.error(`Failed to log OpenCode stderr: ${err.message}`);
});
}
}

View File

@@ -80,7 +80,9 @@ const executeTask: ExecuteTaskFunction = async (
try {
// Log handler starting
await taskLogger.info(`Starting ${handler.name} for task type: ${taskType}`);
await taskLogger.info(
`Starting ${handler.name} for task type: ${taskType}`,
);
// Validate metadata if the handler supports it
if (handler.validateMetadata && !handler.validateMetadata(metadata)) {
@@ -106,9 +108,11 @@ const executeTask: ExecuteTaskFunction = async (
// Flush all pending logs
await taskLogger.flush();
// If the task was not successful and we want to report it as an error
// Note: Based on user requirements, "no fix found" should be Completed, not Error
// Only throw if there was an actual error (not just "no action taken")
/*
* If the task was not successful and we want to report it as an error
* Note: Based on user requirements, "no fix found" should be Completed, not Error
* Only throw if there was an actual error (not just "no action taken")
*/
if (!result.success && result.data?.["isError"]) {
throw new Error(result.message);
}

View File

@@ -16,7 +16,9 @@ import RepositoryManager, {
RepositoryConfig,
CloneResult,
} from "../Utils/RepositoryManager";
import PullRequestCreator, { PullRequestResult } from "../Utils/PullRequestCreator";
import PullRequestCreator, {
PullRequestResult,
} from "../Utils/PullRequestCreator";
import WorkspaceManager, { WorkspaceInfo } from "../Utils/WorkspaceManager";
import {
CodeAgentFactory,
@@ -48,7 +50,10 @@ export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptio
): Promise<TaskResult> {
const metadata: FixExceptionMetadata = context.metadata;
await this.log(context, `Starting Fix Exception task for exception: ${metadata.exceptionId} (taskId: ${context.taskId.toString()})`);
await this.log(
context,
`Starting Fix Exception task for exception: ${metadata.exceptionId} (taskId: ${context.taskId.toString()})`,
);
let workspace: WorkspaceInfo | null = null;
@@ -114,7 +119,9 @@ export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptio
);
// Step 4: Create workspace for the task
workspace = await WorkspaceManager.createWorkspace(context.taskId.toString());
workspace = await WorkspaceManager.createWorkspace(
context.taskId.toString(),
);
await this.log(context, `Created workspace: ${workspace.workspacePath}`);
// Step 5: Process each repository
@@ -222,9 +229,8 @@ export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptio
`Getting access token for ${repo.organizationName}/${repo.repositoryName}...`,
);
const tokenData: RepositoryToken = await context.backendAPI.getRepositoryToken(
repo.id,
);
const tokenData: RepositoryToken =
await context.backendAPI.getRepositoryToken(repo.id);
// Clone the repository
await this.log(
@@ -239,7 +245,9 @@ export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptio
repositoryUrl: tokenData.repositoryUrl,
};
const repoManager: RepositoryManager = new RepositoryManager(context.logger);
const repoManager: RepositoryManager = new RepositoryManager(
context.logger,
);
const cloneResult: CloneResult = await repoManager.cloneRepository(
repoConfig,
workspace.workspacePath,
@@ -258,7 +266,9 @@ export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptio
// Initialize code agent
await this.log(context, "Initializing code agent...");
const agent: CodeAgent = CodeAgentFactory.createAgent(CodeAgentType.OpenCode);
const agent: CodeAgent = CodeAgentFactory.createAgent(
CodeAgentType.OpenCode,
);
const agentConfig: CodeAgentLLMConfig = {
llmType: llmConfig.llmType,
};
@@ -321,11 +331,17 @@ export default class FixExceptionTaskHandler extends BaseTaskHandler<FixExceptio
// Push the branch
await this.log(context, `Pushing branch ${branchName}...`);
await repoManager.pushBranch(cloneResult.repositoryPath, branchName, repoConfig);
await repoManager.pushBranch(
cloneResult.repositoryPath,
branchName,
repoConfig,
);
// Create pull request
await this.log(context, "Creating pull request...");
const prCreator: PullRequestCreator = new PullRequestCreator(context.logger);
const prCreator: PullRequestCreator = new PullRequestCreator(
context.logger,
);
const prTitle: string = PullRequestCreator.generatePRTitle(
exceptionDetails.exception.message,

View File

@@ -2,8 +2,10 @@ import { TaskHandler } from "./TaskHandlerInterface";
import AIAgentTaskType from "Common/Types/AI/AIAgentTaskType";
import logger from "Common/Server/Utils/Logger";
// Registry for task handlers
// Allows dynamic registration and lookup of handlers by task type
/*
* Registry for task handlers
* Allows dynamic registration and lookup of handlers by task type
*/
export default class TaskHandlerRegistry {
private static instance: TaskHandlerRegistry | null = null;
private handlers: Map<AIAgentTaskType, TaskHandler> = new Map();

View File

@@ -139,7 +139,9 @@ export default class BackendAPI {
// Get LLM configuration for a project
public async getLLMConfig(projectId: string): Promise<LLMConfig> {
const url: URL = URL.fromURL(this.baseUrl).addRoute("/api/ai-agent-data/get-llm-config");
const url: URL = URL.fromURL(this.baseUrl).addRoute(
"/api/ai-agent-data/get-llm-config",
);
const response = await API.post({
url,

View File

@@ -30,7 +30,9 @@ export default class RepositoryManager {
config: RepositoryConfig,
workDir: string,
): Promise<CloneResult> {
await this.log(`Cloning repository ${config.organizationName}/${config.repositoryName}...`);
await this.log(
`Cloning repository ${config.organizationName}/${config.repositoryName}...`,
);
// Build the authenticated URL
const authUrl: string = this.buildAuthenticatedUrl(config);
@@ -112,11 +114,7 @@ export default class RepositoryManager {
): Promise<void> {
try {
// Check if branch exists locally
await this.runGitCommand(repoPath, [
"rev-parse",
"--verify",
branchName,
]);
await this.runGitCommand(repoPath, ["rev-parse", "--verify", branchName]);
await this.checkoutBranch(repoPath, branchName);
} catch (error) {
// Branch doesn't exist, create it
@@ -169,10 +167,7 @@ export default class RepositoryManager {
}
// Commit changes
public async commitChanges(
repoPath: string,
message: string,
): Promise<void> {
public async commitChanges(repoPath: string, message: string): Promise<void> {
await this.log(`Committing changes: ${message.substring(0, 50)}...`);
await Execute.executeCommandFile({
@@ -196,15 +191,15 @@ export default class RepositoryManager {
const authUrl: string = this.buildAuthenticatedUrl(config);
// Update the remote URL
await this.runGitCommand(repoPath, ["remote", "set-url", "origin", authUrl]);
await this.runGitCommand(repoPath, [
"remote",
"set-url",
"origin",
authUrl,
]);
// Push with tracking
await this.runGitCommand(repoPath, [
"push",
"-u",
"origin",
branchName,
]);
await this.runGitCommand(repoPath, ["push", "-u", "origin", branchName]);
await this.log(`Branch ${branchName} pushed to remote`);
}

View File

@@ -174,8 +174,10 @@ export default class WorkspaceManager {
const workspacePath: string = path.join(this.BASE_TEMP_DIR, entry.name);
// Try to extract timestamp from directory name
// Format: task-{taskId}-{timestamp}-{uniqueId}
/*
* Try to extract timestamp from directory name
* Format: task-{taskId}-{timestamp}-{uniqueId}
*/
const match: RegExpMatchArray | null = entry.name.match(
/task-[^-]+-(\d+)-[^-]+/,
);
@@ -203,7 +205,9 @@ export default class WorkspaceManager {
public static async initialize(): Promise<void> {
try {
await LocalFile.makeDirectory(this.BASE_TEMP_DIR);
logger.debug(`Workspace base directory initialized: ${this.BASE_TEMP_DIR}`);
logger.debug(
`Workspace base directory initialized: ${this.BASE_TEMP_DIR}`,
);
} catch (error) {
logger.error("Error initializing workspace manager:");
logger.error(error);

View File

@@ -543,7 +543,7 @@ export default class AIAgentTask extends BaseModel {
@Column({
type: ColumnType.Number,
nullable: false,
default: 1
default: 1,
})
public taskNumber?: number = undefined;
}

View File

@@ -81,7 +81,8 @@ export default class AIAgentDataAPI {
const projectId: ObjectID = new ObjectID(data["projectId"] as string);
// Check if this is a Project AI Agent (has a projectId)
const isProjectAIAgent: boolean = aiAgent.projectId !== null && aiAgent.projectId !== undefined;
const isProjectAIAgent: boolean =
aiAgent.projectId !== null && aiAgent.projectId !== undefined;
// Get LLM provider for the project
const llmProvider: LlmProvider | null =
@@ -97,8 +98,10 @@ export default class AIAgentDataAPI {
);
}
// Security check: Project AI Agents cannot access Global LLM Providers
// Only Global AI Agents (projectId is null) can access Global LLM Providers
/*
* Security check: Project AI Agents cannot access Global LLM Providers
* Only Global AI Agents (projectId is null) can access Global LLM Providers
*/
const isGlobalLLMProvider: boolean = llmProvider.isGlobalLlm === true;
if (isProjectAIAgent && isGlobalLLMProvider) {
@@ -281,11 +284,12 @@ export default class AIAgentDataAPI {
// Extract service catalog IDs
const serviceCatalogIds: Array<ObjectID> =
serviceCatalogTelemetryServices
.filter((s: ServiceCatalogTelemetryService) => s.serviceCatalogId)
.map(
(s: ServiceCatalogTelemetryService) =>
s.serviceCatalogId as ObjectID,
);
.filter((s: ServiceCatalogTelemetryService) => {
return s.serviceCatalogId;
})
.map((s: ServiceCatalogTelemetryService) => {
return s.serviceCatalogId as ObjectID;
});
// Step 2: Find CodeRepositories linked to these ServiceCatalogs
const repositories: Array<{
@@ -338,7 +342,9 @@ export default class AIAgentDataAPI {
mainBranchName: string;
servicePathInRepository: string | null;
gitHubAppInstallationId: string | null;
}) => r.id === scr.codeRepository?._id?.toString(),
}) => {
return r.id === scr.codeRepository?._id?.toString();
},
);
if (!existingRepo) {
repositories.push({
@@ -346,11 +352,9 @@ export default class AIAgentDataAPI {
name: scr.codeRepository.name || "",
repositoryHostedAt:
scr.codeRepository.repositoryHostedAt || "",
organizationName:
scr.codeRepository.organizationName || "",
organizationName: scr.codeRepository.organizationName || "",
repositoryName: scr.codeRepository.repositoryName || "",
mainBranchName:
scr.codeRepository.mainBranchName || "main",
mainBranchName: scr.codeRepository.mainBranchName || "main",
servicePathInRepository:
scr.servicePathInRepository || null,
gitHubAppInstallationId:

View File

@@ -1,20 +1,35 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1767009661768 implements MigrationInterface {
public name = 'MigrationName1767009661768'
public name = "MigrationName1767009661768";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "AIAgentTask" ADD "taskNumber" integer NOT NULL DEFAULT '1'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`);
await queryRunner.query(`CREATE INDEX "IDX_5294ce8ea35d411bc972803e8b" ON "AIAgentTask" ("taskNumber") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "public"."IDX_5294ce8ea35d411bc972803e8b"`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`);
await queryRunner.query(`ALTER TABLE "AIAgentTask" DROP COLUMN "taskNumber"`);
}
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "AIAgentTask" ADD "taskNumber" integer NOT NULL DEFAULT '1'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
);
await queryRunner.query(
`CREATE INDEX "IDX_5294ce8ea35d411bc972803e8b" ON "AIAgentTask" ("taskNumber") `,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."IDX_5294ce8ea35d411bc972803e8b"`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
);
await queryRunner.query(
`ALTER TABLE "AIAgentTask" DROP COLUMN "taskNumber"`,
);
}
}

View File

@@ -429,5 +429,5 @@ export default [
AddAIAgentIsDefault1766918848434,
MigrationName1766923324521,
AddGitHubAppInstallationIdToProject1766958924188,
MigrationName1767009661768
MigrationName1767009661768,
];

View File

@@ -63,10 +63,7 @@ const HeaderIconDropdownButton: FunctionComponent<ComponentProps> = (
/>
)}
{props.icon && (
<Icon
className="h-5 w-5 text-gray-500"
icon={props.icon}
/>
<Icon className="h-5 w-5 text-gray-500" icon={props.icon} />
)}
</button>
{props.title && (

View File

@@ -188,9 +188,11 @@ describe("ExceptionUtil", () => {
test("handles null/undefined gracefully", () => {
// @ts-expect-error - testing edge case
const normalizedNull: string = ExceptionUtil.normalizeForFingerprint(null);
const normalizedNull: string =
ExceptionUtil.normalizeForFingerprint(null);
// @ts-expect-error - testing edge case
const normalizedUndefined: string = ExceptionUtil.normalizeForFingerprint(undefined);
const normalizedUndefined: string =
ExceptionUtil.normalizeForFingerprint(undefined);
expect(normalizedNull).toBe("");
expect(normalizedUndefined).toBe("");