diff --git a/AIAgent/CodeAgents/CodeAgentFactory.ts b/AIAgent/CodeAgents/CodeAgentFactory.ts index 2666bc4eb5..a0318e3795 100644 --- a/AIAgent/CodeAgents/CodeAgentFactory.ts +++ b/AIAgent/CodeAgents/CodeAgentFactory.ts @@ -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 { diff --git a/AIAgent/CodeAgents/CodeAgentInterface.ts b/AIAgent/CodeAgents/CodeAgentInterface.ts index d2b4827c35..95ef9c71b6 100644 --- a/AIAgent/CodeAgents/CodeAgentInterface.ts +++ b/AIAgent/CodeAgents/CodeAgentInterface.ts @@ -40,17 +40,16 @@ export type CodeAgentProgressCallback = ( event: CodeAgentProgressEvent, ) => void | Promise; -// 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; + initialize(config: CodeAgentLLMConfig, logger?: TaskLogger): Promise; // Execute a task and return the result executeTask(task: CodeAgentTask): Promise; @@ -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 diff --git a/AIAgent/CodeAgents/OpenCodeAgent.ts b/AIAgent/CodeAgents/OpenCodeAgent.ts index 286eac0597..e51cec0e60 100644 --- a/AIAgent/CodeAgents/OpenCodeAgent.ts +++ b/AIAgent/CodeAgents/OpenCodeAgent.ts @@ -51,12 +51,15 @@ export default class OpenCodeAgent implements CodeAgent { public async executeTask(task: CodeAgentTask): Promise { 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 = []; - 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 = ["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 = 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}`); + }); } } diff --git a/AIAgent/Jobs/ProcessScheduledTasks.ts b/AIAgent/Jobs/ProcessScheduledTasks.ts index 4419649d3c..dd04562868 100644 --- a/AIAgent/Jobs/ProcessScheduledTasks.ts +++ b/AIAgent/Jobs/ProcessScheduledTasks.ts @@ -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); } diff --git a/AIAgent/TaskHandlers/FixExceptionTaskHandler.ts b/AIAgent/TaskHandlers/FixExceptionTaskHandler.ts index 0f8e8bfbfc..ec78fa5d29 100644 --- a/AIAgent/TaskHandlers/FixExceptionTaskHandler.ts +++ b/AIAgent/TaskHandlers/FixExceptionTaskHandler.ts @@ -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 { 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 = new Map(); diff --git a/AIAgent/Utils/BackendAPI.ts b/AIAgent/Utils/BackendAPI.ts index 4f5a437ed8..38c23fafd9 100644 --- a/AIAgent/Utils/BackendAPI.ts +++ b/AIAgent/Utils/BackendAPI.ts @@ -139,7 +139,9 @@ export default class BackendAPI { // Get LLM configuration for a project public async getLLMConfig(projectId: string): Promise { - 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, diff --git a/AIAgent/Utils/RepositoryManager.ts b/AIAgent/Utils/RepositoryManager.ts index 89a869107b..c96fc28c75 100644 --- a/AIAgent/Utils/RepositoryManager.ts +++ b/AIAgent/Utils/RepositoryManager.ts @@ -30,7 +30,9 @@ export default class RepositoryManager { config: RepositoryConfig, workDir: string, ): Promise { - 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 { 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 { + public async commitChanges(repoPath: string, message: string): Promise { 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`); } diff --git a/AIAgent/Utils/WorkspaceManager.ts b/AIAgent/Utils/WorkspaceManager.ts index 6a10e799a8..6d5066eab6 100644 --- a/AIAgent/Utils/WorkspaceManager.ts +++ b/AIAgent/Utils/WorkspaceManager.ts @@ -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 { 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); diff --git a/Common/Models/DatabaseModels/AIAgentTask.ts b/Common/Models/DatabaseModels/AIAgentTask.ts index 6183545945..121a5eaa2a 100644 --- a/Common/Models/DatabaseModels/AIAgentTask.ts +++ b/Common/Models/DatabaseModels/AIAgentTask.ts @@ -543,7 +543,7 @@ export default class AIAgentTask extends BaseModel { @Column({ type: ColumnType.Number, nullable: false, - default: 1 + default: 1, }) public taskNumber?: number = undefined; } diff --git a/Common/Server/API/AIAgentDataAPI.ts b/Common/Server/API/AIAgentDataAPI.ts index 6e532f758a..f44587b1e1 100644 --- a/Common/Server/API/AIAgentDataAPI.ts +++ b/Common/Server/API/AIAgentDataAPI.ts @@ -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 = 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: diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1767009661768-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1767009661768-MigrationName.ts index 1eb671a187..ea7d646207 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1767009661768-MigrationName.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1767009661768-MigrationName.ts @@ -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 { - 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 { - 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 { + 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 { + 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"`, + ); + } } diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts index cd254cce27..0c17467312 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts @@ -429,5 +429,5 @@ export default [ AddAIAgentIsDefault1766918848434, MigrationName1766923324521, AddGitHubAppInstallationIdToProject1766958924188, - MigrationName1767009661768 + MigrationName1767009661768, ]; diff --git a/Common/UI/Components/Header/HeaderIconDropdownButton.tsx b/Common/UI/Components/Header/HeaderIconDropdownButton.tsx index c4a96f2f6d..32ba0e837e 100644 --- a/Common/UI/Components/Header/HeaderIconDropdownButton.tsx +++ b/Common/UI/Components/Header/HeaderIconDropdownButton.tsx @@ -63,10 +63,7 @@ const HeaderIconDropdownButton: FunctionComponent = ( /> )} {props.icon && ( - + )} {props.title && ( diff --git a/Telemetry/Tests/Utils/Exception.test.ts b/Telemetry/Tests/Utils/Exception.test.ts index 884b44748a..8412227035 100644 --- a/Telemetry/Tests/Utils/Exception.test.ts +++ b/Telemetry/Tests/Utils/Exception.test.ts @@ -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("");