mirror of
https://github.com/cjo4m06/mcp-shrimp-task-manager.git
synced 2025-07-27 00:12:26 +08:00
新增任務摘要功能,允許用戶在完成任務時提供摘要,並在任務狀態更新時自動生成摘要。更新相關模型和工具以支持此功能,並在任務列表中顯示摘要信息。
This commit is contained in:
parent
74f700e3cd
commit
17937e7a9b
@ -188,6 +188,12 @@ async function main() {
|
||||
taskId: z
|
||||
.string()
|
||||
.describe("待完成任務的唯一標識符,必須是系統中存在的有效任務ID"),
|
||||
summary: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)"
|
||||
),
|
||||
},
|
||||
async (args) => {
|
||||
return await completeTask(args);
|
||||
|
@ -127,6 +127,14 @@ export async function updateTaskStatus(
|
||||
return await updateTask(taskId, updates);
|
||||
}
|
||||
|
||||
// 更新任務摘要
|
||||
export async function updateTaskSummary(
|
||||
taskId: string,
|
||||
summary: string
|
||||
): Promise<Task | null> {
|
||||
return await updateTask(taskId, { summary });
|
||||
}
|
||||
|
||||
// 批量創建或更新任務
|
||||
export async function batchCreateOrUpdateTasks(
|
||||
taskDataList: Array<{
|
||||
|
@ -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<typeof completeTaskSchema>) {
|
||||
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,
|
||||
"任務完成"
|
||||
);
|
||||
|
@ -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; // 任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)
|
||||
}
|
||||
|
||||
// 對話參與者類型:定義對話中的參與方身份
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user