From 17937e7a9b6e13cfe822f6b8cbb9a27ad54370df Mon Sep 17 00:00:00 2001 From: siage Date: Fri, 11 Apr 2025 16:57:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BB=BB=E5=8B=99=E6=91=98?= =?UTF-8?q?=E8=A6=81=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=85=81=E8=A8=B1=E7=94=A8?= =?UTF-8?q?=E6=88=B6=E5=9C=A8=E5=AE=8C=E6=88=90=E4=BB=BB=E5=8B=99=E6=99=82?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E6=91=98=E8=A6=81=EF=BC=8C=E4=B8=A6=E5=9C=A8?= =?UTF-8?q?=E4=BB=BB=E5=8B=99=E7=8B=80=E6=85=8B=E6=9B=B4=E6=96=B0=E6=99=82?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E7=94=9F=E6=88=90=E6=91=98=E8=A6=81=E3=80=82?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9B=B8=E9=97=9C=E6=A8=A1=E5=9E=8B=E5=92=8C?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E4=BB=A5=E6=94=AF=E6=8C=81=E6=AD=A4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E4=B8=A6=E5=9C=A8=E4=BB=BB=E5=8B=99=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=B8=AD=E9=A1=AF=E7=A4=BA=E6=91=98=E8=A6=81=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 6 +++ src/models/taskModel.ts | 8 +++ src/tools/taskTools.ts | 34 ++++++++++-- src/types/index.ts | 2 + src/utils/summaryExtractor.ts | 98 ++++++++++++++++------------------- 5 files changed, 91 insertions(+), 57 deletions(-) diff --git a/src/index.ts b/src/index.ts index df81818..f0ceaf3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -188,6 +188,12 @@ async function main() { taskId: z .string() .describe("待完成任務的唯一標識符,必須是系統中存在的有效任務ID"), + summary: z + .string() + .optional() + .describe( + "任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)" + ), }, async (args) => { return await completeTask(args); diff --git a/src/models/taskModel.ts b/src/models/taskModel.ts index 420224b..b7890c0 100644 --- a/src/models/taskModel.ts +++ b/src/models/taskModel.ts @@ -127,6 +127,14 @@ export async function updateTaskStatus( return await updateTask(taskId, updates); } +// 更新任務摘要 +export async function updateTaskSummary( + taskId: string, + summary: string +): Promise { + return await updateTask(taskId, { summary }); +} + // 批量創建或更新任務 export async function batchCreateOrUpdateTasks( taskDataList: Array<{ diff --git a/src/tools/taskTools.ts b/src/tools/taskTools.ts index 63285e5..94e7c27 100644 --- a/src/tools/taskTools.ts +++ b/src/tools/taskTools.ts @@ -6,10 +6,14 @@ import { canExecuteTask, batchCreateOrUpdateTasks, deleteTask as modelDeleteTask, + updateTaskSummary, } from "../models/taskModel.js"; import { TaskStatus, ConversationParticipant } from "../types/index.js"; import { addConversationEntry } from "../models/conversationLogModel.js"; -import { extractSummary } from "../utils/summaryExtractor.js"; +import { + extractSummary, + generateTaskSummary, +} from "../utils/summaryExtractor.js"; // 開始規劃工具 export const planTaskSchema = z.object({ @@ -377,6 +381,11 @@ export async function listTasks() { result += `- **完成時間:** ${task.completedAt.toISOString()}\n`; } + // 顯示摘要(如果有) + if (task.summary && task.status === TaskStatus.COMPLETED) { + result += `- **摘要:** ${task.summary}\n`; + } + result += "\n"; }); } @@ -641,10 +650,17 @@ export const completeTaskSchema = z.object({ .describe( "待標記為完成的任務唯一標識符,必須是狀態為「進行中」的有效任務ID" ), + summary: z + .string() + .optional() + .describe( + "任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)" + ), }); export async function completeTask({ taskId, + summary, }: z.infer) { const task = await getTaskById(taskId); @@ -696,14 +712,26 @@ export async function completeTask({ }; } - // 更新任務狀態為已完成 + // 處理摘要信息 + let taskSummary = summary; + if (!taskSummary) { + // 自動生成摘要 + taskSummary = generateTaskSummary(task.name, task.description); + } + + // 更新任務狀態為已完成,並添加摘要 await updateTaskStatus(taskId, TaskStatus.COMPLETED); + await updateTaskSummary(taskId, taskSummary); // 記錄任務完成 try { await addConversationEntry( ConversationParticipant.MCP, - `任務成功完成:${task.name} (ID: ${task.id})`, + `任務成功完成:${task.name} (ID: ${ + task.id + }),摘要:${taskSummary.substring(0, 100)}${ + taskSummary.length > 100 ? "..." : "" + }`, task.id, "任務完成" ); diff --git a/src/types/index.ts b/src/types/index.ts index eb38735..8c0c8cc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -22,6 +22,7 @@ export interface Task { createdAt: Date; // 任務創建的時間戳 updatedAt: Date; // 任務最後更新的時間戳 completedAt?: Date; // 任務完成的時間戳(僅適用於已完成的任務) + summary?: string; // 任務完成摘要,簡潔描述實施結果和重要決策(僅適用於已完成的任務) } // 規劃任務的參數:用於初始化任務規劃階段 @@ -69,6 +70,7 @@ export interface VerifyTaskArgs { // 完成任務的參數:用於標記任務為已完成狀態 export interface CompleteTaskArgs { taskId: string; // 待標記為完成的任務唯一標識符,必須是狀態為「進行中」的有效任務ID + summary?: string; // 任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成) } // 對話參與者類型:定義對話中的參與方身份 diff --git a/src/utils/summaryExtractor.ts b/src/utils/summaryExtractor.ts index f920d12..2fdb664 100644 --- a/src/utils/summaryExtractor.ts +++ b/src/utils/summaryExtractor.ts @@ -68,67 +68,57 @@ const KEYWORDS = { }; /** - * 從一段文本中提取關鍵信息作為摘要 - * - * @param text 完整的對話文本 - * @param maxLength 摘要的最大長度(字符數) + * 從文本中提取簡短摘要 + * @param text 要提取摘要的文本 + * @param maxLength 摘要的最大長度 * @returns 提取的摘要文本 */ -export function extractSummary(text: string, maxLength: number = 200): string { - // 防禦性檢查 - if (!text || text.trim().length === 0) { - return ""; +export function extractSummary(text: string, maxLength: number = 100): string { + if (!text) return ""; + + // 移除 Markdown 格式 + const plainText = text + .replace(/```[\s\S]*?```/g, "") // 移除代碼塊 + .replace(/#+\s/g, "") // 移除標題標記 + .replace(/\*\*/g, "") // 移除粗體標記 + .replace(/\*/g, "") // 移除斜體標記 + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // 將連結替換為文本 + .replace(/\n/g, " ") // 將換行符替換為空格 + .replace(/\s+/g, " ") // 將多個空格替換為單個空格 + .trim(); + + // 如果文本長度在允許範圍內,直接返回 + if (plainText.length <= maxLength) { + return plainText; } - // 將文本分割為句子 - const sentences = splitIntoSentences(text); + // 取前段並添加省略號 + return plainText.substring(0, maxLength - 3) + "..."; +} - // 如果只有一個句子且小於最大長度,直接返回 - if (sentences.length === 1 && sentences[0].length <= maxLength) { - return sentences[0]; +/** + * 生成任務完成摘要 + * @param taskName 任務名稱 + * @param taskDescription 任務描述 + * @param completionDetails 完成細節(可選) + * @returns 生成的任務摘要 + */ +export function generateTaskSummary( + taskName: string, + taskDescription: string, + completionDetails?: string +): string { + // 如果提供了完成細節,優先使用 + if (completionDetails) { + return extractSummary(completionDetails, 250); } - // 為每個句子評分 - const scoredSentences = sentences.map((sentence, index) => ({ - text: sentence, - score: scoreSentence(sentence, index, sentences.length), - index, - })); - - // 按評分排序 - scoredSentences.sort((a, b) => b.score - a.score); - - // 選擇評分最高的句子,直到達到最大長度 - let summary = ""; - let sentencesToInclude: { text: string; index: number }[] = []; - - for (const scored of scoredSentences) { - if ((summary + scored.text).length <= maxLength) { - sentencesToInclude.push({ - text: scored.text, - index: scored.index, - }); - } else { - // 如果還沒有選中任何句子,選擇第一個句子並截斷 - if (sentencesToInclude.length === 0) { - return scored.text.substring(0, maxLength); - } - break; - } - } - - // 按原文順序排列選中的句子 - sentencesToInclude.sort((a, b) => a.index - b.index); - - // 組合成最終摘要 - summary = sentencesToInclude.map((s) => s.text).join(" "); - - // 如果摘要仍然太長,進行截斷 - if (summary.length > maxLength) { - summary = summary.substring(0, maxLength - 3) + "..."; - } - - return summary; + // 否則從任務名稱和描述生成摘要 + const baseText = `${taskName}已成功完成。該任務涉及${extractSummary( + taskDescription, + 200 + )}`; + return extractSummary(baseText, 250); } /**