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

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

View File

@ -127,6 +127,14 @@ export async function updateTaskStatus(
return await updateTask(taskId, updates); 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( export async function batchCreateOrUpdateTasks(
taskDataList: Array<{ taskDataList: Array<{

View File

@ -6,10 +6,14 @@ import {
canExecuteTask, canExecuteTask,
batchCreateOrUpdateTasks, batchCreateOrUpdateTasks,
deleteTask as modelDeleteTask, deleteTask as modelDeleteTask,
updateTaskSummary,
} from "../models/taskModel.js"; } from "../models/taskModel.js";
import { TaskStatus, ConversationParticipant } from "../types/index.js"; import { TaskStatus, ConversationParticipant } from "../types/index.js";
import { addConversationEntry } from "../models/conversationLogModel.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({ export const planTaskSchema = z.object({
@ -377,6 +381,11 @@ export async function listTasks() {
result += `- **完成時間:** ${task.completedAt.toISOString()}\n`; result += `- **完成時間:** ${task.completedAt.toISOString()}\n`;
} }
// 顯示摘要(如果有)
if (task.summary && task.status === TaskStatus.COMPLETED) {
result += `- **摘要:** ${task.summary}\n`;
}
result += "\n"; result += "\n";
}); });
} }
@ -641,10 +650,17 @@ export const completeTaskSchema = z.object({
.describe( .describe(
"待標記為完成的任務唯一標識符必須是狀態為「進行中」的有效任務ID" "待標記為完成的任務唯一標識符必須是狀態為「進行中」的有效任務ID"
), ),
summary: z
.string()
.optional()
.describe(
"任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)"
),
}); });
export async function completeTask({ export async function completeTask({
taskId, taskId,
summary,
}: z.infer<typeof completeTaskSchema>) { }: z.infer<typeof completeTaskSchema>) {
const task = await getTaskById(taskId); 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 updateTaskStatus(taskId, TaskStatus.COMPLETED);
await updateTaskSummary(taskId, taskSummary);
// 記錄任務完成 // 記錄任務完成
try { try {
await addConversationEntry( await addConversationEntry(
ConversationParticipant.MCP, ConversationParticipant.MCP,
`任務成功完成:${task.name} (ID: ${task.id})`, `任務成功完成:${task.name} (ID: ${
task.id
})${taskSummary.substring(0, 100)}${
taskSummary.length > 100 ? "..." : ""
}`,
task.id, task.id,
"任務完成" "任務完成"
); );

View File

@ -22,6 +22,7 @@ export interface Task {
createdAt: Date; // 任務創建的時間戳 createdAt: Date; // 任務創建的時間戳
updatedAt: Date; // 任務最後更新的時間戳 updatedAt: Date; // 任務最後更新的時間戳
completedAt?: Date; // 任務完成的時間戳(僅適用於已完成的任務) completedAt?: Date; // 任務完成的時間戳(僅適用於已完成的任務)
summary?: string; // 任務完成摘要,簡潔描述實施結果和重要決策(僅適用於已完成的任務)
} }
// 規劃任務的參數:用於初始化任務規劃階段 // 規劃任務的參數:用於初始化任務規劃階段
@ -69,6 +70,7 @@ export interface VerifyTaskArgs {
// 完成任務的參數:用於標記任務為已完成狀態 // 完成任務的參數:用於標記任務為已完成狀態
export interface CompleteTaskArgs { export interface CompleteTaskArgs {
taskId: string; // 待標記為完成的任務唯一標識符必須是狀態為「進行中」的有效任務ID taskId: string; // 待標記為完成的任務唯一標識符必須是狀態為「進行中」的有效任務ID
summary?: string; // 任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)
} }
// 對話參與者類型:定義對話中的參與方身份 // 對話參與者類型:定義對話中的參與方身份

View File

@ -68,67 +68,57 @@ const KEYWORDS = {
}; };
/** /**
* *
* * @param text
* @param text * @param maxLength
* @param maxLength
* @returns * @returns
*/ */
export function extractSummary(text: string, maxLength: number = 200): string { export function extractSummary(text: string, maxLength: number = 100): string {
// 防禦性檢查 if (!text) return "";
if (!text || text.trim().length === 0) {
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) => ({ const baseText = `${taskName}已成功完成。該任務涉及${extractSummary(
text: sentence, taskDescription,
score: scoreSentence(sentence, index, sentences.length), 200
index, )}`;
})); return extractSummary(baseText, 250);
// 按評分排序
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;
} }
/** /**