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
c786d4ef6b
commit
8ac0ef269b
@ -27,12 +27,12 @@
|
||||
|
|
||||
+------------------+------------------+
|
||||
| | |
|
||||
+---------v--------+ +-------v---------+ +------v-----------+
|
||||
| | | | | |
|
||||
| 工具實現層 | | 模型邏輯層 | | 工具實現層 |
|
||||
| (taskTools.ts) | | (taskModel.ts) | | (logTools.ts) |
|
||||
| | | | | |
|
||||
+------------------+ +-------+---------+ +------------------+
|
||||
+---------v--------+ +-------v---------+
|
||||
| | | |
|
||||
| 工具實現層 | | 模型邏輯層 |
|
||||
| (taskTools.ts) | | (taskModel.ts) |
|
||||
| | | |
|
||||
+------------------+ +-------+---------+
|
||||
|
|
||||
|
|
||||
+---------v--------+
|
||||
@ -63,8 +63,6 @@
|
||||
- 註冊 `clear_all_tasks` 工具函數,用於清除所有未完成的任務
|
||||
- 註冊 `update_task` 工具函數,用於更新任務內容
|
||||
- 註冊 `update_task_files` 工具函數,用於更新任務相關文件列表
|
||||
- 註冊 `list_conversation_log` 工具函數,用於查詢系統對話日誌
|
||||
- 註冊 `clear_conversation_log` 工具函數,用於清除所有對話日誌記錄
|
||||
|
||||
### 2.2 工具實現層 (tools/\*.ts)
|
||||
|
||||
@ -73,7 +71,6 @@
|
||||
**核心文件**:
|
||||
|
||||
- `taskTools.ts`: 任務管理相關工具
|
||||
- `logTools.ts`: 日誌管理相關工具
|
||||
- `fileLoader.ts`: 任務相關文件的摘要生成工具
|
||||
|
||||
**主要功能**:
|
||||
@ -90,8 +87,6 @@
|
||||
- 實現 `clearAllTasks` 工具函數,刪除所有未完成的任務
|
||||
- 實現 `updateTask` 工具函數,更新任務內容
|
||||
- 實現 `updateTaskRelatedFiles` 工具函數,更新任務相關文件列表
|
||||
- 實現 `listConversationLog` 工具函數,查詢系統對話日誌
|
||||
- 實現 `clearConversationLog` 工具函數,清除所有對話日誌記錄
|
||||
|
||||
### 2.3 模型邏輯層 (models/\*.ts)
|
||||
|
||||
@ -100,7 +95,6 @@
|
||||
**核心文件**:
|
||||
|
||||
- `taskModel.ts`: 任務數據模型和操作
|
||||
- `conversationLogModel.ts`: 對話日誌數據模型和操作
|
||||
|
||||
**主要功能**:
|
||||
|
||||
@ -112,7 +106,6 @@
|
||||
- 在 `taskModel.ts` 中實現 `assessTaskComplexity` 函數,評估任務複雜度
|
||||
- 在 `taskModel.ts` 中實現 `loadTaskById` 函數,載入特定任務
|
||||
- 在 `taskModel.ts` 中實現 `completeTask` 函數,標記任務為完成狀態
|
||||
- 在 `conversationLogModel.ts` 中實現對話日誌的記錄、查詢和清除功能
|
||||
|
||||
### 2.4 數據模型定義 (types/index.ts)
|
||||
|
||||
@ -125,8 +118,6 @@
|
||||
- `RelatedFileType` 枚舉:定義文件關聯類型,如待修改、參考資料、輸出結果、依賴文件和其他
|
||||
- `RelatedFile` 接口:定義任務相關文件結構
|
||||
- `Task` 接口:定義任務的完整數據結構
|
||||
- `ConversationParticipant` 枚舉:定義對話參與者類型,如 MCP 和 LLM
|
||||
- `ConversationEntry` 接口:定義對話日誌條目
|
||||
- `TaskComplexityLevel` 枚舉:定義任務複雜度級別,如低複雜度、中等複雜度、高複雜度和極高複雜度
|
||||
- `TaskComplexityThresholds` 常量:定義複雜度評估閾值
|
||||
- `TaskComplexityAssessment` 接口:記錄複雜度評估結果
|
||||
@ -145,18 +136,16 @@
|
||||
- 實現 `extractSummary` 函數,從文本中提取簡短摘要
|
||||
- 實現 `generateTaskSummary` 函數,基於任務名稱和描述生成任務完成摘要
|
||||
- 實現 `extractTitle` 函數,從內容中提取適合作為標題的文本
|
||||
- 實現 `extractSummaryFromConversation` 函數,從對話記錄中提取摘要信息
|
||||
- 實現 `loadTaskRelatedFiles` 函數,生成任務相關文件的內容摘要
|
||||
- 實現 `generateFileInfo` 函數,生成文件基本資訊摘要
|
||||
|
||||
### 2.6 數據存儲層
|
||||
|
||||
使用 JSON 文件作為數據存儲,保存任務信息和對話日誌。
|
||||
使用 JSON 文件作為數據存儲,保存任務信息。
|
||||
|
||||
**核心文件**:
|
||||
|
||||
- `data/tasks.json`: 存儲所有任務數據
|
||||
- `data/conversation_log.json`: 存儲對話日誌數據
|
||||
- `data/backups/`: 存儲任務數據備份
|
||||
|
||||
## 3. 數據流
|
||||
@ -179,9 +168,6 @@ LLM 調用 delete_task
|
||||
執行刪除操作
|
||||
|
|
||||
v
|
||||
記錄操作到日誌
|
||||
|
|
||||
v
|
||||
返回結果給 LLM
|
||||
```
|
||||
|
||||
@ -245,9 +231,6 @@ LLM 調用 complete_task
|
||||
保存摘要到任務記錄
|
||||
|
|
||||
v
|
||||
記錄操作到日誌
|
||||
|
|
||||
v
|
||||
返回結果給 LLM
|
||||
```
|
||||
|
||||
@ -269,18 +252,12 @@ LLM 調用 clear_all_tasks
|
||||
篩選出未完成的任務
|
||||
|
|
||||
v
|
||||
記錄操作開始到日誌
|
||||
|
|
||||
v
|
||||
執行批量刪除
|
||||
|
|
||||
v
|
||||
更新數據文件
|
||||
|
|
||||
v
|
||||
記錄操作結果到日誌
|
||||
|
|
||||
v
|
||||
返回清除結果給 LLM
|
||||
```
|
||||
|
||||
@ -305,9 +282,6 @@ LLM 調用 update_task
|
||||
執行更新操作
|
||||
|
|
||||
v
|
||||
記錄操作到日誌
|
||||
|
|
||||
v
|
||||
返回更新結果給 LLM
|
||||
```
|
||||
|
||||
@ -341,9 +315,6 @@ LLM 調用 update_task_files
|
||||
更新任務的相關文件列表
|
||||
|
|
||||
v
|
||||
記錄操作到日誌
|
||||
|
|
||||
v
|
||||
返回更新結果給 LLM
|
||||
```
|
||||
|
||||
@ -368,30 +339,6 @@ LLM 調用 update_task_files
|
||||
返回文件摘要結果
|
||||
```
|
||||
|
||||
### 3.8 對話日誌查詢流程
|
||||
|
||||
```
|
||||
LLM 調用 list_conversation_log
|
||||
|
|
||||
v
|
||||
處理過濾參數
|
||||
| |
|
||||
| +---> 任務ID過濾(如有提供)
|
||||
| |
|
||||
| +---> 日期範圍過濾(如有提供)
|
||||
| |
|
||||
| +---> 應用分頁參數(limit和offset)
|
||||
|
|
||||
v
|
||||
查詢符合條件的日誌條目
|
||||
|
|
||||
v
|
||||
格式化日誌條目列表
|
||||
|
|
||||
v
|
||||
返回查詢結果給 LLM
|
||||
```
|
||||
|
||||
## 4. 系統交互圖
|
||||
|
||||
### 4.1 LLM 與任務管理器交互
|
||||
@ -573,36 +520,6 @@ LLM 調用 list_conversation_log
|
||||
+-------+-------------------------+------+
|
||||
```
|
||||
|
||||
### 5.12 日誌查詢功能
|
||||
|
||||
```
|
||||
+------------------------+ +-------------------+ +---------------------+
|
||||
| | | | | |
|
||||
| listConversationLogSchema|---->| listConversationLog|---->| getConversationLogs |
|
||||
| | | (logTools.ts) | | (logModel.ts) |
|
||||
+------------------------+ +---------+---------+ +---------+-----------+
|
||||
| |
|
||||
v v
|
||||
+-------+------------------------+-----+
|
||||
| 過濾參數處理和分頁控制 |
|
||||
+-------+------------------------+-----+
|
||||
```
|
||||
|
||||
### 5.13 清除日誌功能
|
||||
|
||||
```
|
||||
+-------------------------+ +--------------------+ +------------------------+
|
||||
| | | | | |
|
||||
| clearConversationLogSchema|---->| clearConversationLog|---->| clearConversationLogs |
|
||||
| | | (logTools.ts) | | (logModel.ts) |
|
||||
+-------------------------+ +---------+----------+ +----------+-------------+
|
||||
| |
|
||||
v v
|
||||
+-------+-------------------------+-----+
|
||||
| 確認參數檢查和刪除操作 |
|
||||
+-------+-------------------------+-----+
|
||||
```
|
||||
|
||||
## 6. 擴展性考慮
|
||||
|
||||
### 6.1 新功能擴展方式
|
||||
@ -622,7 +539,7 @@ LLM 調用 list_conversation_log
|
||||
- **複雜度評估擴展**:可在 `assessTaskComplexity` 中添加更多評估指標
|
||||
- **摘要生成擴展**:可增強 `summaryExtractor.ts` 中的算法
|
||||
- **文件處理擴展**:可在 `fileLoader.ts` 中支持更多文件類型的摘要格式化
|
||||
- **過濾條件擴展**:可在日誌查詢中添加更多過濾條件選項
|
||||
- **過濾條件擴展**:可在任務查詢中添加更多過濾條件選項
|
||||
|
||||
## 7. 系統限制與未來改進
|
||||
|
||||
@ -646,7 +563,7 @@ LLM 調用 list_conversation_log
|
||||
|
||||
## 8. 結論
|
||||
|
||||
蝦米任務管理器採用模塊化、分層設計,使系統具有良好的可維護性和擴展性。通過 14 個核心工具函數和完善的數據模型,系統能夠有效地管理複雜項目的任務流程,特別在需要長期上下文記憶的場景中表現出色。
|
||||
蝦米任務管理器採用模塊化、分層設計,使系統具有良好的可維護性和擴展性。通過 12 個核心工具函數和完善的數據模型,系統能夠有效地管理複雜項目的任務流程,特別在需要長期上下文記憶的場景中表現出色。
|
||||
|
||||
系統的設計重點在於提供清晰的任務管理流程,同時增強 LLM 在執行任務時的上下文記憶能力,通過精確的文件關聯和智能上下文載入,有效解決了 LLM 在處理長期複雜任務時的記憶限制問題。
|
||||
|
||||
|
@ -45,20 +45,6 @@
|
||||
- `summary?: string` - 任務完成摘要,簡潔描述實施結果和重要決策(僅適用於已完成的任務)
|
||||
- `relatedFiles?: RelatedFile[]` - 與任務相關的文件列表(選填)
|
||||
|
||||
### 對話參與者類型枚舉 (ConversationParticipant)
|
||||
|
||||
- `MCP = "MCP"` - 系統方(MCP)
|
||||
- `LLM = "LLM"` - 模型方(LLM)
|
||||
|
||||
### 對話日誌條目 (ConversationEntry)
|
||||
|
||||
- `id: string` - 日誌條目的唯一標識符
|
||||
- `timestamp: Date` - 記錄的時間戳
|
||||
- `participant: ConversationParticipant` - 對話參與者(MCP 或 LLM)
|
||||
- `summary: string` - 消息摘要,只記錄關鍵信息點而非完整對話
|
||||
- `relatedTaskId?: string` - 關聯的任務 ID(選填),用於將對話與特定任務關聯
|
||||
- `context?: string` - 額外的上下文信息(選填),提供對話發生的背景
|
||||
|
||||
### 任務複雜度級別枚舉 (TaskComplexityLevel)
|
||||
|
||||
- `LOW = "低複雜度"` - 簡單且直接的任務,通常不需要特殊處理
|
||||
@ -251,34 +237,6 @@
|
||||
|
||||
- 返回一個包含文件更新結果的響應,包括成功或失敗的訊息
|
||||
|
||||
### 13. list_conversation_log
|
||||
|
||||
**描述**:查詢系統對話日誌,支持按任務 ID 或時間範圍過濾,提供分頁功能處理大量記錄
|
||||
|
||||
**參數**:
|
||||
|
||||
- `taskId?: string` (選填) - 按任務 ID 過濾對話記錄
|
||||
- `startDate?: string` (選填) - 起始日期過濾,格式為 ISO 日期字串
|
||||
- `endDate?: string` (選填) - 結束日期過濾,格式為 ISO 日期字串
|
||||
- `limit?: number` (選填,預設 20) - 返回結果數量限制,最大 100
|
||||
- `offset?: number` (選填,預設 0) - 分頁偏移量
|
||||
|
||||
**返回值**:
|
||||
|
||||
- 返回一個包含對話日誌查詢結果的響應,包括日誌條目列表及分頁信息
|
||||
|
||||
### 14. clear_conversation_log
|
||||
|
||||
**描述**:清除所有對話日誌記錄,需要明確確認以避免意外操作
|
||||
|
||||
**參數**:
|
||||
|
||||
- `confirm: boolean` (必填) - 確認刪除所有日誌記錄(此操作不可逆)
|
||||
|
||||
**返回值**:
|
||||
|
||||
- 返回一個包含清除操作結果的響應,包括成功或失敗的訊息
|
||||
|
||||
## 工具函數重要細節
|
||||
|
||||
### 依賴關係 (dependencies) 處理
|
||||
@ -300,13 +258,6 @@
|
||||
- 文件按類型優先級排序:待修改 > 參考資料 > 依賴文件 > 輸出結果 > 其他
|
||||
- 支持指定代碼區塊行號範圍,便於精確定位關鍵實現
|
||||
|
||||
### 日誌管理
|
||||
|
||||
- 系統會自動記錄重要操作到對話日誌
|
||||
- 長文本會自動使用 `extractSummary` 函數提取摘要,避免日誌過於冗長
|
||||
- 日誌條目數量超過閾值時會進行自動輪換和歸檔
|
||||
- 日誌查詢支持多種過濾條件和分頁功能
|
||||
|
||||
## 實用工具函數
|
||||
|
||||
### 摘要提取 (summaryExtractor.ts)
|
||||
@ -314,7 +265,6 @@
|
||||
- `extractSummary` - 從文本中提取簡短摘要,自動處理 Markdown 格式
|
||||
- `generateTaskSummary` - 基於任務名稱和描述生成任務完成摘要
|
||||
- `extractTitle` - 從內容中提取適合作為標題的文本
|
||||
- `extractSummaryFromConversation` - 從對話記錄中提取摘要信息
|
||||
|
||||
### 文件加載 (fileLoader.ts)
|
||||
|
||||
|
60
src/index.ts
60
src/index.ts
@ -30,14 +30,6 @@ import {
|
||||
updateTaskRelatedFilesSchema,
|
||||
} from "./tools/taskTools.js";
|
||||
|
||||
// 導入日誌工具函數
|
||||
import {
|
||||
listConversationLog,
|
||||
listConversationLogSchema,
|
||||
clearConversationLog,
|
||||
clearConversationLogSchema,
|
||||
} from "./tools/logTools.js";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log("啟動蝦米任務管理器服務...");
|
||||
@ -133,12 +125,12 @@ async function main() {
|
||||
|
||||
server.tool(
|
||||
"split_tasks",
|
||||
"將複雜任務分解為獨立且可追蹤的子任務,建立明確的依賴關係和優先順序。支援四種任務更新模式:追加(append)、覆蓋(overwrite)、選擇性更新(selective)和清除所有任務(clearAllTasks),其中覆蓋模式只會刪除未完成的任務並保留已完成任務,選擇性更新模式可根據任務名稱智能匹配更新現有任務,同時保留其他任務,如果你需要規劃全新的任務請使用清除所有任務模式會清除所有任務並創建備份。",
|
||||
"將複雜任務分解為獨立且可追蹤的子任務,建立明確的依賴關係和優先順序。支援四種任務更新模式:追加(append)、覆蓋(overwrite)、選擇性更新(selective)和清除所有任務(clearAllTasks),其中覆蓋模式只會刪除未完成的任務並保留已完成任務,選擇性更新模式可根據任務名稱智能匹配更新現有任務,同時保留其他任務,如果你需要規劃全新的任務請使用清除所有任務模式會清除所有任務並創建備份。請優先使用清除所有任務模式,只有用戶要求變更或修改計畫內容才使用其他模式",
|
||||
{
|
||||
updateMode: z
|
||||
.enum(["append", "overwrite", "selective", "clearAllTasks"])
|
||||
.describe(
|
||||
"任務更新模式選擇:'append'(保留所有現有任務並添加新任務)、'overwrite'(清除所有未完成任務並完全替換,保留已完成任務)、'selective'(智能更新:根據任務名稱匹配更新現有任務,保留不在列表中的任務,推薦用於任務微調)、'clearAllTasks'(清除所有任務並創建備份)"
|
||||
"任務更新模式選擇:'append'(保留所有現有任務並添加新任務)、'overwrite'(清除所有未完成任務並完全替換,保留已完成任務)、'selective'(智能更新:根據任務名稱匹配更新現有任務,保留不在列表中的任務,推薦用於任務微調)、'clearAllTasks'(清除所有任務並創建備份)。\n預設為'clearAllTasks'模式,只有用戶要求變更或修改計畫內容才使用其他模式"
|
||||
),
|
||||
tasks: z
|
||||
.array(
|
||||
@ -341,54 +333,6 @@ async function main() {
|
||||
}
|
||||
);
|
||||
|
||||
// 註冊日誌查詢工具
|
||||
server.tool(
|
||||
"list_conversation_log",
|
||||
"查詢系統對話日誌,支持按任務 ID 或時間範圍過濾,提供分頁功能處理大量記錄",
|
||||
{
|
||||
taskId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("按任務 ID 過濾對話記錄(選填)"),
|
||||
startDate: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("起始日期過濾,格式為 ISO 日期字串(選填)"),
|
||||
endDate: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("結束日期過濾,格式為 ISO 日期字串(選填)"),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.max(100)
|
||||
.default(20)
|
||||
.describe("返回結果數量限制,最大 100(預設:20)"),
|
||||
offset: z
|
||||
.number()
|
||||
.int()
|
||||
.nonnegative()
|
||||
.default(0)
|
||||
.describe("分頁偏移量(預設:0)"),
|
||||
},
|
||||
async (args) => {
|
||||
return await listConversationLog(args);
|
||||
}
|
||||
);
|
||||
|
||||
// 註冊日誌清除工具
|
||||
server.tool(
|
||||
"clear_conversation_log",
|
||||
"清除所有對話日誌記錄,需要明確確認以避免意外操作",
|
||||
{
|
||||
confirm: z.boolean().describe("確認刪除所有日誌記錄(此操作不可逆)"),
|
||||
},
|
||||
async (args) => {
|
||||
return await clearConversationLog(args);
|
||||
}
|
||||
);
|
||||
|
||||
// 建立連接
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
@ -1,462 +0,0 @@
|
||||
import { ConversationEntry, ConversationParticipant } from "../types/index.js";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { fileURLToPath } from "url";
|
||||
import { extractSummary } from "../utils/summaryExtractor.js";
|
||||
|
||||
// 確保獲取專案資料夾路徑
|
||||
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 CONVERSATION_LOG_FILE = path.join(DATA_DIR, "conversation_log.json");
|
||||
|
||||
// 配置參數
|
||||
const MAX_LOG_ENTRIES = 10000; // 單個日誌文件最大條目數
|
||||
const MAX_ARCHIVED_LOGS = 5; // 最大歸檔日誌文件數
|
||||
const LOG_ENTRY_TRIM_THRESHOLD = 8000; // 當日誌超過該條目數時進行精簡
|
||||
|
||||
// 確保數據目錄存在
|
||||
async function ensureDataDir() {
|
||||
try {
|
||||
await fs.access(DATA_DIR);
|
||||
} catch (error) {
|
||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.access(CONVERSATION_LOG_FILE);
|
||||
} catch (error) {
|
||||
await fs.writeFile(CONVERSATION_LOG_FILE, JSON.stringify({ entries: [] }));
|
||||
}
|
||||
}
|
||||
|
||||
// 讀取所有對話日誌
|
||||
async function readConversationLog(): Promise<ConversationEntry[]> {
|
||||
await ensureDataDir();
|
||||
const data = await fs.readFile(CONVERSATION_LOG_FILE, "utf-8");
|
||||
const entries = JSON.parse(data).entries;
|
||||
|
||||
// 將日期字串轉換回 Date 物件
|
||||
return entries.map((entry: any) => ({
|
||||
...entry,
|
||||
timestamp: entry.timestamp ? new Date(entry.timestamp) : new Date(),
|
||||
}));
|
||||
}
|
||||
|
||||
// 寫入所有對話日誌
|
||||
async function writeConversationLog(
|
||||
entries: ConversationEntry[]
|
||||
): Promise<void> {
|
||||
await ensureDataDir();
|
||||
await fs.writeFile(
|
||||
CONVERSATION_LOG_FILE,
|
||||
JSON.stringify({ entries }, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
// 獲取所有對話日誌
|
||||
export async function getAllConversationEntries(): Promise<
|
||||
ConversationEntry[]
|
||||
> {
|
||||
return await readConversationLog();
|
||||
}
|
||||
|
||||
// 根據 ID 獲取對話日誌條目
|
||||
export async function getConversationEntryById(
|
||||
entryId: string
|
||||
): Promise<ConversationEntry | null> {
|
||||
const entries = await readConversationLog();
|
||||
return entries.find((entry) => entry.id === entryId) || null;
|
||||
}
|
||||
|
||||
// 添加新的對話日誌條目
|
||||
export async function addConversationEntry(
|
||||
participant: ConversationParticipant,
|
||||
summary: string,
|
||||
relatedTaskId?: string,
|
||||
context?: string
|
||||
): Promise<ConversationEntry> {
|
||||
const entries = await readConversationLog();
|
||||
|
||||
// 如果日誌條目超過閾值,進行日誌輪換
|
||||
if (entries.length >= MAX_LOG_ENTRIES) {
|
||||
await rotateLogFile();
|
||||
return addConversationEntry(participant, summary, relatedTaskId, context);
|
||||
}
|
||||
|
||||
// 如果日誌條目超過精簡閾值,進行精簡處理
|
||||
if (entries.length >= LOG_ENTRY_TRIM_THRESHOLD) {
|
||||
await trimLogEntries();
|
||||
}
|
||||
|
||||
// 摘要太長時自動縮減
|
||||
const processedSummary =
|
||||
summary.length > 500 ? extractSummary(summary, 300) : summary;
|
||||
|
||||
const newEntry: ConversationEntry = {
|
||||
id: uuidv4(),
|
||||
timestamp: new Date(),
|
||||
participant,
|
||||
summary: processedSummary,
|
||||
relatedTaskId,
|
||||
context,
|
||||
};
|
||||
|
||||
entries.push(newEntry);
|
||||
await writeConversationLog(entries);
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
// 更新對話日誌條目
|
||||
export async function updateConversationEntry(
|
||||
entryId: string,
|
||||
updates: Partial<ConversationEntry>
|
||||
): Promise<ConversationEntry | null> {
|
||||
const entries = await readConversationLog();
|
||||
const entryIndex = entries.findIndex((entry) => entry.id === entryId);
|
||||
|
||||
if (entryIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
entries[entryIndex] = {
|
||||
...entries[entryIndex],
|
||||
...updates,
|
||||
};
|
||||
|
||||
await writeConversationLog(entries);
|
||||
|
||||
return entries[entryIndex];
|
||||
}
|
||||
|
||||
// 獲取特定任務的對話日誌條目
|
||||
export async function getConversationEntriesByTaskId(
|
||||
taskId: string
|
||||
): Promise<ConversationEntry[]> {
|
||||
const entries = await readConversationLog();
|
||||
return entries.filter((entry) => entry.relatedTaskId === taskId);
|
||||
}
|
||||
|
||||
// 刪除對話日誌條目
|
||||
export async function deleteConversationEntry(
|
||||
entryId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const entries = await readConversationLog();
|
||||
const initialLength = entries.length;
|
||||
|
||||
const filteredEntries = entries.filter((entry) => entry.id !== entryId);
|
||||
|
||||
if (filteredEntries.length === initialLength) {
|
||||
return {
|
||||
success: false,
|
||||
message: `找不到 ID 為 ${entryId} 的對話日誌條目`,
|
||||
};
|
||||
}
|
||||
|
||||
await writeConversationLog(filteredEntries);
|
||||
return { success: true, message: "對話日誌條目已成功刪除" };
|
||||
}
|
||||
|
||||
// 根據時間範圍獲取對話日誌條目
|
||||
export async function getConversationEntriesByDateRange(
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
): Promise<ConversationEntry[]> {
|
||||
const entries = await readConversationLog();
|
||||
|
||||
return entries.filter((entry) => {
|
||||
const entryTime = entry.timestamp.getTime();
|
||||
return entryTime >= startDate.getTime() && entryTime <= endDate.getTime();
|
||||
});
|
||||
}
|
||||
|
||||
// 清除所有對話日誌
|
||||
export async function clearAllConversationEntries(): Promise<void> {
|
||||
await writeConversationLog([]);
|
||||
}
|
||||
|
||||
// 獲取分頁的對話日誌條目
|
||||
export async function getPaginatedConversationEntries(
|
||||
limit: number = 10,
|
||||
offset: number = 0,
|
||||
taskId?: string,
|
||||
startDate?: Date,
|
||||
endDate?: Date
|
||||
): Promise<{ entries: ConversationEntry[]; total: number }> {
|
||||
let entries = await readConversationLog();
|
||||
|
||||
// 根據任務 ID 過濾
|
||||
if (taskId) {
|
||||
entries = entries.filter((entry) => entry.relatedTaskId === taskId);
|
||||
}
|
||||
|
||||
// 根據日期範圍過濾
|
||||
if (startDate && endDate) {
|
||||
entries = entries.filter((entry) => {
|
||||
const entryTime = entry.timestamp.getTime();
|
||||
return entryTime >= startDate.getTime() && entryTime <= endDate.getTime();
|
||||
});
|
||||
} else if (startDate) {
|
||||
entries = entries.filter(
|
||||
(entry) => entry.timestamp.getTime() >= startDate.getTime()
|
||||
);
|
||||
} else if (endDate) {
|
||||
entries = entries.filter(
|
||||
(entry) => entry.timestamp.getTime() <= endDate.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
// 按時間排序(降序,最新的在前)
|
||||
entries.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
||||
|
||||
const total = entries.length;
|
||||
const paginatedEntries = entries.slice(offset, offset + limit);
|
||||
|
||||
return { entries: paginatedEntries, total };
|
||||
}
|
||||
|
||||
/**
|
||||
* 日誌文件輪換功能 - 將當前日誌歸檔並創建新的空日誌
|
||||
*/
|
||||
async function rotateLogFile(): Promise<void> {
|
||||
console.log("執行日誌輪換操作...");
|
||||
|
||||
try {
|
||||
// 確保目錄存在
|
||||
await ensureDataDir();
|
||||
|
||||
// 檢查當前日誌文件是否存在
|
||||
try {
|
||||
await fs.access(CONVERSATION_LOG_FILE);
|
||||
} catch (error) {
|
||||
// 如果不存在,創建空文件並返回
|
||||
await fs.writeFile(
|
||||
CONVERSATION_LOG_FILE,
|
||||
JSON.stringify({ entries: [] })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成歸檔文件名 (conversation_log_時間戳.json)
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/:/g, "-")
|
||||
.replace(/\..+/, "");
|
||||
const archiveFileName = `conversation_log_${timestamp}.json`;
|
||||
const archiveFilePath = path.join(DATA_DIR, archiveFileName);
|
||||
|
||||
// 將當前日誌複製到歸檔文件
|
||||
await fs.copyFile(CONVERSATION_LOG_FILE, archiveFilePath);
|
||||
|
||||
// 創建新的空日誌文件
|
||||
await fs.writeFile(CONVERSATION_LOG_FILE, JSON.stringify({ entries: [] }));
|
||||
|
||||
// 清理過多的歸檔文件
|
||||
await cleanupArchivedLogs();
|
||||
|
||||
console.log(`日誌輪換完成,歸檔文件: ${archiveFileName}`);
|
||||
} catch (error) {
|
||||
console.error("日誌輪換過程中發生錯誤:", error);
|
||||
// 即使輪換失敗,也要確保主日誌文件存在
|
||||
try {
|
||||
await fs.writeFile(
|
||||
CONVERSATION_LOG_FILE,
|
||||
JSON.stringify({ entries: [] })
|
||||
);
|
||||
} catch (innerError) {
|
||||
console.error("創建新日誌文件失敗:", innerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理過舊的歸檔日誌文件
|
||||
*/
|
||||
async function cleanupArchivedLogs(): Promise<void> {
|
||||
try {
|
||||
// 讀取數據目錄中的所有文件
|
||||
const files = await fs.readdir(DATA_DIR);
|
||||
|
||||
// 過濾出歸檔日誌文件
|
||||
const archivedLogs = files
|
||||
.filter(
|
||||
(file) => file.startsWith("conversation_log_") && file.endsWith(".json")
|
||||
)
|
||||
.map((file) => ({
|
||||
name: file,
|
||||
path: path.join(DATA_DIR, file),
|
||||
// 從文件名中提取時間戳
|
||||
timestamp: file.replace("conversation_log_", "").replace(".json", ""),
|
||||
}))
|
||||
// 按時間戳降序排序(最新的在前)
|
||||
.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
||||
|
||||
// 如果歸檔日誌文件數量超過最大保留數量,刪除最舊的
|
||||
if (archivedLogs.length > MAX_ARCHIVED_LOGS) {
|
||||
const logsToDelete = archivedLogs.slice(MAX_ARCHIVED_LOGS);
|
||||
for (const log of logsToDelete) {
|
||||
try {
|
||||
await fs.unlink(log.path);
|
||||
console.log(`已刪除過舊的日誌歸檔: ${log.name}`);
|
||||
} catch (error) {
|
||||
console.error(`刪除日誌歸檔 ${log.name} 失敗:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("清理歸檔日誌時發生錯誤:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 對日誌條目進行精簡,移除不重要的舊條目
|
||||
*/
|
||||
async function trimLogEntries(): Promise<void> {
|
||||
try {
|
||||
const entries = await readConversationLog();
|
||||
|
||||
// 如果條目數量未達到精簡閾值,不進行處理
|
||||
if (entries.length < LOG_ENTRY_TRIM_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`日誌條目數量(${entries.length})超過精簡閾值(${LOG_ENTRY_TRIM_THRESHOLD}),進行精簡處理...`
|
||||
);
|
||||
|
||||
// 策略:保留最新的75%條目,優先移除一般日誌,保留錯誤和重要操作
|
||||
const entriesToKeep = Math.floor(entries.length * 0.75);
|
||||
|
||||
// 先按重要性對條目進行分類
|
||||
const errorEntries = entries.filter(
|
||||
(entry) =>
|
||||
entry.context?.includes("錯誤") ||
|
||||
entry.context?.includes("失敗") ||
|
||||
entry.summary.includes("錯誤") ||
|
||||
entry.summary.includes("失敗")
|
||||
);
|
||||
|
||||
const taskEntries = entries.filter(
|
||||
(entry) =>
|
||||
entry.relatedTaskId && !errorEntries.some((e) => e.id === entry.id)
|
||||
);
|
||||
|
||||
const generalEntries = entries.filter(
|
||||
(entry) =>
|
||||
!errorEntries.some((e) => e.id === entry.id) &&
|
||||
!taskEntries.some((e) => e.id === entry.id)
|
||||
);
|
||||
|
||||
// 確定每類保留多少條目
|
||||
const totalToRemove = entries.length - entriesToKeep;
|
||||
|
||||
// 優先從一般日誌中移除
|
||||
let trimmedGeneralEntries = generalEntries;
|
||||
if (generalEntries.length > totalToRemove) {
|
||||
// 按時間排序,移除最舊的
|
||||
trimmedGeneralEntries = generalEntries
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||
)
|
||||
.slice(0, generalEntries.length - totalToRemove);
|
||||
} else {
|
||||
// 如果一般日誌不夠,需要從任務日誌中移除
|
||||
trimmedGeneralEntries = [];
|
||||
const remainingToRemove = totalToRemove - generalEntries.length;
|
||||
|
||||
if (remainingToRemove > 0 && taskEntries.length > remainingToRemove) {
|
||||
const trimmedTaskEntries = taskEntries
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
||||
)
|
||||
.slice(0, taskEntries.length - remainingToRemove);
|
||||
|
||||
// 合併保留的條目
|
||||
const newEntries = [
|
||||
...errorEntries,
|
||||
...trimmedTaskEntries,
|
||||
...trimmedGeneralEntries,
|
||||
];
|
||||
await writeConversationLog(newEntries);
|
||||
|
||||
console.log(
|
||||
`日誌精簡完成: 從 ${entries.length} 條減少到 ${newEntries.length} 條`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 合併保留的條目
|
||||
const newEntries = [
|
||||
...errorEntries,
|
||||
...taskEntries,
|
||||
...trimmedGeneralEntries,
|
||||
];
|
||||
await writeConversationLog(newEntries);
|
||||
|
||||
console.log(
|
||||
`日誌精簡完成: 從 ${entries.length} 條減少到 ${newEntries.length} 條`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("精簡日誌條目時發生錯誤:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取所有歸檔日誌文件列表
|
||||
*/
|
||||
export async function getArchivedLogFiles(): Promise<string[]> {
|
||||
try {
|
||||
// 確保目錄存在
|
||||
await ensureDataDir();
|
||||
|
||||
const files = await fs.readdir(DATA_DIR);
|
||||
|
||||
// 過濾出歸檔日誌文件
|
||||
return files
|
||||
.filter(
|
||||
(file) => file.startsWith("conversation_log_") && file.endsWith(".json")
|
||||
)
|
||||
.sort()
|
||||
.reverse(); // 最新的在前
|
||||
} catch (error) {
|
||||
console.error("獲取歸檔日誌文件列表時發生錯誤:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 讀取特定歸檔日誌文件
|
||||
*/
|
||||
export async function readArchivedLog(
|
||||
archiveFileName: string
|
||||
): Promise<ConversationEntry[]> {
|
||||
// 安全性檢查:確保文件名格式正確
|
||||
if (!archiveFileName.match(/^conversation_log_[\d-]+T[\d-]+\.json$/)) {
|
||||
throw new Error("無效的歸檔日誌文件名");
|
||||
}
|
||||
|
||||
const archiveFilePath = path.join(DATA_DIR, archiveFileName);
|
||||
|
||||
try {
|
||||
await fs.access(archiveFilePath);
|
||||
const data = await fs.readFile(archiveFilePath, "utf-8");
|
||||
const entries = JSON.parse(data).entries;
|
||||
|
||||
// 將日期字串轉換回 Date 物件
|
||||
return entries.map((entry: any) => ({
|
||||
...entry,
|
||||
timestamp: entry.timestamp ? new Date(entry.timestamp) : new Date(),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error(`讀取歸檔日誌 ${archiveFileName} 時發生錯誤:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
@ -1,485 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getAllConversationEntries,
|
||||
getConversationEntriesByTaskId,
|
||||
getPaginatedConversationEntries,
|
||||
clearAllConversationEntries,
|
||||
getArchivedLogFiles,
|
||||
readArchivedLog,
|
||||
} from "../models/conversationLogModel.js";
|
||||
import { getTaskById } from "../models/taskModel.js";
|
||||
import { ListConversationLogArgs } from "../types/index.js";
|
||||
|
||||
// 列出對話日誌工具
|
||||
export const listConversationLogSchema = z
|
||||
.object({
|
||||
taskId: z
|
||||
.string()
|
||||
.uuid({ message: "任務ID格式無效,請提供有效的UUID格式" })
|
||||
.optional()
|
||||
.describe("按任務 ID 過濾對話記錄(選填)"),
|
||||
startDate: z
|
||||
.string()
|
||||
.refine(
|
||||
(val) => {
|
||||
const date = new Date(val);
|
||||
return !isNaN(date.getTime());
|
||||
},
|
||||
{
|
||||
message:
|
||||
"起始日期格式無效,請使用ISO日期格式,例如:2025-04-11T12:13:49.751Z",
|
||||
}
|
||||
)
|
||||
.optional()
|
||||
.describe("起始日期過濾,格式為 ISO 日期字串(選填)"),
|
||||
endDate: z
|
||||
.string()
|
||||
.refine(
|
||||
(val) => {
|
||||
const date = new Date(val);
|
||||
return !isNaN(date.getTime());
|
||||
},
|
||||
{
|
||||
message:
|
||||
"結束日期格式無效,請使用ISO日期格式,例如:2025-04-11T12:13:49.751Z",
|
||||
}
|
||||
)
|
||||
.optional()
|
||||
.describe("結束日期過濾,格式為 ISO 日期字串(選填)"),
|
||||
limit: z
|
||||
.number()
|
||||
.int({ message: "限制必須是整數" })
|
||||
.positive({ message: "限制必須是正數" })
|
||||
.max(100, { message: "限制不能超過100條記錄" })
|
||||
.default(20)
|
||||
.describe("返回結果數量限制,最大 100(預設:20)"),
|
||||
offset: z
|
||||
.number()
|
||||
.int({ message: "偏移量必須是整數" })
|
||||
.nonnegative({ message: "偏移量不能為負數" })
|
||||
.default(0)
|
||||
.describe("分頁偏移量(預設:0)"),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// 驗證起始日期和結束日期的順序
|
||||
if (data.startDate && data.endDate) {
|
||||
const start = new Date(data.startDate);
|
||||
const end = new Date(data.endDate);
|
||||
return start <= end;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "起始日期必須早於或等於結束日期",
|
||||
path: ["endDate"],
|
||||
}
|
||||
);
|
||||
|
||||
export async function listConversationLog({
|
||||
taskId,
|
||||
startDate,
|
||||
endDate,
|
||||
limit,
|
||||
offset,
|
||||
}: z.infer<typeof listConversationLogSchema>) {
|
||||
// 將日期字串轉換為 Date 物件
|
||||
const startDateObj = startDate ? new Date(startDate) : undefined;
|
||||
const endDateObj = endDate ? new Date(endDate) : undefined;
|
||||
|
||||
// 驗證日期格式
|
||||
if (startDate && isNaN(startDateObj?.getTime() ?? NaN)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 參數錯誤\n\n起始日期格式無效。請使用 ISO 日期字串格式(例如:2023-01-01T00:00:00Z)。`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (endDate && isNaN(endDateObj?.getTime() ?? NaN)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 參數錯誤\n\n結束日期格式無效。請使用 ISO 日期字串格式(例如:2023-01-01T00:00:00Z)。`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
// 如果指定了任務 ID,檢查任務是否存在
|
||||
let taskInfo = "";
|
||||
if (taskId) {
|
||||
const task = await getTaskById(taskId);
|
||||
if (!task) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 參數錯誤\n\n找不到 ID 為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務 ID 後再試。`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
taskInfo = `任務:${task.name} (ID: \`${task.id}\`)`;
|
||||
}
|
||||
|
||||
// 獲取分頁的對話日誌
|
||||
const { entries, total } = await getPaginatedConversationEntries(
|
||||
limit,
|
||||
offset,
|
||||
taskId,
|
||||
startDateObj,
|
||||
endDateObj
|
||||
);
|
||||
|
||||
if (entries.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 查詢結果\n\n未找到符合條件的對話日誌記錄。${
|
||||
taskId ? `\n\n${taskInfo}` : ""
|
||||
}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 構建過濾條件描述
|
||||
const filterDescs = [];
|
||||
if (taskInfo) filterDescs.push(taskInfo);
|
||||
if (startDateObj) filterDescs.push(`開始日期:${startDateObj.toISOString()}`);
|
||||
if (endDateObj) filterDescs.push(`結束日期:${endDateObj.toISOString()}`);
|
||||
const filterDesc =
|
||||
filterDescs.length > 0 ? `\n\n過濾條件:${filterDescs.join(",")}` : "";
|
||||
|
||||
// 構建分頁信息
|
||||
const pageInfo = `\n\n當前顯示:第 ${offset + 1} 到 ${Math.min(
|
||||
offset + limit,
|
||||
total
|
||||
)} 條,共 ${total} 條記錄`;
|
||||
|
||||
// 構建分頁導航提示
|
||||
let navTips = "";
|
||||
if (total > limit) {
|
||||
const prevPageAvailable = offset > 0;
|
||||
const nextPageAvailable = offset + limit < total;
|
||||
|
||||
navTips = "\n\n分頁導航:";
|
||||
if (prevPageAvailable) {
|
||||
const prevOffset = Math.max(0, offset - limit);
|
||||
navTips += `\n- 上一頁:使用 offset=${prevOffset}`;
|
||||
}
|
||||
if (nextPageAvailable) {
|
||||
const nextOffset = offset + limit;
|
||||
navTips += `\n- 下一頁:使用 offset=${nextOffset}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化對話日誌列表
|
||||
const formattedEntries = entries
|
||||
.map((entry, index) => {
|
||||
const entryNumber = offset + index + 1;
|
||||
return `### ${entryNumber}. ${entry.participant} (${new Date(
|
||||
entry.timestamp
|
||||
).toISOString()})\n${
|
||||
entry.relatedTaskId ? `關聯任務:\`${entry.relatedTaskId}\`\n` : ""
|
||||
}${entry.context ? `上下文:${entry.context}\n` : ""}摘要:${
|
||||
entry.summary
|
||||
}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const result = `## 對話日誌查詢結果${filterDesc}${pageInfo}\n\n${formattedEntries}${navTips}`;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: result,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 清除所有對話日誌工具
|
||||
export const clearConversationLogSchema = z.object({
|
||||
confirm: z
|
||||
.boolean()
|
||||
.refine((val) => val === true, {
|
||||
message:
|
||||
"必須明確確認清除操作,請將 confirm 參數設置為 true 以確認此危險操作",
|
||||
})
|
||||
.describe("確認刪除所有日誌記錄(此操作不可逆)"),
|
||||
});
|
||||
|
||||
export async function clearConversationLog({
|
||||
confirm,
|
||||
}: z.infer<typeof clearConversationLogSchema>) {
|
||||
if (!confirm) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 操作取消\n\n未確認清除操作。如要清除所有對話日誌,請將 confirm 參數設為 true。`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 執行清除操作
|
||||
await clearAllConversationEntries();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 操作成功\n\n所有對話日誌已成功清除。`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 列出歸檔日誌工具
|
||||
export const listArchivedLogsSchema = z.object({
|
||||
includeDetails: z
|
||||
.boolean()
|
||||
.default(false)
|
||||
.describe("是否包含歸檔文件的詳細信息(預設:否)"),
|
||||
});
|
||||
|
||||
export async function listArchivedLogs({
|
||||
includeDetails = false,
|
||||
}: z.infer<typeof listArchivedLogsSchema>) {
|
||||
const archiveFiles = await getArchivedLogFiles();
|
||||
|
||||
let content = "";
|
||||
|
||||
if (archiveFiles.length === 0) {
|
||||
content = "## 日誌歸檔\n\n目前沒有任何日誌歸檔文件。";
|
||||
} else {
|
||||
content = "## 日誌歸檔文件\n\n";
|
||||
|
||||
if (includeDetails) {
|
||||
// 為每個歸檔文件獲取更多詳細信息
|
||||
const detailedFiles = await Promise.all(
|
||||
archiveFiles.map(async (file) => {
|
||||
try {
|
||||
const entries = await readArchivedLog(file);
|
||||
const timestamp = file
|
||||
.replace("conversation_log_", "")
|
||||
.replace(".json", "");
|
||||
const formattedDate = new Date(
|
||||
timestamp.replace(/-/g, ":").replace("T", " ")
|
||||
).toLocaleString();
|
||||
|
||||
return {
|
||||
filename: file,
|
||||
date: formattedDate,
|
||||
entriesCount: entries.length,
|
||||
firstEntry: entries.length > 0 ? entries[0] : null,
|
||||
lastEntry:
|
||||
entries.length > 0 ? entries[entries.length - 1] : null,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
filename: file,
|
||||
date: "未知",
|
||||
entriesCount: 0,
|
||||
error: (error as Error).message,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 格式化輸出
|
||||
content += detailedFiles
|
||||
.map((file, index) => {
|
||||
let fileInfo = `${index + 1}. **${file.filename}**\n`;
|
||||
fileInfo += ` - 創建日期: ${file.date}\n`;
|
||||
fileInfo += ` - 條目數量: ${file.entriesCount}\n`;
|
||||
|
||||
if (file.firstEntry) {
|
||||
const firstDate = new Date(
|
||||
file.firstEntry.timestamp
|
||||
).toLocaleString();
|
||||
fileInfo += ` - 最早條目: ${firstDate}\n`;
|
||||
}
|
||||
|
||||
if (file.lastEntry) {
|
||||
const lastDate = new Date(
|
||||
file.lastEntry.timestamp
|
||||
).toLocaleString();
|
||||
fileInfo += ` - 最晚條目: ${lastDate}\n`;
|
||||
}
|
||||
|
||||
if (file.error) {
|
||||
fileInfo += ` - 錯誤: ${file.error}\n`;
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
})
|
||||
.join("\n");
|
||||
} else {
|
||||
// 簡單列出歸檔文件名
|
||||
content += archiveFiles
|
||||
.map((file, index) => {
|
||||
// 從文件名提取日期
|
||||
const timestamp = file
|
||||
.replace("conversation_log_", "")
|
||||
.replace(".json", "");
|
||||
const formattedDate = new Date(
|
||||
timestamp.replace(/-/g, ":").replace("T", " ")
|
||||
).toLocaleString();
|
||||
return `${index + 1}. **${file}** (${formattedDate})`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
content += "\n\n使用讀取歸檔日誌工具可查看特定歸檔文件的內容。";
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 讀取特定歸檔日誌工具
|
||||
export const readArchivedLogSchema = z.object({
|
||||
filename: z
|
||||
.string()
|
||||
.min(1, { message: "文件名不能為空" })
|
||||
.refine((val) => val.match(/^conversation_log_[\d-]+T[\d-]+\.json$/), {
|
||||
message:
|
||||
"無效的歸檔日誌文件名,正確格式為 'conversation_log_[timestamp].json'",
|
||||
})
|
||||
.describe("歸檔日誌文件名,格式為 'conversation_log_[timestamp].json'"),
|
||||
limit: z
|
||||
.number()
|
||||
.int({ message: "限制必須是整數" })
|
||||
.positive({ message: "限制必須是正數" })
|
||||
.max(100, { message: "限制不能超過100條記錄" })
|
||||
.default(50)
|
||||
.describe("返回結果數量限制,最大 100(預設:50)"),
|
||||
offset: z
|
||||
.number()
|
||||
.int({ message: "偏移量必須是整數" })
|
||||
.nonnegative({ message: "偏移量不能為負數" })
|
||||
.default(0)
|
||||
.describe("分頁起始位置(預設:0)"),
|
||||
});
|
||||
|
||||
export async function readArchivedLogTool({
|
||||
filename,
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
}: z.infer<typeof readArchivedLogSchema>) {
|
||||
try {
|
||||
// 安全性檢查:確保文件名格式正確
|
||||
if (!filename.match(/^conversation_log_[\d-]+T[\d-]+\.json$/)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "## 錯誤\n\n無效的歸檔日誌文件名。正確格式為 'conversation_log_[timestamp].json'。",
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const entries = await readArchivedLog(filename);
|
||||
|
||||
// 分頁處理
|
||||
const paginatedEntries = entries.slice(offset, offset + limit);
|
||||
const total = entries.length;
|
||||
|
||||
let content = `## 歸檔日誌: ${filename}\n\n`;
|
||||
|
||||
if (paginatedEntries.length === 0) {
|
||||
content += "此歸檔文件沒有任何日誌條目。";
|
||||
} else {
|
||||
// 添加分頁信息
|
||||
content += `顯示 ${Math.min(total, offset + 1)}-${Math.min(
|
||||
total,
|
||||
offset + limit
|
||||
)} 條,共 ${total} 條\n\n`;
|
||||
|
||||
// 格式化日誌條目
|
||||
content += paginatedEntries
|
||||
.map((entry, index) => {
|
||||
const date = new Date(entry.timestamp).toLocaleString();
|
||||
let entryContent = `### ${offset + index + 1}. ${date} (${
|
||||
entry.participant
|
||||
})\n`;
|
||||
|
||||
if (entry.context) {
|
||||
entryContent += `**上下文:** ${entry.context}\n`;
|
||||
}
|
||||
|
||||
if (entry.relatedTaskId) {
|
||||
entryContent += `**相關任務:** ${entry.relatedTaskId}\n`;
|
||||
}
|
||||
|
||||
entryContent += `\n${entry.summary}\n`;
|
||||
|
||||
return entryContent;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
// 添加導航鏈接
|
||||
if (offset > 0 || offset + limit < total) {
|
||||
content += "\n\n### 分頁導航\n";
|
||||
|
||||
if (offset > 0) {
|
||||
const prevOffset = Math.max(0, offset - limit);
|
||||
content += `- 上一頁 (${prevOffset + 1}-${Math.min(
|
||||
total,
|
||||
prevOffset + limit
|
||||
)})\n`;
|
||||
}
|
||||
|
||||
if (offset + limit < total) {
|
||||
const nextOffset = offset + limit;
|
||||
content += `- 下一頁 (${nextOffset + 1}-${Math.min(
|
||||
total,
|
||||
nextOffset + limit
|
||||
)})\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 錯誤\n\n讀取歸檔日誌時發生錯誤: ${
|
||||
(error as Error).message
|
||||
}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
@ -14,17 +14,12 @@ import {
|
||||
} from "../models/taskModel.js";
|
||||
import {
|
||||
TaskStatus,
|
||||
ConversationParticipant,
|
||||
TaskComplexityLevel,
|
||||
RelatedFileType,
|
||||
RelatedFile,
|
||||
Task,
|
||||
TaskDependency,
|
||||
} from "../types/index.js";
|
||||
import {
|
||||
addConversationEntry,
|
||||
getConversationEntriesByTaskId,
|
||||
} from "../models/conversationLogModel.js";
|
||||
import {
|
||||
extractSummary,
|
||||
generateTaskSummary,
|
||||
@ -84,20 +79,6 @@ export async function planTask({
|
||||
requirements,
|
||||
existingTasksReference = false,
|
||||
}: z.infer<typeof planTaskSchema>) {
|
||||
// 記錄任務規劃開始
|
||||
try {
|
||||
// 使用摘要提取工具處理較長的描述
|
||||
const descriptionSummary = extractSummary(description, 100);
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`開始新任務規劃,描述:${descriptionSummary}`,
|
||||
undefined,
|
||||
"任務規劃"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
let prompt = `## 任務分析請求\n\n請仔細分析以下任務問題,理解其核心要求、範圍和約束條件:\n\n\`\`\`\n${description}\n\`\`\`\n\n`;
|
||||
|
||||
if (requirements) {
|
||||
@ -300,23 +281,6 @@ export async function analyzeTask({
|
||||
initialConcept,
|
||||
previousAnalysis,
|
||||
}: z.infer<typeof analyzeTaskSchema>) {
|
||||
// 記錄任務分析
|
||||
try {
|
||||
// 使用摘要提取工具處理較長的概念描述
|
||||
const conceptSummary = extractSummary(initialConcept, 100);
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`開始分析任務:${extractSummary(
|
||||
summary,
|
||||
100
|
||||
)},初步概念:${conceptSummary}`,
|
||||
undefined,
|
||||
"任務分析"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
let prompt = `## 代碼庫分析任務\n\n### 任務摘要\n\`\`\`\n${summary}\n\`\`\`\n\n已收到您的初步解答構想:\n\n\`\`\`\n${initialConcept}\n\`\`\`\n\n`;
|
||||
|
||||
prompt += `## 技術審核指引\n\n請執行以下詳細的技術分析步驟:\n\n### 1. 代碼庫分析
|
||||
@ -400,23 +364,6 @@ export async function reflectTask({
|
||||
summary,
|
||||
analysis,
|
||||
}: z.infer<typeof reflectTaskSchema>) {
|
||||
// 記錄任務反思
|
||||
try {
|
||||
// 使用摘要提取工具處理較長的分析
|
||||
const analysisSummary = extractSummary(analysis, 100);
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`開始反思任務解決方案:${extractSummary(
|
||||
summary,
|
||||
50
|
||||
)},分析:${analysisSummary}`,
|
||||
undefined,
|
||||
"任務反思"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
const prompt = `## 解決方案反思與評估\n\n### 任務摘要\n\`\`\`\n${summary}\n\`\`\`\n\n### 詳細分析結果\n\`\`\`\n${analysis}\n\`\`\`\n\n## 批判性評估指引\n\n請從以下多個維度對您的解決方案進行全面且批判性的審查:\n\n### 1. 技術完整性評估
|
||||
- 方案是否存在技術缺陷或邏輯漏洞?從各個角度檢驗解決方案的完整性。
|
||||
- 所有邊緣情況和異常處理是否周全?列舉可能的異常場景並驗證處理方式。
|
||||
@ -552,20 +499,6 @@ export async function splitTasks({
|
||||
if (updateMode === "clearAllTasks") {
|
||||
const clearResult = await modelClearAllTasks();
|
||||
|
||||
// 記錄清除結果
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`清除所有任務:${clearResult.success ? "成功" : "失敗"},${
|
||||
clearResult.message
|
||||
}`,
|
||||
undefined,
|
||||
"任務清除"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 返回清除操作結果
|
||||
let prompt = `## 任務清除結果\n\n### 系統通知\n${clearResult.message}\n\n`;
|
||||
|
||||
@ -613,34 +546,9 @@ export async function splitTasks({
|
||||
updateModeMessage = "清除模式:清除所有任務並創建備份";
|
||||
}
|
||||
|
||||
// 記錄任務拆分
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`拆分任務:${updateModeMessage},任務數量:${tasks.length}`,
|
||||
undefined,
|
||||
"任務拆分"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 批量創建任務 - 將 updateMode 傳遞給 batchCreateOrUpdateTasks
|
||||
const createdTasks = await batchCreateOrUpdateTasks(tasks, updateMode);
|
||||
|
||||
// 記錄任務創建成功
|
||||
try {
|
||||
const taskNames = createdTasks.map((task) => task.name).join(", ");
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`成功創建任務:${taskNames}`,
|
||||
undefined,
|
||||
"任務創建"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 獲取所有任務,用於顯示完整的依賴關係
|
||||
const allTasks = await getAllTasks();
|
||||
|
||||
@ -742,18 +650,6 @@ export async function splitTasks({
|
||||
|
||||
// 列出任務工具
|
||||
export async function listTasks() {
|
||||
// 記錄查詢任務列表
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
"查詢所有任務列表",
|
||||
undefined,
|
||||
"任務列表查詢"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
const tasks = await getAllTasks();
|
||||
|
||||
if (tasks.length === 0) {
|
||||
@ -824,18 +720,6 @@ export async function executeTask({
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
// 記錄錯誤日誌
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`執行任務失敗:找不到ID為 ${taskId} 的任務`,
|
||||
undefined,
|
||||
"錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -848,18 +732,6 @@ export async function executeTask({
|
||||
}
|
||||
|
||||
if (task.status === TaskStatus.COMPLETED) {
|
||||
// 記錄已完成任務的嘗試執行
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`嘗試執行已完成的任務:${task.name} (ID: ${task.id})`,
|
||||
task.id,
|
||||
"狀態通知"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -884,18 +756,6 @@ export async function executeTask({
|
||||
: `ID: \`${id}\``;
|
||||
});
|
||||
|
||||
// 記錄任務被阻擋的情況
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務 ${task.name} (ID: ${task.id}) 被依賴阻擋,等待完成的依賴任務數量: ${blockedBy.length}`,
|
||||
task.id,
|
||||
"依賴阻擋"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -917,22 +777,6 @@ export async function executeTask({
|
||||
// 更新任務狀態為進行中
|
||||
await updateTaskStatus(taskId, TaskStatus.IN_PROGRESS);
|
||||
|
||||
// 記錄任務開始執行
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`開始執行任務:${task.name} (ID: ${task.id})${
|
||||
complexityAssessment
|
||||
? `, 複雜度評估:${complexityAssessment.level}`
|
||||
: ""
|
||||
}`,
|
||||
task.id,
|
||||
"任務啟動"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 構建任務執行提示
|
||||
let prompt = `## 任務執行指示\n\n### 任務詳情\n\n- **名稱:** ${
|
||||
task.name
|
||||
@ -968,14 +812,6 @@ export async function executeTask({
|
||||
contextInfo += `*無完成摘要*\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 記錄依賴任務加載
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`已加載依賴任務完成摘要,共 ${completedDepTasks.length} 個已完成依賴任務`,
|
||||
task.id,
|
||||
"加載依賴任務摘要"
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@ -985,39 +821,13 @@ export async function executeTask({
|
||||
|
||||
if (task.relatedFiles && task.relatedFiles.length > 0) {
|
||||
try {
|
||||
// 記錄加載文件操作
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`正在生成任務相關文件摘要,共 ${task.relatedFiles.length} 個文件`,
|
||||
task.id,
|
||||
"生成相關文件摘要"
|
||||
);
|
||||
|
||||
// 生成任務相關文件的摘要資訊
|
||||
// 使用loadTaskRelatedFiles生成文件摘要,現在函數直接返回格式化的文本
|
||||
// 而不是包含content和summary的物件
|
||||
const relatedFilesSummary = await loadTaskRelatedFiles(task.relatedFiles);
|
||||
|
||||
// 記錄摘要生成完成
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務相關文件摘要生成完成,已為 ${task.relatedFiles.length} 個文件生成摘要資訊`,
|
||||
task.id,
|
||||
"相關文件摘要生成完成"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("生成任務相關文件摘要時發生錯誤:", error);
|
||||
|
||||
// 記錄錯誤
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`生成任務相關文件摘要時發生錯誤: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
task.id,
|
||||
"相關文件摘要生成錯誤"
|
||||
);
|
||||
|
||||
relatedFilesSummary =
|
||||
"## 相關文件摘要生成失敗\n\n生成文件摘要時發生錯誤,請手動查看相關文件。";
|
||||
}
|
||||
@ -1061,16 +871,6 @@ export async function executeTask({
|
||||
});
|
||||
|
||||
relatedFilesSummary += `\n使用 update_task_files 工具關聯相關文件,以獲得更好的上下文記憶支持。`;
|
||||
|
||||
// 記錄文件推薦
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`為任務推薦了潛在相關文件關鍵詞: ${potentialFileKeywords
|
||||
.slice(0, 5)
|
||||
.join(", ")}`,
|
||||
task.id,
|
||||
"文件推薦"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("推薦相關文件時發生錯誤:", error);
|
||||
@ -1216,18 +1016,6 @@ export async function verifyTask({ taskId }: z.infer<typeof verifyTaskSchema>) {
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
// 記錄錯誤日誌
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`驗證任務失敗:找不到ID為 ${taskId} 的任務`,
|
||||
undefined,
|
||||
"錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1240,18 +1028,6 @@ export async function verifyTask({ taskId }: z.infer<typeof verifyTaskSchema>) {
|
||||
}
|
||||
|
||||
if (task.status !== TaskStatus.IN_PROGRESS) {
|
||||
// 記錄狀態錯誤
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`驗證任務狀態錯誤:任務 ${task.name} (ID: ${task.id}) 當前狀態為 ${task.status},不處於進行中狀態`,
|
||||
task.id,
|
||||
"狀態錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1263,18 +1039,6 @@ export async function verifyTask({ taskId }: z.infer<typeof verifyTaskSchema>) {
|
||||
};
|
||||
}
|
||||
|
||||
// 記錄開始驗證
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`開始驗證任務:${task.name} (ID: ${task.id})`,
|
||||
task.id,
|
||||
"任務驗證"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
const prompt = `## 任務驗證評估\n\n### 任務資料\n\n- **名稱:** ${
|
||||
task.name
|
||||
}\n- **ID:** \`${task.id}\`\n- **描述:** ${task.description}\n${
|
||||
@ -1446,18 +1210,6 @@ export async function completeTask({
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
// 記錄錯誤日誌
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`完成任務失敗:找不到ID為 ${taskId} 的任務`,
|
||||
undefined,
|
||||
"錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1470,18 +1222,6 @@ export async function completeTask({
|
||||
}
|
||||
|
||||
if (task.status !== TaskStatus.IN_PROGRESS) {
|
||||
// 記錄狀態錯誤
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`完成任務狀態錯誤:任務 ${task.name} (ID: ${task.id}) 當前狀態為 ${task.status},不是進行中狀態`,
|
||||
task.id,
|
||||
"狀態錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1504,21 +1244,6 @@ export async function completeTask({
|
||||
await updateTaskStatus(taskId, TaskStatus.COMPLETED);
|
||||
await updateTaskSummary(taskId, taskSummary);
|
||||
|
||||
// 記錄任務完成
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務成功完成:${task.name} (ID: ${task.id}),完成摘要:${extractSummary(
|
||||
taskSummary,
|
||||
100
|
||||
)}`,
|
||||
task.id,
|
||||
"任務完成"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1543,18 +1268,6 @@ export async function deleteTask({ taskId }: z.infer<typeof deleteTaskSchema>) {
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
// 記錄錯誤日誌
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`刪除任務失敗:找不到ID為 ${taskId} 的任務`,
|
||||
undefined,
|
||||
"錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1567,18 +1280,6 @@ export async function deleteTask({ taskId }: z.infer<typeof deleteTaskSchema>) {
|
||||
}
|
||||
|
||||
if (task.status === TaskStatus.COMPLETED) {
|
||||
// 記錄操作被拒絕
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`刪除操作被拒絕:嘗試刪除已完成的任務 ${task.name} (ID: ${task.id})`,
|
||||
task.id,
|
||||
"操作被拒絕"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1590,34 +1291,8 @@ export async function deleteTask({ taskId }: z.infer<typeof deleteTaskSchema>) {
|
||||
};
|
||||
}
|
||||
|
||||
// 記錄要刪除的任務
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`正在刪除任務:${task.name} (ID: ${task.id})`,
|
||||
task.id,
|
||||
"任務刪除"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
const result = await modelDeleteTask(taskId);
|
||||
|
||||
// 記錄刪除結果
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務刪除${result.success ? "成功" : "失敗"}:${task.name} (ID: ${
|
||||
task.id
|
||||
}),原因:${result.message}`,
|
||||
result.success ? undefined : task.id,
|
||||
result.success ? "任務刪除成功" : "任務刪除失敗"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1664,18 +1339,6 @@ export async function clearAllTasks({
|
||||
// 檢查是否真的有任務需要清除
|
||||
const allTasks = await getAllTasks();
|
||||
if (allTasks.length === 0) {
|
||||
// 記錄操作
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`清除所有任務操作:無任務需要清除`,
|
||||
undefined,
|
||||
"任務清除"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1686,35 +1349,9 @@ export async function clearAllTasks({
|
||||
};
|
||||
}
|
||||
|
||||
// 記錄操作開始
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`開始清除所有任務操作:共 ${allTasks.length} 個任務`,
|
||||
undefined,
|
||||
"任務清除"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 執行清除操作
|
||||
const result = await modelClearAllTasks();
|
||||
|
||||
// 記錄操作結果
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務清除${result.success ? "成功" : "失敗"}:${result.message}${
|
||||
result.backupFile ? `,備份文件: ${result.backupFile}` : ""
|
||||
}`,
|
||||
undefined,
|
||||
result.success ? "任務清除成功" : "任務清除失敗"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1832,18 +1469,6 @@ export async function updateTaskContent({
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
// 記錄錯誤日誌
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`更新任務失敗:找不到ID為 ${taskId} 的任務`,
|
||||
undefined,
|
||||
"錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -1863,17 +1488,6 @@ export async function updateTaskContent({
|
||||
if (relatedFiles)
|
||||
updateSummary += `,更新相關文件 (${relatedFiles.length} 個)`;
|
||||
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
updateSummary,
|
||||
task.id,
|
||||
"任務更新"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 執行更新操作
|
||||
const result = await modelUpdateTaskContent(taskId, {
|
||||
name,
|
||||
@ -1882,20 +1496,6 @@ export async function updateTaskContent({
|
||||
relatedFiles,
|
||||
});
|
||||
|
||||
// 記錄更新結果
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務更新${result.success ? "成功" : "失敗"}:${task.name} (ID: ${
|
||||
task.id
|
||||
}),原因:${result.message}`,
|
||||
task.id,
|
||||
result.success ? "任務更新成功" : "任務更新失敗"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 構建響應消息
|
||||
const responseTitle = result.success ? "操作成功" : "操作失敗";
|
||||
let responseMessage = result.message;
|
||||
@ -2042,18 +1642,6 @@ export async function updateTaskRelatedFiles({
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
// 記錄錯誤日誌
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`更新任務相關文件失敗:找不到ID為 ${taskId} 的任務`,
|
||||
undefined,
|
||||
"錯誤"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@ -2075,34 +1663,9 @@ export async function updateTaskRelatedFiles({
|
||||
.map(([type, count]) => `${type} ${count} 個`)
|
||||
.join(",");
|
||||
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`準備更新任務相關文件:${task.name} (ID: ${task.id}),共 ${relatedFiles.length} 個文件(${fileTypeSummary})`,
|
||||
task.id,
|
||||
"更新相關文件"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 執行更新操作
|
||||
const result = await modelUpdateTaskRelatedFiles(taskId, relatedFiles);
|
||||
|
||||
// 記錄更新結果
|
||||
try {
|
||||
await addConversationEntry(
|
||||
ConversationParticipant.MCP,
|
||||
`任務相關文件更新${result.success ? "成功" : "失敗"}:${task.name} (ID: ${
|
||||
task.id
|
||||
}),${result.message}`,
|
||||
task.id,
|
||||
result.success ? "相關文件更新成功" : "相關文件更新失敗"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("記錄對話日誌時發生錯誤:", error);
|
||||
}
|
||||
|
||||
// 構建響應消息
|
||||
const responseTitle = result.success ? "操作成功" : "操作失敗";
|
||||
let responseMessage = result.message;
|
||||
|
@ -1,49 +1,43 @@
|
||||
// 任務狀態枚舉:定義任務在工作流程中的當前階段
|
||||
export var TaskStatus;
|
||||
(function (TaskStatus) {
|
||||
TaskStatus["PENDING"] = "\u5F85\u8655\u7406";
|
||||
TaskStatus["IN_PROGRESS"] = "\u9032\u884C\u4E2D";
|
||||
TaskStatus["COMPLETED"] = "\u5DF2\u5B8C\u6210";
|
||||
TaskStatus["BLOCKED"] = "\u88AB\u963B\u64CB";
|
||||
TaskStatus["PENDING"] = "\u5F85\u8655\u7406";
|
||||
TaskStatus["IN_PROGRESS"] = "\u9032\u884C\u4E2D";
|
||||
TaskStatus["COMPLETED"] = "\u5DF2\u5B8C\u6210";
|
||||
TaskStatus["BLOCKED"] = "\u88AB\u963B\u64CB";
|
||||
})(TaskStatus || (TaskStatus = {}));
|
||||
// 相關文件類型:定義文件與任務的關係類型
|
||||
export var RelatedFileType;
|
||||
(function (RelatedFileType) {
|
||||
RelatedFileType["TO_MODIFY"] = "\u5F85\u4FEE\u6539";
|
||||
RelatedFileType["REFERENCE"] = "\u53C3\u8003\u8CC7\u6599";
|
||||
RelatedFileType["OUTPUT"] = "\u8F38\u51FA\u7D50\u679C";
|
||||
RelatedFileType["DEPENDENCY"] = "\u4F9D\u8CF4\u6587\u4EF6";
|
||||
RelatedFileType["OTHER"] = "\u5176\u4ED6";
|
||||
RelatedFileType["TO_MODIFY"] = "\u5F85\u4FEE\u6539";
|
||||
RelatedFileType["REFERENCE"] = "\u53C3\u8003\u8CC7\u6599";
|
||||
RelatedFileType["OUTPUT"] = "\u8F38\u51FA\u7D50\u679C";
|
||||
RelatedFileType["DEPENDENCY"] = "\u4F9D\u8CF4\u6587\u4EF6";
|
||||
RelatedFileType["OTHER"] = "\u5176\u4ED6";
|
||||
})(RelatedFileType || (RelatedFileType = {}));
|
||||
// 對話參與者類型:定義對話中的參與方身份
|
||||
export var ConversationParticipant;
|
||||
(function (ConversationParticipant) {
|
||||
ConversationParticipant["MCP"] = "MCP";
|
||||
ConversationParticipant["LLM"] = "LLM";
|
||||
})(ConversationParticipant || (ConversationParticipant = {}));
|
||||
// 任務複雜度級別:定義任務的複雜程度分類
|
||||
export var TaskComplexityLevel;
|
||||
(function (TaskComplexityLevel) {
|
||||
TaskComplexityLevel["LOW"] = "\u4F4E\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["MEDIUM"] = "\u4E2D\u7B49\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["HIGH"] = "\u9AD8\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["VERY_HIGH"] = "\u6975\u9AD8\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["LOW"] = "\u4F4E\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["MEDIUM"] = "\u4E2D\u7B49\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["HIGH"] = "\u9AD8\u8907\u96DC\u5EA6";
|
||||
TaskComplexityLevel["VERY_HIGH"] = "\u6975\u9AD8\u8907\u96DC\u5EA6";
|
||||
})(TaskComplexityLevel || (TaskComplexityLevel = {}));
|
||||
// 任務複雜度閾值:定義任務複雜度評估的參考標準
|
||||
export const TaskComplexityThresholds = {
|
||||
DESCRIPTION_LENGTH: {
|
||||
MEDIUM: 500, // 超過此字數判定為中等複雜度
|
||||
HIGH: 1000, // 超過此字數判定為高複雜度
|
||||
VERY_HIGH: 2000, // 超過此字數判定為極高複雜度
|
||||
},
|
||||
DEPENDENCIES_COUNT: {
|
||||
MEDIUM: 2, // 超過此依賴數量判定為中等複雜度
|
||||
HIGH: 5, // 超過此依賴數量判定為高複雜度
|
||||
VERY_HIGH: 10, // 超過此依賴數量判定為極高複雜度
|
||||
},
|
||||
NOTES_LENGTH: {
|
||||
MEDIUM: 200, // 超過此字數判定為中等複雜度
|
||||
HIGH: 500, // 超過此字數判定為高複雜度
|
||||
VERY_HIGH: 1000, // 超過此字數判定為極高複雜度
|
||||
},
|
||||
DESCRIPTION_LENGTH: {
|
||||
MEDIUM: 500, // 超過此字數判定為中等複雜度
|
||||
HIGH: 1000, // 超過此字數判定為高複雜度
|
||||
VERY_HIGH: 2000, // 超過此字數判定為極高複雜度
|
||||
},
|
||||
DEPENDENCIES_COUNT: {
|
||||
MEDIUM: 2, // 超過此依賴數量判定為中等複雜度
|
||||
HIGH: 5, // 超過此依賴數量判定為高複雜度
|
||||
VERY_HIGH: 10, // 超過此依賴數量判定為極高複雜度
|
||||
},
|
||||
NOTES_LENGTH: {
|
||||
MEDIUM: 200, // 超過此字數判定為中等複雜度
|
||||
HIGH: 500, // 超過此字數判定為高複雜度
|
||||
VERY_HIGH: 1000, // 超過此字數判定為極高複雜度
|
||||
},
|
||||
};
|
||||
|
@ -100,31 +100,6 @@ export interface CompleteTaskArgs {
|
||||
summary?: string; // 任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)
|
||||
}
|
||||
|
||||
// 對話參與者類型:定義對話中的參與方身份
|
||||
export enum ConversationParticipant {
|
||||
MCP = "MCP", // 系統方(MCP)
|
||||
LLM = "LLM", // 模型方(LLM)
|
||||
}
|
||||
|
||||
// 對話日誌條目:記錄 MCP 與 LLM 之間的重要對話內容
|
||||
export interface ConversationEntry {
|
||||
id: string; // 日誌條目的唯一標識符
|
||||
timestamp: Date; // 記錄的時間戳
|
||||
participant: ConversationParticipant; // 對話參與者(MCP 或 LLM)
|
||||
summary: string; // 消息摘要,只記錄關鍵信息點而非完整對話
|
||||
relatedTaskId?: string; // 關聯的任務 ID(選填),用於將對話與特定任務關聯
|
||||
context?: string; // 額外的上下文信息(選填),提供對話發生的背景
|
||||
}
|
||||
|
||||
// 對話日誌列表參數:用於查詢對話日誌的參數
|
||||
export interface ListConversationLogArgs {
|
||||
taskId?: string; // 按任務 ID 過濾(選填)
|
||||
startDate?: Date; // 起始日期過濾(選填)
|
||||
endDate?: Date; // 結束日期過濾(選填)
|
||||
limit?: number; // 返回結果數量限制(選填)
|
||||
offset?: number; // 分頁偏移量(選填)
|
||||
}
|
||||
|
||||
// 任務複雜度級別:定義任務的複雜程度分類
|
||||
export enum TaskComplexityLevel {
|
||||
LOW = "低複雜度", // 簡單且直接的任務,通常不需要特殊處理
|
||||
|
Loading…
x
Reference in New Issue
Block a user