新增任務摘要功能,允許用戶在完成任務時提供摘要,並在任務狀態更新時自動生成摘要。更新相關模型和工具以支持此功能,並在任務列表中顯示摘要信息。

This commit is contained in:
siage 2025-04-11 16:57:05 +08:00
parent 74f700e3cd
commit 17937e7a9b
5 changed files with 91 additions and 57 deletions

View File

@ -188,6 +188,12 @@ async function main() {
taskId: z
.string()
.describe("待完成任務的唯一標識符必須是系統中存在的有效任務ID"),
summary: z
.string()
.optional()
.describe(
"任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)"
),
},
async (args) => {
return await completeTask(args);

View File

@ -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<{

View File

@ -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,
"任務完成"
);

View File

@ -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; // 任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)
}
// 對話參與者類型:定義對話中的參與方身份

View File

@ -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);
// 如果只有一個句子且小於最大長度,直接返回
if (sentences.length === 1 && sentences[0].length <= maxLength) {
return sentences[0];
// 取前段並添加省略號
return plainText.substring(0, maxLength - 3) + "...";
}
// 為每個句子評分
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;
}
/**
*
* @param taskName
* @param taskDescription
* @param completionDetails
* @returns
*/
export function generateTaskSummary(
taskName: string,
taskDescription: string,
completionDetails?: string
): string {
// 如果提供了完成細節,優先使用
if (completionDetails) {
return extractSummary(completionDetails, 250);
}
// 按原文順序排列選中的句子
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);
}
/**