diff --git a/src/core/agent.ts b/src/core/agent.ts index 18f6392..9535217 100644 --- a/src/core/agent.ts +++ b/src/core/agent.ts @@ -11,6 +11,7 @@ import { import type { Tool, ToolResult, Message, AgentConfig, ProviderType } from '../types/index.js'; import { buildZodSchema } from '../types/index.js'; import { ToolRegistry } from '../tools/registry.js'; +import { SessionManager } from '../session/index.js'; // Provider 工厂函数类型 type ProviderFactory = (apiKey: string) => (model: string) => LanguageModel; @@ -41,6 +42,9 @@ export class Agent { // 兼容旧模式:直接注册的工具 private legacyTools: Map = new Map(); + // 会话管理器(可选) + private sessionManager: SessionManager | null = null; + constructor(config: AgentConfig) { this.config = config; @@ -58,6 +62,26 @@ export class Agent { this.registry = registry; } + /** + * 设置会话管理器(启用会话持久化) + */ + setSessionManager(manager: SessionManager): void { + this.sessionManager = manager; + // 从会话恢复状态 + const session = manager.getSession(); + if (session) { + this.conversationHistory = [...session.messages]; + this.discoveredTools = new Set(session.discoveredTools); + } + } + + /** + * 获取会话管理器 + */ + getSessionManager(): SessionManager | null { + return this.sessionManager; + } + /** * 注册单个工具(兼容旧代码) */ @@ -206,15 +230,33 @@ export class Agent { content: fullResponse, }); + // 持久化会话 + await this.persistSession(); + return fullResponse; } + /** + * 持久化当前会话状态 + */ + private async persistSession(): Promise { + if (!this.sessionManager) return; + + await this.sessionManager.setMessages(this.conversationHistory); + await this.sessionManager.setDiscoveredTools([...this.discoveredTools]); + } + /** * 清空对话历史和发现的工具 */ - clearHistory(): void { + async clearHistory(): Promise { this.conversationHistory = []; this.discoveredTools.clear(); + + // 如果有会话管理器,创建新会话 + if (this.sessionManager) { + await this.sessionManager.newSession(); + } } /** diff --git a/src/index.ts b/src/index.ts index f1b2f3f..1072419 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,9 @@ import { Command } from 'commander'; import { Agent } from './core/agent.js'; import { TerminalUI } from './ui/terminal.js'; import { loadConfig, initConfig } from './utils/config.js'; -import { toolRegistry } from './tools/index.js'; +import { toolRegistry, todoManager } from './tools/index.js'; import { getPermissionManager, promptPermission } from './permission/index.js'; +import { SessionManager } from './session/index.js'; const program = new Command(); @@ -63,12 +64,27 @@ program.action(async () => { // 设置工具注册表(支持动态工具发现) agent.setRegistry(toolRegistry); + // 初始化会话管理器(支持会话持久化) + const sessionManager = new SessionManager(); + await sessionManager.init(process.cwd()); + agent.setSessionManager(sessionManager); + + // 初始化 todoManager(让 todo 工具可以访问会话) + todoManager.setSessionManager(sessionManager); + + // 显示会话恢复信息 + const session = sessionManager.getSession(); + if (session && session.messages.length > 0) { + console.log(`\n📂 已恢复会话 (${session.messages.length} 条消息)`); + } + // 启动终端 UI const ui = new TerminalUI(agent); // 优雅退出 - process.on('SIGINT', () => { + process.on('SIGINT', async () => { console.log('\n\n👋 再见!'); + await sessionManager.close(); ui.close(); process.exit(0); }); diff --git a/src/session/index.ts b/src/session/index.ts new file mode 100644 index 0000000..893c3ea --- /dev/null +++ b/src/session/index.ts @@ -0,0 +1,10 @@ +export type { + SessionData, + SessionSummary, + SessionManagerConfig, + Todo, + TodoStatus, +} from './types.js'; + +export { SessionStorage, sessionStorage } from './storage.js'; +export { SessionManager, sessionManager } from './manager.js'; diff --git a/src/session/manager.ts b/src/session/manager.ts new file mode 100644 index 0000000..0e915fa --- /dev/null +++ b/src/session/manager.ts @@ -0,0 +1,218 @@ +import type { ModelMessage } from 'ai'; +import type { SessionData, Todo, SessionSummary } from './types.js'; +import { SessionStorage, sessionStorage } from './storage.js'; + +/** + * 会话管理器 + * 提供高级会话操作接口 + */ +export class SessionManager { + private storage: SessionStorage; + private currentSession: SessionData | null = null; + private autoSaveInterval: ReturnType | null = null; + + constructor(storage?: SessionStorage) { + this.storage = storage || sessionStorage; + } + + /** + * 初始化 - 尝试恢复或创建新会话 + */ + async init(workdir: string): Promise { + // 尝试加载当前会话 + const existing = await this.storage.loadCurrentSession(); + + if (existing && existing.workdir === workdir) { + // 同一工作目录,恢复会话 + this.currentSession = existing; + } else { + // 不同目录或无会话,归档旧会话并创建新的 + if (existing) { + await this.storage.archiveCurrentSession(); + } + this.currentSession = this.createNewSession(workdir); + await this.save(); + } + + // 启动自动保存 + this.startAutoSave(); + + return this.currentSession; + } + + /** + * 创建新会话 + */ + private createNewSession(workdir: string): SessionData { + return { + id: this.storage.generateSessionId(), + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + workdir, + messages: [], + discoveredTools: [], + todos: [], + }; + } + + /** + * 获取当前会话 + */ + getSession(): SessionData | null { + return this.currentSession; + } + + /** + * 保存当前会话 + */ + async save(): Promise { + if (this.currentSession) { + await this.storage.saveCurrentSession(this.currentSession); + } + } + + /** + * 添加消息 + */ + async addMessage(message: ModelMessage): Promise { + if (!this.currentSession) return; + this.currentSession.messages.push(message); + await this.save(); + } + + /** + * 批量设置消息(用于同步整个对话历史) + */ + async setMessages(messages: ModelMessage[]): Promise { + if (!this.currentSession) return; + this.currentSession.messages = messages; + await this.save(); + } + + /** + * 获取对话历史 + */ + getMessages(): ModelMessage[] { + return this.currentSession?.messages || []; + } + + /** + * 设置已发现的工具 + */ + async setDiscoveredTools(tools: string[]): Promise { + if (!this.currentSession) return; + this.currentSession.discoveredTools = tools; + await this.save(); + } + + /** + * 获取已发现的工具 + */ + getDiscoveredTools(): string[] { + return this.currentSession?.discoveredTools || []; + } + + /** + * 更新待办事项 + */ + async setTodos(todos: Todo[]): Promise { + if (!this.currentSession) return; + this.currentSession.todos = todos; + await this.save(); + } + + /** + * 获取待办事项 + */ + getTodos(): Todo[] { + return this.currentSession?.todos || []; + } + + /** + * 清空当前会话并创建新会话 + */ + async newSession(workdir?: string): Promise { + // 归档当前会话 + if (this.currentSession && this.currentSession.messages.length > 0) { + await this.storage.archiveCurrentSession(); + } + + // 创建新会话 + const newWorkdir = workdir || this.currentSession?.workdir || process.cwd(); + this.currentSession = this.createNewSession(newWorkdir); + await this.save(); + + return this.currentSession; + } + + /** + * 恢复指定会话 + */ + async restoreSession(sessionId: string): Promise { + const session = await this.storage.loadSession(sessionId); + if (!session) return null; + + // 归档当前会话 + if (this.currentSession && this.currentSession.messages.length > 0) { + await this.storage.archiveCurrentSession(); + } + + this.currentSession = session; + await this.save(); + + return session; + } + + /** + * 列出历史会话 + */ + async listSessions(): Promise { + return this.storage.listSessions(); + } + + /** + * 删除历史会话 + */ + async deleteSession(sessionId: string): Promise { + return this.storage.deleteSession(sessionId); + } + + /** + * 启动自动保存(每 30 秒) + */ + private startAutoSave(): void { + if (this.autoSaveInterval) return; + + this.autoSaveInterval = setInterval(async () => { + await this.save(); + }, 30000); + } + + /** + * 停止自动保存 + */ + stopAutoSave(): void { + if (this.autoSaveInterval) { + clearInterval(this.autoSaveInterval); + this.autoSaveInterval = null; + } + } + + /** + * 关闭管理器(保存并停止自动保存) + */ + async close(): Promise { + this.stopAutoSave(); + await this.save(); + } + + /** + * 清理旧会话 + */ + async cleanup(keepCount?: number): Promise { + return this.storage.cleanupOldSessions(keepCount); + } +} + +// 导出默认实例 +export const sessionManager = new SessionManager(); diff --git a/src/session/storage.ts b/src/session/storage.ts new file mode 100644 index 0000000..052f7a4 --- /dev/null +++ b/src/session/storage.ts @@ -0,0 +1,205 @@ +import * as fs from 'fs/promises'; +import * as path from 'path'; +import * as os from 'os'; +import type { SessionData, SessionSummary } from './types.js'; + +/** + * 获取默认存储目录 + * 遵循 XDG 规范:~/.local/share/ai-assist/ + */ +function getDefaultStorageDir(): string { + const xdgDataHome = process.env.XDG_DATA_HOME; + if (xdgDataHome) { + return path.join(xdgDataHome, 'ai-assist'); + } + return path.join(os.homedir(), '.local', 'share', 'ai-assist'); +} + +/** + * 会话存储类 + * 负责会话数据的读写操作 + */ +export class SessionStorage { + private storageDir: string; + private sessionsDir: string; + private currentSessionFile: string; + + constructor(storageDir?: string) { + this.storageDir = storageDir || getDefaultStorageDir(); + this.sessionsDir = path.join(this.storageDir, 'sessions'); + this.currentSessionFile = path.join(this.storageDir, 'current-session.json'); + } + + /** + * 确保存储目录存在 + */ + async ensureDir(): Promise { + await fs.mkdir(this.sessionsDir, { recursive: true }); + } + + /** + * 生成会话 ID + */ + generateSessionId(): string { + const now = new Date(); + const timestamp = now.toISOString().slice(0, 10); // YYYY-MM-DD + const random = Math.random().toString(36).substring(2, 8); + return `${timestamp}_${random}`; + } + + /** + * 保存当前会话 + */ + async saveCurrentSession(session: SessionData): Promise { + await this.ensureDir(); + session.updatedAt = new Date().toISOString(); + await fs.writeFile( + this.currentSessionFile, + JSON.stringify(session, null, 2), + 'utf-8' + ); + } + + /** + * 加载当前会话 + */ + async loadCurrentSession(): Promise { + try { + const content = await fs.readFile(this.currentSessionFile, 'utf-8'); + return JSON.parse(content) as SessionData; + } catch { + return null; + } + } + + /** + * 归档当前会话到历史 + */ + async archiveCurrentSession(): Promise { + const current = await this.loadCurrentSession(); + if (!current || current.messages.length === 0) { + return; + } + + await this.ensureDir(); + const archivePath = path.join(this.sessionsDir, `${current.id}.json`); + await fs.writeFile(archivePath, JSON.stringify(current, null, 2), 'utf-8'); + } + + /** + * 删除当前会话文件 + */ + async clearCurrentSession(): Promise { + try { + await fs.unlink(this.currentSessionFile); + } catch { + // 文件不存在,忽略 + } + } + + /** + * 列出历史会话 + */ + async listSessions(): Promise { + await this.ensureDir(); + const files = await fs.readdir(this.sessionsDir); + const summaries: SessionSummary[] = []; + + for (const file of files) { + if (!file.endsWith('.json')) continue; + + try { + const filePath = path.join(this.sessionsDir, file); + const content = await fs.readFile(filePath, 'utf-8'); + const session = JSON.parse(content) as SessionData; + + summaries.push({ + id: session.id, + title: session.title || this.generateTitle(session), + workdir: session.workdir, + messageCount: session.messages.length, + createdAt: session.createdAt, + updatedAt: session.updatedAt, + }); + } catch { + // 跳过无法解析的文件 + } + } + + // 按更新时间降序排列 + return summaries.sort( + (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() + ); + } + + /** + * 加载指定会话 + */ + async loadSession(sessionId: string): Promise { + try { + const filePath = path.join(this.sessionsDir, `${sessionId}.json`); + const content = await fs.readFile(filePath, 'utf-8'); + return JSON.parse(content) as SessionData; + } catch { + return null; + } + } + + /** + * 删除指定会话 + */ + async deleteSession(sessionId: string): Promise { + try { + const filePath = path.join(this.sessionsDir, `${sessionId}.json`); + await fs.unlink(filePath); + return true; + } catch { + return false; + } + } + + /** + * 清理旧会话(保留最近 N 个) + */ + async cleanupOldSessions(keepCount: number = 50): Promise { + const sessions = await this.listSessions(); + if (sessions.length <= keepCount) { + return 0; + } + + const toDelete = sessions.slice(keepCount); + let deletedCount = 0; + + for (const session of toDelete) { + if (await this.deleteSession(session.id)) { + deletedCount++; + } + } + + return deletedCount; + } + + /** + * 从会话生成标题 + */ + private generateTitle(session: SessionData): string { + // 从第一条用户消息生成标题 + const firstUserMessage = session.messages.find((m) => m.role === 'user'); + if (firstUserMessage && typeof firstUserMessage.content === 'string') { + const content = firstUserMessage.content; + // 取前 50 个字符 + return content.length > 50 ? content.substring(0, 50) + '...' : content; + } + return `会话 ${session.id}`; + } + + /** + * 获取存储目录路径 + */ + getStorageDir(): string { + return this.storageDir; + } +} + +// 导出默认实例 +export const sessionStorage = new SessionStorage(); diff --git a/src/session/types.ts b/src/session/types.ts new file mode 100644 index 0000000..0b3167b --- /dev/null +++ b/src/session/types.ts @@ -0,0 +1,61 @@ +import type { ModelMessage } from 'ai'; + +/** + * 待办项状态 + */ +export type TodoStatus = 'pending' | 'in_progress' | 'completed'; + +/** + * 待办项 + */ +export interface Todo { + id: string; + content: string; + status: TodoStatus; + createdAt: string; + updatedAt: string; +} + +/** + * 会话数据(持久化存储格式) + */ +export interface SessionData { + /** 会话 ID */ + id: string; + /** 创建时间 */ + createdAt: string; + /** 最后更新时间 */ + updatedAt: string; + /** 工作目录 */ + workdir: string; + /** 会话标题(可选,从第一条消息生成) */ + title?: string; + /** 对话历史 */ + messages: ModelMessage[]; + /** 已发现的工具 */ + discoveredTools: string[]; + /** 待办事项 */ + todos: Todo[]; +} + +/** + * 会话摘要(用于列表展示) + */ +export interface SessionSummary { + id: string; + title: string; + workdir: string; + messageCount: number; + createdAt: string; + updatedAt: string; +} + +/** + * 会话管理器配置 + */ +export interface SessionManagerConfig { + /** 存储目录 */ + storageDir: string; + /** 最大历史会话数量 */ + maxHistorySessions?: number; +} diff --git a/src/tools/index.ts b/src/tools/index.ts index 52db516..85aad83 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -6,6 +6,7 @@ import { bashTool } from './shell/index.js'; // 核心工具 import { toolSearchTool } from './tool-search.js'; +import { todoReadTool, todoWriteTool } from './todo/index.js'; // 文件系统工具 import { @@ -27,6 +28,8 @@ const allToolsWithMetadata: ToolWithMetadata[] = [ // 核心工具 (deferLoading: false) toolSearchTool, bashTool, + todoReadTool, + todoWriteTool, // 文件系统工具 (deferLoading: true) readFileTool, @@ -48,6 +51,7 @@ toolRegistry.registerAll(allToolsWithMetadata); // 导出 export { toolRegistry } from './registry.js'; export { toolSearchTool } from './tool-search.js'; +export { todoManager } from './todo/index.js'; export type { ToolWithMetadata, ToolMetadata, ToolCategory, ToolSearchResult } from './types.js'; // 兼容旧代码:导出所有工具数组(基础 Tool 类型) diff --git a/src/tools/todo/index.ts b/src/tools/todo/index.ts new file mode 100644 index 0000000..53c80bf --- /dev/null +++ b/src/tools/todo/index.ts @@ -0,0 +1,3 @@ +export { todoReadTool } from './todoread.js'; +export { todoWriteTool } from './todowrite.js'; +export { todoManager } from './todo-manager.js'; diff --git a/src/tools/todo/todo-manager.ts b/src/tools/todo/todo-manager.ts new file mode 100644 index 0000000..76ac555 --- /dev/null +++ b/src/tools/todo/todo-manager.ts @@ -0,0 +1,106 @@ +import type { Todo, TodoStatus } from '../../session/types.js'; +import type { SessionManager } from '../../session/index.js'; + +/** + * Todo 管理器 + * 提供对当前会话 todo 列表的操作接口 + */ +class TodoManager { + private sessionManager: SessionManager | null = null; + + /** + * 设置会话管理器 + */ + setSessionManager(manager: SessionManager): void { + this.sessionManager = manager; + } + + /** + * 获取当前 todo 列表 + */ + getTodos(): Todo[] { + if (!this.sessionManager) { + return []; + } + return this.sessionManager.getTodos(); + } + + /** + * 更新 todo 列表 + */ + async setTodos(todos: Todo[]): Promise { + if (!this.sessionManager) { + return; + } + await this.sessionManager.setTodos(todos); + } + + /** + * 添加单个 todo + */ + async addTodo(content: string, status: TodoStatus = 'pending'): Promise { + const todos = this.getTodos(); + const now = new Date().toISOString(); + const newTodo: Todo = { + id: this.generateId(), + content, + status, + createdAt: now, + updatedAt: now, + }; + todos.push(newTodo); + await this.setTodos(todos); + return newTodo; + } + + /** + * 更新 todo 状态 + */ + async updateTodoStatus(id: string, status: TodoStatus): Promise { + const todos = this.getTodos(); + const todo = todos.find((t) => t.id === id); + if (!todo) return false; + + todo.status = status; + todo.updatedAt = new Date().toISOString(); + await this.setTodos(todos); + return true; + } + + /** + * 删除 todo + */ + async deleteTodo(id: string): Promise { + const todos = this.getTodos(); + const index = todos.findIndex((t) => t.id === id); + if (index === -1) return false; + + todos.splice(index, 1); + await this.setTodos(todos); + return true; + } + + /** + * 清空所有 todo + */ + async clearTodos(): Promise { + await this.setTodos([]); + } + + /** + * 生成唯一 ID + */ + private generateId(): string { + return Math.random().toString(36).substring(2, 10); + } + + /** + * 检查是否已初始化 + */ + isInitialized(): boolean { + return this.sessionManager !== null; + } +} + +// 导出单例 +export const todoManager = new TodoManager(); diff --git a/src/tools/todo/todoread.ts b/src/tools/todo/todoread.ts new file mode 100644 index 0000000..92a9291 --- /dev/null +++ b/src/tools/todo/todoread.ts @@ -0,0 +1,51 @@ +import type { ToolResult } from '../../types/index.js'; +import type { ToolWithMetadata } from '../types.js'; +import { todoManager } from './todo-manager.js'; + +export const todoReadTool: ToolWithMetadata = { + name: 'todoread', + description: `读取当前会话的待办事项列表。 + +使用场景: +- 在对话开始时查看待处理的任务 +- 开始新任务前了解当前进度 +- 用户询问之前的任务或计划时 +- 不确定下一步做什么时 +- 完成任务后更新对剩余工作的理解 +- 每隔几条消息检查一次以确保进度正常 + +返回格式: +- 返回 JSON 格式的待办事项列表 +- 每个事项包含 id、content(内容)、status(状态) +- 状态:pending(待处理)、in_progress(进行中)、completed(已完成)`, + metadata: { + name: 'todoread', + category: 'core', + description: '读取待办事项列表', + keywords: ['todo', 'task', 'list', 'read', '待办', '任务', '列表', '进度'], + deferLoading: false, // 核心工具,始终加载 + }, + parameters: {}, + execute: async (): Promise => { + if (!todoManager.isInitialized()) { + return { + success: false, + output: '', + error: '会话管理器未初始化,无法读取待办事项', + }; + } + + const todos = todoManager.getTodos(); + const pendingCount = todos.filter((t) => t.status !== 'completed').length; + + return { + success: true, + output: JSON.stringify(todos, null, 2), + metadata: { + todos, + pendingCount, + totalCount: todos.length, + }, + }; + }, +}; diff --git a/src/tools/todo/todowrite.ts b/src/tools/todo/todowrite.ts new file mode 100644 index 0000000..66818aa --- /dev/null +++ b/src/tools/todo/todowrite.ts @@ -0,0 +1,139 @@ +import type { ToolResult } from '../../types/index.js'; +import type { ToolWithMetadata } from '../types.js'; +import type { Todo, TodoStatus } from '../../session/types.js'; +import { todoManager } from './todo-manager.js'; + +/** + * 验证 todo 项 + */ +function validateTodo(item: unknown): item is { content: string; status: TodoStatus } { + if (typeof item !== 'object' || item === null) return false; + const obj = item as Record; + if (typeof obj.content !== 'string' || obj.content.trim() === '') return false; + if (!['pending', 'in_progress', 'completed'].includes(obj.status as string)) return false; + return true; +} + +export const todoWriteTool: ToolWithMetadata = { + name: 'todowrite', + description: `创建和管理当前会话的待办事项列表。用于跟踪进度、组织复杂任务,并向用户展示工作进展。 + +## 使用场景 + +主动使用此工具的情况: +1. 复杂多步骤任务 - 任务需要 3 个或更多步骤 +2. 非平凡的复杂任务 - 需要仔细规划或多个操作的任务 +3. 用户明确要求使用待办列表 +4. 用户提供多个任务 - 用户给出编号列表或逗号分隔的任务 +5. 收到新指令后 - 立即将用户需求记录为待办 +6. 开始处理任务时 - 将其标记为 in_progress +7. 完成任务后 - 标记为 completed 并添加发现的后续任务 + +## 不使用此工具的情况 + +跳过使用的情况: +1. 只有单个简单任务 +2. 任务太简单,跟踪没有意义 +3. 任务可以在 3 个简单步骤内完成 +4. 纯粹的对话或信息性请求 + +## 任务状态 + +- pending: 待处理,尚未开始 +- in_progress: 进行中(同一时间只能有一个) +- completed: 已完成 + +## 任务管理规则 + +- 实时更新任务状态 +- 完成后立即标记(不要批量标记) +- 同一时间只有一个任务处于 in_progress +- 完成当前任务后再开始新任务`, + metadata: { + name: 'todowrite', + category: 'core', + description: '创建和更新待办事项列表', + keywords: ['todo', 'task', 'write', 'update', 'create', '待办', '任务', '创建', '更新'], + deferLoading: false, // 核心工具,始终加载 + }, + parameters: { + todos: { + type: 'array', + description: + '更新后的待办事项列表。每个事项包含 content(任务内容)和 status(pending/in_progress/completed)', + required: true, + }, + }, + execute: async (params: Record): Promise => { + if (!todoManager.isInitialized()) { + return { + success: false, + output: '', + error: '会话管理器未初始化,无法更新待办事项', + }; + } + + const todosInput = params.todos; + if (!Array.isArray(todosInput)) { + return { + success: false, + output: '', + error: 'todos 参数必须是数组', + }; + } + + // 验证并转换输入 + const now = new Date().toISOString(); + const existingTodos = todoManager.getTodos(); + const existingMap = new Map(existingTodos.map((t) => [t.content, t])); + + const newTodos: Todo[] = []; + for (let i = 0; i < todosInput.length; i++) { + const item = todosInput[i]; + if (!validateTodo(item)) { + return { + success: false, + output: '', + error: `第 ${i + 1} 个待办事项格式无效。需要 { content: string, status: 'pending' | 'in_progress' | 'completed' }`, + }; + } + + // 查找是否已存在(通过内容匹配) + const existing = existingMap.get(item.content); + if (existing) { + // 更新现有项 + newTodos.push({ + ...existing, + status: item.status, + updatedAt: now, + }); + } else { + // 创建新项 + newTodos.push({ + id: Math.random().toString(36).substring(2, 10), + content: item.content, + status: item.status, + createdAt: now, + updatedAt: now, + }); + } + } + + await todoManager.setTodos(newTodos); + + const pendingCount = newTodos.filter((t) => t.status !== 'completed').length; + const completedCount = newTodos.filter((t) => t.status === 'completed').length; + const inProgressCount = newTodos.filter((t) => t.status === 'in_progress').length; + + return { + success: true, + output: `待办事项已更新: ${pendingCount} 待处理, ${inProgressCount} 进行中, ${completedCount} 已完成`, + metadata: { + todos: newTodos, + pendingCount, + inProgressCount, + completedCount, + }, + }; + }, +}; diff --git a/src/types/index.ts b/src/types/index.ts index 73ecfca..f023f56 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -18,6 +18,8 @@ export interface ToolResult { success: boolean; output: string; error?: string; + /** 额外的元数据(可选) */ + metadata?: Record; } // 工具定义(兼容 Vercel AI SDK 的 tool 格式) diff --git a/src/ui/terminal.ts b/src/ui/terminal.ts index 1e90aa5..f6c6cdb 100644 --- a/src/ui/terminal.ts +++ b/src/ui/terminal.ts @@ -50,7 +50,8 @@ export class TerminalUI { return true; case '/clear': - this.agent.clearHistory(); + // clearHistory 现在是异步的 + void this.agent.clearHistory(); console.log(chalk.green('✓ 对话历史已清空\n')); return true;