import { z } from "zod"; import path from "path"; import { fileURLToPath } from "url"; import { getAllTasks, getTaskById, updateTaskStatus, canExecuteTask, batchCreateOrUpdateTasks, deleteTask as modelDeleteTask, updateTaskSummary, assessTaskComplexity, clearAllTasks as modelClearAllTasks, updateTaskContent as modelUpdateTaskContent, updateTaskRelatedFiles as modelUpdateTaskRelatedFiles, searchTasksWithCommand, } from "../models/taskModel.js"; import { TaskStatus, TaskComplexityLevel, RelatedFileType, RelatedFile, Task, TaskDependency, } from "../types/index.js"; import { extractSummary, generateTaskSummary, } from "../utils/summaryExtractor.js"; import { loadTaskRelatedFiles } from "../utils/fileLoader.js"; // 導入prompt生成器 import { getPlanTaskPrompt, getAnalyzeTaskPrompt, getReflectTaskPrompt, getSplitTasksPrompt, getExecuteTaskPrompt, getVerifyTaskPrompt, getCompleteTaskPrompt, getListTasksPrompt, getQueryTaskPrompt, getGetTaskDetailPrompt, } from "../prompts/index.js"; /** * 將任務狀態轉換為更友好的顯示文字 */ function getTaskStatusDisplay(status: TaskStatus): string { switch (status) { case TaskStatus.PENDING: return "待處理"; case TaskStatus.IN_PROGRESS: return "進行中"; case TaskStatus.COMPLETED: return "已完成"; default: return status; } } /** * 格式化日期為更友好的顯示格式 */ function formatDate(date: Date): string { return date.toLocaleString("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } // 開始規劃工具 export const planTaskSchema = z.object({ description: z .string() .min(10, { message: "任務描述不能少於10個字符,請提供更詳細的描述以確保任務目標明確", }) .describe("完整詳細的任務問題描述,應包含任務目標、背景及預期成果"), requirements: z .string() .optional() .describe("任務的特定技術要求、業務約束條件或品質標準(選填)"), existingTasksReference: z .boolean() .optional() .default(false) .describe("是否參考現有任務作為規劃基礎,用於任務調整和延續性規劃"), }); export async function planTask({ description, requirements, existingTasksReference = false, }: z.infer) { // 獲取基礎目錄路徑 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PROJECT_ROOT = path.resolve(__dirname, "../.."); const DATA_DIR = process.env.DATA_DIR || path.join(PROJECT_ROOT, "data"); const MEMORY_DIR = path.join(DATA_DIR, "memory"); // 準備所需參數 let completedTasks: Task[] = []; let pendingTasks: Task[] = []; // 當 existingTasksReference 為 true 時,從數據庫中載入所有任務作為參考 if (existingTasksReference) { try { const allTasks = await getAllTasks(); // 將任務分為已完成和未完成兩類 completedTasks = allTasks.filter( (task) => task.status === TaskStatus.COMPLETED ); pendingTasks = allTasks.filter( (task) => task.status !== TaskStatus.COMPLETED ); } catch (error) { console.error("載入現有任務時發生錯誤:", error); } } // 使用prompt生成器獲取最終prompt const prompt = getPlanTaskPrompt({ description, requirements, existingTasksReference, completedTasks, pendingTasks, memoryDir: MEMORY_DIR, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } // 分析問題工具 export const analyzeTaskSchema = z.object({ summary: z .string() .min(20, { message: "任務摘要不能少於20個字符,請提供更詳細的描述以確保任務目標明確", }) .describe("結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰"), initialConcept: z .string() .min(50, { message: "初步解答構想不能少於50個字符,請提供更詳細的內容確保技術方案清晰", }) .describe( "初步解答構想,包含技術方案、架構設計和實施策略,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" ), previousAnalysis: z .string() .optional() .describe("前次迭代的分析結果,用於持續改進方案(僅在重新分析時需提供)"), }); export async function analyzeTask({ summary, initialConcept, previousAnalysis, }: z.infer) { // 使用prompt生成器獲取最終prompt const prompt = getAnalyzeTaskPrompt({ summary, initialConcept, previousAnalysis, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } // 反思構想工具 export const reflectTaskSchema = z.object({ summary: z .string() .min(20, { message: "任務摘要不能少於20個字符,請提供更詳細的描述以確保任務目標明確", }) .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), analysis: z .string() .min(100, { message: "技術分析內容不夠詳盡,請提供完整的技術分析和實施方案", }) .describe( "完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" ), }); export async function reflectTask({ summary, analysis, }: z.infer) { // 使用prompt生成器獲取最終prompt const prompt = getReflectTaskPrompt({ summary, analysis, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } // 拆分任務工具 export const splitTasksSchema = z.object({ updateMode: z .enum(["append", "overwrite", "selective", "clearAllTasks"]) .describe( "任務更新模式選擇:'append'(保留所有現有任務並添加新任務)、'overwrite'(清除所有未完成任務並完全替換,保留已完成任務)、'selective'(智能更新:根據任務名稱匹配更新現有任務,保留不在列表中的任務,推薦用於任務微調)、'clearAllTasks'(清除所有任務並創建備份)。\n預設為'clearAllTasks'模式,只有用戶要求變更或修改計劃內容才使用其他模式" ), tasks: z .array( z.object({ name: z .string() .max(100, { message: "任務名稱過長,請限制在100個字符以內", }) .describe("簡潔明確的任務名稱,應能清晰表達任務目的"), description: z .string() .min(10, { message: "任務描述過短,請提供更詳細的內容以確保理解", }) .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), implementationGuide: z .string() .describe( "此特定任務的具體實現方法和步驟,請參考之前的分析結果提供 pseudocode" ), dependencies: z .array(z.string()) .optional() .describe( "此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀,是一個字串陣列" ), notes: z .string() .optional() .describe("補充說明、特殊處理要求或實施建議(選填)"), relatedFiles: z .array( z.object({ path: z .string() .min(1, { message: "文件路徑不能為空", }) .describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"), type: z .enum(["待修改", "參考資料", "待建立", "依賴文件", "其他"]) .describe("文件類型,用於區分不同類型的文件"), description: z .string() .min(1, { message: "文件描述不能為空", }) .describe("文件描述,用於說明文件的用途和內容"), lineStart: z .number() .int() .positive() .optional() .describe("相關代碼區塊的起始行(選填)"), lineEnd: z .number() .int() .positive() .optional() .describe("相關代碼區塊的結束行(選填)"), }) ) .optional() .describe( "與任務相關的文件列表,用於記錄與任務相關的代碼文件、參考資料、要建立的文件等(選填)" ), verificationCriteria: z .string() .optional() .describe("此特定任務的驗證標準和檢驗方法"), }) ) .min(1, { message: "請至少提供一個任務", }) .describe("結構化的任務清單,每個任務應保持原子性且有明確的完成標準"), globalAnalysisResult: z .string() .optional() .describe( "全局分析結果:來自 reflect_task 的完整分析結果,適用於所有任務的通用部分" ), }); export async function splitTasks({ updateMode, tasks, globalAnalysisResult, }: z.infer) { try { // 檢查 tasks 裡面的 name 是否有重複 const nameSet = new Set(); for (const task of tasks) { if (nameSet.has(task.name)) { return { content: [ { type: "text" as const, text: "tasks 參數中存在重複的任務名稱,請確保每個任務名稱是唯一的", }, ], }; } nameSet.add(task.name); } // 根據不同的更新模式處理任務 let message = ""; let actionSuccess = true; let backupFile = null; let createdTasks: Task[] = []; let allTasks: Task[] = []; // 將任務資料轉換為符合batchCreateOrUpdateTasks的格式 const convertedTasks = tasks.map((task) => ({ name: task.name, description: task.description, notes: task.notes, dependencies: task.dependencies, implementationGuide: task.implementationGuide, verificationCriteria: task.verificationCriteria, relatedFiles: task.relatedFiles?.map((file) => ({ path: file.path, type: file.type as RelatedFileType, description: file.description, lineStart: file.lineStart, lineEnd: file.lineEnd, })), })); // 處理 clearAllTasks 模式 if (updateMode === "clearAllTasks") { const clearResult = await modelClearAllTasks(); if (clearResult.success) { message = clearResult.message; backupFile = clearResult.backupFile; try { // 清空任務後再創建新任務 createdTasks = await batchCreateOrUpdateTasks( convertedTasks, "append", globalAnalysisResult ); message += `\n成功創建了 ${createdTasks.length} 個新任務。`; } catch (error) { actionSuccess = false; message += `\n創建新任務時發生錯誤: ${ error instanceof Error ? error.message : String(error) }`; } } else { actionSuccess = false; message = clearResult.message; } } else { // 對於其他模式,直接使用 batchCreateOrUpdateTasks try { createdTasks = await batchCreateOrUpdateTasks( convertedTasks, updateMode, globalAnalysisResult ); // 根據不同的更新模式生成消息 switch (updateMode) { case "append": message = `成功追加了 ${createdTasks.length} 個新任務。`; break; case "overwrite": message = `成功清除未完成任務並創建了 ${createdTasks.length} 個新任務。`; break; case "selective": message = `成功選擇性更新/創建了 ${createdTasks.length} 個任務。`; break; } } catch (error) { actionSuccess = false; message = `任務創建失敗:${ error instanceof Error ? error.message : String(error) }`; } } // 獲取所有任務用於顯示依賴關係 try { allTasks = await getAllTasks(); } catch (error) { console.error("獲取所有任務時發生錯誤:", error); allTasks = [...createdTasks]; // 如果獲取失敗,至少使用剛創建的任務 } // 使用prompt生成器獲取最終prompt const prompt = getSplitTasksPrompt({ updateMode, createdTasks, allTasks, }); return { content: [ { type: "text" as const, text: prompt, }, ], ephemeral: { taskCreationResult: { success: actionSuccess, message, backupFilePath: backupFile, }, }, }; } catch (error) { console.error("執行任務拆分時發生錯誤:", error); return { content: [ { type: "text" as const, text: "執行任務拆分時發生錯誤: " + (error instanceof Error ? error.message : String(error)), }, ], ephemeral: { taskCreationResult: { success: false, message: `任務創建失敗:${ error instanceof Error ? error.message : String(error) }`, }, }, }; } } export const listTasksSchema = z.object({ status: z .enum(["all", "pending", "in_progress", "completed"]) .describe("要列出的任務狀態,可選擇 'all' 列出所有任務,或指定具體狀態"), }); // 列出任務工具 export async function listTasks({ status }: z.infer) { const tasks = await getAllTasks(); let filteredTasks = tasks; switch (status) { case "all": break; case "pending": filteredTasks = tasks.filter( (task) => task.status === TaskStatus.PENDING ); break; case "in_progress": filteredTasks = tasks.filter( (task) => task.status === TaskStatus.IN_PROGRESS ); break; case "completed": filteredTasks = tasks.filter( (task) => task.status === TaskStatus.COMPLETED ); break; } if (filteredTasks.length === 0) { return { content: [ { type: "text" as const, text: `## 系統通知\n\n目前系統中沒有${ status === "all" ? "任何" : `任何 ${status} 的` }任務。請查詢其他狀態任務或先使用「split_tasks」工具創建任務結構,再進行後續操作。`, }, ], }; } const tasksByStatus = tasks.reduce((acc, task) => { if (!acc[task.status]) { acc[task.status] = []; } acc[task.status].push(task); return acc; }, {} as Record); // 使用prompt生成器獲取最終prompt const prompt = getListTasksPrompt({ status, tasks: tasksByStatus, allTasks: filteredTasks, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } // 執行任務工具 export const executeTaskSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID必須是有效的UUID格式", }) .describe("待執行任務的唯一標識符,必須是系統中存在的有效任務ID"), }); export async function executeTask({ taskId, }: z.infer) { try { // 檢查任務是否存在 const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `找不到ID為 \`${taskId}\` 的任務。請確認ID是否正確。`, }, ], }; } // 檢查任務是否可以執行(依賴任務都已完成) const executionCheck = await canExecuteTask(taskId); if (!executionCheck.canExecute) { const blockedByTasksText = executionCheck.blockedBy && executionCheck.blockedBy.length > 0 ? `被以下未完成的依賴任務阻擋: ${executionCheck.blockedBy.join(", ")}` : "無法確定阻擋原因"; return { content: [ { type: "text" as const, text: `任務 "${task.name}" (ID: \`${taskId}\`) 目前無法執行。${blockedByTasksText}`, }, ], }; } // 如果任務已經標記為「進行中」,提示用戶 if (task.status === TaskStatus.IN_PROGRESS) { return { content: [ { type: "text" as const, text: `任務 "${task.name}" (ID: \`${taskId}\`) 已經處於進行中狀態。`, }, ], }; } // 如果任務已經標記為「已完成」,提示用戶 if (task.status === TaskStatus.COMPLETED) { return { content: [ { type: "text" as const, text: `任務 "${task.name}" (ID: \`${taskId}\`) 已經標記為完成。如需重新執行,請先使用 delete_task 刪除該任務並重新創建。`, }, ], }; } // 更新任務狀態為「進行中」 await updateTaskStatus(taskId, TaskStatus.IN_PROGRESS); // 評估任務複雜度 const complexityResult = await assessTaskComplexity(taskId); // 將複雜度結果轉換為適當的格式 const complexityAssessment = complexityResult ? { level: complexityResult.level, metrics: { descriptionLength: complexityResult.metrics.descriptionLength, dependenciesCount: complexityResult.metrics.dependenciesCount, }, recommendations: complexityResult.recommendations, } : undefined; // 獲取依賴任務,用於顯示完成摘要 const dependencyTasks: Task[] = []; if (task.dependencies && task.dependencies.length > 0) { for (const dep of task.dependencies) { const depTask = await getTaskById(dep.taskId); if (depTask) { dependencyTasks.push(depTask); } } } // 加載任務相關的文件內容 let relatedFilesSummary = ""; if (task.relatedFiles && task.relatedFiles.length > 0) { try { const relatedFilesResult = await loadTaskRelatedFiles( task.relatedFiles ); relatedFilesSummary = typeof relatedFilesResult === "string" ? relatedFilesResult : relatedFilesResult.summary || ""; } catch (error) { console.error("載入相關文件時發生錯誤:", error); relatedFilesSummary = "載入相關文件時發生錯誤,請手動查看文件。"; } } // 嘗試自動發現相關文件 let potentialFiles: string[] = []; if (!task.relatedFiles || task.relatedFiles.length === 0) { // 基於任務名稱和描述關鍵詞,嘗試推測可能相關的文件 const taskWords = [ ...task.name.split(/[\s,.;:]+/), ...task.description.split(/[\s,.;:]+/), ] .filter((word) => word.length > 3) .map((word) => word.toLowerCase()); // 從關鍵詞中提取可能的文件名或路徑片段 potentialFiles = taskWords.filter( (word) => /^[a-z0-9]+$/i.test(word) && ![ "task", "function", "model", "index", "with", "from", "this", ].includes(word.toLowerCase()) ); // 只保留前5個 potentialFiles = potentialFiles.slice(0, 5); } // 使用prompt生成器獲取最終prompt const prompt = getExecuteTaskPrompt({ task, complexityAssessment, relatedFilesSummary, dependencyTasks, potentialFiles, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } catch (error) { console.error("執行任務時發生錯誤:", error); return { content: [ { type: "text" as const, text: `執行任務時發生錯誤: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } // 檢驗任務工具 export const verifyTaskSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待驗證任務的唯一標識符,必須是系統中存在的有效任務ID"), }); export async function verifyTask({ taskId }: z.infer) { const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`, }, ], isError: true, }; } if (task.status !== TaskStatus.IN_PROGRESS) { return { content: [ { type: "text" as const, text: `## 狀態錯誤\n\n任務 "${task.name}" (ID: \`${task.id}\`) 當前狀態為 "${task.status}",不處於進行中狀態,無法進行檢驗。\n\n只有狀態為「進行中」的任務才能進行檢驗。請先使用「execute_task」工具開始任務執行。`, }, ], isError: true, }; } // 使用prompt生成器獲取最終prompt const prompt = getVerifyTaskPrompt({ task }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } // 完成任務工具 export const completeTaskSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe( "待標記為完成的任務唯一標識符,必須是狀態為「進行中」的有效任務ID" ), summary: z .string() .min(30, { message: "任務摘要太簡短,請提供更詳細的完成報告,包含實施結果和主要決策", }) .optional() .describe( "任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)" ), }); export async function completeTask({ taskId, summary, }: z.infer) { const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`, }, ], isError: true, }; } if (task.status !== TaskStatus.IN_PROGRESS) { return { content: [ { type: "text" as const, text: `## 狀態錯誤\n\n任務 "${task.name}" (ID: \`${task.id}\`) 當前狀態為 "${task.status}",不是進行中狀態,無法標記為完成。\n\n只有狀態為「進行中」的任務才能標記為完成。請先使用「execute_task」工具開始任務執行。`, }, ], isError: true, }; } // 處理摘要信息 let taskSummary = summary; if (!taskSummary) { // 自動生成摘要 taskSummary = generateTaskSummary(task.name, task.description); } // 更新任務狀態為已完成,並添加摘要 await updateTaskStatus(taskId, TaskStatus.COMPLETED); await updateTaskSummary(taskId, taskSummary); // 使用prompt生成器獲取最終prompt const prompt = getCompleteTaskPrompt({ task, completionTime: new Date().toISOString(), }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } // 刪除任務工具 export const deleteTaskSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待刪除任務的唯一標識符,必須是系統中存在且未完成的任務ID"), }); export async function deleteTask({ taskId }: z.infer) { const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`, }, ], isError: true, }; } if (task.status === TaskStatus.COMPLETED) { return { content: [ { type: "text" as const, text: `## 操作被拒絕\n\n任務 "${task.name}" (ID: \`${task.id}\`) 已完成,不允許刪除已完成的任務。`, }, ], isError: true, }; } const result = await modelDeleteTask(taskId); return { content: [ { type: "text" as const, text: `## ${result.success ? "操作成功" : "操作失敗"}\n\n${ result.message }`, }, ], isError: !result.success, }; } // 清除所有任務工具 export const clearAllTasksSchema = z.object({ confirm: z .boolean() .refine((val) => val === true, { message: "必須明確確認清除操作,請將 confirm 參數設置為 true 以確認此危險操作", }) .describe("確認刪除所有未完成的任務(此操作不可逆)"), }); export async function clearAllTasks({ confirm, }: z.infer) { // 安全檢查:如果沒有確認,則拒絕操作 if (!confirm) { return { content: [ { type: "text" as const, text: `## 操作取消\n\n未確認清除操作。如要清除所有任務,請將 confirm 參數設為 true。\n\n⚠️ 此操作將刪除所有未完成的任務且無法恢復。`, }, ], }; } // 檢查是否真的有任務需要清除 const allTasks = await getAllTasks(); if (allTasks.length === 0) { return { content: [ { type: "text" as const, text: `## 操作提示\n\n系統中沒有任何任務需要清除。`, }, ], }; } // 執行清除操作 const result = await modelClearAllTasks(); return { content: [ { type: "text" as const, text: `## ${result.success ? "操作成功" : "操作失敗"}\n\n${ result.message }${ result.backupFile ? `\n\n已自動創建備份: \`${result.backupFile}\`` : "" }`, }, ], isError: !result.success, }; } // 更新任務內容工具 export const updateTaskContentSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), name: z.string().optional().describe("任務的新名稱(選填)"), description: z.string().optional().describe("任務的新描述內容(選填)"), notes: z.string().optional().describe("任務的新補充說明(選填)"), dependencies: z .array(z.string()) .optional() .describe("任務的新依賴關係(選填)"), relatedFiles: z .array( z.object({ path: z .string() .min(1, { message: "文件路徑不能為空,請提供有效的文件路徑" }) .describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"), type: z.nativeEnum(RelatedFileType).describe("文件與任務的關係類型"), description: z.string().optional().describe("文件的補充描述(選填)"), lineStart: z .number() .int() .positive() .optional() .describe("相關代碼區塊的起始行(選填)"), lineEnd: z .number() .int() .positive() .optional() .describe("相關代碼區塊的結束行(選填)"), }) ) .optional() .describe( "與任務相關的文件列表,用於記錄與任務相關的代碼文件、參考資料、要建立的檔案等(選填)" ), implementationGuide: z .string() .optional() .describe("任務的新實現指南(選填)"), verificationCriteria: z .string() .optional() .describe("任務的新驗證標準(選填)"), }); export async function updateTaskContent({ taskId, name, description, notes, relatedFiles, dependencies, implementationGuide, verificationCriteria, }: z.infer) { if (relatedFiles) { for (const file of relatedFiles) { if ( (file.lineStart && !file.lineEnd) || (!file.lineStart && file.lineEnd) || (file.lineStart && file.lineEnd && file.lineStart > file.lineEnd) ) { return { content: [ { type: "text" as const, text: `## 操作失敗\n\n行號設置無效:必須同時設置起始行和結束行,且起始行必須小於結束行`, }, ], }; } } } if ( !( name || description || notes || dependencies || implementationGuide || verificationCriteria || relatedFiles ) ) { return { content: [ { type: "text" as const, text: `## 操作失敗\n\n至少需要更新一個字段(名稱、描述、注記或相關文件)`, }, ], }; } // 獲取任務以檢查它是否存在 const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`, }, ], isError: true, }; } // 記錄要更新的任務和內容 let updateSummary = `準備更新任務:${task.name} (ID: ${task.id})`; if (name) updateSummary += `,新名稱:${name}`; if (description) updateSummary += `,更新描述`; if (notes) updateSummary += `,更新注記`; if (relatedFiles) updateSummary += `,更新相關文件 (${relatedFiles.length} 個)`; if (dependencies) updateSummary += `,更新依賴關係 (${dependencies.length} 個)`; if (implementationGuide) updateSummary += `,更新實現指南`; if (verificationCriteria) updateSummary += `,更新驗證標準`; // 執行更新操作 const result = await modelUpdateTaskContent(taskId, { name, description, notes, relatedFiles, dependencies, implementationGuide, verificationCriteria, }); // 構建響應消息 const responseTitle = result.success ? "操作成功" : "操作失敗"; let responseMessage = result.message; if (result.success && result.task) { // 顯示更新後的任務詳情 responseMessage += "\n\n### 更新後的任務詳情\n"; responseMessage += `- **名稱:** ${result.task.name}\n`; responseMessage += `- **描述:** ${result.task.description.substring( 0, 100 )}${result.task.description.length > 100 ? "..." : ""}\n`; if (result.task.notes) { responseMessage += `- **注記:** ${result.task.notes.substring(0, 100)}${ result.task.notes.length > 100 ? "..." : "" }\n`; } responseMessage += `- **狀態:** ${result.task.status}\n`; responseMessage += `- **更新時間:** ${new Date( result.task.updatedAt ).toISOString()}\n`; // 顯示相關文件信息 if (result.task.relatedFiles && result.task.relatedFiles.length > 0) { responseMessage += `- **相關文件:** ${result.task.relatedFiles.length} 個\n`; // 按文件類型分組 const filesByType = result.task.relatedFiles.reduce((acc, file) => { if (!acc[file.type]) { acc[file.type] = []; } acc[file.type].push(file); return acc; }, {} as Record); for (const [type, files] of Object.entries(filesByType)) { responseMessage += ` - ${type} (${files.length} 個): `; responseMessage += files.map((file) => `\`${file.path}\``).join(", "); responseMessage += "\n"; } } } return { content: [ { type: "text" as const, text: `## ${responseTitle}\n\n${responseMessage}`, }, ], isError: !result.success, }; } // 更新任務相關文件工具 export const updateTaskRelatedFilesSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), relatedFiles: z .array( z.object({ path: z .string() .min(1, { message: "文件路徑不能為空,請提供有效的文件路徑" }) .describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"), type: z .enum([ RelatedFileType.TO_MODIFY, RelatedFileType.REFERENCE, RelatedFileType.CREATE, RelatedFileType.DEPENDENCY, RelatedFileType.OTHER, ]) .describe("文件與任務的關係類型"), description: z.string().optional().describe("文件的補充描述(選填)"), lineStart: z .number() .int() .positive() .optional() .describe("相關代碼區塊的起始行(選填)"), lineEnd: z .number() .int() .positive() .optional() .describe("相關代碼區塊的結束行(選填)"), }) ) .min(1, { message: "至少需要提供一個相關文件,請確保文件列表不為空" }) .describe("與任務相關的文件列表"), }); export async function updateTaskRelatedFiles({ taskId, relatedFiles, }: z.infer) { if (relatedFiles) { for (const file of relatedFiles) { if ( (file.lineStart && !file.lineEnd) || (!file.lineStart && file.lineEnd) || (file.lineStart && file.lineEnd && file.lineStart > file.lineEnd) ) { return { content: [ { type: "text" as const, text: `## 操作失敗\n\n行號設置無效:必須同時設置起始行和結束行,且起始行必須小於結束行`, }, ], }; } } } // 獲取任務以檢查它是否存在 const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`, }, ], isError: true, }; } // 記錄要更新的任務和相關文件 const fileTypeCount = relatedFiles.reduce((acc, file) => { acc[file.type] = (acc[file.type] || 0) + 1; return acc; }, {} as Record); const fileTypeSummary = Object.entries(fileTypeCount) .map(([type, count]) => `${type} ${count} 個`) .join(","); // 執行更新操作 const result = await modelUpdateTaskRelatedFiles(taskId, relatedFiles); // 構建響應消息 const responseTitle = result.success ? "操作成功" : "操作失敗"; let responseMessage = result.message; if (result.success && result.task && result.task.relatedFiles) { // 顯示更新後的相關文件列表 responseMessage += "\n\n### 任務相關文件列表\n"; // 按文件類型分組顯示 const filesByType = result.task.relatedFiles.reduce((acc, file) => { acc[file.type] = acc[file.type] || []; acc[file.type].push(file); return acc; }, {} as Record); for (const [type, files] of Object.entries(filesByType)) { responseMessage += `\n#### ${type} (${files.length} 個)\n`; files.forEach((file, index) => { responseMessage += `${index + 1}. \`${file.path}\`${ file.description ? ` - ${file.description}` : "" }${ file.lineStart && file.lineEnd ? ` (行 ${file.lineStart}-${file.lineEnd})` : "" }\n`; }); } } return { content: [ { type: "text" as const, text: `## ${responseTitle}\n\n${responseMessage}`, }, ], isError: !result.success, }; } // 格式化單個任務的詳細資訊 const formatTaskDetails = (task: Task) => { let details = `### ${task.name}\n**ID:** \`${task.id}\`\n**描述:** ${task.description}\n`; if (task.notes) { details += `**注記:** ${task.notes}\n`; } details += `**狀態:** ${task.status}\n`; if (task.dependencies.length > 0) { const depIds = task.dependencies .map((dep: TaskDependency) => `\`${dep.taskId}\``) .join(", "); details += `**依賴任務:** ${depIds}\n`; } // 添加相關文件信息 if (task.relatedFiles && task.relatedFiles.length > 0) { details += `**相關文件:** ${task.relatedFiles.length} 個\n`; // 按文件類型分組 const filesByType = task.relatedFiles.reduce( (acc: Record, file: RelatedFile) => { if (!acc[file.type]) { acc[file.type] = []; } acc[file.type].push(file); return acc; }, {} as Record ); for (const [type, files] of Object.entries(filesByType)) { details += ` - ${type} (${files.length} 個): `; details += files .map((file: RelatedFile) => `\`${file.path}\``) .join(", "); details += "\n"; } } details += `**創建時間:** ${new Date(task.createdAt).toISOString()}\n`; details += `**更新時間:** ${new Date(task.updatedAt).toISOString()}\n`; if (task.completedAt) { details += `**完成時間:** ${new Date(task.completedAt).toISOString()}\n`; } if (task.summary) { details += `**完成摘要:** ${task.summary}\n`; } return details; }; // 查詢任務工具 export const queryTaskSchema = z.object({ query: z .string() .min(1, { message: "查詢內容不能為空,請提供任務ID或搜尋關鍵字", }) .describe("搜尋查詢文字,可以是任務ID或多個關鍵字(空格分隔)"), isId: z .boolean() .optional() .default(false) .describe("指定是否為ID查詢模式,默認為否(關鍵字模式)"), page: z .number() .int() .positive() .optional() .default(1) .describe("分頁頁碼,默認為第1頁"), pageSize: z .number() .int() .positive() .min(1) .max(20) .optional() .default(5) .describe("每頁顯示的任務數量,默認為5筆,最大20筆"), }); export async function queryTask({ query, isId = false, page = 1, pageSize = 3, }: z.infer) { try { // 使用系統指令搜尋函數 const results = await searchTasksWithCommand(query, isId, page, pageSize); // 使用prompt生成器獲取最終prompt const prompt = getQueryTaskPrompt({ query, isId, tasks: results.tasks, totalTasks: results.pagination.totalResults, page: results.pagination.currentPage, pageSize, totalPages: results.pagination.totalPages, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } catch (error) { console.error("查詢任務時發生錯誤:", error); return { content: [ { type: "text" as const, text: `## 系統錯誤\n\n查詢任務時發生錯誤: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } // 格式化任務顯示內容的輔助函數 function formatTaskForDisplay(task: Task): string { let taskInfo = `### ${task.name}\n**ID:** \`${task.id}\`\n**狀態:** ${task.status}\n**描述:** ${task.description}\n`; if (task.notes) { taskInfo += `**注記:** ${task.notes}\n`; } if (task.implementationGuide) { taskInfo += `**實現指南:** ${ task.implementationGuide.length > 300 ? task.implementationGuide.substring(0, 300) + "..." : task.implementationGuide }\n`; } if (task.verificationCriteria) { taskInfo += `**驗證標準:** ${ task.verificationCriteria.length > 300 ? task.verificationCriteria.substring(0, 300) + "..." : task.verificationCriteria }\n`; } if (task.summary) { taskInfo += `**完成摘要:** ${task.summary}\n`; } taskInfo += `**創建時間:** ${new Date(task.createdAt).toLocaleString( "zh-TW" )}\n`; taskInfo += `**更新時間:** ${new Date(task.updatedAt).toLocaleString( "zh-TW" )}\n`; if (task.completedAt) { taskInfo += `**完成時間:** ${new Date(task.completedAt).toLocaleString( "zh-TW" )}\n`; } taskInfo += `**詳細內容:** 請使用「get_task_detail」工具查看 ${task.id} 完整任務詳情`; return taskInfo; } // 取得完整任務詳情的參數 export const getTaskDetailSchema = z.object({ taskId: z .string() .min(1, { message: "任務ID不能為空,請提供有效的任務ID", }) .describe("欲檢視詳情的任務ID"), }); // 取得任務完整詳情 export async function getTaskDetail({ taskId, }: z.infer) { try { // 檢查任務是否存在 const task = await getTaskById(taskId); if (!task) { return { content: [ { type: "text" as const, text: `## 錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請確認任務ID是否正確。`, }, ], isError: true, }; } // 使用prompt生成器獲取最終prompt const prompt = getGetTaskDetailPrompt({ taskId, task, }); return { content: [ { type: "text" as const, text: prompt, }, ], }; } catch (error) { console.error("取得任務詳情時發生錯誤:", error); // 使用prompt生成器獲取錯誤訊息 const errorPrompt = getGetTaskDetailPrompt({ taskId, error: error instanceof Error ? error.message : String(error), }); return { content: [ { type: "text" as const, text: errorPrompt, }, ], }; } } // 格式化完整任務詳情的輔助函數,不截斷長內容 function formatFullTaskDetail(task: Task): string { let taskDetail = `### ${task.name}\n\n**ID:** \`${task.id}\`\n\n**狀態:** ${task.status}\n\n**描述:**\n${task.description}\n\n`; if (task.notes) { taskDetail += `**注記:**\n${task.notes}\n\n`; } if (task.dependencies && task.dependencies.length > 0) { taskDetail += `**依賴任務:** ${task.dependencies .map((dep) => `\`${dep.taskId}\``) .join(", ")}\n\n`; } if (task.implementationGuide) { taskDetail += `**實現指南:**\n\`\`\`\n${task.implementationGuide}\n\`\`\`\n\n`; } if (task.verificationCriteria) { taskDetail += `**驗證標準:**\n\`\`\`\n${task.verificationCriteria}\n\`\`\`\n\n`; } if (task.relatedFiles && task.relatedFiles.length > 0) { taskDetail += `**相關文件:**\n`; for (const file of task.relatedFiles) { taskDetail += `- \`${file.path}\` (${file.type})${ file.description ? `: ${file.description}` : "" }\n`; } taskDetail += `\n`; } taskDetail += `**創建時間:** ${new Date(task.createdAt).toLocaleString( "zh-TW" )}\n`; taskDetail += `**更新時間:** ${new Date(task.updatedAt).toLocaleString( "zh-TW" )}\n`; if (task.completedAt) { taskDetail += `**完成時間:** ${new Date(task.completedAt).toLocaleString( "zh-TW" )}\n\n`; } if (task.summary) { taskDetail += `**完成摘要:**\n${task.summary}\n\n`; } return taskDetail; }