From c60f157b6f84a8b289a550b6b4435bc387d19654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 4 Jun 2026 14:57:41 +0800 Subject: [PATCH 01/24] =?UTF-8?q?init:=20=E6=90=AD=E5=BB=BA=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 ++ .gitignore | 3 +++ package.json | 17 +++++++++++++++++ src/index.ts | 1 + tsconfig.json | 7 +++++++ 5 files changed, 30 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2428ff7 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +OPENAI_API_KEY=sk-your-key-here +OPENAI_BASE_URL=https://api.openai.com/v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4b4711 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +.env +pnpm-lock.yaml \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..9c4262c --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "llm-to-agent", + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "tsx src/index.ts" + }, + "dependencies": { + "dotenv": "^16.x", + "openai": "^4.x" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d4463f3 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +console.log("Hello, LLM to Agent!"); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..057c24d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} \ No newline at end of file From 1accefc1daa739ebf4821b7aec8bac2e1b16b86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 5 Jun 2026 09:48:51 +0800 Subject: [PATCH 02/24] =?UTF-8?q?feat:=20=E7=AE=80=E5=8D=95=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index d4463f3..f40fca4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,19 @@ -console.log("Hello, LLM to Agent!"); \ No newline at end of file +import OpenAI from 'openai'; +import 'dotenv/config'; + +const client = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + baseURL: process.env.OPENAI_BASE_URL, +}); + +const response = await client.chat.completions.create({ + model: 'deepseek-v4-pro', + messages: [ + { role: 'system', content: '你是一个直爽的编程助手,回答尽量简洁。' }, + { role: 'user', content: '什么是闭包?用一句话解释。' }, + ], + temperature: 0.7, +}); + +const reply = response.choices[0]?.message?.content; +console.log(reply); \ No newline at end of file From bf8c9e1e12be84839ee00c21cec33daff1f9caa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 5 Jun 2026 10:39:38 +0800 Subject: [PATCH 03/24] =?UTF-8?q?feat:=20=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/context.ts | 16 ++++++++++++++++ src/index.ts | 36 +++++++++++++++++++++--------------- src/llm.ts | 16 ++++++++++++++++ 3 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 src/context.ts create mode 100644 src/llm.ts diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..9610f4f --- /dev/null +++ b/src/context.ts @@ -0,0 +1,16 @@ +type Message = { role: 'system' | 'user' | 'assistant'; content: string }; + +export class ContextManager { + private messages: Message[] = []; + + constructor() { + } + + add(message: Message) { + this.messages.push(message); + } + + getMessages(): Message[] { + return [...this.messages]; + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index f40fca4..f54a402 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,25 @@ -import OpenAI from 'openai'; -import 'dotenv/config'; +import { ContextManager } from './context.js'; +import { chat } from './llm.js'; +import * as readline from 'node:readline/promises'; -const client = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, - baseURL: process.env.OPENAI_BASE_URL, +const context = new ContextManager(); +context.add({ role: 'system', content: '你是一个直爽的小助手,回答尽量简洁。' }); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, }); -const response = await client.chat.completions.create({ - model: 'deepseek-v4-pro', - messages: [ - { role: 'system', content: '你是一个直爽的编程助手,回答尽量简洁。' }, - { role: 'user', content: '什么是闭包?用一句话解释。' }, - ], - temperature: 0.7, -}); +console.log('Agent已启动,输入 "exit" 退出。\n'); -const reply = response.choices[0]?.message?.content; -console.log(reply); \ No newline at end of file +while (true) { + const userInput = await rl.question('我: '); + if (userInput.toLowerCase() === 'exit') break; + + context.add({ role: 'user', content: userInput }); + const reply = await chat(context.getMessages()); + console.log('助手:', reply.content); + context.add({ role: 'assistant', content: reply.content! }); +} + +rl.close(); \ No newline at end of file diff --git a/src/llm.ts b/src/llm.ts new file mode 100644 index 0000000..bc444df --- /dev/null +++ b/src/llm.ts @@ -0,0 +1,16 @@ +import OpenAI from 'openai'; +import 'dotenv/config'; + +const client = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + baseURL: process.env.OPENAI_BASE_URL, +}); + +export async function chat(messages: { role: string; content: string }[]) { + const response = await client.chat.completions.create({ + model: 'deepseek-v4-pro', + messages: messages as any, + temperature: 0.7, + }); + return response.choices[0]!.message!; +} \ No newline at end of file From aa6ab0d379d8422ca40f1d768d2433c17da852b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 5 Jun 2026 10:49:39 +0800 Subject: [PATCH 04/24] =?UTF-8?q?feat:=20=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 13 +++++++++++-- src/prompts/system.ts | 9 +++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/prompts/system.ts diff --git a/src/index.ts b/src/index.ts index f54a402..57b7c01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,25 @@ import { ContextManager } from './context.js'; import { chat } from './llm.js'; +import { PROMPTS } from './prompts/system.js'; import * as readline from 'node:readline/promises'; +const promptName = process.argv[2] || 'default'; +const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; + +if (!systemPrompt) { + console.error(`未知提示词: ${promptName},可选: ${Object.keys(PROMPTS).join(', ')}`); + process.exit(1); +} + const context = new ContextManager(); -context.add({ role: 'system', content: '你是一个直爽的小助手,回答尽量简洁。' }); +context.add({ role: 'system', content: systemPrompt }); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); -console.log('Agent已启动,输入 "exit" 退出。\n'); +console.log(`提示词模式: ${promptName},输入 "exit" 退出。\n`); while (true) { const userInput = await rl.question('我: '); diff --git a/src/prompts/system.ts b/src/prompts/system.ts new file mode 100644 index 0000000..5d40546 --- /dev/null +++ b/src/prompts/system.ts @@ -0,0 +1,9 @@ +export const PROMPTS = { + default: '你是一个直爽的代码审查员,回答尽量简洁。', + toxic: '你是一个毒舌代码审查员,用讽刺的语气表达。', + json: `你是一个 API 格式化助手。你的回答必须是纯 JSON,不要加任何解释。 +{ + "answer": "你的回答", + "confidence": 0.0-1.0 之间的数字 +}`, +} as const; \ No newline at end of file From c2a906773f1c0308b3f788a0ebb5a8a4c93a44f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 5 Jun 2026 17:16:33 +0800 Subject: [PATCH 05/24] =?UTF-8?q?feat:=20=E8=B0=83=E7=94=A8=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 32 ++++++++++++++++++++++++++++++-- src/llm.ts | 6 +++++- src/prompts/system.ts | 2 ++ src/tools/calculator.ts | 22 ++++++++++++++++++++++ src/tools/registry.ts | 24 ++++++++++++++++++++++++ src/tools/weather.ts | 19 +++++++++++++++++++ src/types/index.ts | 14 ++++++++++++++ 7 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/tools/calculator.ts create mode 100644 src/tools/registry.ts create mode 100644 src/tools/weather.ts create mode 100644 src/types/index.ts diff --git a/src/index.ts b/src/index.ts index 57b7c01..9931b36 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import { ContextManager } from './context.js'; import { chat } from './llm.js'; import { PROMPTS } from './prompts/system.js'; +import { getOpenAITools, findTool } from './tools/registry.js'; import * as readline from 'node:readline/promises'; -const promptName = process.argv[2] || 'default'; +const promptName = process.argv[2] || 'agent'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; if (!systemPrompt) { @@ -26,7 +27,34 @@ while (true) { if (userInput.toLowerCase() === 'exit') break; context.add({ role: 'user', content: userInput }); - const reply = await chat(context.getMessages()); + + // 第一轮:带工具定义调用 + const tools = getOpenAITools(); + let reply = await chat(context.getMessages(), tools); + + // 如果模型要求调用工具,执行并在上下文中构造 tool 消息 + if (reply.tool_calls && reply.tool_calls.length > 0) { + // 先把 assistant 消息(含 tool_calls)加入上下文 + context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); + + for (const tc of reply.tool_calls) { + const tool = findTool(tc.function.name); + const args = JSON.parse(tc.function.arguments); + const result = await tool!.execute(args); + console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); + + // tool 消息:必须回传 tool_call_id + context.add({ + role: 'tool', + tool_call_id: tc.id, + content: result, + } as any); + } + + // 继续调用,看模型还需要不需要更多工具 + reply = await chat(context.getMessages(), tools); + } + console.log('助手:', reply.content); context.add({ role: 'assistant', content: reply.content! }); } diff --git a/src/llm.ts b/src/llm.ts index bc444df..8409ffa 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -6,11 +6,15 @@ const client = new OpenAI({ baseURL: process.env.OPENAI_BASE_URL, }); -export async function chat(messages: { role: string; content: string }[]) { +export async function chat( + messages: { role: string; content: string }[], + tools?: any[] // OpenAI 格式的工具定义数组 +) { const response = await client.chat.completions.create({ model: 'deepseek-v4-pro', messages: messages as any, temperature: 0.7, + ...(tools && { tools }), }); return response.choices[0]!.message!; } \ No newline at end of file diff --git a/src/prompts/system.ts b/src/prompts/system.ts index 5d40546..d99bef1 100644 --- a/src/prompts/system.ts +++ b/src/prompts/system.ts @@ -6,4 +6,6 @@ export const PROMPTS = { "answer": "你的回答", "confidence": 0.0-1.0 之间的数字 }`, + agent: `你是一个有工具调用能力的助手。当需要查询信息或执行计算时,请使用提供的工具。 +如果不需要工具,直接回答即可。回答简洁。`, } as const; \ No newline at end of file diff --git a/src/tools/calculator.ts b/src/tools/calculator.ts new file mode 100644 index 0000000..fb22ec0 --- /dev/null +++ b/src/tools/calculator.ts @@ -0,0 +1,22 @@ +import { Tool } from '../types/index.js'; + +export const calculatorTool: Tool = { + name: 'calculate', + description: '执行数学计算,支持加减乘除和括号', + parameters: { + type: 'object', + properties: { + expression: { type: 'string', description: '数学表达式,如"2+3*4"' }, + }, + required: ['expression'], + }, + execute: async (args) => { + try { + // 安全警告:生产环境绝不可以用 eval + const result = eval(args.expression); + return `${args.expression} = ${result}`; + } catch (e: any) { + return `计算错误: ${e.message}`; + } + }, +}; \ No newline at end of file diff --git a/src/tools/registry.ts b/src/tools/registry.ts new file mode 100644 index 0000000..c8d5a75 --- /dev/null +++ b/src/tools/registry.ts @@ -0,0 +1,24 @@ +import { weatherTool } from './weather.js'; +import { calculatorTool } from './calculator.js'; +import { Tool } from '../types/index.js'; + +const tools: Tool[] = [weatherTool, calculatorTool]; + +export function getTools(): Tool[] { + return tools; +} + +export function getOpenAITools() { + return tools.map((t) => ({ + type: 'function' as const, + function: { + name: t.name, + description: t.description, + parameters: t.parameters, + }, + })); +} + +export function findTool(name: string): Tool | undefined { + return tools.find((t) => t.name === name); +} \ No newline at end of file diff --git a/src/tools/weather.ts b/src/tools/weather.ts new file mode 100644 index 0000000..3dcf11b --- /dev/null +++ b/src/tools/weather.ts @@ -0,0 +1,19 @@ +import { Tool } from '../types/index.js'; + +export const weatherTool: Tool = { + name: 'get_weather', + description: '获取指定城市的当前天气信息', + parameters: { + type: 'object', + properties: { + city: { type: 'string', description: '城市名称,如"北京"、"上海"' }, + }, + required: ['city'], + }, + execute: async (args) => { + // 模拟异步 API 调用 + const weathers = ['晴', '多云', '小雨', '阴天']; + const picked = weathers[Math.floor(Math.random() * weathers.length)]; + return `城市:${args.city},天气:${picked},温度:${Math.floor(Math.random() * 15 + 15)}°C`; + }, +}; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..1c30214 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,14 @@ +export interface Tool { + name: string; + description: string; + parameters: { + type: 'object'; + properties: Record; + required: string[]; + }; + execute: (args: Record) => Promise | string; +} \ No newline at end of file From a0023c49f49a1c1f6127a5dd3e50e307d94bb453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Mon, 8 Jun 2026 17:34:29 +0800 Subject: [PATCH 06/24] =?UTF-8?q?feat:=20=E8=87=AA=E4=B8=BB=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 4 ++-- src/prompts/reAct.ts | 16 ++++++++++++++++ src/prompts/system.ts | 6 +++++- src/tools/guess.ts | 20 ++++++++++++++++++++ src/tools/registry.ts | 3 ++- 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/prompts/reAct.ts create mode 100644 src/tools/guess.ts diff --git a/src/index.ts b/src/index.ts index 9931b36..13fc33a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { PROMPTS } from './prompts/system.js'; import { getOpenAITools, findTool } from './tools/registry.js'; import * as readline from 'node:readline/promises'; -const promptName = process.argv[2] || 'agent'; +const promptName = process.argv[2] || 'reAct'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; if (!systemPrompt) { @@ -33,7 +33,7 @@ while (true) { let reply = await chat(context.getMessages(), tools); // 如果模型要求调用工具,执行并在上下文中构造 tool 消息 - if (reply.tool_calls && reply.tool_calls.length > 0) { + while (reply.tool_calls && reply.tool_calls.length > 0) { // 先把 assistant 消息(含 tool_calls)加入上下文 context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); diff --git a/src/prompts/reAct.ts b/src/prompts/reAct.ts new file mode 100644 index 0000000..ad64baf --- /dev/null +++ b/src/prompts/reAct.ts @@ -0,0 +1,16 @@ +export const REACT_SYSTEM_PROMPT = `你是一个自主智能体,能够使用工具来完成目标。 + +## 工作方式 +你需要反复执行以下步骤,直到目标完成: + +1. **思考**:分析当前状态,决定下一步行动。 +2. **行动**:调用一个工具,或者给出最终答案。 + +## 行动格式 +- 如果需要调用工具,只返回工具调用的 JSON,不要其他内容。 + +## 重要规则 +- 如果工具返回了错误,分析错误并尝试修复,不要重复相同的错误调用。 +- 如果连续三次调用没有进展,给出最终答案并说明遇到困难。 +- 诚实:如果无法完成,直接说明,不要编造。 +- 使用中文回复。`; \ No newline at end of file diff --git a/src/prompts/system.ts b/src/prompts/system.ts index d99bef1..8e2a0ab 100644 --- a/src/prompts/system.ts +++ b/src/prompts/system.ts @@ -1,3 +1,5 @@ +import { REACT_SYSTEM_PROMPT } from './reAct.js'; + export const PROMPTS = { default: '你是一个直爽的代码审查员,回答尽量简洁。', toxic: '你是一个毒舌代码审查员,用讽刺的语气表达。', @@ -6,6 +8,8 @@ export const PROMPTS = { "answer": "你的回答", "confidence": 0.0-1.0 之间的数字 }`, - agent: `你是一个有工具调用能力的助手。当需要查询信息或执行计算时,请使用提供的工具。 + agent: `你是一个有工具调用能力的助手。当需要查询信息/执行计算或者玩猜谜游戏时,请使用提供的工具。 +如果调用了工具,根据工具执行结果给出答案。 如果不需要工具,直接回答即可。回答简洁。`, + reAct: REACT_SYSTEM_PROMPT, } as const; \ No newline at end of file diff --git a/src/tools/guess.ts b/src/tools/guess.ts new file mode 100644 index 0000000..49088ca --- /dev/null +++ b/src/tools/guess.ts @@ -0,0 +1,20 @@ +import { Tool } from '../types/index.js'; + +export const guessTool: Tool = { + name: 'guess_number', + description: '猜一个1到100之间的整数。返回“大了”、“小了”或“猜对了”。', + parameters: { + type: 'object', + properties: { + number: { type: 'number', description: '你猜的数字' }, + }, + required: ['number'], + }, + execute: async (args) => { + // 答案写死在工具内部,模型绝对不知道 + const answer = 67; + const guess = args.number; + if (guess === answer) return '猜对了!'; + return guess > answer ? '大了' : '小了'; + }, +}; \ No newline at end of file diff --git a/src/tools/registry.ts b/src/tools/registry.ts index c8d5a75..0a74703 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -1,8 +1,9 @@ import { weatherTool } from './weather.js'; import { calculatorTool } from './calculator.js'; +import { guessTool } from './guess.js'; import { Tool } from '../types/index.js'; -const tools: Tool[] = [weatherTool, calculatorTool]; +const tools: Tool[] = [weatherTool, calculatorTool, guessTool]; export function getTools(): Tool[] { return tools; From e7207400e4757471e760ddfb7d744d91ccc7c5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Wed, 10 Jun 2026 17:11:56 +0800 Subject: [PATCH 07/24] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=92=A9?= =?UTF-8?q?=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 23 +++++++++++++++++++++++ src/hooks/log.hooks.ts | 26 ++++++++++++++++++++++++++ src/hooks/registry.ts | 29 +++++++++++++++++++++++++++++ src/index.ts | 15 +++++++++++---- 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/log.hooks.ts create mode 100644 src/hooks/registry.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..a8c0c21 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,23 @@ +type EventName = 'process:start' | 'agent:start' | 'step:before' | 'tool:before' | 'tool:after' | 'agent:end'; +type Listener = (data: any) => void | Promise; + +export class HookBus { + private listeners = new Map(); + + on(event: EventName, fn: Listener) { + if (!this.listeners.has(event)) { + this.listeners.set(event, []); + } + this.listeners.get(event)!.push(fn); + } + + async emit(event: EventName, data: any) { + const fns = this.listeners.get(event); + if (!fns) return; + for (const fn of fns) { + await fn(data); + } + } +} + +export const hooks = new HookBus(); diff --git a/src/hooks/log.hooks.ts b/src/hooks/log.hooks.ts new file mode 100644 index 0000000..06429e0 --- /dev/null +++ b/src/hooks/log.hooks.ts @@ -0,0 +1,26 @@ +import type { HookBus } from './index.js'; + +export default (hooks: HookBus) => { + // hooks.on('agent:start', async (data) => { + // console.log('🚀 ~ agent:start ~ data:', data); + // }); + + // hooks.on('step:before', async (data) => { + // console.log('🚀 ~ step:before ~ data:', data); + // }); + + hooks.on('tool:before', async (data) => { + const { toolCall: tc, result } = data; + console.log(` [工具调用] ${tc.function.name}(${tc.function.arguments})`); + }); + + hooks.on('tool:after', async (data) => { + const { toolCall: tc, result } = data; + console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); + }); + + hooks.on('agent:end', async (data) => { + const { reply } = data; + console.log('助手:', reply.content); + }); +} \ No newline at end of file diff --git a/src/hooks/registry.ts b/src/hooks/registry.ts new file mode 100644 index 0000000..4ecc961 --- /dev/null +++ b/src/hooks/registry.ts @@ -0,0 +1,29 @@ +// 从内置hooks目录中获取所有hooks并注册到HookBus + +import path from 'node:path'; +import fs from 'node:fs'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { hooks } from './index.js'; + +// 这里可以自动扫描hooks目录下的所有文件并导入它们,假设每个文件都默认导出一个函数来注册hook + +const getAllHooks = async () => { + // 动态读取hooks目录下的所有 .hooks.ts 结尾的文件 + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + const hooksDir = path.join(__dirname); // hooks 目录即当前目录 + const hookFiles = fs.readdirSync(hooksDir).filter(file => file.endsWith('.hooks.ts')); + return Promise.all( + hookFiles.map(async (file) => { + const mod = await import(pathToFileURL(path.join(hooksDir, file)).href); + return mod.default; + }) + ); +} + +export const registerHooks = async () => { + const hookModules = await getAllHooks(); + for (const hookModule of hookModules) { + // 每个hook模块默认导出一个函数,调用它并传入hooks实例 + hookModule(hooks); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 13fc33a..9e94ad2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,10 @@ import { chat } from './llm.js'; import { PROMPTS } from './prompts/system.js'; import { getOpenAITools, findTool } from './tools/registry.js'; import * as readline from 'node:readline/promises'; +import { hooks } from './hooks/index.js'; +import { registerHooks } from './hooks/registry.js'; + +await registerHooks(); const promptName = process.argv[2] || 'reAct'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; @@ -27,6 +31,8 @@ while (true) { if (userInput.toLowerCase() === 'exit') break; context.add({ role: 'user', content: userInput }); + + hooks.emit('agent:start', { userInput }); // 第一轮:带工具定义调用 const tools = getOpenAITools(); @@ -34,15 +40,16 @@ while (true) { // 如果模型要求调用工具,执行并在上下文中构造 tool 消息 while (reply.tool_calls && reply.tool_calls.length > 0) { + hooks.emit('step:before', { userInput, reply }); // 先把 assistant 消息(含 tool_calls)加入上下文 context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); for (const tc of reply.tool_calls) { + hooks.emit('tool:before', { toolCall: tc }); const tool = findTool(tc.function.name); const args = JSON.parse(tc.function.arguments); const result = await tool!.execute(args); - console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); - + hooks.emit('tool:after', { toolCall: tc, result }); // tool 消息:必须回传 tool_call_id context.add({ role: 'tool', @@ -54,8 +61,8 @@ while (true) { // 继续调用,看模型还需要不需要更多工具 reply = await chat(context.getMessages(), tools); } - - console.log('助手:', reply.content); + + hooks.emit('agent:end', { userInput, reply }); context.add({ role: 'assistant', content: reply.content! }); } From a99ace883162eb68c656ef976a0566c561d66aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 11 Jun 2026 18:05:04 +0800 Subject: [PATCH 08/24] =?UTF-8?q?feat:=20=E5=AD=90=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agents/agent.ts | 92 +++++++++++++++++++++++++++++++++++++ src/agents/index.ts | 4 ++ src/agents/manager.ts | 90 ++++++++++++++++++++++++++++++++++++ src/agents/tools.ts | 57 +++++++++++++++++++++++ src/hooks/log.hooks.ts | 14 ++++-- src/index.ts | 49 +++++--------------- src/prompts/orchestrator.ts | 18 ++++++++ src/prompts/system.ts | 2 + src/tools/registry.ts | 18 ++++++-- 9 files changed, 298 insertions(+), 46 deletions(-) create mode 100644 src/agents/agent.ts create mode 100644 src/agents/index.ts create mode 100644 src/agents/manager.ts create mode 100644 src/agents/tools.ts create mode 100644 src/prompts/orchestrator.ts diff --git a/src/agents/agent.ts b/src/agents/agent.ts new file mode 100644 index 0000000..61f5766 --- /dev/null +++ b/src/agents/agent.ts @@ -0,0 +1,92 @@ +import { ContextManager } from '../context.js'; +import { chat } from '../llm.js'; +import { getTools } from '../tools/registry.js'; +import { Tool } from '../types/index.js'; +import { hooks } from '../hooks/index.js'; + +export interface AgentConfig { + name: string; + systemPrompt: string; + /** 该 Agent 可用的工具列表,默认使用全局注册的所有工具 */ + tools?: Tool[]; +} + +export class Agent { + public name: string; + private context: ContextManager; + private tools: Tool[]; + + constructor(config: AgentConfig) { + this.name = config.name; + this.tools = config.tools ?? getTools(); + this.context = new ContextManager(); + this.context.add({ role: 'system', content: config.systemPrompt }); + } + + /** + * 运行 Agent 循环:发送消息 → 处理 tool_calls → 返回最终回复 + */ + async run(userMessage: string): Promise { + this.context.add({ role: 'user', content: userMessage }); + + const openaiTools = this.tools.length > 0 + ? this.tools.map((t) => ({ + type: 'function' as const, + function: { + name: t.name, + description: t.description, + parameters: t.parameters, + }, + })) + : undefined; + + let reply = await chat(this.context.getMessages(), openaiTools); + + // 工具调用循环 + while (reply.tool_calls && reply.tool_calls.length > 0) { + this.context.add({ + role: 'assistant', + content: '', + tool_calls: reply.tool_calls, + } as any); + + for (const tc of reply.tool_calls) { + const tool = this.tools.find((t) => t.name === tc.function.name); + if (!tool) { + this.context.add({ + role: 'tool', + tool_call_id: tc.id, + content: `错误: 未知工具 ${tc.function.name}`, + } as any); + continue; + } + + hooks.emit('tool:before', { toolCall: tc, agentName: this.name }); + + try { + const args = JSON.parse(tc.function.arguments); + const result = await tool.execute(args); + this.context.add({ + role: 'tool', + tool_call_id: tc.id, + content: result, + } as any); + hooks.emit('tool:after', { toolCall: tc, result, agentName: this.name }); + } catch (e: any) { + this.context.add({ + role: 'tool', + tool_call_id: tc.id, + content: `工具执行错误: ${e.message}`, + } as any); + hooks.emit('tool:after', { toolCall: tc, result: `错误: ${e.message}`, agentName: this.name }); + } + } + + reply = await chat(this.context.getMessages(), openaiTools); + } + + const content = reply.content!; + this.context.add({ role: 'assistant', content }); + return content; + } +} diff --git a/src/agents/index.ts b/src/agents/index.ts new file mode 100644 index 0000000..f2bf247 --- /dev/null +++ b/src/agents/index.ts @@ -0,0 +1,4 @@ +export { Agent } from './agent.js'; +export type { AgentConfig } from './agent.js'; +export { SubAgentManager, subAgentManager } from './manager.js'; +export { spawnAgentTool, runConversationTool, subAgentTools } from './tools.js'; diff --git a/src/agents/manager.ts b/src/agents/manager.ts new file mode 100644 index 0000000..57781ba --- /dev/null +++ b/src/agents/manager.ts @@ -0,0 +1,90 @@ +import { Agent } from './agent.js'; +import { getTools } from '../tools/registry.js'; + +/** + * SubAgentManager 单例 — 管理所有子 Agent 的创建、查询和多轮对话编排 + */ +export class SubAgentManager { + private agents: Map = new Map(); + + /** + * 创建一个子 Agent(只有普通工具,没有 sub-agent 管理工具,防止无限递归) + */ + spawn(name: string, systemPrompt: string): Agent { + if (this.agents.has(name)) { + throw new Error(`子 Agent "${name}" 已存在`); + } + const agent = new Agent({ + name, + systemPrompt, + tools: getTools(), // 只有全局注册的普通工具 + }); + this.agents.set(name, agent); + return agent; + } + + get(name: string): Agent | undefined { + return this.agents.get(name); + } + + list(): Agent[] { + return Array.from(this.agents.values()); + } + + /** + * 运行两个 Agent 之间的多轮对话 + * @param agent1Name 发起方 Agent 名称 + * @param agent2Name 回应方 Agent 名称 + * @param maxTurns 最大对话轮次(一轮 = agent1 发言 + agent2 回应) + * @param topic 对话主题 + * @returns 完整对话记录 + */ + async runConversation( + agent1Name: string, + agent2Name: string, + maxTurns: number, + topic: string, + ): Promise { + const agent1 = this.agents.get(agent1Name); + const agent2 = this.agents.get(agent2Name); + + if (!agent1) { + return `错误: 子 Agent "${agent1Name}" 不存在。可用的 Agent: ${this.listNames()}`; + } + if (!agent2) { + return `错误: 子 Agent "${agent2Name}" 不存在。可用的 Agent: ${this.listNames()}`; + } + + const transcript: string[] = []; + transcript.push(`=== 对话开始: ${agent1Name} vs ${agent2Name},主题: ${topic},轮次: ${maxTurns} ===\n`); + + // 第一轮:agent1 发起对话 + let currentMessage = `请就以下话题开始对话:${topic}。你是对话的发起方,请先发言。`; + let speaker = agent1; + let listener = agent2; + + for (let turn = 1; turn <= maxTurns; turn++) { + // 当前发言者回复 + const response = await speaker.run(currentMessage); + const line = `[${speaker.name}]: ${response}`; + console.log(line); + transcript.push(line); + + // 将回复传给另一方 + currentMessage = `[${speaker.name}]: ${response}\n请回复。`; + + // 交换发言者 + [speaker, listener] = [listener, speaker]; + } + + transcript.push(`\n=== 对话结束 ===`); + return transcript.join('\n'); + } + + private listNames(): string { + return Array.from(this.agents.keys()).join(', ') || '(无)'; + } +} + +/** 全局单例 */ +export const subAgentManager = new SubAgentManager(); diff --git a/src/agents/tools.ts b/src/agents/tools.ts new file mode 100644 index 0000000..2d576ce --- /dev/null +++ b/src/agents/tools.ts @@ -0,0 +1,57 @@ +import { Tool } from '../types/index.js'; +import { subAgentManager } from './manager.js'; + +/** + * spawn_agent — 创建一个指定角色和名称的子 Agent + */ +export const spawnAgentTool: Tool = { + name: 'spawn_agent', + description: '创建一个子Agent,指定其名称和角色/系统提示词。用于创建具有特定人设的对话角色(如销售、顾客等)。', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: '子Agent的唯一名称,如"sales"、"customer"' }, + role_prompt: { type: 'string', description: '子Agent的角色描述/系统提示词,如"你是一个热情的汽车销售"' }, + }, + required: ['name', 'role_prompt'], + }, + execute: async (args) => { + const { name, role_prompt } = args; + try { + const agent = subAgentManager.spawn(name as string, role_prompt as string); + return `子Agent "${agent.name}" 创建成功。`; + } catch (e: any) { + return `创建失败: ${e.message}`; + } + }, +}; + +/** + * run_conversation — 运行两个子 Agent 之间的多轮对话 + */ +export const runConversationTool: Tool = { + name: 'run_conversation', + description: '在两个已创建的子Agent之间运行多轮对话。一方先发起,另一方回应,交替进行。返回完整对话记录。', + parameters: { + type: 'object', + properties: { + agent1: { type: 'string', description: '发起方Agent名称(先说话的那个)' }, + agent2: { type: 'string', description: '回应方Agent名称' }, + max_turns: { type: 'number', description: '最大对话轮次,如5表示agent1发起 + 4轮交替 = 共5次发言' }, + topic: { type: 'string', description: '对话主题/场景描述,如"汽车购买谈判"' }, + }, + required: ['agent1', 'agent2', 'max_turns', 'topic'], + }, + execute: async (args) => { + const { agent1, agent2, max_turns, topic } = args; + return await subAgentManager.runConversation( + agent1 as string, + agent2 as string, + max_turns as number, + topic as string, + ); + }, +}; + +/** 所有 sub-agent 管理工具(仅供主 Agent 使用) */ +export const subAgentTools: Tool[] = [spawnAgentTool, runConversationTool]; diff --git a/src/hooks/log.hooks.ts b/src/hooks/log.hooks.ts index 06429e0..0da9020 100644 --- a/src/hooks/log.hooks.ts +++ b/src/hooks/log.hooks.ts @@ -10,17 +10,21 @@ export default (hooks: HookBus) => { // }); hooks.on('tool:before', async (data) => { - const { toolCall: tc, result } = data; - console.log(` [工具调用] ${tc.function.name}(${tc.function.arguments})`); + const { toolCall: tc, agentName } = data; + const prefix = agentName ? `[${agentName}] ` : ''; + console.log(` ${prefix}[工具调用] ${tc.function.name}(${tc.function.arguments})`); }); hooks.on('tool:after', async (data) => { - const { toolCall: tc, result } = data; - console.log(` [工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); + const { toolCall: tc, result, agentName } = data; + const prefix = agentName ? `[${agentName}] ` : ''; + console.log(` ${prefix}[工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); }); hooks.on('agent:end', async (data) => { const { reply } = data; - console.log('助手:', reply.content); + // reply 可能是字符串(Agent.run 返回值)或 OpenAI message 对象 + const content = typeof reply === 'string' ? reply : reply.content; + console.log('助手:', content); }); } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9e94ad2..0a06b67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,13 @@ -import { ContextManager } from './context.js'; -import { chat } from './llm.js'; +import { Agent } from './agents/index.js'; +import { getOrchestratorTools } from './tools/registry.js'; import { PROMPTS } from './prompts/system.js'; -import { getOpenAITools, findTool } from './tools/registry.js'; import * as readline from 'node:readline/promises'; import { hooks } from './hooks/index.js'; import { registerHooks } from './hooks/registry.js'; await registerHooks(); -const promptName = process.argv[2] || 'reAct'; +const promptName = process.argv[2] || 'orchestrator'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; if (!systemPrompt) { @@ -16,8 +15,12 @@ if (!systemPrompt) { process.exit(1); } -const context = new ContextManager(); -context.add({ role: 'system', content: systemPrompt }); +// 主 orchestrator Agent,拥有全部工具(包括 spawn_agent / run_conversation) +const mainAgent = new Agent({ + name: 'Orchestrator', + systemPrompt, + tools: getOrchestratorTools(), +}); const rl = readline.createInterface({ input: process.stdin, @@ -30,40 +33,12 @@ while (true) { const userInput = await rl.question('我: '); if (userInput.toLowerCase() === 'exit') break; - context.add({ role: 'user', content: userInput }); - hooks.emit('agent:start', { userInput }); - - // 第一轮:带工具定义调用 - const tools = getOpenAITools(); - let reply = await chat(context.getMessages(), tools); - // 如果模型要求调用工具,执行并在上下文中构造 tool 消息 - while (reply.tool_calls && reply.tool_calls.length > 0) { - hooks.emit('step:before', { userInput, reply }); - // 先把 assistant 消息(含 tool_calls)加入上下文 - context.add({ role: 'assistant', content: '', tool_calls: reply.tool_calls } as any); - - for (const tc of reply.tool_calls) { - hooks.emit('tool:before', { toolCall: tc }); - const tool = findTool(tc.function.name); - const args = JSON.parse(tc.function.arguments); - const result = await tool!.execute(args); - hooks.emit('tool:after', { toolCall: tc, result }); - // tool 消息:必须回传 tool_call_id - context.add({ - role: 'tool', - tool_call_id: tc.id, - content: result, - } as any); - } - - // 继续调用,看模型还需要不需要更多工具 - reply = await chat(context.getMessages(), tools); - } + const reply = await mainAgent.run(userInput); hooks.emit('agent:end', { userInput, reply }); - context.add({ role: 'assistant', content: reply.content! }); + // agent:end hook 会打印回复,这里不需要重复打印 } -rl.close(); \ No newline at end of file +rl.close(); diff --git a/src/prompts/orchestrator.ts b/src/prompts/orchestrator.ts new file mode 100644 index 0000000..993ed8b --- /dev/null +++ b/src/prompts/orchestrator.ts @@ -0,0 +1,18 @@ +export const ORCHESTRATOR_PROMPT = `你是一个智能编排助手,能够创建子Agent并编排它们之间的多轮对话。 + +## 你拥有的工具 +1. **spawn_agent** — 创建一个子Agent,需要指定名称和角色描述。用于根据用户需求创建具有特定人设的角色(如销售、顾客、谈判者等)。 +2. **run_conversation** — 在两个已创建的子Agent之间运行多轮对话。需要指定发起方、回应方、对话轮次和主题。 + +## 工作流程 +当用户要求创建Agent并进行对话时: +1. 分析用户需求,确定需要哪些角色 +2. 使用 spawn_agent 分别创建每个子Agent,为它们编写合适的角色描述/系统提示词 +3. 使用 run_conversation 运行对话,设定合理的轮次和主题 +4. 对话结束后,根据对话记录进行简要总结 + +## 重要规则 +- 子Agent的角色提示词要具体、生动,包含角色背景、性格特点和目标 +- 对话轮次根据用户要求设定,默认5-10轮 +- 总结时聚焦关键转折点、各方策略和最终结果 +- 使用中文回复`; diff --git a/src/prompts/system.ts b/src/prompts/system.ts index 8e2a0ab..29c6e81 100644 --- a/src/prompts/system.ts +++ b/src/prompts/system.ts @@ -1,4 +1,5 @@ import { REACT_SYSTEM_PROMPT } from './reAct.js'; +import { ORCHESTRATOR_PROMPT } from './orchestrator.js'; export const PROMPTS = { default: '你是一个直爽的代码审查员,回答尽量简洁。', @@ -12,4 +13,5 @@ export const PROMPTS = { 如果调用了工具,根据工具执行结果给出答案。 如果不需要工具,直接回答即可。回答简洁。`, reAct: REACT_SYSTEM_PROMPT, + orchestrator: ORCHESTRATOR_PROMPT, } as const; \ No newline at end of file diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 0a74703..2356b9c 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -2,15 +2,25 @@ import { weatherTool } from './weather.js'; import { calculatorTool } from './calculator.js'; import { guessTool } from './guess.js'; import { Tool } from '../types/index.js'; +import { subAgentTools } from '../agents/tools.js'; -const tools: Tool[] = [weatherTool, calculatorTool, guessTool]; +const baseTools: Tool[] = [weatherTool, calculatorTool, guessTool]; +/** 所有工具(基础工具 + sub-agent 管理工具),供主 orchestrator Agent 使用 */ +const allTools: Tool[] = [...baseTools, ...subAgentTools]; + +/** 返回基础工具列表(供子 Agent 使用,不含 sub-agent 管理工具以防递归) */ export function getTools(): Tool[] { - return tools; + return baseTools; +} + +/** 返回全部工具列表(供主 orchestrator Agent 使用) */ +export function getOrchestratorTools(): Tool[] { + return allTools; } export function getOpenAITools() { - return tools.map((t) => ({ + return baseTools.map((t) => ({ type: 'function' as const, function: { name: t.name, @@ -21,5 +31,5 @@ export function getOpenAITools() { } export function findTool(name: string): Tool | undefined { - return tools.find((t) => t.name === name); + return allTools.find((t) => t.name === name); } \ No newline at end of file From a6924ebf483400222fe3d92243c7c5ac2a59f31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 3 Jul 2026 17:23:53 +0800 Subject: [PATCH 09/24] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=9F=A5?= =?UTF-8?q?=E8=AF=86=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- knowledge/llm-to-agent-guide.md | 60 +++++++++++++++ src/index.ts | 2 + src/knowledge/knowledge-base.ts | 128 ++++++++++++++++++++++++++++++++ src/knowledge/search-tool.ts | 53 +++++++++++++ src/tools/registry.ts | 3 +- 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 knowledge/llm-to-agent-guide.md create mode 100644 src/knowledge/knowledge-base.ts create mode 100644 src/knowledge/search-tool.ts diff --git a/knowledge/llm-to-agent-guide.md b/knowledge/llm-to-agent-guide.md new file mode 100644 index 0000000..9a41a5a --- /dev/null +++ b/knowledge/llm-to-agent-guide.md @@ -0,0 +1,60 @@ +# LLM-to-Agent 框架知识库 + +## Agent 工具调用流程 + +Agent 的 `run()` 方法执行以下循环: +1. 将用户消息加入上下文 +2. 调用 LLM,传入可用工具列表 +3. 如果 LLM 返回 `tool_calls`,逐个执行工具,将结果加入上下文,回到步骤 2 +4. 如果 LLM 直接返回文本,结束循环,返回最终回复 + +工具定义需包含 `name`、`description`、`parameters`(JSON Schema)和 `execute` 函数。 + +## 子 Agent 管理 + +通过 `spawn_agent` 工具可以创建具有独立人设的子 Agent(如销售、顾客)。 +每个子 Agent 拥有独立的上下文和基础工具集(不含 sub-agent 管理工具,防止无限递归)。 +通过 `run_conversation` 工具可以在两个子 Agent 之间运行多轮对话。 + +## 知识库功能 + +知识库位于 `knowledge/` 目录,支持 `.md` 格式文档。 +文档按 `##` 二级标题自动分块,通过 `search_knowledge` 工具进行关键词检索。 +检索算法基于关键词命中次数,标题命中加权 ×3。 +也提供 `list_knowledge` 工具查看知识库全貌。 + +## 常用命令 + +- 启动框架:`pnpm dev` +- 指定提示词模式:`pnpm dev orchestrator` +- 可用模式:`default`、`toxic`、`json`、`agent`、`reAct`、`orchestrator` + +## 项目结构 + +``` +src/ + index.ts -- CLI 入口 + llm.ts -- LLM 客户端(OpenAI 兼容) + context.ts -- 上下文管理器 + types/index.ts -- 类型定义 + agents/ + agent.ts -- Agent 核心类 + manager.ts -- 子 Agent 管理器 + tools.ts -- spawn_agent / run_conversation 工具 + tools/ + registry.ts -- 工具注册表 + weather.ts -- 天气查询工具 + calculator.ts -- 计算器工具 + guess.ts -- 猜数字游戏工具 + knowledge/ + knowledge-base.ts -- 知识库类 + search-tool.ts -- 知识库搜索工具 + hooks/ + index.ts -- Hook 总线 + log.hooks.ts -- 日志 Hook + registry.ts -- Hook 注册 + prompts/ + system.ts -- 系统提示词 + orchestrator.ts -- Orchestrator 提示词 + reAct.ts -- ReAct 提示词 +``` diff --git a/src/index.ts b/src/index.ts index 0a06b67..560954b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,10 @@ import { PROMPTS } from './prompts/system.js'; import * as readline from 'node:readline/promises'; import { hooks } from './hooks/index.js'; import { registerHooks } from './hooks/registry.js'; +import { knowledgeBase } from './knowledge/knowledge-base.js'; await registerHooks(); +await knowledgeBase.load(); const promptName = process.argv[2] || 'orchestrator'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; diff --git a/src/knowledge/knowledge-base.ts b/src/knowledge/knowledge-base.ts new file mode 100644 index 0000000..afd6b64 --- /dev/null +++ b/src/knowledge/knowledge-base.ts @@ -0,0 +1,128 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +/** 知识库中的一个文档块 */ +interface Chunk { + /** 来源文件名 */ + source: string; + /** 块内文本 */ + content: string; +} + +/** + * 轻量知识库:从 knowledge/ 目录加载 .md 文件,按 ## 标题分块, + * 提供基于关键词匹配的检索能力。 + * + * 用法: + * const kb = new KnowledgeBase('./knowledge'); + * await kb.load(); + * const results = kb.search('Agent 工具调用'); + */ +export class KnowledgeBase { + private chunks: Chunk[] = []; + private knowledgeDir: string; + + constructor(knowledgeDir: string) { + this.knowledgeDir = knowledgeDir; + } + + /** 加载 knowledgeDir 下所有 .md 文件并分块 */ + async load(): Promise { + this.chunks = []; + + if (!fs.existsSync(this.knowledgeDir)) { + console.warn(`知识库目录不存在: ${this.knowledgeDir}`); + return; + } + + const files = fs + .readdirSync(this.knowledgeDir) + .filter((f) => f.endsWith('.md')); + + for (const file of files) { + const filePath = path.join(this.knowledgeDir, file); + const raw = fs.readFileSync(filePath, 'utf-8'); + const fileChunks = this.splitChunks(raw, file); + this.chunks.push(...fileChunks); + } + + console.log(`知识库已加载: ${this.chunks.length} 个块,来自 ${files.length} 个文件`); + } + + /** 按 ## 标题将文档拆分为块 */ + private splitChunks(raw: string, source: string): Chunk[] { + const blocks = raw.split(/(?=^## )/m); + return blocks + .map((b) => b.trim()) + .filter(Boolean) + .map((content) => ({ source, content })); + } + + /** + * 基于关键词匹配搜索,返回相关块(按相关性降序) + * + * 算法:将查询分词,统计每个块命中关键词的次数, + * 同时给标题匹配额外加权。 + */ + search(query: string, topK: number = 3): Chunk[] { + const keywords = this.tokenize(query); + if (keywords.length === 0) return []; + + const scored = this.chunks.map((chunk) => { + const lower = chunk.content.toLowerCase(); + let score = 0; + for (const kw of keywords) { + // 标题行命中加权 ×3 + const headlineRegex = /^## .+$/gm; + let match: RegExpExecArray | null; + while ((match = headlineRegex.exec(chunk.content)) !== null) { + if (match[0].toLowerCase().includes(kw)) { + score += 3; + } + } + // 正文命中 + const count = (lower.match(new RegExp(this.escapeRegex(kw), 'gi')) || []).length; + score += count; + } + // 标题匹配额外加分 + return { chunk, score }; + }); + + return scored + .filter((s) => s.score > 0) + .sort((a, b) => b.score - a.score) + .slice(0, topK) + .map((s) => s.chunk); + } + + /** 中文 + 英文简单分词 */ + private tokenize(text: string): string[] { + // 按空白/标点拆分,过滤长度 ≤1 的词 + return text + .split(/[\s,,。.!!??::;;、]+/) + .map((t) => t.toLowerCase().trim()) + .filter((t) => t.length > 1); + } + + private escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + /** 获取知识库摘要(供 Agent 概览) */ + summary(): string { + if (this.chunks.length === 0) return '知识库为空'; + const sources = [...new Set(this.chunks.map((c) => c.source))]; + const titles = this.chunks + .map((c) => { + const m = c.content.match(/^## (.+)$/m); + return m ? ` - ${m[1]} (${c.source})` : null; + }) + .filter(Boolean); + return `知识库包含 ${sources.length} 个文档:\n${titles.join('\n')}`; + } +} + +/** 全局单例 */ +export const knowledgeBase = new KnowledgeBase( + path.join(process.cwd(), 'knowledge'), +); diff --git a/src/knowledge/search-tool.ts b/src/knowledge/search-tool.ts new file mode 100644 index 0000000..8d4cdc8 --- /dev/null +++ b/src/knowledge/search-tool.ts @@ -0,0 +1,53 @@ +import { Tool } from '../types/index.js'; +import { knowledgeBase } from './knowledge-base.js'; + +/** + * search_knowledge — 在知识库中检索相关信息 + */ +export const searchKnowledgeTool: Tool = { + name: 'search_knowledge', + description: + '在本地知识库中搜索与查询相关的文档片段。当你需要查找项目文档、技术说明、业务规则等存储在知识库中的信息时使用此工具。', + parameters: { + type: 'object', + properties: { + query: { + type: 'string', + description: '搜索关键词或问题,如"Agent 工具调用流程"、"如何创建子Agent"', + }, + }, + required: ['query'], + }, + execute: async (args) => { + const { query } = args; + const results = knowledgeBase.search(query as string, 3); + if (results.length === 0) { + return `未找到与 "${query}" 相关的知识。当前知识库摘要:\n${knowledgeBase.summary()}`; + } + return results + .map( + (r, i) => + `--- 结果 ${i + 1} (来源: ${r.source}) ---\n${r.content}`, + ) + .join('\n\n'); + }, +}; + +/** + * list_knowledge — 列出知识库中所有文档和章节 + */ +export const listKnowledgeTool: Tool = { + name: 'list_knowledge', + description: '列出知识库中所有文档及其章节标题,用于了解知识库包含哪些内容。', + parameters: { + type: 'object', + properties: {}, + required: [], + }, + execute: async () => { + return knowledgeBase.summary(); + }, +}; + +/** 知识库相关工具集 */ +export const knowledgeTools: Tool[] = [searchKnowledgeTool, listKnowledgeTool]; diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 2356b9c..381c425 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -3,8 +3,9 @@ import { calculatorTool } from './calculator.js'; import { guessTool } from './guess.js'; import { Tool } from '../types/index.js'; import { subAgentTools } from '../agents/tools.js'; +import { knowledgeTools } from '../knowledge/search-tool.js'; -const baseTools: Tool[] = [weatherTool, calculatorTool, guessTool]; +const baseTools: Tool[] = [weatherTool, calculatorTool, guessTool, ...knowledgeTools]; /** 所有工具(基础工具 + sub-agent 管理工具),供主 orchestrator Agent 使用 */ const allTools: Tool[] = [...baseTools, ...subAgentTools]; From ad9ee895860dc09b1f3241c16a8baf83924d1092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 10 Jul 2026 15:58:32 +0800 Subject: [PATCH 10/24] =?UTF-8?q?refactor:=20=E9=A1=B9=E7=9B=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 17 ++ .gitignore | 4 +- docs/design.md | 178 ++++++++++++++++++ package.json | 15 +- packages/cli/package.json | 19 ++ packages/cli/src/index.ts | 1 + packages/cli/tsconfig.json | 7 + packages/core/package.json | 19 ++ packages/core/src/index.ts | 6 + packages/core/tsconfig.json | 7 + packages/desktop/package.json | 19 ++ packages/desktop/src/main.ts | 1 + packages/desktop/tsconfig.json | 7 + .../old/knowledge}/llm-to-agent-guide.md | 0 packages/old/package.json | 18 ++ {src => packages/old/src}/agents/agent.ts | 0 {src => packages/old/src}/agents/index.ts | 0 {src => packages/old/src}/agents/manager.ts | 0 {src => packages/old/src}/agents/tools.ts | 0 {src => packages/old/src}/context.ts | 0 {src => packages/old/src}/hooks/index.ts | 0 {src => packages/old/src}/hooks/log.hooks.ts | 0 {src => packages/old/src}/hooks/registry.ts | 0 {src => packages/old/src}/index.ts | 0 .../old/src}/knowledge/knowledge-base.ts | 0 .../old/src}/knowledge/search-tool.ts | 0 {src => packages/old/src}/llm.ts | 0 .../old/src}/prompts/orchestrator.ts | 0 {src => packages/old/src}/prompts/reAct.ts | 0 {src => packages/old/src}/prompts/system.ts | 0 {src => packages/old/src}/tools/calculator.ts | 0 {src => packages/old/src}/tools/guess.ts | 0 {src => packages/old/src}/tools/registry.ts | 0 {src => packages/old/src}/tools/weather.ts | 0 {src => packages/old/src}/types/index.ts | 0 packages/old/tsconfig.json | 7 + packages/plugins-builtin/package.json | 16 ++ packages/plugins-builtin/src/index.ts | 1 + packages/plugins-builtin/tsconfig.json | 7 + packages/server/package.json | 19 ++ packages/server/src/index.ts | 1 + packages/server/tsconfig.json | 7 + packages/tests/package.json | 19 ++ packages/tests/src/core.test.ts | 17 ++ packages/tests/src/plugins-builtin.test.ts | 11 ++ packages/tests/src/types.test.ts | 57 ++++++ packages/tests/tsconfig.json | 7 + packages/types/package.json | 12 ++ packages/types/src/index.ts | 46 +++++ packages/types/tsconfig.json | 7 + packages/web/index.html | 14 ++ packages/web/package.json | 10 + pnpm-workspace.yaml | 2 + tsconfig.json | 4 + 54 files changed, 575 insertions(+), 7 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 docs/design.md create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/tsconfig.json create mode 100644 packages/core/package.json create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/tsconfig.json create mode 100644 packages/desktop/package.json create mode 100644 packages/desktop/src/main.ts create mode 100644 packages/desktop/tsconfig.json rename {knowledge => packages/old/knowledge}/llm-to-agent-guide.md (100%) create mode 100644 packages/old/package.json rename {src => packages/old/src}/agents/agent.ts (100%) rename {src => packages/old/src}/agents/index.ts (100%) rename {src => packages/old/src}/agents/manager.ts (100%) rename {src => packages/old/src}/agents/tools.ts (100%) rename {src => packages/old/src}/context.ts (100%) rename {src => packages/old/src}/hooks/index.ts (100%) rename {src => packages/old/src}/hooks/log.hooks.ts (100%) rename {src => packages/old/src}/hooks/registry.ts (100%) rename {src => packages/old/src}/index.ts (100%) rename {src => packages/old/src}/knowledge/knowledge-base.ts (100%) rename {src => packages/old/src}/knowledge/search-tool.ts (100%) rename {src => packages/old/src}/llm.ts (100%) rename {src => packages/old/src}/prompts/orchestrator.ts (100%) rename {src => packages/old/src}/prompts/reAct.ts (100%) rename {src => packages/old/src}/prompts/system.ts (100%) rename {src => packages/old/src}/tools/calculator.ts (100%) rename {src => packages/old/src}/tools/guess.ts (100%) rename {src => packages/old/src}/tools/registry.ts (100%) rename {src => packages/old/src}/tools/weather.ts (100%) rename {src => packages/old/src}/types/index.ts (100%) create mode 100644 packages/old/tsconfig.json create mode 100644 packages/plugins-builtin/package.json create mode 100644 packages/plugins-builtin/src/index.ts create mode 100644 packages/plugins-builtin/tsconfig.json create mode 100644 packages/server/package.json create mode 100644 packages/server/src/index.ts create mode 100644 packages/server/tsconfig.json create mode 100644 packages/tests/package.json create mode 100644 packages/tests/src/core.test.ts create mode 100644 packages/tests/src/plugins-builtin.test.ts create mode 100644 packages/tests/src/types.test.ts create mode 100644 packages/tests/tsconfig.json create mode 100644 packages/types/package.json create mode 100644 packages/types/src/index.ts create mode 100644 packages/types/tsconfig.json create mode 100644 packages/web/index.html create mode 100644 packages/web/package.json create mode 100644 pnpm-workspace.yaml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..487c65e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,17 @@ +# 开发准则 + +## 驾驭约束 + +* **开始任务前先明确任务内容**:不允许为了不报错正确而随意修改架构/目录/接口,不允许为了结果不报错而随意修改测试用例 +* **先读文档再动手**:涉及架构/目录/接口的修改,必须先读 `docs/design.md` +* **只实现当前要求的**:禁止预建未来可能需要的模块、方法、字段 +* **最小改动范围**:每次只改最少的文件,改完一批确认一批,不要一口气创建大量文件 +* **改前先读**:编辑任何文件前必须先 `read_file` 确认当前内容(用户可能已手动修改) +* **禁止 `npx tsx -e` 内联测试**:测试代码统一写到 `packages/tests/src/` 下,用 `pnpm test` 运行 +* **改后必验证**:代码改动后运行 `pnpm test`,确保用例全通过 + +## 代码风格 + +* 不得为简单的需求过度抽象和设计 +* 简单功能不需要写注释 +* 临时文件统一放在 `tmp` 目录下,且必须在 `.gitignore` 中忽略 \ No newline at end of file diff --git a/.gitignore b/.gitignore index b4b4711..4c5307c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules .env -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +.DS_Store +tmp \ No newline at end of file diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..9c44769 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,178 @@ +# llm-to-agent 设计文档 + +## 架构设计 + +### 核心理念:微内核 + 事件驱动 + +Agent 的本质是一个循环:**用户输入 → LLM 思考 → 工具调用 → LLM 再思考 → 最终回复**。 + +本项目的答案是:**内核只做一件事——驱动这个循环**。LLM 调用、工具执行、日志输出等一切能力全部通过事件总线交给外部插件。 + +### 三层架构 + +``` +┌──────────────────────────────────────────┐ +│ Route 层 CLI / Desktop / Web │ ← 只决定 I/O 方式 +├──────────────────────────────────────────┤ +│ Plugin 层 provider / tool / hook │ ← 可替换的能力单元 +├──────────────────────────────────────────┤ +│ Core 层 Scheduler + HookBus │ ← 只做循环 + 事件路由 +└──────────────────────────────────────────┘ +``` + +### 事件总线 + +内核循环的每一步都通过 `HookBus` 发出事件,插件注册响应,形成“事件驱动 + 插件化”的架构。 + +### 插件类型 + +| 类型 | 职责 | 响应的事件 | +|------|------|-----------| +| `provider` | LLM 提供商适配 | `llm:call`、`tool:select` | +| `tool` | 工具能力 | `tool:schema`、`tool:execute` | +| `hook` | 生命周期副作用 | `tool:before`、`tool:after`、`run:end` | +| `prompt` | 系统提示词 | 通过配置注入,不响应事件 | + +每个插件通过 `manifest.json` 声明元信息(名称、类型、提供的能力、适用平台),由 `PluginManager` 统一加载。 + +### 三条产品线 + +``` + ┌── @llm-to-agent/core ──┐ + │ MicroKernel + HookBus │ + └────────────────────────┘ + │ + ┌─────────────────┼─────────────────┐ + ▼ ▼ ▼ + CLI Desktop Web + (readline) (Electron) (Browser HTTP) + │ │ │ + ┌───────┴────────┐ ┌──────┴───────┐ ┌──────┴───────┐ + │ 本地工具全部 │ │ 同 CLI │ │ 仅远程工具 │ + │ console 日志 │ │ IPC 日志 │ │ SSE 日志 │ + └────────────────┘ └──────────────┘ └──────────────┘ +``` + +CLI 与 Desktop 共享本地工具(文件读写、Shell 执行),Web 端通过 `platforms` 字段自动跳过本地工具。 + +--- + +## 项目目录设计 + +### Monorepo 结构(pnpm workspace) + +``` +llm-to-agent/ +├── pnpm-workspace.yaml +├── package.json ← 根(private,统一 dev/test 脚本) +├── tsconfig.json +│ +├── docs/ ← 设计文档 +│ └── design.md +│ +├── packages/ +│ │ +│ ├── types/ ← @llm-to-agent/types +│ │ └── src/index.ts ← 所有共享类型(Message/ToolDef/PluginManifest/...) +│ │ +│ ├── core/ ← @llm-to-agent/core +│ │ └── src/ +│ │ ├── hook-bus.ts ← 事件总线(on/emit/request/collect) +│ │ ├── scheduler.ts ← Agent 循环(think→tool→think) +│ │ ├── context.ts ← 消息上下文管理器 +│ │ ├── plugin-manager.ts← 插件加载 + 生命周期 +│ │ └── kernel.ts ← MicroKernel 入口(组合以上模块) +│ │ +│ ├── plugins-builtin/ ← @llm-to-agent/plugins-builtin +│ │ └── [xxxxx]/ ← 内置插件 +│ │ +│ ├── cli/ ← @llm-to-agent/cli(终端入口) +│ │ └── src/index.ts ← readline 循环 + kernel.init([...]) +│ │ +│ ├── desktop/ ← @llm-to-agent/desktop(桌面入口) +│ │ └── src/main.ts ← Electron 主进程 +│ │ +│ ├── server/ ← @llm-to-agent/server(Web 后端) +│ │ └── src/index.ts ← Express + SSE +│ │ +│ ├── web/ ← @llm-to-agent/web(Web 前端) +│ │ └── index.html ← 待实现 React 聊天界面 +│ │ +│ ├── tests/ ← @llm-to-agent/tests +│ │ └── src/ ← *.test.ts(Node 原生 test runner) +│ │ +│ └── old/ ← @llm-to-agent/old(旧代码归档) +│ └── src/ ← 重构前的 Agent 实现 +``` + +### 包依赖关系 + +``` +@llm-to-agent/types ← 零依赖,纯类型 + ↑ ↑ + core plugins-builtin + ↑ ↑ + ├───────────┴────────────┐ + ↓ ↓ ↓ + cli desktop server + ↑ + web +``` + +### 插件清单规范 + +每个插件目录包含 `manifest.json`: + +```json +{ + "name": "@agent/tool-file", + "version": "1.0.0", + "type": "tool", + "provides": ["file-read", "file-write"], + "entry": "./index.ts", + "platforms": ["cli", "desktop"] +} +``` + +| 字段 | 说明 | +|------|------| +| `type` | `provider` / `tool` / `hook` / `prompt` | +| `provides` | 提供的能力标识 | +| `entry` | 入口文件,默认导出 `register(bus: HookBus)` | +| `platforms` | 可用平台,PluginManager 加载时自动过滤 | + +### 关键类型 + +```typescript +// 消息 +interface Message { + role: 'system' | 'user' | 'assistant' | 'tool'; + content: string; + tool_calls?: ToolCall[]; + tool_call_id?: string; +} + +// 工具定义(OpenAI 兼容) +interface ToolDef { + type: 'function'; + function: { + name: string; + description: string; + parameters: Record; + }; +} + +// 插件清单 +interface PluginManifest { + name: string; + version: string; + type: 'provider' | 'tool' | 'hook' | 'prompt'; + provides: string | string[]; + entry: string; + platforms?: ('cli' | 'desktop' | 'web')[]; +} + +// 事件总线 +type EventName = string; +type Listener = (data: any) => any | Promise; +``` diff --git a/package.json b/package.json index 9c4262c..7597174 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,16 @@ { "name": "llm-to-agent", - "version": "0.1.0", + "version": "0.2.0", + "private": true, "type": "module", "scripts": { - "dev": "tsx src/index.ts" - }, - "dependencies": { - "dotenv": "^16.x", - "openai": "^4.x" + "dev": "pnpm --filter @llm-to-agent/cli dev", + "dev:old": "pnpm --filter @llm-to-agent/old dev", + "dev:core": "pnpm --filter @llm-to-agent/core dev", + "dev:server": "pnpm --filter @llm-to-agent/server dev", + "dev:web": "pnpm --filter @llm-to-agent/web dev", + "dev:desktop": "pnpm --filter @llm-to-agent/desktop dev", + "test": "pnpm --filter @llm-to-agent/tests test" }, "devDependencies": { "@types/node": "^25.9.1", diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..04314a7 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,19 @@ +{ + "name": "@llm-to-agent/cli", + "version": "0.1.0", + "type": "module", + "private": true, + "main": "./src/index.ts", + "scripts": { + "dev": "tsx src/index.ts" + }, + "dependencies": { + "@llm-to-agent/core": "workspace:*", + "@llm-to-agent/plugins-builtin": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 0000000..ad1964e --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1 @@ +console.log('🚀 LLM-to-Agent CLI 初始化已完成,待开发。\n'); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..0a7ee4e --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,19 @@ +{ + "name": "@llm-to-agent/core", + "version": "0.1.0", + "type": "module", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "dev": "tsx src/index.ts" + }, + "dependencies": { + "@llm-to-agent/types": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..817e4a2 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,6 @@ +export * from '@llm-to-agent/types'; + +export const init = () => { + console.log('🚀 Core 入口已初始化完成,待开发。'); +} + diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/packages/desktop/package.json b/packages/desktop/package.json new file mode 100644 index 0000000..a2f5e4e --- /dev/null +++ b/packages/desktop/package.json @@ -0,0 +1,19 @@ +{ + "name": "@llm-to-agent/desktop", + "version": "0.1.0", + "type": "module", + "private": true, + "main": "./src/main.ts", + "scripts": { + "dev": "tsx './src/main.ts'" + }, + "dependencies": { + "@llm-to-agent/core": "workspace:*", + "@llm-to-agent/plugins-builtin": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts new file mode 100644 index 0000000..1c84edc --- /dev/null +++ b/packages/desktop/src/main.ts @@ -0,0 +1 @@ +console.log('🚀 Desktop 入口已初始化完成,待开发。'); diff --git a/packages/desktop/tsconfig.json b/packages/desktop/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/desktop/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/knowledge/llm-to-agent-guide.md b/packages/old/knowledge/llm-to-agent-guide.md similarity index 100% rename from knowledge/llm-to-agent-guide.md rename to packages/old/knowledge/llm-to-agent-guide.md diff --git a/packages/old/package.json b/packages/old/package.json new file mode 100644 index 0000000..92310d0 --- /dev/null +++ b/packages/old/package.json @@ -0,0 +1,18 @@ +{ + "name": "@llm-to-agent/old", + "version": "0.2.0", + "type": "module", + "private": true, + "scripts": { + "dev": "tsx src/index.ts" + }, + "dependencies": { + "dotenv": "^16.x", + "openai": "^4.x" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/src/agents/agent.ts b/packages/old/src/agents/agent.ts similarity index 100% rename from src/agents/agent.ts rename to packages/old/src/agents/agent.ts diff --git a/src/agents/index.ts b/packages/old/src/agents/index.ts similarity index 100% rename from src/agents/index.ts rename to packages/old/src/agents/index.ts diff --git a/src/agents/manager.ts b/packages/old/src/agents/manager.ts similarity index 100% rename from src/agents/manager.ts rename to packages/old/src/agents/manager.ts diff --git a/src/agents/tools.ts b/packages/old/src/agents/tools.ts similarity index 100% rename from src/agents/tools.ts rename to packages/old/src/agents/tools.ts diff --git a/src/context.ts b/packages/old/src/context.ts similarity index 100% rename from src/context.ts rename to packages/old/src/context.ts diff --git a/src/hooks/index.ts b/packages/old/src/hooks/index.ts similarity index 100% rename from src/hooks/index.ts rename to packages/old/src/hooks/index.ts diff --git a/src/hooks/log.hooks.ts b/packages/old/src/hooks/log.hooks.ts similarity index 100% rename from src/hooks/log.hooks.ts rename to packages/old/src/hooks/log.hooks.ts diff --git a/src/hooks/registry.ts b/packages/old/src/hooks/registry.ts similarity index 100% rename from src/hooks/registry.ts rename to packages/old/src/hooks/registry.ts diff --git a/src/index.ts b/packages/old/src/index.ts similarity index 100% rename from src/index.ts rename to packages/old/src/index.ts diff --git a/src/knowledge/knowledge-base.ts b/packages/old/src/knowledge/knowledge-base.ts similarity index 100% rename from src/knowledge/knowledge-base.ts rename to packages/old/src/knowledge/knowledge-base.ts diff --git a/src/knowledge/search-tool.ts b/packages/old/src/knowledge/search-tool.ts similarity index 100% rename from src/knowledge/search-tool.ts rename to packages/old/src/knowledge/search-tool.ts diff --git a/src/llm.ts b/packages/old/src/llm.ts similarity index 100% rename from src/llm.ts rename to packages/old/src/llm.ts diff --git a/src/prompts/orchestrator.ts b/packages/old/src/prompts/orchestrator.ts similarity index 100% rename from src/prompts/orchestrator.ts rename to packages/old/src/prompts/orchestrator.ts diff --git a/src/prompts/reAct.ts b/packages/old/src/prompts/reAct.ts similarity index 100% rename from src/prompts/reAct.ts rename to packages/old/src/prompts/reAct.ts diff --git a/src/prompts/system.ts b/packages/old/src/prompts/system.ts similarity index 100% rename from src/prompts/system.ts rename to packages/old/src/prompts/system.ts diff --git a/src/tools/calculator.ts b/packages/old/src/tools/calculator.ts similarity index 100% rename from src/tools/calculator.ts rename to packages/old/src/tools/calculator.ts diff --git a/src/tools/guess.ts b/packages/old/src/tools/guess.ts similarity index 100% rename from src/tools/guess.ts rename to packages/old/src/tools/guess.ts diff --git a/src/tools/registry.ts b/packages/old/src/tools/registry.ts similarity index 100% rename from src/tools/registry.ts rename to packages/old/src/tools/registry.ts diff --git a/src/tools/weather.ts b/packages/old/src/tools/weather.ts similarity index 100% rename from src/tools/weather.ts rename to packages/old/src/tools/weather.ts diff --git a/src/types/index.ts b/packages/old/src/types/index.ts similarity index 100% rename from src/types/index.ts rename to packages/old/src/types/index.ts diff --git a/packages/old/tsconfig.json b/packages/old/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/old/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/packages/plugins-builtin/package.json b/packages/plugins-builtin/package.json new file mode 100644 index 0000000..b5dcd57 --- /dev/null +++ b/packages/plugins-builtin/package.json @@ -0,0 +1,16 @@ +{ + "name": "@llm-to-agent/plugins-builtin", + "version": "0.1.0", + "type": "module", + "private": true, + "main": "./src/index.ts", + "dependencies": { + "@llm-to-agent/core": "workspace:*", + "@llm-to-agent/types": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/packages/plugins-builtin/src/index.ts b/packages/plugins-builtin/src/index.ts new file mode 100644 index 0000000..56004c9 --- /dev/null +++ b/packages/plugins-builtin/src/index.ts @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/packages/plugins-builtin/tsconfig.json b/packages/plugins-builtin/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/plugins-builtin/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/packages/server/package.json b/packages/server/package.json new file mode 100644 index 0000000..6e16493 --- /dev/null +++ b/packages/server/package.json @@ -0,0 +1,19 @@ +{ + "name": "@llm-to-agent/server", + "version": "0.1.0", + "type": "module", + "private": true, + "main": "./src/index.ts", + "scripts": { + "dev": "tsx src/index.ts" + }, + "dependencies": { + "@llm-to-agent/core": "workspace:*", + "@llm-to-agent/plugins-builtin": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts new file mode 100644 index 0000000..49a6c64 --- /dev/null +++ b/packages/server/src/index.ts @@ -0,0 +1 @@ +console.log('🚀 Server 入口已初始化完成,待开发。'); diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/server/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/packages/tests/package.json b/packages/tests/package.json new file mode 100644 index 0000000..a6ee9b9 --- /dev/null +++ b/packages/tests/package.json @@ -0,0 +1,19 @@ +{ + "name": "@llm-to-agent/tests", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "test": "tsx --test src/**/*.test.ts" + }, + "dependencies": { + "@llm-to-agent/types": "workspace:*", + "@llm-to-agent/core": "workspace:*", + "@llm-to-agent/plugins-builtin": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.x", + "typescript": "^5.x" + } +} diff --git a/packages/tests/src/core.test.ts b/packages/tests/src/core.test.ts new file mode 100644 index 0000000..716ded6 --- /dev/null +++ b/packages/tests/src/core.test.ts @@ -0,0 +1,17 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { init } from '@llm-to-agent/core'; + +describe('@llm-to-agent/core', () => { + + it('init() 不抛异常', () => { + assert.doesNotThrow(() => init()); + }); + + it('core 通过 re-export 暴露 types', async () => { + const mod = await import('@llm-to-agent/core'); + assert.equal(typeof mod.init, 'function', '应导出 init 函数'); + }); + +}); diff --git a/packages/tests/src/plugins-builtin.test.ts b/packages/tests/src/plugins-builtin.test.ts new file mode 100644 index 0000000..c7cc2fe --- /dev/null +++ b/packages/tests/src/plugins-builtin.test.ts @@ -0,0 +1,11 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe('@llm-to-agent/plugins-builtin', () => { + + it('入口文件可正常 import', async () => { + const mod = await import('@llm-to-agent/plugins-builtin'); + assert.ok(mod.default !== undefined); + }); + +}); diff --git a/packages/tests/src/types.test.ts b/packages/tests/src/types.test.ts new file mode 100644 index 0000000..54a4fb7 --- /dev/null +++ b/packages/tests/src/types.test.ts @@ -0,0 +1,57 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { Message, ToolCall, ToolDef, PluginManifest, SchedulerConfig, EventName, Listener } from '@llm-to-agent/types'; + +describe('@llm-to-agent/types', () => { + + it('Message 类型可正常构造', () => { + const msg: Message = { role: 'user', content: 'hello' }; + assert.equal(msg.role, 'user'); + assert.equal(msg.content, 'hello'); + }); + + it('Message 支持 tool_calls 和 tool_call_id', () => { + const tc: ToolCall = { id: '1', function: { name: 'test', arguments: '{}' } }; + const msg: Message = { role: 'assistant', content: '', tool_calls: [tc] }; + assert.equal(msg.tool_calls![0].id, '1'); + }); + + it('ToolDef 可正常构造', () => { + const tool: ToolDef = { + type: 'function', + function: { + name: 'weather', + description: '查询天气', + parameters: { type: 'object', properties: {}, required: [] }, + }, + }; + assert.equal(tool.function.name, 'weather'); + }); + + it('PluginManifest 类型完整', () => { + const manifest: PluginManifest = { + name: '@agent/test', + version: '1.0.0', + type: 'tool', + provides: ['test-tool'], + entry: './index.ts', + platforms: ['cli', 'desktop'], + }; + assert.equal(manifest.type, 'tool'); + assert.deepEqual(manifest.platforms, ['cli', 'desktop']); + }); + + it('SchedulerConfig 可正常构造', () => { + const config: SchedulerConfig = { maxSteps: 5 }; + assert.equal(config.maxSteps, 5); + }); + + it('EventName 和 Listener 类型定义正确', () => { + const name: EventName = 'llm:call'; + const fn: Listener = (data: any) => data; + assert.equal(typeof fn, 'function'); + assert.equal(name, 'llm:call'); + }); + +}); diff --git a/packages/tests/tsconfig.json b/packages/tests/tsconfig.json new file mode 100644 index 0000000..f27ff83 --- /dev/null +++ b/packages/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"] + } +} diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..9844c38 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,12 @@ +{ + "name": "@llm-to-agent/types", + "version": "0.1.0", + "type": "module", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": {}, + "devDependencies": { + "typescript": "^5.x" + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..99f958c --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,46 @@ +// ===== Agent 消息 ===== + +export interface Message { + role: 'system' | 'user' | 'assistant' | 'tool'; + content: string; + tool_calls?: ToolCall[]; + tool_call_id?: string; +} + +export interface ToolCall { + id: string; + function: { name: string; arguments: string }; +} + +// ===== 工具 ===== + +export interface ToolDef { + type: 'function'; + function: { + name: string; + description: string; + parameters: Record; + }; +} + +// ===== 插件 ===== + +export interface PluginManifest { + name: string; + version: string; + type: 'provider' | 'tool' | 'hook' | 'prompt'; + provides: string | string[]; + entry: string; + platforms?: ('cli' | 'desktop' | 'web')[]; +} + +// ===== 事件总线 ===== + +export type EventName = string; +export type Listener = (data: any) => any | Promise; + +// ===== 调度器 ===== + +export interface SchedulerConfig { + maxSteps?: number; +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..efdc208 --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "types": [] + } +} diff --git a/packages/web/index.html b/packages/web/index.html new file mode 100644 index 0000000..b2cd966 --- /dev/null +++ b/packages/web/index.html @@ -0,0 +1,14 @@ + + + + + + LLM-to-Agent Web + + +
+

🚀 LLM-to-Agent Web

+

占位页面,待实现 React 聊天界面。

+
+ + diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 0000000..cf96ca1 --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,10 @@ +{ + "name": "@llm-to-agent/web", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "dev": "echo 'TODO: vite dev'", + "build": "echo 'TODO: vite build'" + } +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..18ec407 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/tsconfig.json b/tsconfig.json index 057c24d..2a77da6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { + "target": "ES2022", "module": "esnext", "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, "types": ["node"] } } \ No newline at end of file From e4b45c59710281259379ba96662cedbdc3ecb2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Wed, 15 Jul 2026 17:56:41 +0800 Subject: [PATCH 11/24] =?UTF-8?q?feat:=20=E7=AC=AC=E4=B8=80=E7=89=88?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 31 ++++ docs/agent/model-chat.md | 21 +++ docs/agent/runs-behavior.md | 22 +++ docs/agent/tools-workspace.md | 23 +++ docs/architecture/local-data.md | 38 +++++ docs/architecture/overview.md | 36 +++++ docs/automation/computer-butler.md | 20 +++ docs/clients/desktop.md | 21 +++ docs/clients/web.md | 21 +++ docs/design.md | 178 ---------------------- docs/development/project-assistant.md | 22 +++ docs/development/system-evolution.md | 22 +++ docs/extension/hooks-extensions.md | 22 +++ docs/foundation/cli.md | 26 ++++ docs/foundation/runtime.md | 21 +++ docs/foundation/spaces.md | 22 +++ docs/intelligence/memory-knowledge.md | 21 +++ docs/intelligence/planning-multi-agent.md | 21 +++ docs/operations/reliability-safety.md | 22 +++ docs/roadmap.md | 27 ++++ 20 files changed, 459 insertions(+), 178 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/agent/model-chat.md create mode 100644 docs/agent/runs-behavior.md create mode 100644 docs/agent/tools-workspace.md create mode 100644 docs/architecture/local-data.md create mode 100644 docs/architecture/overview.md create mode 100644 docs/automation/computer-butler.md create mode 100644 docs/clients/desktop.md create mode 100644 docs/clients/web.md delete mode 100644 docs/design.md create mode 100644 docs/development/project-assistant.md create mode 100644 docs/development/system-evolution.md create mode 100644 docs/extension/hooks-extensions.md create mode 100644 docs/foundation/cli.md create mode 100644 docs/foundation/runtime.md create mode 100644 docs/foundation/spaces.md create mode 100644 docs/intelligence/memory-knowledge.md create mode 100644 docs/intelligence/planning-multi-agent.md create mode 100644 docs/operations/reliability-safety.md create mode 100644 docs/roadmap.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..24968db --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# 文档索引 + +本文档记录 `llm-to-agent` 当前已达成的产品与架构共识。它不是固定开发清单:实现前再细化当前功能点,发现新需求则补到对应功能文档中。 + +## 使用方式 + +- 每个大功能点一个文件,文件头标记 `状态` 与 `期望阶段`。 +- `todo`、`doing`、`done` 表示当前进度;阶段只表示建议先后,不构成严格依赖顺序。 +- 每完成一个独立提交,在对应文档中补充提交、blog 和实现说明。 +- 未明确的方案写在“待细化”中,不提前冻结实现。 + +## 导航 + +- [路线图与管理规则](roadmap.md) +- [架构总览](architecture/overview.md) +- [本地数据与目录](architecture/local-data.md) +- [Runtime](foundation/runtime.md) +- [CLI](foundation/cli.md) +- [Space 与 Conversation](foundation/spaces.md) +- [模型与聊天](agent/model-chat.md) +- [Run 与 Agent Behavior](agent/runs-behavior.md) +- [Tools 与 Workspace](agent/tools-workspace.md) +- [Project 开发助手](development/project-assistant.md) +- [System Evolution](development/system-evolution.md) +- [Hooks、扩展与 Profile](extension/hooks-extensions.md) +- [计划与多 Agent](intelligence/planning-multi-agent.md) +- [记忆与知识](intelligence/memory-knowledge.md) +- [电脑管家与自动化](automation/computer-butler.md) +- [可靠性与个人安全](operations/reliability-safety.md) +- [Web](clients/web.md) +- [Desktop](clients/desktop.md) diff --git a/docs/agent/model-chat.md b/docs/agent/model-chat.md new file mode 100644 index 0000000..706ad32 --- /dev/null +++ b/docs/agent/model-chat.md @@ -0,0 +1,21 @@ +# 模型与聊天 + +状态:todo +期望阶段:P1 + +## 目标 + +先提供可靠、连续的 DeepSeek 聊天能力;模型接入保持可替换,但不提前实现多 Provider 系统。 + +## 已确定 + +- 第一版只兼容 DeepSeek API。 +- 支持流式回复和会话历史加载。 +- 模型调用由 Runtime 管理,CLI 只渲染流。 +- 未来模型提供商属于 Adapter 层。 + +## 待细化 + +- API Key 的读取位置与配置体验。 +- 默认模型、请求参数和失败/限流提示。 +- 上下文窗口压缩策略。 diff --git a/docs/agent/runs-behavior.md b/docs/agent/runs-behavior.md new file mode 100644 index 0000000..4fe5db2 --- /dev/null +++ b/docs/agent/runs-behavior.md @@ -0,0 +1,22 @@ +# Run 与 Agent Behavior + +状态:todo +期望阶段:P1 + +## 目标 + +使一次用户请求拥有可观察的运行边界,并为未来不同 Agent 行为留下接缝。 + +## 已确定 + +- 一次用户消息默认产生一个 Run。 +- 纯聊天 Run 只产生模型回答;行动 Run 还包含 Tool 调用、输出和产物。 +- 第一版仅实现 `DefaultAgent`,不预设 Planner/Worker/Reviewer 流程。 +- Behavior 定义 Agent 如何准备上下文、调用模型、调用 Tool 和结束 Run。 +- Run 记录输入、输出、错误、时间和原始执行日志。 + +## 待细化 + +- Run 的取消、重试和失败显示。 +- 行为接口的具体 TypeScript 形状。 +- 后续 Plan/Step 与 Run 的关系。 diff --git a/docs/agent/tools-workspace.md b/docs/agent/tools-workspace.md new file mode 100644 index 0000000..0c10b93 --- /dev/null +++ b/docs/agent/tools-workspace.md @@ -0,0 +1,23 @@ +# Tools 与 Workspace + +状态:todo +期望阶段:P2 + +## 目标 + +让单 Agent 在隔离工作区中使用文件与 Shell 完成真实任务。 + +## 已确定 + +- Tool 是 Agent 主动调用的外部能力。 +- 第一批能力:读写文件、目录浏览、文本搜索和 Shell 执行。 +- 每个 Task 有独立 Workspace;Shell 默认在该目录执行。 +- Tool 调用参数、输出、错误和生成脚本文本写入 Run 日志。 +- Agent 通过模型 Tool Calling 自主选择、调用并读取 Tool 结果。 +- 初期不建设复杂审批或权限语言;真实破坏性操作出现后再增强。 + +## 待细化 + +- Shell 超时、取消、后台进程和大输出处理。 +- 文件写入与补丁的交互方式。 +- Tool 描述和参数 Schema 的具体格式。 diff --git a/docs/architecture/local-data.md b/docs/architecture/local-data.md new file mode 100644 index 0000000..3ff8d92 --- /dev/null +++ b/docs/architecture/local-data.md @@ -0,0 +1,38 @@ +# 本地数据与目录 + +状态:todo +期望阶段:P1 + +## 原则 + +数据以本地普通文件保存,保持易读、易调试、易由 Agent 修改。早期不承诺兼容性,不预先建设 schema migration;重要数据依靠备份,格式变更需要时再写一次性转换脚本。 + +Runtime 是唯一写入者,CLI/Web/Desktop 均通过 Runtime 读取或修改数据。 + +## 目录 + +```text +~/.agent/ + tasks// + space.json + conversations/ + runs/ + workspace/ + +/.agent/ + project.json + conversations/ + runs/ +``` + +Conversation 与 Run 优先采用 JSONL;Space/Project 元信息采用小型 JSON 文件。生成并执行的脚本文本进入 Run 日志,不额外维护脚本库;真正写进 Workspace 的文件自然保留。 + +## Task 升级 + +Task Workspace 的组织尽量与 Project 工作区一致。升级为 Project 时,将该专属目录迁移至用户指定路径并写入 Project 元信息。 + +## 待细化 + +- ID 和文件命名规则。 +- Run 日志与产物的具体划分。 +- 备份、归档和数据清理策略。 diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..855a9a2 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,36 @@ +# 架构总览 + +状态:todo +期望阶段:P1 + +## 定位 + +这是纯个人使用的开发助手与电脑管家。它支持 Project 和 Task 两种并列 Space,并通过 CLI、Web、Desktop 三个客户端访问同一个本地 Runtime。 + +## 分层 + +```text +Launcher / Supervisor + ↓ +本地 Runtime(微内核) + ↓ +Adapter / Tool / Hook / Behavior Package + ↓ +Project / Task / System Evolution Profile + ↓ +CLI / Web / Desktop +``` + +微内核只负责运行规则:本地状态、Run 生命周期、Tool 调用边界、Hook 调度和扩展接缝。它不预先承载具体模型、浏览器、Git、记忆或 Agent 协作策略。 + +## 扩展边界 + +- **Adapter**:可替换的底层实现,例如模型供应商、检索或浏览器驱动。 +- **Tool**:Agent 主动调用的外部能力,例如文件、Shell、Git、浏览器。 +- **Hook**:围绕生命周期做观察、限制或后续动作。 +- **Behavior Package**:Agent 的思考、协作和完成方式。 +- **Profile**:为 Project、Task、System Evolution 组合默认行为与能力。 + +## 演化原则 + +内核可以被修改。Agent 对自身的改动必须发生在隔离 worktree 中,经过测试和 Git 确认后再由 Supervisor 切换版本;失败时能够回退。 diff --git a/docs/automation/computer-butler.md b/docs/automation/computer-butler.md new file mode 100644 index 0000000..5321f95 --- /dev/null +++ b/docs/automation/computer-butler.md @@ -0,0 +1,20 @@ +# 电脑管家与自动化 + +状态:todo +期望阶段:P4 + +## 目标 + +将 Task 从临时聊天空间发展为可处理本地日常事务的电脑管家。 + +## 已确定 + +- 能力通过 Tool 逐项加入,不预先建设完整桌面自动化平台。 +- 候选能力包括剪贴板、通知、文件整理、进程信息、浏览器读取与交互、应用/窗口控制、截图、键鼠自动化、定时任务和后台任务。 +- 常用工作流可以沉淀为模板或 Behavior,但不在早期固定格式。 + +## 待细化 + +- 首个接入的本机/浏览器能力由真实日常需求决定。 +- 浏览器驱动、桌面自动化方式和各平台兼容性。 +- 自动化任务的确认、停止和恢复体验。 diff --git a/docs/clients/desktop.md b/docs/clients/desktop.md new file mode 100644 index 0000000..fdc665c --- /dev/null +++ b/docs/clients/desktop.md @@ -0,0 +1,21 @@ +# Desktop + +状态:todo +期望阶段:P6 + +## 目标 + +将 Desktop 建设为本地常驻控制台和系统集成层,复用 Web 界面与 Runtime。 + +## 已确定 + +- Desktop 在 CLI 和 Web 产品线之后开发。 +- 负责 Runtime 生命周期、托盘、通知、快捷键、原生确认、版本切换与健康状态。 +- 可逐步承担剪贴板、窗口、文件系统和其他原生能力桥接。 +- Desktop 框架在进入本阶段前单独讨论确定。 + +## 待细化 + +- 框架选择与打包更新策略。 +- Web 前端复用方式。 +- 原生权限、后台常驻和跨平台边界。 diff --git a/docs/clients/web.md b/docs/clients/web.md new file mode 100644 index 0000000..a759b14 --- /dev/null +++ b/docs/clients/web.md @@ -0,0 +1,21 @@ +# Web + +状态:todo +期望阶段:P5 + +## 目标 + +将 Web 建设为 Runtime 的第二个客户端,不复制 Agent、存储或 Tool 逻辑。 + +## 已确定 + +- Web 在 CLI 产品线之后开发。 +- 首先覆盖 Space、Conversation、流式聊天和 Run 观察。 +- 后续覆盖 Tool 日志、产物、Plan、项目记忆、Diff、扩展、确认和 System Evolution 管理。 +- Web 框架进入本阶段前单独讨论确定。 + +## 待细化 + +- Runtime 的 HTTP/SSE/WebSocket 访问层。 +- 本地与远程访问方式。 +- 前端框架、状态管理和视觉设计。 diff --git a/docs/design.md b/docs/design.md deleted file mode 100644 index 9c44769..0000000 --- a/docs/design.md +++ /dev/null @@ -1,178 +0,0 @@ -# llm-to-agent 设计文档 - -## 架构设计 - -### 核心理念:微内核 + 事件驱动 - -Agent 的本质是一个循环:**用户输入 → LLM 思考 → 工具调用 → LLM 再思考 → 最终回复**。 - -本项目的答案是:**内核只做一件事——驱动这个循环**。LLM 调用、工具执行、日志输出等一切能力全部通过事件总线交给外部插件。 - -### 三层架构 - -``` -┌──────────────────────────────────────────┐ -│ Route 层 CLI / Desktop / Web │ ← 只决定 I/O 方式 -├──────────────────────────────────────────┤ -│ Plugin 层 provider / tool / hook │ ← 可替换的能力单元 -├──────────────────────────────────────────┤ -│ Core 层 Scheduler + HookBus │ ← 只做循环 + 事件路由 -└──────────────────────────────────────────┘ -``` - -### 事件总线 - -内核循环的每一步都通过 `HookBus` 发出事件,插件注册响应,形成“事件驱动 + 插件化”的架构。 - -### 插件类型 - -| 类型 | 职责 | 响应的事件 | -|------|------|-----------| -| `provider` | LLM 提供商适配 | `llm:call`、`tool:select` | -| `tool` | 工具能力 | `tool:schema`、`tool:execute` | -| `hook` | 生命周期副作用 | `tool:before`、`tool:after`、`run:end` | -| `prompt` | 系统提示词 | 通过配置注入,不响应事件 | - -每个插件通过 `manifest.json` 声明元信息(名称、类型、提供的能力、适用平台),由 `PluginManager` 统一加载。 - -### 三条产品线 - -``` - ┌── @llm-to-agent/core ──┐ - │ MicroKernel + HookBus │ - └────────────────────────┘ - │ - ┌─────────────────┼─────────────────┐ - ▼ ▼ ▼ - CLI Desktop Web - (readline) (Electron) (Browser HTTP) - │ │ │ - ┌───────┴────────┐ ┌──────┴───────┐ ┌──────┴───────┐ - │ 本地工具全部 │ │ 同 CLI │ │ 仅远程工具 │ - │ console 日志 │ │ IPC 日志 │ │ SSE 日志 │ - └────────────────┘ └──────────────┘ └──────────────┘ -``` - -CLI 与 Desktop 共享本地工具(文件读写、Shell 执行),Web 端通过 `platforms` 字段自动跳过本地工具。 - ---- - -## 项目目录设计 - -### Monorepo 结构(pnpm workspace) - -``` -llm-to-agent/ -├── pnpm-workspace.yaml -├── package.json ← 根(private,统一 dev/test 脚本) -├── tsconfig.json -│ -├── docs/ ← 设计文档 -│ └── design.md -│ -├── packages/ -│ │ -│ ├── types/ ← @llm-to-agent/types -│ │ └── src/index.ts ← 所有共享类型(Message/ToolDef/PluginManifest/...) -│ │ -│ ├── core/ ← @llm-to-agent/core -│ │ └── src/ -│ │ ├── hook-bus.ts ← 事件总线(on/emit/request/collect) -│ │ ├── scheduler.ts ← Agent 循环(think→tool→think) -│ │ ├── context.ts ← 消息上下文管理器 -│ │ ├── plugin-manager.ts← 插件加载 + 生命周期 -│ │ └── kernel.ts ← MicroKernel 入口(组合以上模块) -│ │ -│ ├── plugins-builtin/ ← @llm-to-agent/plugins-builtin -│ │ └── [xxxxx]/ ← 内置插件 -│ │ -│ ├── cli/ ← @llm-to-agent/cli(终端入口) -│ │ └── src/index.ts ← readline 循环 + kernel.init([...]) -│ │ -│ ├── desktop/ ← @llm-to-agent/desktop(桌面入口) -│ │ └── src/main.ts ← Electron 主进程 -│ │ -│ ├── server/ ← @llm-to-agent/server(Web 后端) -│ │ └── src/index.ts ← Express + SSE -│ │ -│ ├── web/ ← @llm-to-agent/web(Web 前端) -│ │ └── index.html ← 待实现 React 聊天界面 -│ │ -│ ├── tests/ ← @llm-to-agent/tests -│ │ └── src/ ← *.test.ts(Node 原生 test runner) -│ │ -│ └── old/ ← @llm-to-agent/old(旧代码归档) -│ └── src/ ← 重构前的 Agent 实现 -``` - -### 包依赖关系 - -``` -@llm-to-agent/types ← 零依赖,纯类型 - ↑ ↑ - core plugins-builtin - ↑ ↑ - ├───────────┴────────────┐ - ↓ ↓ ↓ - cli desktop server - ↑ - web -``` - -### 插件清单规范 - -每个插件目录包含 `manifest.json`: - -```json -{ - "name": "@agent/tool-file", - "version": "1.0.0", - "type": "tool", - "provides": ["file-read", "file-write"], - "entry": "./index.ts", - "platforms": ["cli", "desktop"] -} -``` - -| 字段 | 说明 | -|------|------| -| `type` | `provider` / `tool` / `hook` / `prompt` | -| `provides` | 提供的能力标识 | -| `entry` | 入口文件,默认导出 `register(bus: HookBus)` | -| `platforms` | 可用平台,PluginManager 加载时自动过滤 | - -### 关键类型 - -```typescript -// 消息 -interface Message { - role: 'system' | 'user' | 'assistant' | 'tool'; - content: string; - tool_calls?: ToolCall[]; - tool_call_id?: string; -} - -// 工具定义(OpenAI 兼容) -interface ToolDef { - type: 'function'; - function: { - name: string; - description: string; - parameters: Record; - }; -} - -// 插件清单 -interface PluginManifest { - name: string; - version: string; - type: 'provider' | 'tool' | 'hook' | 'prompt'; - provides: string | string[]; - entry: string; - platforms?: ('cli' | 'desktop' | 'web')[]; -} - -// 事件总线 -type EventName = string; -type Listener = (data: any) => any | Promise; -``` diff --git a/docs/development/project-assistant.md b/docs/development/project-assistant.md new file mode 100644 index 0000000..fd4e5b9 --- /dev/null +++ b/docs/development/project-assistant.md @@ -0,0 +1,22 @@ +# Project 开发助手 + +状态:todo +期望阶段:P2 + +## 目标 + +让 Agent 能在绑定源码目录中完成“理解 → 修改 → 测试 → 汇报”的开发闭环。 + +## 已确定 + +- Project 在项目根目录使用 `.agent/` 存放自身会话、Run 和项目上下文。 +- 项目规则、架构摘要和决策记录会逐步沉淀,但不预先引入 RAG。 +- 开发 Tool 包含代码搜索、修改/补丁、测试执行和测试结果收集。 +- 初期由单 Agent 完整完成开发任务;多 Agent 协作由后续真实需求驱动。 +- Git 支持 status、diff、log 与 worktree;候选修改默认放在隔离 worktree。 + +## 待细化 + +- 项目说明文件的名称和注入时机。 +- worktree 的目录、清理和默认分支策略。 +- Task 升级 Project 的 CLI/TUI 流程。 diff --git a/docs/development/system-evolution.md b/docs/development/system-evolution.md new file mode 100644 index 0000000..d4e3cbb --- /dev/null +++ b/docs/development/system-evolution.md @@ -0,0 +1,22 @@ +# System Evolution + +状态:todo +期望阶段:P3 + +## 目标 + +使 Agent 能像维护普通开发项目一样维护自身。进化由用户在专属 Project 中提出需求或反馈驱动,不要求 Agent 主动发现问题。 + +## 已确定 + +- `System Evolution` 是特殊 Project Profile,绑定 Agent 自身源码仓库。 +- Agent 在 Git worktree 中分析、修改、测试候选版本。 +- 用户查看 Diff 后确认 Git commit/merge/push 等写操作。 +- Supervisor 在内核外负责候选版本切换、启动、健康检查和失败回退。 +- 初期以 Git 历史和 Run 日志记录演化过程;复杂评估与进化档案后置。 + +## 待细化 + +- Supervisor 的最小实现和版本切换方式。 +- 候选版本测试、健康检查和回退判定。 +- 发布确认在 CLI/TUI/Web/Desktop 中的体验。 diff --git a/docs/extension/hooks-extensions.md b/docs/extension/hooks-extensions.md new file mode 100644 index 0000000..1b97475 --- /dev/null +++ b/docs/extension/hooks-extensions.md @@ -0,0 +1,22 @@ +# Hooks、扩展与 Profile + +状态:todo +期望阶段:P4 + +## 目标 + +让新能力在需要时能以 Tool、Hook、Behavior、Adapter 或 Profile 的形式优雅生长。 + +## 已确定 + +- 第一版只保留 `beforeTool`、`afterTool`、`afterRun` 三个轻量 Hook 点。 +- Hook 用于观察、约束或响应生命周期;不承担 Run 状态、存储一致性或 Tool 实际执行。 +- Tool、Hook、Behavior、Adapter 是不同扩展形态,不混为“插件”。 +- Project、Task、System Evolution 的差异最终由 Profile 组合表达。 +- 自动发现、manifest、依赖管理、调试台均后置,等出现真实的独立扩展需求再实现。 + +## 待细化 + +- Hook 的注册、顺序、异常隔离和启停。 +- 扩展目录、加载方式和本地开发体验。 +- Profile 的配置格式与覆盖规则。 diff --git a/docs/foundation/cli.md b/docs/foundation/cli.md new file mode 100644 index 0000000..d0fe070 --- /dev/null +++ b/docs/foundation/cli.md @@ -0,0 +1,26 @@ +# CLI + +状态:todo +期望阶段:P1 + +## 目标 + +CLI 是第一条完整产品线,应能独立完成聊天、空间管理、执行观察和日常开发/临时事务。 + +## 已确定 + +- 以自然语言 REPL 为主;普通文本发送给当前 Conversation。 +- 使用少量斜杠命令处理 Space、会话、Run 等确定性控制操作。 +- 提示符需要明确显示当前 Task/Project 与 Conversation。 +- Agent 的普通回答流式输出;Tool 调用显示简短状态、结果和失败信息,不展示内部推理。 +- 除 REPL 外,CLI 还应提供全屏 TUI,用于浏览 Space、会话、Run、日志与产物;它属于 CLI 产品线,不是可遗忘的远期附属功能。 + +## 候选命令 + +`/new`、`/conversations`、`/switch`、`/tasks`、`/task`、`/project`、`/runs`、`/log`、`/cancel`、`/exit`。 + +## 待细化 + +- 启动时恢复最近上下文还是提供编号选择器。 +- REPL 与 TUI 的切换方式、TUI 库、布局和快捷键。 +- 长 Tool 输出的折叠、复制与完整日志查看方式。 diff --git a/docs/foundation/runtime.md b/docs/foundation/runtime.md new file mode 100644 index 0000000..96f48fb --- /dev/null +++ b/docs/foundation/runtime.md @@ -0,0 +1,21 @@ +# Runtime + +状态:todo +期望阶段:P1 + +## 目标 + +建立一个常驻本地 Runtime。它是唯一的状态写入者,也是 Agent、Tool 和未来客户端的运行宿主。 + +## 已确定 + +- 技术基础为 TypeScript + Node.js + monorepo。 +- CLI 从第一天起连接常驻 Runtime,而不是把 Runtime 嵌入在 CLI 进程中。 +- Runtime 负责 Space、Conversation、Run、本地数据和 Tool 执行。 +- Web 与 Desktop 以后只是同一 Runtime 的客户端,不重复 Agent 逻辑。 + +## 待细化 + +- CLI 与 Runtime 的本地通信方式。 +- Node 版本、包管理器、monorepo 工具和包边界。 +- Runtime 启动、健康检查、单实例和停止行为。 diff --git a/docs/foundation/spaces.md b/docs/foundation/spaces.md new file mode 100644 index 0000000..46be972 --- /dev/null +++ b/docs/foundation/spaces.md @@ -0,0 +1,22 @@ +# Space 与 Conversation + +状态:todo +期望阶段:P1 + +## 目标 + +提供两种并列的一级空间,并让每个空间拥有多个独立会话。 + +## 已确定 + +- `Project`:长期开发空间,绑定本地项目目录。 +- `Task`:临时个人事务空间,可用于闲聊、脚本、浏览器或应用控制。 +- 两类 Space 均包含多个 Conversation。 +- 一次实际 Agent 执行称为 `Run`,避免与 Task 概念混淆。 +- `inbox` 可以作为默认 Task;具体生命周期在实现前确认。 + +## 待细化 + +- Task 的默认命名、归档和删除体验。 +- Conversation 标题生成与重命名体验。 +- Project/Task 切换在 REPL 与 TUI 中的展示。 diff --git a/docs/intelligence/memory-knowledge.md b/docs/intelligence/memory-knowledge.md new file mode 100644 index 0000000..c2b0c92 --- /dev/null +++ b/docs/intelligence/memory-knowledge.md @@ -0,0 +1,21 @@ +# 记忆与知识 + +状态:todo +期望阶段:P4 + +## 目标 + +让三端共享有用的个人、项目和会话上下文,同时保持数据本地化和可编辑。 + +## 已确定 + +- 目标层级:Conversation、Task/Project、全局个人记忆,以及短暂的 Run 工作记忆。 +- 初期优先使用摘要、项目说明、架构记录和决策记录等普通文件。 +- 记忆应能查看、编辑、固定或遗忘。 +- 全文检索、文档导入、向量检索和混合检索均后置。 + +## 待细化 + +- 何时自动摘要与提炼记忆。 +- 跨 Space 的记忆引用和隔离规则。 +- 个人偏好写入全局记忆的确认体验。 diff --git a/docs/intelligence/planning-multi-agent.md b/docs/intelligence/planning-multi-agent.md new file mode 100644 index 0000000..9149dc8 --- /dev/null +++ b/docs/intelligence/planning-multi-agent.md @@ -0,0 +1,21 @@ +# 计划与多 Agent + +状态:todo +期望阶段:P4 + +## 目标 + +在单 Agent 已表现出真实瓶颈后,为复杂目标加入计划、步骤和多 Agent 协作。 + +## 已确定 + +- 复杂行动目标可拆为可执行步骤并自动依序执行。 +- 计划、步骤与子 Agent 都是 Run 之上的行为能力,不应先写死进 MVP。 +- Planner、Worker、Reviewer 是候选角色,不构成强制工作流。 +- 并行、DAG、自动重规划和模型路由均在确认需要后再引入。 + +## 待细化 + +- Plan/Step 的交互编辑与 CLI/TUI 展示。 +- 子 Agent 上下文隔离、结果回收和失败处理。 +- 协作质量评估与成本控制。 diff --git a/docs/operations/reliability-safety.md b/docs/operations/reliability-safety.md new file mode 100644 index 0000000..60843f8 --- /dev/null +++ b/docs/operations/reliability-safety.md @@ -0,0 +1,22 @@ +# 可靠性与个人安全 + +状态:todo +期望阶段:P4 + +## 目标 + +在 Agent 开始影响真实项目、文件、网站和本机系统后,逐步补足恢复与确认能力,而不阻塞前期个人探索。 + +## 已确定 + +- 系统是单用户、本地优先项目;不考虑多租户、账号和企业权限。 +- Git 写操作在 System Evolution 中必须经用户确认。 +- 后续高影响操作包括删除文件、真实网页提交、系统设置和应用控制。 +- 候选可靠性能力包括取消/暂停/恢复、崩溃恢复、备份、导入导出、Workspace 清理和产物归档。 +- 候选安全能力包括终端确认、Keychain、敏感日志处理和最小风险等级。 + +## 待细化 + +- 哪些操作先加入确认、确认的默认交互。 +- 数据备份位置和保留策略。 +- 长期任务、异常退出和资源回收。 diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..62f6292 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,27 @@ +# 路线图与管理规则 + +路线图只描述阶段目标。每个功能的具体拆分、提交顺序和实现方案以各自的功能文档为准。 + +| 阶段 | 目标 | 已知范围 | +| --- | --- | --- | +| P1 | 本地 Runtime 与终端产品基础 | Runtime、CLI(REPL 与全屏 TUI)、Space、会话、DeepSeek 聊天、Run、基础 Tool/Hook 接缝 | +| P2 | 终端行动能力与开发助手 | Workspace、文件/Shell、单 Agent Tool Calling、Project、代码与 Git Worktree 能力 | +| P3 | System Evolution 闭环 | 候选 worktree、自身修改与测试、确认发布、Supervisor 回退 | +| P4 | Runtime 成长与个人自动化 | 扩展、计划/多 Agent、记忆、浏览器/电脑控制、可靠性与按需安全能力 | +| P5 | Web 产品线 | Runtime 的 Web 客户端与管理界面 | +| P6 | Desktop 产品线 | 本地常驻控制台与系统集成 | + +## 进度标记 + +每个功能文档使用: + +```text +状态:todo | doing | done +期望阶段:P1 | P2 | ... +``` + +新增需求先归类到已有大功能点;如果它确实是新的长期能力,再新建一个功能文件。实现过程中允许调整阶段和拆分,不要求回头重排整份路线图。 + +## 提交与 blog + +一次提交应交付一个可验证的纵向能力,而不是单独提交类型、枚举或预留接口。完成后在相关功能文档追加:提交链接、验证方式、实现取舍和对应 blog。 From da87569131cc50a1c8afb6c095d208da657775d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 16 Jul 2026 09:24:26 +0800 Subject: [PATCH 12/24] =?UTF-8?q?feat:=20=E7=AC=AC=E4=BA=8C=E7=89=88?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 8 ++-- docs/agent/model-chat.md | 36 ++++++++++------ docs/agent/runs-behavior.md | 37 +++++++++------- docs/agent/tools-workspace.md | 45 +++++++++++++------- docs/architecture/local-data.md | 3 -- docs/architecture/overview.md | 3 -- docs/automation/computer-butler.md | 49 ++++++++++++++++------ docs/clients/desktop.md | 36 ++++++++++------ docs/clients/web.md | 43 +++++++++++++------ docs/development/project-assistant.md | 44 ++++++++++++------- docs/development/system-evolution.md | 51 ++++++++++++++++------- docs/extension/hooks-extensions.md | 44 ++++++++++++------- docs/foundation/cli.md | 51 ++++++++++++++++------- docs/foundation/runtime.md | 36 ++++++++++------ docs/foundation/spaces.md | 37 +++++++++------- docs/intelligence/memory-knowledge.md | 43 +++++++++++++------ docs/intelligence/planning-multi-agent.md | 43 +++++++++++++------ docs/operations/reliability-safety.md | 44 ++++++++++++------- docs/roadmap.md | 12 +++--- 19 files changed, 445 insertions(+), 220 deletions(-) diff --git a/docs/README.md b/docs/README.md index 24968db..379b13f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,10 +4,10 @@ ## 使用方式 -- 每个大功能点一个文件,文件头标记 `状态` 与 `期望阶段`。 -- `todo`、`doing`、`done` 表示当前进度;阶段只表示建议先后,不构成严格依赖顺序。 -- 每完成一个独立提交,在对应文档中补充提交、blog 和实现说明。 -- 未明确的方案写在“待细化”中,不提前冻结实现。 +- 每个大功能点一个文件,大功能本身不设置状态或阶段。 +- 大功能文档内部拆分小功能点,每项分别记录期望实现阶段、实际开发状态、已确认点和待确认点。 +- 阶段只表示期望先后,不构成严格依赖顺序;实际开发状态按实施进展更新。 +- 每完成一个独立提交,在对应小功能点中补充提交、blog 和实现说明。 ## 导航 diff --git a/docs/agent/model-chat.md b/docs/agent/model-chat.md index 706ad32..986c3e6 100644 --- a/docs/agent/model-chat.md +++ b/docs/agent/model-chat.md @@ -1,21 +1,31 @@ # 模型与聊天 -状态:todo -期望阶段:P1 +首版聚焦可靠的 DeepSeek 聊天体验,模型扩展能力在真实需求出现后逐步提炼。 -## 目标 +## DeepSeek Adapter -先提供可靠、连续的 DeepSeek 聊天能力;模型接入保持可替换,但不提前实现多 Provider 系统。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:第一版只兼容 DeepSeek API;模型调用由 Runtime 管理。 +- 待确认点:默认模型、API 地址、请求参数、Key 的读取与配置方式。 -## 已确定 +## 流式聊天 -- 第一版只兼容 DeepSeek API。 -- 支持流式回复和会话历史加载。 -- 模型调用由 Runtime 管理,CLI 只渲染流。 -- 未来模型提供商属于 Adapter 层。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:回答通过 Runtime 流式传递给客户端,并持久化至当前 Conversation。 +- 待确认点:断线处理、中途取消、重连以及部分回复的保存规则。 -## 待细化 +## 对话上下文 -- API Key 的读取位置与配置体验。 -- 默认模型、请求参数和失败/限流提示。 -- 上下文窗口压缩策略。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:聊天加载当前 Conversation 的历史;复杂记忆系统后置。 +- 待确认点:系统提示词、上下文窗口裁剪、历史过长时的摘要策略。 + +## 多模型支持 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:未来模型提供商属于 Adapter 层,不应写死在 Agent Behavior 中。 +- 待确认点:Provider 接口、模型路由、降级、成本与 Token 统计。 diff --git a/docs/agent/runs-behavior.md b/docs/agent/runs-behavior.md index 4fe5db2..cdaa11f 100644 --- a/docs/agent/runs-behavior.md +++ b/docs/agent/runs-behavior.md @@ -1,22 +1,31 @@ # Run 与 Agent Behavior -状态:todo -期望阶段:P1 +Run 为一次用户请求提供可观察的执行边界;Behavior 定义 Agent 如何完成这次执行。 -## 目标 +## Run 生命周期 -使一次用户请求拥有可观察的运行边界,并为未来不同 Agent 行为留下接缝。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:一次用户消息默认产生一个 Run;纯聊天和 Tool 行动使用同一个 Run 概念。 +- 待确认点:首版状态、取消、失败、重试和后台运行语义。 -## 已确定 +## DefaultAgent Behavior -- 一次用户消息默认产生一个 Run。 -- 纯聊天 Run 只产生模型回答;行动 Run 还包含 Tool 调用、输出和产物。 -- 第一版仅实现 `DefaultAgent`,不预设 Planner/Worker/Reviewer 流程。 -- Behavior 定义 Agent 如何准备上下文、调用模型、调用 Tool 和结束 Run。 -- Run 记录输入、输出、错误、时间和原始执行日志。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:首版只有单 Agent;Behavior 负责准备上下文、调用模型、调用 Tool 和结束 Run。 +- 待确认点:Behavior 的 TypeScript 接口,以及系统提示词与上下文组装的位置。 -## 待细化 +## 执行日志 -- Run 的取消、重试和失败显示。 -- 行为接口的具体 TypeScript 形状。 -- 后续 Plan/Step 与 Run 的关系。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:记录输入、模型输出、Tool 调用、错误和时间;生成脚本文本记录在日志中,不额外保存脚本副本。 +- 待确认点:JSONL 记录粒度、大输出截断、日志与产物的边界。 + +## Run 控制 + +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:客户端最终需要取消、查看和重新执行 Run。 +- 待确认点:取消信号传播、失败后续跑和重试时的上下文复用。 diff --git a/docs/agent/tools-workspace.md b/docs/agent/tools-workspace.md index 0c10b93..22f86d9 100644 --- a/docs/agent/tools-workspace.md +++ b/docs/agent/tools-workspace.md @@ -1,23 +1,38 @@ # Tools 与 Workspace -状态:todo -期望阶段:P2 +Tool 是 Agent 主动调用外部能力的统一边界;Workspace 是文件与 Shell 操作的默认作用域。 -## 目标 +## Tool 契约与注册 -让单 Agent 在隔离工作区中使用文件与 Shell 完成真实任务。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:Tool 声明名称、描述、参数并返回结构化结果;实际执行由 Runtime 承载。 +- 待确认点:参数 Schema、错误结果、流式 Tool 输出与注册 API。 -## 已确定 +## Task Workspace -- Tool 是 Agent 主动调用的外部能力。 -- 第一批能力:读写文件、目录浏览、文本搜索和 Shell 执行。 -- 每个 Task 有独立 Workspace;Shell 默认在该目录执行。 -- Tool 调用参数、输出、错误和生成脚本文本写入 Run 日志。 -- Agent 通过模型 Tool Calling 自主选择、调用并读取 Tool 结果。 -- 初期不建设复杂审批或权限语言;真实破坏性操作出现后再增强。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:位于 `~/.agent/tasks//workspace/`;Shell 和文件 Tool 默认以其为作用域。 +- 待确认点:初始化内容、路径展示、临时文件和清理行为。 -## 待细化 +## 文件 Tool -- Shell 超时、取消、后台进程和大输出处理。 -- 文件写入与补丁的交互方式。 -- Tool 描述和参数 Schema 的具体格式。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:首批支持读写文件、目录浏览和文本搜索。 +- 待确认点:补丁接口、编码处理、大文件限制与二进制文件策略。 + +## Shell Tool + +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:记录命令、输出、退出码和错误;首期安全机制保持轻量。 +- 待确认点:超时、取消、后台进程、交互命令和输出上限。 + +## 单 Agent Tool Calling + +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:Agent 自主选择 Tool、读取结果并继续调用模型,直至给出最终回复。 +- 待确认点:循环上限、失败反馈、并行调用和客户端事件协议。 diff --git a/docs/architecture/local-data.md b/docs/architecture/local-data.md index 3ff8d92..610a7b3 100644 --- a/docs/architecture/local-data.md +++ b/docs/architecture/local-data.md @@ -1,8 +1,5 @@ # 本地数据与目录 -状态:todo -期望阶段:P1 - ## 原则 数据以本地普通文件保存,保持易读、易调试、易由 Agent 修改。早期不承诺兼容性,不预先建设 schema migration;重要数据依靠备份,格式变更需要时再写一次性转换脚本。 diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 855a9a2..b2a7f6c 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -1,8 +1,5 @@ # 架构总览 -状态:todo -期望阶段:P1 - ## 定位 这是纯个人使用的开发助手与电脑管家。它支持 Project 和 Task 两种并列 Space,并通过 CLI、Web、Desktop 三个客户端访问同一个本地 Runtime。 diff --git a/docs/automation/computer-butler.md b/docs/automation/computer-butler.md index 5321f95..317ffc9 100644 --- a/docs/automation/computer-butler.md +++ b/docs/automation/computer-butler.md @@ -1,20 +1,45 @@ # 电脑管家与自动化 -状态:todo -期望阶段:P4 +电脑管家能力让 Task 能处理浏览器、本机应用和日常临时事务。 -## 目标 +## 本机基础能力 -将 Task 从临时聊天空间发展为可处理本地日常事务的电脑管家。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:候选能力包括剪贴板、通知、文件整理和进程信息,并通过 Tool 逐项加入。 +- 待确认点:优先能力、跨平台范围和系统 API 选择。 -## 已确定 +## 浏览器读取与交互 -- 能力通过 Tool 逐项加入,不预先建设完整桌面自动化平台。 -- 候选能力包括剪贴板、通知、文件整理、进程信息、浏览器读取与交互、应用/窗口控制、截图、键鼠自动化、定时任务和后台任务。 -- 常用工作流可以沉淀为模板或 Behavior,但不在早期固定格式。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:需要覆盖网页搜索、读取、结构化提取和交互操作。 +- 待确认点:浏览器驱动、登录态复用、下载、标签页管理和真实提交确认。 -## 待细化 +## 应用与窗口控制 -- 首个接入的本机/浏览器能力由真实日常需求决定。 -- 浏览器驱动、桌面自动化方式和各平台兼容性。 -- 自动化任务的确认、停止和恢复体验。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:逐步加入应用启动、窗口管理和文件定位等能力。 +- 待确认点:操作系统优先级、原生桥接方式和无障碍权限。 + +## 桌面自动化 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:候选能力包括截图、屏幕理解、键盘和鼠标操作。 +- 待确认点:视觉驱动方式、坐标可靠性、停止机制和操作确认。 + +## 定时与后台任务 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:未来支持提醒、定时执行与长期后台任务。 +- 待确认点:调度器归属、Runtime 重启恢复、错过任务和结果通知。 + +## 自动化模板 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:重复工作可沉淀为 Behavior 或工作流模板,但不提前固定格式。 +- 待确认点:模板创建、参数化、分享、修改和版本管理。 diff --git a/docs/clients/desktop.md b/docs/clients/desktop.md index fdc665c..6e555c6 100644 --- a/docs/clients/desktop.md +++ b/docs/clients/desktop.md @@ -1,21 +1,31 @@ # Desktop -状态:todo -期望阶段:P6 +Desktop 是本地常驻控制台与系统集成层,复用 Web 界面与同一个 Runtime。 -## 目标 +## Desktop Host -将 Desktop 建设为本地常驻控制台和系统集成层,复用 Web 界面与 Runtime。 +- 期望实现阶段:P6 +- 实际开发状态:未开始 +- 已确认点:负责拉起、连接和观察本地 Runtime;Desktop 框架在进入本阶段前单独讨论。 +- 待确认点:框架、打包、更新、Web 前端复用和跨平台目标。 -## 已确定 +## 常驻体验 -- Desktop 在 CLI 和 Web 产品线之后开发。 -- 负责 Runtime 生命周期、托盘、通知、快捷键、原生确认、版本切换与健康状态。 -- 可逐步承担剪贴板、窗口、文件系统和其他原生能力桥接。 -- Desktop 框架在进入本阶段前单独讨论确定。 +- 期望实现阶段:P6 +- 实际开发状态:未开始 +- 已确认点:支持托盘、通知、全局快捷键和后台状态提示。 +- 待确认点:开机启动、任务完成通知、快捷入口和菜单设计。 -## 待细化 +## 原生确认与版本控制 -- 框架选择与打包更新策略。 -- Web 前端复用方式。 -- 原生权限、后台常驻和跨平台边界。 +- 期望实现阶段:P6 +- 实际开发状态:未开始 +- 已确认点:提供高影响操作确认、版本切换、Runtime 重启、回退与健康状态面板。 +- 待确认点:确认弹窗队列、Supervisor 连接和故障恢复体验。 + +## 原生能力桥接 + +- 期望实现阶段:P6 +- 实际开发状态:未开始 +- 已确认点:可逐步承载剪贴板、窗口、文件系统和其他原生能力桥接。 +- 待确认点:与 Runtime Tool 的边界、权限申请、平台差异和无界面运行方式。 diff --git a/docs/clients/web.md b/docs/clients/web.md index a759b14..ef3c832 100644 --- a/docs/clients/web.md +++ b/docs/clients/web.md @@ -1,21 +1,38 @@ # Web -状态:todo -期望阶段:P5 +Web 是 Runtime 的第二个客户端,不复制 Agent、存储或 Tool 逻辑。 -## 目标 +## Web 连接层 -将 Web 建设为 Runtime 的第二个客户端,不复制 Agent、存储或 Tool 逻辑。 +- 期望实现阶段:P5 +- 实际开发状态:未开始 +- 已确认点:复用同一个本地 Runtime,并支持请求、流式事件和状态查询。 +- 待确认点:HTTP/SSE/WebSocket、远程访问方式和断线恢复。 -## 已确定 +## 会话工作台 -- Web 在 CLI 产品线之后开发。 -- 首先覆盖 Space、Conversation、流式聊天和 Run 观察。 -- 后续覆盖 Tool 日志、产物、Plan、项目记忆、Diff、扩展、确认和 System Evolution 管理。 -- Web 框架进入本阶段前单独讨论确定。 +- 期望实现阶段:P5 +- 实际开发状态:未开始 +- 已确认点:覆盖 Space、Conversation、流式聊天与历史浏览。 +- 待确认点:前端框架、状态管理、路由与移动端适配。 -## 待细化 +## 执行观察台 -- Runtime 的 HTTP/SSE/WebSocket 访问层。 -- 本地与远程访问方式。 -- 前端框架、状态管理和视觉设计。 +- 期望实现阶段:P5 +- 实际开发状态:未开始 +- 已确认点:查看 Run、Tool、Plan、Agent、日志和产物,并提供必要的取消/恢复操作。 +- 待确认点:实时视图、长日志渲染、后台任务和多 Run 并行展示。 + +## 项目与进化工作台 + +- 期望实现阶段:P5 +- 实际开发状态:未开始 +- 已确认点:后续展示项目规则、记忆、测试结果、Git Diff 和 System Evolution 过程。 +- 待确认点:文件编辑、Diff Review、发布确认和版本回退体验。 + +## Web 管理能力 + +- 期望实现阶段:P5 +- 实际开发状态:未开始 +- 已确认点:后续管理配置、扩展、记忆和高影响操作确认。 +- 待确认点:功能范围、本地/远程访问边界与是否需要轻量认证。 diff --git a/docs/development/project-assistant.md b/docs/development/project-assistant.md index fd4e5b9..4082e90 100644 --- a/docs/development/project-assistant.md +++ b/docs/development/project-assistant.md @@ -1,22 +1,38 @@ # Project 开发助手 -状态:todo -期望阶段:P2 +Project 模式让 Agent 在绑定的源码目录中完成长期开发工作。 -## 目标 +## Project 初始化与绑定 -让 Agent 能在绑定源码目录中完成“理解 → 修改 → 测试 → 汇报”的开发闭环。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:Project 绑定本地源码目录,并在项目根目录使用 `.agent/` 保存会话、Run 和项目上下文。 +- 待确认点:初始化命令、已有 `.agent/` 的处理、路径移动与解绑。 -## 已确定 +## 代码工程 Toolset -- Project 在项目根目录使用 `.agent/` 存放自身会话、Run 和项目上下文。 -- 项目规则、架构摘要和决策记录会逐步沉淀,但不预先引入 RAG。 -- 开发 Tool 包含代码搜索、修改/补丁、测试执行和测试结果收集。 -- 初期由单 Agent 完整完成开发任务;多 Agent 协作由后续真实需求驱动。 -- Git 支持 status、diff、log 与 worktree;候选修改默认放在隔离 worktree。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:包括代码搜索、修改/补丁、测试执行和测试结果收集;复用通用 Tool Runtime。 +- 待确认点:测试命令发现、语言适配、代码索引和大仓库性能。 -## 待细化 +## 单 Agent 开发工作流 -- 项目说明文件的名称和注入时机。 -- worktree 的目录、清理和默认分支策略。 -- Task 升级 Project 的 CLI/TUI 流程。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:先跑通“理解源码 → 修改 → 测试 → 汇报”;第一版不强制 Planner/Worker/Reviewer。 +- 待确认点:项目上下文装配、修改前计划、完成判定与失败反馈。 + +## Git 与 Worktree + +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:支持 status、diff、log 和 worktree;候选修改默认发生在隔离 worktree。 +- 待确认点:worktree 路径、分支命名、清理策略与普通 Project 的 Git 写操作确认。 + +## 项目知识沉淀 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:项目规则、架构摘要和决策记录以普通文件起步,不预先引入 RAG。 +- 待确认点:文件名称、自动更新时机、是否进入 Git 和人工编辑体验。 diff --git a/docs/development/system-evolution.md b/docs/development/system-evolution.md index d4e3cbb..16bd32c 100644 --- a/docs/development/system-evolution.md +++ b/docs/development/system-evolution.md @@ -1,22 +1,45 @@ # System Evolution -状态:todo -期望阶段:P3 +System Evolution 让 Agent 像维护普通开发项目一样维护自身。进化目标由用户提出,不要求 Agent 主动发现问题。 -## 目标 +## System Evolution Profile -使 Agent 能像维护普通开发项目一样维护自身。进化由用户在专属 Project 中提出需求或反馈驱动,不要求 Agent 主动发现问题。 +- 期望实现阶段:P3 +- 实际开发状态:未开始 +- 已确认点:它是绑定 Agent 自身源码仓库的特殊 Project Profile,不是独立系统。 +- 待确认点:初始化方式、默认项目规则、允许使用的 Tool 与上下文装配。 -## 已确定 +## 候选版本工作区 -- `System Evolution` 是特殊 Project Profile,绑定 Agent 自身源码仓库。 -- Agent 在 Git worktree 中分析、修改、测试候选版本。 -- 用户查看 Diff 后确认 Git commit/merge/push 等写操作。 -- Supervisor 在内核外负责候选版本切换、启动、健康检查和失败回退。 -- 初期以 Git 历史和 Run 日志记录演化过程;复杂评估与进化档案后置。 +- 期望实现阶段:P3 +- 实际开发状态:未开始 +- 已确认点:Agent 在隔离 Git worktree 中分析、修改和测试自身,不直接篡改当前运行副本。 +- 待确认点:worktree 生命周期、并行候选版本和磁盘清理。 -## 待细化 +## 变更与测试报告 -- Supervisor 的最小实现和版本切换方式。 -- 候选版本测试、健康检查和回退判定。 -- 发布确认在 CLI/TUI/Web/Desktop 中的体验。 +- 期望实现阶段:P3 +- 实际开发状态:未开始 +- 已确认点:发布前向用户展示 Diff、测试结果和变更说明。 +- 待确认点:最低测试门槛、健康检查、失败结果的保留与重新修改。 + +## Git 发布确认 + +- 期望实现阶段:P3 +- 实际开发状态:未开始 +- 已确认点:commit、merge、push 等 Git 写操作必须经用户确认。 +- 待确认点:确认粒度、CLI/TUI 交互、拒绝后的候选版本处理。 + +## Supervisor 与回退 + +- 期望实现阶段:P3 +- 实际开发状态:未开始 +- 已确认点:Supervisor 位于内核外,负责版本切换、启动、健康检查和失败回退。 +- 待确认点:部署布局、进程交接、版本指针和自动回退判定。 + +## 进化历史与评估 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:初期 Git 历史和 Run 日志即为进化记录;后续可关联需求、Diff、测试、发布和回退。 +- 待确认点:是否需要独立 Evolution Record、基准任务以及新旧版本比较方式。 diff --git a/docs/extension/hooks-extensions.md b/docs/extension/hooks-extensions.md index 1b97475..5eb6caf 100644 --- a/docs/extension/hooks-extensions.md +++ b/docs/extension/hooks-extensions.md @@ -1,22 +1,38 @@ # Hooks、扩展与 Profile -状态:todo -期望阶段:P4 +扩展体系让新能力以 Tool、Hook、Behavior、Adapter 或 Profile 的形式生长,而不是全部进入微内核。 -## 目标 +## 基础 Hook 接缝 -让新能力在需要时能以 Tool、Hook、Behavior、Adapter 或 Profile 的形式优雅生长。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:第一版只保留 `beforeTool`、`afterTool`、`afterRun` 三个轻量 Hook 点,并允许内置代码注册。 +- 待确认点:回调签名、错误传播、是否允许修改输入输出。 -## 已确定 +## Hook Runtime -- 第一版只保留 `beforeTool`、`afterTool`、`afterRun` 三个轻量 Hook 点。 -- Hook 用于观察、约束或响应生命周期;不承担 Run 状态、存储一致性或 Tool 实际执行。 -- Tool、Hook、Behavior、Adapter 是不同扩展形态,不混为“插件”。 -- Project、Task、System Evolution 的差异最终由 Profile 组合表达。 -- 自动发现、manifest、依赖管理、调试台均后置,等出现真实的独立扩展需求再实现。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:Hook 用于观察、约束或响应生命周期,不承担存储一致性、Run 状态或 Tool 实际执行。 +- 待确认点:优先级、异常隔离、启停、同步/异步行为和更多稳定事件。 -## 待细化 +## 扩展加载 -- Hook 的注册、顺序、异常隔离和启停。 -- 扩展目录、加载方式和本地开发体验。 -- Profile 的配置格式与覆盖规则。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:当独立扩展需求出现后,再支持 Tool、Hook、Behavior、Adapter 的本地发现与加载。 +- 待确认点:目录布局、manifest、版本依赖、热加载和调试方式。 + +## Profile 组合 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:Project、Task、System Evolution 的差异最终由默认 Tool、Hook、Behavior 和策略组合表达。 +- 待确认点:配置格式、继承覆盖、项目级自定义和运行时切换。 + +## 扩展开发体验 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:扩展应可单独测试、启停和定位运行问题。 +- 待确认点:模板生成、调试 CLI/TUI、扩展回滚和是否需要独立分发机制。 diff --git a/docs/foundation/cli.md b/docs/foundation/cli.md index d0fe070..6471371 100644 --- a/docs/foundation/cli.md +++ b/docs/foundation/cli.md @@ -1,26 +1,45 @@ # CLI -状态:todo -期望阶段:P1 +CLI 是第一条完整产品线,既要适合快速对话,也要能观察和管理复杂运行。 -## 目标 +## 自然语言 REPL -CLI 是第一条完整产品线,应能独立完成聊天、空间管理、执行观察和日常开发/临时事务。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:普通输入发送给当前 Conversation;提示符展示当前 Task/Project 与 Conversation;回答支持流式输出。 +- 待确认点:启动时恢复最近上下文还是显示选择器;输入历史、多行输入与中断体验。 -## 已确定 +## 斜杠命令 -- 以自然语言 REPL 为主;普通文本发送给当前 Conversation。 -- 使用少量斜杠命令处理 Space、会话、Run 等确定性控制操作。 -- 提示符需要明确显示当前 Task/Project 与 Conversation。 -- Agent 的普通回答流式输出;Tool 调用显示简短状态、结果和失败信息,不展示内部推理。 -- 除 REPL 外,CLI 还应提供全屏 TUI,用于浏览 Space、会话、Run、日志与产物;它属于 CLI 产品线,不是可遗忘的远期附属功能。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:斜杠命令只处理确定性控制操作;候选包括 `/new`、`/switch`、`/task`、`/project`、`/runs`、`/log`、`/cancel`、`/exit`。 +- 待确认点:第一批命令范围、参数格式、命令补全和错误提示。 -## 候选命令 +## Space 与会话导航 -`/new`、`/conversations`、`/switch`、`/tasks`、`/task`、`/project`、`/runs`、`/log`、`/cancel`、`/exit`。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:CLI 能创建、查看、切换 Task/Project 和它们内部的多个 Conversation。 +- 待确认点:列表样式、编号/名称选择、最近使用记录与快速切换方式。 -## 待细化 +## Tool 与 Run 呈现 -- 启动时恢复最近上下文还是提供编号选择器。 -- REPL 与 TUI 的切换方式、TUI 库、布局和快捷键。 -- 长 Tool 输出的折叠、复制与完整日志查看方式。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:显示 Tool 名称、关键参数、结果和失败,不展示模型内部推理;长输出保留完整日志入口。 +- 待确认点:流式事件样式、折叠规则、颜色、产物链接和后台 Run 展示。 + +## 全屏 TUI + +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:TUI 属于 CLI 正式能力,用于浏览 Space、Conversation、Run、日志与产物;REPL 仍保留用于快速工作。 +- 待确认点:TUI 库、布局、快捷键、命令面板,以及与 REPL 的切换方式。 + +## CLI 诊断与配置 + +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:CLI 应能查看 Runtime 状态、当前配置与日志位置。 +- 待确认点:配置修改入口、诊断报告和调试模式。 diff --git a/docs/foundation/runtime.md b/docs/foundation/runtime.md index 96f48fb..0a3023c 100644 --- a/docs/foundation/runtime.md +++ b/docs/foundation/runtime.md @@ -1,21 +1,31 @@ # Runtime -状态:todo -期望阶段:P1 +Runtime 是本地常驻的唯一运行宿主,负责状态写入、Agent 执行与 Tool 调用。CLI、Web、Desktop 都是它的客户端。 -## 目标 +## 工程与包边界 -建立一个常驻本地 Runtime。它是唯一的状态写入者,也是 Agent、Tool 和未来客户端的运行宿主。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:TypeScript + Node.js + monorepo;Runtime、客户端与共享边界需要清晰。 +- 待确认点:Node 版本、包管理器、monorepo 工具、首批包的职责与依赖方向。 -## 已确定 +## 常驻进程 -- 技术基础为 TypeScript + Node.js + monorepo。 -- CLI 从第一天起连接常驻 Runtime,而不是把 Runtime 嵌入在 CLI 进程中。 -- Runtime 负责 Space、Conversation、Run、本地数据和 Tool 执行。 -- Web 与 Desktop 以后只是同一 Runtime 的客户端,不重复 Agent 逻辑。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:Runtime 从第一天起独立常驻;CLI 日常启动时自动连接,必要时自动拉起。 +- 待确认点:单实例检测、启动/停止、健康检查、日志位置和异常退出行为。 -## 待细化 +## 本地通信 -- CLI 与 Runtime 的本地通信方式。 -- Node 版本、包管理器、monorepo 工具和包边界。 -- Runtime 启动、健康检查、单实例和停止行为。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:客户端不直接读写 Agent 数据;协议不绑定 CLI,并应能支持流式输出。 +- 待确认点:HTTP + SSE、Unix Socket 或其他本地 RPC 方案。 + +## Runtime 服务边界 + +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:Runtime 负责 Space、Conversation、Run、本地数据、Agent Behavior 与 Tool 执行。 +- 待确认点:首版内部模块边界,以及哪些能力进入微内核、哪些保留为可替换实现。 diff --git a/docs/foundation/spaces.md b/docs/foundation/spaces.md index 46be972..8aed449 100644 --- a/docs/foundation/spaces.md +++ b/docs/foundation/spaces.md @@ -1,22 +1,31 @@ # Space 与 Conversation -状态:todo -期望阶段:P1 +Project 与 Task 是并列的一级空间,两者内部都可以包含多个 Conversation。 -## 目标 +## Task Space -提供两种并列的一级空间,并让每个空间拥有多个独立会话。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:用于闲聊与临时事务;数据存放于用户目录;每个 Task 拥有专属 Workspace。 +- 待确认点:默认 `inbox`、自动命名、归档、删除与最近使用策略。 -## 已确定 +## Project Space -- `Project`:长期开发空间,绑定本地项目目录。 -- `Task`:临时个人事务空间,可用于闲聊、脚本、浏览器或应用控制。 -- 两类 Space 均包含多个 Conversation。 -- 一次实际 Agent 执行称为 `Run`,避免与 Task 概念混淆。 -- `inbox` 可以作为默认 Task;具体生命周期在实现前确认。 +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:用于长期开发工作;绑定本地项目目录;Agent 数据存放在项目的 `.agent/` 中。 +- 待确认点:项目初始化、路径移动、解绑与多工作区支持。 -## 待细化 +## Conversation -- Task 的默认命名、归档和删除体验。 -- Conversation 标题生成与重命名体验。 -- Project/Task 切换在 REPL 与 TUI 中的展示。 +- 期望实现阶段:P1 +- 实际开发状态:未开始 +- 已确认点:每个 Space 可以拥有多个 Conversation;会话承载消息与相关 Run。 +- 待确认点:标题生成、重命名、归档、搜索和跨会话引用。 + +## Task 升级为 Project + +- 期望实现阶段:P2 +- 实际开发状态:未开始 +- 已确认点:Task Workspace 结构尽量与 Project 工作区一致;升级时迁移到用户指定位置并补充 Project 元数据。 +- 待确认点:目录冲突、历史记录路径更新、Git 初始化与升级交互。 diff --git a/docs/intelligence/memory-knowledge.md b/docs/intelligence/memory-knowledge.md index c2b0c92..a546e7d 100644 --- a/docs/intelligence/memory-knowledge.md +++ b/docs/intelligence/memory-knowledge.md @@ -1,21 +1,38 @@ # 记忆与知识 -状态:todo -期望阶段:P4 +记忆能力为 CLI、Web、Desktop 共享个人、项目和会话上下文,同时保持数据本地、可读和可编辑。 -## 目标 +## Conversation 记忆 -让三端共享有用的个人、项目和会话上下文,同时保持数据本地化和可编辑。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:会话可以生成摘要,降低长历史的上下文成本。 +- 待确认点:生成时机、更新方式、人工编辑和与原始消息的关系。 -## 已确定 +## Space 记忆 -- 目标层级:Conversation、Task/Project、全局个人记忆,以及短暂的 Run 工作记忆。 -- 初期优先使用摘要、项目说明、架构记录和决策记录等普通文件。 -- 记忆应能查看、编辑、固定或遗忘。 -- 全文检索、文档导入、向量检索和混合检索均后置。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:Task/Project 各自保存长期上下文;Project 可沉淀规则、架构和决策。 +- 待确认点:文件布局、跨会话提炼、失效信息和 Task/Project 差异。 -## 待细化 +## 全局个人记忆 -- 何时自动摘要与提炼记忆。 -- 跨 Space 的记忆引用和隔离规则。 -- 个人偏好写入全局记忆的确认体验。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:保存跨 Space 有效的个人偏好与长期事实,供三端共享。 +- 待确认点:写入确认、隐私边界、冲突处理和从 Space 提升的规则。 + +## 记忆管理 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:记忆应能查看、编辑、固定、遗忘并追溯来源。 +- 待确认点:客户端体验、自动清理、可信度与过期策略。 + +## 检索与知识导入 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:先使用普通文件和全文检索;向量/混合检索、文档导入按真实需求增加。 +- 待确认点:索引方式、切分、引用来源、支持格式与向量存储。 diff --git a/docs/intelligence/planning-multi-agent.md b/docs/intelligence/planning-multi-agent.md index 9149dc8..08a11f0 100644 --- a/docs/intelligence/planning-multi-agent.md +++ b/docs/intelligence/planning-multi-agent.md @@ -1,21 +1,38 @@ # 计划与多 Agent -状态:todo -期望阶段:P4 +这一能力在单 Agent 出现真实瓶颈后引入,用于拆解复杂目标并协调多个执行者。 -## 目标 +## Plan 与 Step -在单 Agent 已表现出真实瓶颈后,为复杂目标加入计划、步骤和多 Agent 协作。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:复杂目标可拆为可执行步骤并自动依序执行;它们属于 Run 之上的行为能力。 +- 待确认点:数据结构、编辑方式、完成条件、CLI/TUI 展示和与 Conversation 的关系。 -## 已确定 +## 步骤执行与重规划 -- 复杂行动目标可拆为可执行步骤并自动依序执行。 -- 计划、步骤与子 Agent 都是 Run 之上的行为能力,不应先写死进 MVP。 -- Planner、Worker、Reviewer 是候选角色,不构成强制工作流。 -- 并行、DAG、自动重规划和模型路由均在确认需要后再引入。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:未来需要失败重试、跳过和重新规划,但不在 MVP 中预设完整状态机。 +- 待确认点:失败分类、重试上限、人工介入点和上下文继承。 -## 待细化 +## 子 Agent 委派 -- Plan/Step 的交互编辑与 CLI/TUI 展示。 -- 子 Agent 上下文隔离、结果回收和失败处理。 -- 协作质量评估与成本控制。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:主 Agent 可以创建子 Run,分配目标并回收结果。 +- 待确认点:上下文隔离、Tool 范围、并发限制、取消传播与结果可信度。 + +## Agent 角色与协作模板 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:Planner、Worker、Reviewer 是候选角色,不构成强制工作流。 +- 待确认点:角色声明、Behavior 组合、Reviewer 门槛和用户自定义方式。 + +## 并行与模型路由 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:并行、DAG、不同角色使用不同模型都属于后续增强。 +- 待确认点:调度策略、成本/Token 统计、并发冲突与结果合并。 diff --git a/docs/operations/reliability-safety.md b/docs/operations/reliability-safety.md index 60843f8..f1c6038 100644 --- a/docs/operations/reliability-safety.md +++ b/docs/operations/reliability-safety.md @@ -1,22 +1,38 @@ # 可靠性与个人安全 -状态:todo -期望阶段:P4 +安全能力不阻塞早期个人探索,但在 Agent 开始影响真实文件、网站和系统后,需要逐步补足恢复与确认能力。 -## 目标 +## Run 恢复 -在 Agent 开始影响真实项目、文件、网站和本机系统后,逐步补足恢复与确认能力,而不阻塞前期个人探索。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:长期需要取消、暂停、恢复与 Runtime 崩溃后的未完成 Run 处理。 +- 待确认点:恢复粒度、可重放 Tool、幂等性和异常进程清理。 -## 已确定 +## 数据备份与迁移 -- 系统是单用户、本地优先项目;不考虑多租户、账号和企业权限。 -- Git 写操作在 System Evolution 中必须经用户确认。 -- 后续高影响操作包括删除文件、真实网页提交、系统设置和应用控制。 -- 候选可靠性能力包括取消/暂停/恢复、崩溃恢复、备份、导入导出、Workspace 清理和产物归档。 -- 候选安全能力包括终端确认、Keychain、敏感日志处理和最小风险等级。 +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:本地数据最终需要备份、导入导出和目录迁移;早期格式变化可使用一次性脚本。 +- 待确认点:备份位置、周期、保留数量、恢复验证和格式兼容范围。 -## 待细化 +## 高影响操作确认 -- 哪些操作先加入确认、确认的默认交互。 -- 数据备份位置和保留策略。 -- 长期任务、异常退出和资源回收。 +- 期望实现阶段:P3 +- 实际开发状态:未开始 +- 已确认点:System Evolution 的 Git 写操作必须确认;删除文件、真实网页提交、系统设置等后续按实际风险加入。 +- 待确认点:确认粒度、默认允许范围、超时和拒绝后的 Run 行为。 + +## 密钥与敏感数据 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:项目是单用户、本地优先,不建设多租户权限;长期可接入系统 Keychain 并处理敏感日志。 +- 待确认点:首版 API Key 保存方式、脱敏规则和敏感 Tool 输出保留策略。 + +## Workspace 与产物清理 + +- 期望实现阶段:P4 +- 实际开发状态:未开始 +- 已确认点:需要管理临时文件、候选 worktree、执行产物和长期磁盘占用。 +- 待确认点:自动清理阈值、回收站、归档和用户固定机制。 diff --git a/docs/roadmap.md b/docs/roadmap.md index 62f6292..bc24c10 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -11,16 +11,18 @@ | P5 | Web 产品线 | Runtime 的 Web 客户端与管理界面 | | P6 | Desktop 产品线 | 本地常驻控制台与系统集成 | -## 进度标记 +## 功能记录格式 -每个功能文档使用: +大功能本身不设置状态和阶段。大功能文档中的每个小功能点分别记录: ```text -状态:todo | doing | done -期望阶段:P1 | P2 | ... +期望实现阶段:P1 | P2 | ... +实际开发状态:未开始 | 设计中 | 开发中 | 已完成 | 暂停 +已确认点:当前已经达成共识的要求 +待确认点:实现前仍需讨论或通过实践决定的问题 ``` -新增需求先归类到已有大功能点;如果它确实是新的长期能力,再新建一个功能文件。实现过程中允许调整阶段和拆分,不要求回头重排整份路线图。 +新增需求先归类到已有大功能点;如果它确实是新的长期能力,再新建一个大功能文件。实现过程中允许新增、合并或拆分小功能点,不要求回头重排整份路线图。 ## 提交与 blog From c4cfbaf83a364b0d7fb747cb64bd03b6fd2a2b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 16 Jul 2026 10:50:57 +0800 Subject: [PATCH 13/24] =?UTF-8?q?feat:=20=E7=AC=AC=E4=B8=89=E7=89=88?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 53 ++++----- docs/agent/model-chat.md | 31 ----- docs/agent/runs-behavior.md | 31 ----- docs/agent/tools-workspace.md | 38 ------- docs/architecture.md | 125 +++++++++++++++++++++ docs/architecture/local-data.md | 35 ------ docs/architecture/overview.md | 33 ------ docs/automation/computer-butler.md | 45 -------- docs/clients/desktop.md | 31 ----- docs/clients/web.md | 38 ------- docs/development/project-assistant.md | 38 ------- docs/development/system-evolution.md | 45 -------- docs/directory-plan.md | 116 +++++++++++++++++++ docs/extension/hooks-extensions.md | 38 ------- docs/features/automation.md | 49 ++++++++ docs/features/browser.md | 49 ++++++++ docs/features/cli.md | 65 +++++++++++ docs/features/data-config.md | 57 ++++++++++ docs/features/desktop-control.md | 51 +++++++++ docs/features/desktop.md | 57 ++++++++++ docs/features/extensions.md | 65 +++++++++++ docs/features/memory-knowledge.md | 57 ++++++++++ docs/features/model-agent.md | 57 ++++++++++ docs/features/observability-reliability.md | 57 ++++++++++ docs/features/permissions-security.md | 57 ++++++++++ docs/features/planning-multi-agent.md | 57 ++++++++++ docs/features/project-assistant.md | 57 ++++++++++ docs/features/run-execution.md | 49 ++++++++ docs/features/runtime.md | 57 ++++++++++ docs/features/spaces.md | 49 ++++++++ docs/features/system-evolution.md | 49 ++++++++ docs/features/tools-execution.md | 65 +++++++++++ docs/features/web.md | 57 ++++++++++ docs/foundation/cli.md | 45 -------- docs/foundation/runtime.md | 31 ----- docs/foundation/spaces.md | 31 ----- docs/intelligence/memory-knowledge.md | 38 ------- docs/intelligence/planning-multi-agent.md | 38 ------- docs/operations/reliability-safety.md | 38 ------- docs/roadmap.md | 88 +++++++++++---- 40 files changed, 1396 insertions(+), 671 deletions(-) delete mode 100644 docs/agent/model-chat.md delete mode 100644 docs/agent/runs-behavior.md delete mode 100644 docs/agent/tools-workspace.md create mode 100644 docs/architecture.md delete mode 100644 docs/architecture/local-data.md delete mode 100644 docs/architecture/overview.md delete mode 100644 docs/automation/computer-butler.md delete mode 100644 docs/clients/desktop.md delete mode 100644 docs/clients/web.md delete mode 100644 docs/development/project-assistant.md delete mode 100644 docs/development/system-evolution.md create mode 100644 docs/directory-plan.md delete mode 100644 docs/extension/hooks-extensions.md create mode 100644 docs/features/automation.md create mode 100644 docs/features/browser.md create mode 100644 docs/features/cli.md create mode 100644 docs/features/data-config.md create mode 100644 docs/features/desktop-control.md create mode 100644 docs/features/desktop.md create mode 100644 docs/features/extensions.md create mode 100644 docs/features/memory-knowledge.md create mode 100644 docs/features/model-agent.md create mode 100644 docs/features/observability-reliability.md create mode 100644 docs/features/permissions-security.md create mode 100644 docs/features/planning-multi-agent.md create mode 100644 docs/features/project-assistant.md create mode 100644 docs/features/run-execution.md create mode 100644 docs/features/runtime.md create mode 100644 docs/features/spaces.md create mode 100644 docs/features/system-evolution.md create mode 100644 docs/features/tools-execution.md create mode 100644 docs/features/web.md delete mode 100644 docs/foundation/cli.md delete mode 100644 docs/foundation/runtime.md delete mode 100644 docs/foundation/spaces.md delete mode 100644 docs/intelligence/memory-knowledge.md delete mode 100644 docs/intelligence/planning-multi-agent.md delete mode 100644 docs/operations/reliability-safety.md diff --git a/docs/README.md b/docs/README.md index 379b13f..1c3526f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,31 +1,32 @@ -# 文档索引 +# llm-to-agent 文档 -本文档记录 `llm-to-agent` 当前已达成的产品与架构共识。它不是固定开发清单:实现前再细化当前功能点,发现新需求则补到对应功能文档中。 +文档分为三部分: -## 使用方式 +- [架构总览](architecture.md):稳定的职责边界与依赖原则; +- [规划目录](directory-plan.md):当前建议的源码与运行数据布局,允许随实际开发调整; +- [实现阶段](roadmap.md):先完成基本能力,再建立自举,随后由 Agent 继续实现自己的开发顺序; +- `features/`:按整个项目的大功能分类,列出尽量完整且有实质性交付的小功能点。 -- 每个大功能点一个文件,大功能本身不设置状态或阶段。 -- 大功能文档内部拆分小功能点,每项分别记录期望实现阶段、实际开发状态、已确认点和待确认点。 -- 阶段只表示期望先后,不构成严格依赖顺序;实际开发状态按实施进展更新。 -- 每完成一个独立提交,在对应小功能点中补充提交、blog 和实现说明。 +## 功能目录 -## 导航 +- [Runtime 与内核](features/runtime.md) +- [本地数据与配置](features/data-config.md) +- [Space、Conversation 与消息](features/spaces.md) +- [模型、聊天与 Agent](features/model-agent.md) +- [Run 与 Agent 执行](features/run-execution.md) +- [Tools、Workspace 与产物](features/tools-execution.md) +- [CLI](features/cli.md) +- [Project 开发助手](features/project-assistant.md) +- [System Evolution](features/system-evolution.md) +- [扩展、Hooks 与 Profile](features/extensions.md) +- [计划、工作流与多 Agent](features/planning-multi-agent.md) +- [记忆、检索与知识](features/memory-knowledge.md) +- [浏览器](features/browser.md) +- [本机与桌面控制](features/desktop-control.md) +- [自动化与后台任务](features/automation.md) +- [可观测性与可靠性](features/observability-reliability.md) +- [权限、确认与敏感数据](features/permissions-security.md) +- [Web](features/web.md) +- [Desktop](features/desktop.md) -- [路线图与管理规则](roadmap.md) -- [架构总览](architecture/overview.md) -- [本地数据与目录](architecture/local-data.md) -- [Runtime](foundation/runtime.md) -- [CLI](foundation/cli.md) -- [Space 与 Conversation](foundation/spaces.md) -- [模型与聊天](agent/model-chat.md) -- [Run 与 Agent Behavior](agent/runs-behavior.md) -- [Tools 与 Workspace](agent/tools-workspace.md) -- [Project 开发助手](development/project-assistant.md) -- [System Evolution](development/system-evolution.md) -- [Hooks、扩展与 Profile](extension/hooks-extensions.md) -- [计划与多 Agent](intelligence/planning-multi-agent.md) -- [记忆与知识](intelligence/memory-knowledge.md) -- [电脑管家与自动化](automation/computer-butler.md) -- [可靠性与个人安全](operations/reliability-safety.md) -- [Web](clients/web.md) -- [Desktop](clients/desktop.md) +具体字段、技术库和交互细节在进入对应实现阶段时讨论,不用早期规划替代真实开发反馈。 diff --git a/docs/agent/model-chat.md b/docs/agent/model-chat.md deleted file mode 100644 index 986c3e6..0000000 --- a/docs/agent/model-chat.md +++ /dev/null @@ -1,31 +0,0 @@ -# 模型与聊天 - -首版聚焦可靠的 DeepSeek 聊天体验,模型扩展能力在真实需求出现后逐步提炼。 - -## DeepSeek Adapter - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:第一版只兼容 DeepSeek API;模型调用由 Runtime 管理。 -- 待确认点:默认模型、API 地址、请求参数、Key 的读取与配置方式。 - -## 流式聊天 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:回答通过 Runtime 流式传递给客户端,并持久化至当前 Conversation。 -- 待确认点:断线处理、中途取消、重连以及部分回复的保存规则。 - -## 对话上下文 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:聊天加载当前 Conversation 的历史;复杂记忆系统后置。 -- 待确认点:系统提示词、上下文窗口裁剪、历史过长时的摘要策略。 - -## 多模型支持 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:未来模型提供商属于 Adapter 层,不应写死在 Agent Behavior 中。 -- 待确认点:Provider 接口、模型路由、降级、成本与 Token 统计。 diff --git a/docs/agent/runs-behavior.md b/docs/agent/runs-behavior.md deleted file mode 100644 index cdaa11f..0000000 --- a/docs/agent/runs-behavior.md +++ /dev/null @@ -1,31 +0,0 @@ -# Run 与 Agent Behavior - -Run 为一次用户请求提供可观察的执行边界;Behavior 定义 Agent 如何完成这次执行。 - -## Run 生命周期 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:一次用户消息默认产生一个 Run;纯聊天和 Tool 行动使用同一个 Run 概念。 -- 待确认点:首版状态、取消、失败、重试和后台运行语义。 - -## DefaultAgent Behavior - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:首版只有单 Agent;Behavior 负责准备上下文、调用模型、调用 Tool 和结束 Run。 -- 待确认点:Behavior 的 TypeScript 接口,以及系统提示词与上下文组装的位置。 - -## 执行日志 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:记录输入、模型输出、Tool 调用、错误和时间;生成脚本文本记录在日志中,不额外保存脚本副本。 -- 待确认点:JSONL 记录粒度、大输出截断、日志与产物的边界。 - -## Run 控制 - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:客户端最终需要取消、查看和重新执行 Run。 -- 待确认点:取消信号传播、失败后续跑和重试时的上下文复用。 diff --git a/docs/agent/tools-workspace.md b/docs/agent/tools-workspace.md deleted file mode 100644 index 22f86d9..0000000 --- a/docs/agent/tools-workspace.md +++ /dev/null @@ -1,38 +0,0 @@ -# Tools 与 Workspace - -Tool 是 Agent 主动调用外部能力的统一边界;Workspace 是文件与 Shell 操作的默认作用域。 - -## Tool 契约与注册 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:Tool 声明名称、描述、参数并返回结构化结果;实际执行由 Runtime 承载。 -- 待确认点:参数 Schema、错误结果、流式 Tool 输出与注册 API。 - -## Task Workspace - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:位于 `~/.agent/tasks//workspace/`;Shell 和文件 Tool 默认以其为作用域。 -- 待确认点:初始化内容、路径展示、临时文件和清理行为。 - -## 文件 Tool - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:首批支持读写文件、目录浏览和文本搜索。 -- 待确认点:补丁接口、编码处理、大文件限制与二进制文件策略。 - -## Shell Tool - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:记录命令、输出、退出码和错误;首期安全机制保持轻量。 -- 待确认点:超时、取消、后台进程、交互命令和输出上限。 - -## 单 Agent Tool Calling - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:Agent 自主选择 Tool、读取结果并继续调用模型,直至给出最终回复。 -- 待确认点:循环上限、失败反馈、并行调用和客户端事件协议。 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..9d10cfe --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,125 @@ +# 架构总览 + +## 定位 + +`llm-to-agent` 是纯个人使用、本地优先的开发助手与电脑管家。它以一个常驻 Runtime 作为唯一运行宿主,并通过 CLI、Web、Desktop 三个客户端共享数据和执行状态。 + +系统采用四层结构: + +```text +Launcher / Supervisor + ↓ +Runtime Host + ↓ +Application Runtime + ↓ +Microkernel + Extensions +``` + +这四层表达职责和依赖方向,不要求第一版拆成四个独立进程。 + +## Launcher / Supervisor + +Supervisor 位于 Agent 运行版本之外,只负责启动、版本切换、健康检查和失败回退。它不理解 Space、Conversation、Tool 或 Agent 推理。 + +Supervisor 在 P3 的 System Evolution 发布闭环中实现,P1/P2 开发阶段可以先由普通启动脚本承担进程拉起。 + +## Runtime Host + +Runtime Host 是 Node.js 进程的组合入口,负责: + +- 创建并启动 Application Runtime 与 Microkernel; +- 装配当前启用的扩展; +- 开启本地客户端连接; +- 处理进程信号、单实例和退出; +- 将配置与基础设施交给内部模块。 + +Host 不包含 Project、Conversation、Agent 工作流等业务规则。 + +## Application Runtime + +Application Runtime 负责这个产品不可缺少的应用能力: + +- Project / Task Space; +- Conversation 与持久化 Run Record; +- 本地数据 Repository 和唯一写入; +- 客户端 Command、Query 与 Event Stream; +- 当前 Space/Conversation 上下文; +- Profile 选择和基础并发策略。 + +它不包含 DeepSeek 请求、Shell、Git、浏览器驱动、提示词或具体 Agent 协作策略。 + +## Microkernel + +Microkernel 负责一次 Agent 执行的通用机制: + +- 创建内存中的 Run Context; +- 调用 Agent Behavior; +- 注册并调用 Tool; +- 执行 Hook Pipeline; +- 发出流式输出和运行事件; +- 传播取消信号并隔离执行错误。 + +Microkernel 不认识 Project、Task 或具体存储,也不依赖任何具体扩展。 + +## Extensions + +扩展只放真正可替换、可按需增加的能力: + +- **Adapter**:DeepSeek、未来模型提供商、浏览器或检索驱动; +- **Tool**:文件、Shell、Git、浏览器、桌面控制; +- **Hook**:围绕 Tool/Run 生命周期观察、限制或响应; +- **Behavior**:DefaultAgent、CodingAgent、Planner、Reviewer。 + +Space、Conversation 和持久化 Run Record 是 Application Runtime 的产品领域,不作为可卸载插件。 + +## Profile + +Profile 是 Application Runtime 管理的声明式能力组合,不是独立运行层: + +```text +Profile +├── 默认 Behavior +├── 可用 Tools +├── 启用 Hooks +├── 模型设置 +└── 上下文/记忆策略 +``` + +Project 和 Task 是 Space 类型;System Evolution 是应用于 Project 的特殊 Profile。 + +## 依赖规则 + +```text +Supervisor → Runtime Host +Runtime Host → Application Runtime / Microkernel / Extensions +Application Runtime → Microkernel 公共接口 +Extensions → Microkernel 扩展接口 +Microkernel ✕ Application Runtime / 具体 Extensions +Clients → Runtime Interface +``` + +目录隔离只是表现,以上依赖方向才是边界是否真实成立的判断依据。 + +## 客户端接口 + +CLI、Web、Desktop 逻辑上只使用三类交互: + +- **Command**:创建 Space、发送消息、取消 Run 等状态变更; +- **Query**:读取 Space、Conversation、Run 和日志; +- **Event Stream**:接收模型增量、Tool 过程和 Run 状态。 + +具体采用 HTTP/SSE、Unix Socket 或其他本地协议在实现时确定,不提前冻结字段。 + +## Event 与 Hook + +- **Run Event** 是发生过的事实,用于日志和客户端实时展示。 +- **Hook** 是事件前后实际执行的扩展代码。 + +两者不能混为同一个事件总线:记录了一个事件,不代表必须触发可修改流程的 Hook。 + +## 演化原则 + +任何一层都允许由 Agent 修改,包括 Application Runtime 和 Microkernel。Agent 在 System Evolution Project 的隔离 worktree 中修改源码、运行测试并展示 Diff;Git 写操作由用户确认,Supervisor 负责切换构建版本和失败回退。 + +自进化依赖的是源码可修改、候选版本隔离、测试、发布和回退,不要求把所有产品能力插件化。 diff --git a/docs/architecture/local-data.md b/docs/architecture/local-data.md deleted file mode 100644 index 610a7b3..0000000 --- a/docs/architecture/local-data.md +++ /dev/null @@ -1,35 +0,0 @@ -# 本地数据与目录 - -## 原则 - -数据以本地普通文件保存,保持易读、易调试、易由 Agent 修改。早期不承诺兼容性,不预先建设 schema migration;重要数据依靠备份,格式变更需要时再写一次性转换脚本。 - -Runtime 是唯一写入者,CLI/Web/Desktop 均通过 Runtime 读取或修改数据。 - -## 目录 - -```text -~/.agent/ - tasks// - space.json - conversations/ - runs/ - workspace/ - -/.agent/ - project.json - conversations/ - runs/ -``` - -Conversation 与 Run 优先采用 JSONL;Space/Project 元信息采用小型 JSON 文件。生成并执行的脚本文本进入 Run 日志,不额外维护脚本库;真正写进 Workspace 的文件自然保留。 - -## Task 升级 - -Task Workspace 的组织尽量与 Project 工作区一致。升级为 Project 时,将该专属目录迁移至用户指定路径并写入 Project 元信息。 - -## 待细化 - -- ID 和文件命名规则。 -- Run 日志与产物的具体划分。 -- 备份、归档和数据清理策略。 diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md deleted file mode 100644 index b2a7f6c..0000000 --- a/docs/architecture/overview.md +++ /dev/null @@ -1,33 +0,0 @@ -# 架构总览 - -## 定位 - -这是纯个人使用的开发助手与电脑管家。它支持 Project 和 Task 两种并列 Space,并通过 CLI、Web、Desktop 三个客户端访问同一个本地 Runtime。 - -## 分层 - -```text -Launcher / Supervisor - ↓ -本地 Runtime(微内核) - ↓ -Adapter / Tool / Hook / Behavior Package - ↓ -Project / Task / System Evolution Profile - ↓ -CLI / Web / Desktop -``` - -微内核只负责运行规则:本地状态、Run 生命周期、Tool 调用边界、Hook 调度和扩展接缝。它不预先承载具体模型、浏览器、Git、记忆或 Agent 协作策略。 - -## 扩展边界 - -- **Adapter**:可替换的底层实现,例如模型供应商、检索或浏览器驱动。 -- **Tool**:Agent 主动调用的外部能力,例如文件、Shell、Git、浏览器。 -- **Hook**:围绕生命周期做观察、限制或后续动作。 -- **Behavior Package**:Agent 的思考、协作和完成方式。 -- **Profile**:为 Project、Task、System Evolution 组合默认行为与能力。 - -## 演化原则 - -内核可以被修改。Agent 对自身的改动必须发生在隔离 worktree 中,经过测试和 Git 确认后再由 Supervisor 切换版本;失败时能够回退。 diff --git a/docs/automation/computer-butler.md b/docs/automation/computer-butler.md deleted file mode 100644 index 317ffc9..0000000 --- a/docs/automation/computer-butler.md +++ /dev/null @@ -1,45 +0,0 @@ -# 电脑管家与自动化 - -电脑管家能力让 Task 能处理浏览器、本机应用和日常临时事务。 - -## 本机基础能力 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:候选能力包括剪贴板、通知、文件整理和进程信息,并通过 Tool 逐项加入。 -- 待确认点:优先能力、跨平台范围和系统 API 选择。 - -## 浏览器读取与交互 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:需要覆盖网页搜索、读取、结构化提取和交互操作。 -- 待确认点:浏览器驱动、登录态复用、下载、标签页管理和真实提交确认。 - -## 应用与窗口控制 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:逐步加入应用启动、窗口管理和文件定位等能力。 -- 待确认点:操作系统优先级、原生桥接方式和无障碍权限。 - -## 桌面自动化 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:候选能力包括截图、屏幕理解、键盘和鼠标操作。 -- 待确认点:视觉驱动方式、坐标可靠性、停止机制和操作确认。 - -## 定时与后台任务 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:未来支持提醒、定时执行与长期后台任务。 -- 待确认点:调度器归属、Runtime 重启恢复、错过任务和结果通知。 - -## 自动化模板 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:重复工作可沉淀为 Behavior 或工作流模板,但不提前固定格式。 -- 待确认点:模板创建、参数化、分享、修改和版本管理。 diff --git a/docs/clients/desktop.md b/docs/clients/desktop.md deleted file mode 100644 index 6e555c6..0000000 --- a/docs/clients/desktop.md +++ /dev/null @@ -1,31 +0,0 @@ -# Desktop - -Desktop 是本地常驻控制台与系统集成层,复用 Web 界面与同一个 Runtime。 - -## Desktop Host - -- 期望实现阶段:P6 -- 实际开发状态:未开始 -- 已确认点:负责拉起、连接和观察本地 Runtime;Desktop 框架在进入本阶段前单独讨论。 -- 待确认点:框架、打包、更新、Web 前端复用和跨平台目标。 - -## 常驻体验 - -- 期望实现阶段:P6 -- 实际开发状态:未开始 -- 已确认点:支持托盘、通知、全局快捷键和后台状态提示。 -- 待确认点:开机启动、任务完成通知、快捷入口和菜单设计。 - -## 原生确认与版本控制 - -- 期望实现阶段:P6 -- 实际开发状态:未开始 -- 已确认点:提供高影响操作确认、版本切换、Runtime 重启、回退与健康状态面板。 -- 待确认点:确认弹窗队列、Supervisor 连接和故障恢复体验。 - -## 原生能力桥接 - -- 期望实现阶段:P6 -- 实际开发状态:未开始 -- 已确认点:可逐步承载剪贴板、窗口、文件系统和其他原生能力桥接。 -- 待确认点:与 Runtime Tool 的边界、权限申请、平台差异和无界面运行方式。 diff --git a/docs/clients/web.md b/docs/clients/web.md deleted file mode 100644 index ef3c832..0000000 --- a/docs/clients/web.md +++ /dev/null @@ -1,38 +0,0 @@ -# Web - -Web 是 Runtime 的第二个客户端,不复制 Agent、存储或 Tool 逻辑。 - -## Web 连接层 - -- 期望实现阶段:P5 -- 实际开发状态:未开始 -- 已确认点:复用同一个本地 Runtime,并支持请求、流式事件和状态查询。 -- 待确认点:HTTP/SSE/WebSocket、远程访问方式和断线恢复。 - -## 会话工作台 - -- 期望实现阶段:P5 -- 实际开发状态:未开始 -- 已确认点:覆盖 Space、Conversation、流式聊天与历史浏览。 -- 待确认点:前端框架、状态管理、路由与移动端适配。 - -## 执行观察台 - -- 期望实现阶段:P5 -- 实际开发状态:未开始 -- 已确认点:查看 Run、Tool、Plan、Agent、日志和产物,并提供必要的取消/恢复操作。 -- 待确认点:实时视图、长日志渲染、后台任务和多 Run 并行展示。 - -## 项目与进化工作台 - -- 期望实现阶段:P5 -- 实际开发状态:未开始 -- 已确认点:后续展示项目规则、记忆、测试结果、Git Diff 和 System Evolution 过程。 -- 待确认点:文件编辑、Diff Review、发布确认和版本回退体验。 - -## Web 管理能力 - -- 期望实现阶段:P5 -- 实际开发状态:未开始 -- 已确认点:后续管理配置、扩展、记忆和高影响操作确认。 -- 待确认点:功能范围、本地/远程访问边界与是否需要轻量认证。 diff --git a/docs/development/project-assistant.md b/docs/development/project-assistant.md deleted file mode 100644 index 4082e90..0000000 --- a/docs/development/project-assistant.md +++ /dev/null @@ -1,38 +0,0 @@ -# Project 开发助手 - -Project 模式让 Agent 在绑定的源码目录中完成长期开发工作。 - -## Project 初始化与绑定 - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:Project 绑定本地源码目录,并在项目根目录使用 `.agent/` 保存会话、Run 和项目上下文。 -- 待确认点:初始化命令、已有 `.agent/` 的处理、路径移动与解绑。 - -## 代码工程 Toolset - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:包括代码搜索、修改/补丁、测试执行和测试结果收集;复用通用 Tool Runtime。 -- 待确认点:测试命令发现、语言适配、代码索引和大仓库性能。 - -## 单 Agent 开发工作流 - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:先跑通“理解源码 → 修改 → 测试 → 汇报”;第一版不强制 Planner/Worker/Reviewer。 -- 待确认点:项目上下文装配、修改前计划、完成判定与失败反馈。 - -## Git 与 Worktree - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:支持 status、diff、log 和 worktree;候选修改默认发生在隔离 worktree。 -- 待确认点:worktree 路径、分支命名、清理策略与普通 Project 的 Git 写操作确认。 - -## 项目知识沉淀 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:项目规则、架构摘要和决策记录以普通文件起步,不预先引入 RAG。 -- 待确认点:文件名称、自动更新时机、是否进入 Git 和人工编辑体验。 diff --git a/docs/development/system-evolution.md b/docs/development/system-evolution.md deleted file mode 100644 index 16bd32c..0000000 --- a/docs/development/system-evolution.md +++ /dev/null @@ -1,45 +0,0 @@ -# System Evolution - -System Evolution 让 Agent 像维护普通开发项目一样维护自身。进化目标由用户提出,不要求 Agent 主动发现问题。 - -## System Evolution Profile - -- 期望实现阶段:P3 -- 实际开发状态:未开始 -- 已确认点:它是绑定 Agent 自身源码仓库的特殊 Project Profile,不是独立系统。 -- 待确认点:初始化方式、默认项目规则、允许使用的 Tool 与上下文装配。 - -## 候选版本工作区 - -- 期望实现阶段:P3 -- 实际开发状态:未开始 -- 已确认点:Agent 在隔离 Git worktree 中分析、修改和测试自身,不直接篡改当前运行副本。 -- 待确认点:worktree 生命周期、并行候选版本和磁盘清理。 - -## 变更与测试报告 - -- 期望实现阶段:P3 -- 实际开发状态:未开始 -- 已确认点:发布前向用户展示 Diff、测试结果和变更说明。 -- 待确认点:最低测试门槛、健康检查、失败结果的保留与重新修改。 - -## Git 发布确认 - -- 期望实现阶段:P3 -- 实际开发状态:未开始 -- 已确认点:commit、merge、push 等 Git 写操作必须经用户确认。 -- 待确认点:确认粒度、CLI/TUI 交互、拒绝后的候选版本处理。 - -## Supervisor 与回退 - -- 期望实现阶段:P3 -- 实际开发状态:未开始 -- 已确认点:Supervisor 位于内核外,负责版本切换、启动、健康检查和失败回退。 -- 待确认点:部署布局、进程交接、版本指针和自动回退判定。 - -## 进化历史与评估 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:初期 Git 历史和 Run 日志即为进化记录;后续可关联需求、Diff、测试、发布和回退。 -- 待确认点:是否需要独立 Evolution Record、基准任务以及新旧版本比较方式。 diff --git a/docs/directory-plan.md b/docs/directory-plan.md new file mode 100644 index 0000000..1c5ac0d --- /dev/null +++ b/docs/directory-plan.md @@ -0,0 +1,116 @@ +# 规划目录 + +本文描述当前架构下建议采用的源码与运行数据目录。它用于指导项目起步,不是不可修改的目录规范。 + +后续实现或 System Evolution 过程中,如果真实依赖、代码规模或开发体验证明某个划分不合理,可以移动、合并或拆分目录;调整时应优先保持职责和依赖方向,而不是机械维持路径兼容。 + +## 源码仓库 + +```text +llm-to-agent/ +├── apps/ +│ ├── runtime/ # Runtime Host / composition root +│ ├── supervisor/ # 启动、版本切换、健康检查、回退 +│ ├── cli/ # REPL、非交互 CLI、TUI +│ ├── web/ # Web 客户端 +│ └── desktop/ # Desktop Host 与原生桥接 +│ +├── packages/ +│ ├── kernel/ # Microkernel +│ ├── application/ # Application Runtime +│ └── client/ # 客户端共享的 Runtime Client +│ +├── extensions/ # 第一方扩展,按完整能力纵向划分 +│ ├── deepseek/ +│ ├── workspace/ +│ ├── shell/ +│ ├── git/ +│ └── .../ +│ +├── docs/ +│ ├── architecture.md +│ ├── directory-plan.md +│ ├── roadmap.md +│ └── features/ +│ +├── tests/ +│ └── e2e/ # 跨包、自举和版本切换测试 +│ +├── tooling/ # 构建、发布、开发辅助 +│ +├── package.json +├── tsconfig.json +└── workspace.yaml +``` + +## 架构对应关系 + +| 架构职责 | 规划目录 | +| --- | --- | +| Launcher / Supervisor | `apps/supervisor/` | +| Runtime Host | `apps/runtime/` | +| Application Runtime | `packages/application/` | +| Microkernel | `packages/kernel/` | +| Extensions | `extensions/` | +| Runtime Client | `packages/client/` | +| CLI / Web / Desktop | `apps/cli/`、`apps/web/`、`apps/desktop/` | + +## 目录原则 + +### Apps 是进程和产品入口 + +`apps/runtime/` 负责创建组件、安装扩展并开启本地连接,不承载 Space、Conversation、Tool 或 Agent Behavior 的具体实现。 + +CLI、Web、Desktop 通过同一个 Runtime Client 工作,不各自复制 Agent、存储和执行逻辑。 + +### Packages 固定核心依赖边界 + +- `kernel/` 只包含 Run Context、Behavior 调度、Tool Gateway、Hook Pipeline、Run Event、流式输出、取消和错误边界。 +- `application/` 承载 Project/Task、Conversation、Message、Run Record、Repository、Profile 以及 Command/Query 等产品领域。 +- `client/` 承载三个客户端共用的 Command、Query、Event Stream 和连接逻辑。 + +不建立泛化的 `shared/` 或 `types/` 大杂烩。类型应尽量由拥有该概念的模块导出,只有真正跨客户端传输的协议进入 `client/`。 + +### Extensions 按完整能力纵向划分 + +扩展不按 Adapter、Tool、Hook、Behavior 等技术类型横向拆目录,而是按能力组织。例如 `browser/` 可以同时包含浏览器 Adapter、相关 Tools、操作 Hook 和 Browser Behavior。 + +第一版不要求每个扩展都是独立 package。只有在依赖、测试、复用、版本或独立发布需求出现后,才将它提升为单独 workspace package。 + +### 功能文档不映射源码目录 + +`docs/features/` 按产品大功能组织,源码按职责和依赖组织,两者不要求一一对应。 + +例如 System Evolution 会同时涉及 Application Runtime、Git 扩展、Runtime Host、Supervisor 和 CLI,不应为了与功能文档对齐而把所有代码放入单个目录。 + +### 测试就近放置 + +单元测试和模块测试与所属源码放在一起。顶层 `tests/e2e/` 只保存真正跨包、跨进程或跨版本的场景,例如 CLI 到 Runtime、Project 开发、自举发布和版本回退。 + +## 运行数据目录 + +运行数据不进入源码仓库: + +```text +~/.agent/ +├── tasks/ +├── registry.json +├── extensions/ # 用户本地安装的扩展 +├── releases/ # Installed Releases +├── runtime/ +└── config/ +``` + +Project 专属 Agent 数据位于项目根目录的 `.agent/`。候选 worktree 和 Installed Release 由 `~/.agent/` 下的运行数据管理,不作为源码仓库中的固定目录。 + +## 允许调整的判断标准 + +满足以下任一情况时,可以调整目录: + +- 一个目录长期承载了多个不相关职责; +- 一项完整能力被迫跨越过多技术分类目录; +- 模块需要独立测试、复用、加载、版本或发布; +- 现有依赖方向导致循环依赖或核心反向依赖具体功能; +- System Evolution 的真实开发过程证明当前结构降低了可理解性或修改效率。 + +目录调整后应同步更新本文,但不需要为保持旧规划而保留无价值的兼容层。 diff --git a/docs/extension/hooks-extensions.md b/docs/extension/hooks-extensions.md deleted file mode 100644 index 5eb6caf..0000000 --- a/docs/extension/hooks-extensions.md +++ /dev/null @@ -1,38 +0,0 @@ -# Hooks、扩展与 Profile - -扩展体系让新能力以 Tool、Hook、Behavior、Adapter 或 Profile 的形式生长,而不是全部进入微内核。 - -## 基础 Hook 接缝 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:第一版只保留 `beforeTool`、`afterTool`、`afterRun` 三个轻量 Hook 点,并允许内置代码注册。 -- 待确认点:回调签名、错误传播、是否允许修改输入输出。 - -## Hook Runtime - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:Hook 用于观察、约束或响应生命周期,不承担存储一致性、Run 状态或 Tool 实际执行。 -- 待确认点:优先级、异常隔离、启停、同步/异步行为和更多稳定事件。 - -## 扩展加载 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:当独立扩展需求出现后,再支持 Tool、Hook、Behavior、Adapter 的本地发现与加载。 -- 待确认点:目录布局、manifest、版本依赖、热加载和调试方式。 - -## Profile 组合 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:Project、Task、System Evolution 的差异最终由默认 Tool、Hook、Behavior 和策略组合表达。 -- 待确认点:配置格式、继承覆盖、项目级自定义和运行时切换。 - -## 扩展开发体验 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:扩展应可单独测试、启停和定位运行问题。 -- 待确认点:模板生成、调试 CLI/TUI、扩展回滚和是否需要独立分发机制。 diff --git a/docs/features/automation.md b/docs/features/automation.md new file mode 100644 index 0000000..72f3bee --- /dev/null +++ b/docs/features/automation.md @@ -0,0 +1,49 @@ +# 自动化与后台任务 + +## 持久后台任务 + +**实现阶段:P7** + +- 将 Run 提交为客户端断开后仍能执行的持久任务。 +- 保存执行状态、下一步动作、关联 Space 和人工介入点。 +- Runtime 重启后识别未完成任务并按策略恢复或等待用户处理。 + +## 提醒与定时执行 + +**实现阶段:P7** + +- 创建一次性提醒和指定时间执行的 Agent 任务。 +- 正确处理时区、设备休眠、Runtime 未运行和错过执行时间。 +- 将执行结果通过 CLI、Web、Desktop 或系统通知反馈。 + +## 周期任务 + +**实现阶段:P7** + +- 支持每日、每周及可表达的周期规则。 +- 管理启停、下次执行、失败重试和避免重复运行。 +- 每次执行生成独立 Run,并保留周期任务的整体历史。 + +## 事件触发自动化 + +**实现阶段:P7** + +- 根据文件变化、下载完成、应用状态或其他本地事件触发 Run。 +- 对频繁事件去抖、合并并限制并发。 +- 显示触发来源,允许用户临时暂停或禁用自动化。 + +## 自动化模板与参数 + +**实现阶段:P7** + +- 将成功的脚本、Tool 组合或工作流保存为可复用自动化。 +- 定义输入参数、默认值、执行条件和所需能力。 +- 修改模板不会篡改既有执行记录,能够查看版本差异。 + +## 自动化执行历史与通知 + +**实现阶段:P7** + +- 汇总每个自动化的成功、失败、耗时、产物和人工介入记录。 +- 对连续失败或异常结果发送通知并暂停后续执行。 +- 从历史 Run 快速重试或进入对应 Conversation 排查。 diff --git a/docs/features/browser.md b/docs/features/browser.md new file mode 100644 index 0000000..19d3a2a --- /dev/null +++ b/docs/features/browser.md @@ -0,0 +1,49 @@ +# 浏览器 + +## 搜索、导航与页面读取 + +**实现阶段:P7** + +- 搜索互联网、打开 URL、读取页面文本和获取页面基本结构。 +- 管理重定向、加载失败、分页和动态页面等待。 +- 将页面内容转为适合模型使用且保留来源的结构化结果。 + +## 页面提取与站内探索 + +**实现阶段:P7** + +- 提取列表、表格、链接、表单和指定区域内容。 +- 在站内跟随链接完成多页资料收集,并避免无界爬取。 +- 保存必要截图、页面快照或下载内容作为 Run 产物。 + +## DOM 交互与表单操作 + +**实现阶段:P7** + +- 点击、输入、选择、滚动、等待和提交表单。 +- 通过可访问性树、DOM 和稳定定位信息减少脆弱坐标操作。 +- 提交、购买、发布等真实外部写操作进入确认流程。 + +## 标签页、会话与登录态 + +**实现阶段:P7** + +- 管理浏览器实例、窗口、标签页、历史和页面间上下文。 +- 按 Profile 或任务复用已有登录态,同时保持不同任务之间的边界。 +- 处理登录过期、验证码和必须由用户接管的页面。 + +## 上传、下载与文件流转 + +**实现阶段:P7** + +- 将 Workspace 文件上传到网页,并将下载内容保存为可追踪 Artifact。 +- 观察下载状态、文件名、重复文件和失败重试。 +- 将浏览器文件与 Task/Project Workspace 建立明确关联。 + +## 页面视觉理解与混合定位 + +**实现阶段:P7** + +- 使用截图和视觉模型理解仅靠 DOM 难以操作的界面。 +- 组合 DOM、可访问性树、图像和坐标完成定位。 +- 视觉操作记录截图和动作序列,便于用户检查和失败恢复。 diff --git a/docs/features/cli.md b/docs/features/cli.md new file mode 100644 index 0000000..261444c --- /dev/null +++ b/docs/features/cli.md @@ -0,0 +1,65 @@ +# CLI + +## Runtime 管理与首次配置 + +**实现阶段:P1** + +- 提供 Runtime 启动、连接、状态查询和停止命令。 +- Runtime 未启动时自动拉起,CLI 退出后 Runtime 保持运行。 +- 完成 DeepSeek Key、数据目录和首个 Task 的最小首次配置。 + +## 自然语言 REPL + +**实现阶段:P1** + +- 在当前 Conversation 中进行流式聊天。 +- 支持输入历史、多行输入、取消当前请求和清晰的当前上下文提示符。 +- 终端重开后恢复最近 Space、Conversation 和消息历史。 + +## Space 与 Conversation 控制 + +**实现阶段:P1** + +- 通过一组一致的斜杠命令创建、列出、切换、重命名和归档 Task/Conversation。 +- 为 Project 绑定和切换复用相同的导航体验。 +- 提供帮助、命令错误提示和最近使用列表。 + +## Run、Tool 与产物呈现 + +**实现阶段:P2** + +- 实时展示模型回复、Tool 调用、Shell 输出、测试结果、错误和最终状态。 +- 长输出默认摘要,并能查看完整 Run 日志和 Artifact。 +- 提供 Run 取消、重新执行以及 Project Diff 查看入口。 + +## System Evolution 终端流程 + +**实现阶段:P3** + +- 在 CLI 中查看候选版本、Diff、测试结果、发布说明和健康检查。 +- 完成 Git 写操作确认、版本切换和回退操作。 +- 确保拒绝发布或发布失败后仍能继续处理候选工作区。 + +## 非交互与管道模式 + +**实现阶段:P4** + +- 支持一次性命令、stdin 输入、stdout 结果、结构化 JSON 输出和可靠退出码。 +- 允许 Shell 脚本或其他程序创建 Run、等待结果或后台提交。 +- 交互模式和非交互模式使用同一 Runtime 接口。 + +## 全屏 TUI + +**实现阶段:P4** + +- 提供 Space/Conversation 导航、聊天、Run 时间线、日志、产物和 Diff 工作区。 +- 加入命令面板、搜索、补全、快捷键、主题和可调整布局。 +- 支持配置、扩展、System Evolution 和后台 Run 管理,不替代快速 REPL。 + +## 多 Agent 与记忆工作台 + +**实现阶段:P6** + +- 展示 Plan、Step、Child Run、Agent 分工、人工检查点和结果验证。 +- 管理 Conversation/Space/全局记忆,查看来源并进行编辑或遗忘。 +- 将 P5/P6 的复杂能力整合到一致的终端交互中。 diff --git a/docs/features/data-config.md b/docs/features/data-config.md new file mode 100644 index 0000000..04e084c --- /dev/null +++ b/docs/features/data-config.md @@ -0,0 +1,57 @@ +# 本地数据与配置 + +## 全局数据根目录与 Space 注册表 + +**实现阶段:P1** + +- 建立 `~/.agent/` 全局目录、Runtime 数据和 Space 注册表。 +- 注册表保存 Space 的 ID、类型、名称、真实路径和最近访问,用于定位 Task 与分散的 Project。 +- 处理首次启动、目录缺失、重复注册和路径不可访问等情况。 + +## Space、Conversation 与 Run 文件仓库 + +**实现阶段:P1** + +- 使用普通 JSON 保存当前元信息,使用 JSONL 保存消息与追加式 Run 记录。 +- 支持创建、读取、更新和列出 Space、Conversation、Message、Run。 +- 采用临时文件替换等简单方式保证单次写入完整,异常文件能够被识别并报告。 + +## 统一工作区目录 + +**实现阶段:P1** + +- Task 根目录本身就是 Workspace,内部使用 `.agent/` 保存 Agent 数据。 +- Project 使用相同目录形状,使 Task 可以整体移动并升级为 Project。 +- 明确工作文件、Agent 元数据和全局注册信息各自的所有权。 + +## Run 日志与产物仓库 + +**实现阶段:P2** + +- 保存模型调用、Tool 调用、Shell 输出、错误、测试结果和 Diff 等执行记录。 +- 将日志中的一次性内容与需要长期保留、预览或下载的 Artifact 分开管理。 +- 支持通过 Conversation、Run 和 Tool 调用定位相关记录与文件。 + +## 配置体系与覆盖规则 + +**实现阶段:P4** + +- 管理全局、Space、Profile 和客户端配置,并定义清晰的覆盖顺序。 +- 区分普通配置、凭据引用和运行时临时选项。 +- 支持查看配置最终值、来源、修改和恢复默认值。 + +## 缓存、索引与临时数据 + +**实现阶段:P6** + +- 为代码索引、知识检索、网页内容和模型缓存提供统一存放位置。 +- 缓存可重建且不与原始数据混淆,支持容量统计和按类型清理。 +- 保持 Project 数据、全局数据和临时数据之间的边界。 + +## 数据导入、导出与修复 + +**实现阶段:P8** + +- 支持全局或按 Space 导出、导入、备份和恢复。 +- 提供目录迁移、格式升级、完整性检查和损坏数据修复工具。 +- 备份过程可验证,恢复前保留现有数据的回退副本。 diff --git a/docs/features/desktop-control.md b/docs/features/desktop-control.md new file mode 100644 index 0000000..2fb9fe0 --- /dev/null +++ b/docs/features/desktop-control.md @@ -0,0 +1,51 @@ +# 本机与桌面控制 + +本功能描述 Agent 面向电脑的 Tool;它不同于 P10 的 Desktop 客户端。 + +## 剪贴板、通知与系统信息 + +**实现阶段:P7** + +- 读取和写入剪贴板文本或文件引用。 +- 发送本地通知并关联触发它的 Run。 +- 查询时间、网络、磁盘、进程和基础系统状态。 + +## 本地文件整理 + +**实现阶段:P7** + +- 对用户指定目录执行分类、移动、重命名、去重和下载目录整理。 +- 先生成变更预览,再执行可能影响大量文件的操作。 +- 删除优先进入系统回收站,并记录可恢复位置。 + +## 应用生命周期与状态 + +**实现阶段:P7** + +- 启动、聚焦、退出应用并查询运行状态。 +- 打开指定文件、URL 或项目位置到对应应用。 +- 处理应用未安装、无响应和需要用户登录等情况。 + +## 窗口与文件定位 + +**实现阶段:P7** + +- 查询、切换、移动、调整大小和排列窗口。 +- 将 Workspace、终端、编辑器和浏览器定位到相关资源。 +- 在多屏幕和多个同名窗口环境中保持明确目标。 + +## 屏幕理解与键鼠操作 + +**实现阶段:P7** + +- 截取屏幕或窗口,使用视觉能力识别界面状态。 +- 执行键盘、鼠标、拖放和快捷键操作。 +- 提供紧急停止、动作记录和操作前后截图。 + +## 系统权限与能力降级 + +**实现阶段:P7** + +- 检测辅助功能、录屏、通知和自动化权限。 +- 指引用户完成必要授权,并在权限缺失时切换到可用能力。 +- 向 Agent 暴露真实平台能力,避免反复调用不可用 Tool。 diff --git a/docs/features/desktop.md b/docs/features/desktop.md new file mode 100644 index 0000000..e5e71cb --- /dev/null +++ b/docs/features/desktop.md @@ -0,0 +1,57 @@ +# Desktop + +## Desktop Host 与共享界面 + +**实现阶段:P10** + +- 选择 Desktop 框架,复用 Web 的主要界面、状态和 Runtime Client。 +- 管理应用窗口、深色模式、链接打开和本地导航。 +- Desktop 不包含第三套 Agent、存储或 Tool 执行逻辑。 + +## Runtime 生命周期管理 + +**实现阶段:P10** + +- 安装、启动、停止、重启并观察本地 Runtime。 +- 处理 Runtime 未安装、启动失败、版本不匹配和客户端重连。 +- 提供健康状态、日志入口和故障恢复操作。 + +## 托盘、快捷键与通知 + +**实现阶段:P10** + +- 支持系统托盘、开机启动、后台常驻和快速状态查看。 +- 使用全局快捷键快速唤起输入、当前任务或运行面板。 +- 将 Run 完成、失败、确认和自动化结果发送为原生通知。 + +## 原生确认与任务总览 + +**实现阶段:P10** + +- 在应用不位于前台时显示原生确认窗口。 +- 汇总后台 Run、定时任务、自动化、资源使用和待处理人工介入。 +- 从通知或托盘直接跳转到对应 Space、Conversation 或 Run。 + +## 快速输入与语音交互 + +**实现阶段:P10** + +- 通过全局快捷入口快速输入文本、粘贴剪贴板内容或发起语音请求。 +- 将语音转写、附件和当前前台应用上下文交给同一个 Conversation/Run 流程。 +- 支持录音状态、取消、转写确认和输出朗读,不建立独立语音会话系统。 + +## 系统能力桥接 + +**实现阶段:P10** + +- 为剪贴板、窗口、文件选择器、浏览器和系统权限提供原生 Adapter。 +- Agent 使用的 Tool 仍归本机与桌面控制功能,Desktop 只提供平台桥接。 +- 处理权限申请、平台差异和应用关闭后的能力可用性。 + +## 版本、安装与更新 + +**实现阶段:P10** + +- 打包 Desktop、Runtime 和必要资源,提供个人设备安装流程。 +- 展示 Installed Release、更新、重启、版本切换和回退状态。 +- 更新失败时保留可启动的旧版本,并与 Supervisor 的版本管理保持一致。 diff --git a/docs/features/extensions.md b/docs/features/extensions.md new file mode 100644 index 0000000..ee5c4e7 --- /dev/null +++ b/docs/features/extensions.md @@ -0,0 +1,65 @@ +# 扩展、Hooks 与 Profile + +## 静态扩展装配 + +**实现阶段:P1** + +- Runtime Host 通过明确的 composition root 装配首批 Adapter 和 Behavior。 +- P2 在同一机制中加入 Tool 与基础 Hook,不使用动态扫描或 manifest。 +- 扩展只能通过 Microkernel 公共接口注册,不能反向依赖 Host 内部实现。 + +## 基础 Hook Pipeline + +**实现阶段:P2** + +- 围绕 Tool 调用和 Run 完成提供少量稳定 Hook 点。 +- Hook 与 Run Event 分离:Hook 执行附加逻辑,Event 记录已经发生的事实。 +- Hook 失败不会悄然破坏主流程,执行结果进入 Run 日志。 + +## Hook 管理与诊断 + +**实现阶段:P4** + +- 支持 Hook 顺序、启停、异常隔离、耗时和调用链查看。 +- 明确哪些 Hook 只观察、哪些可以拒绝或结构化修改调用。 +- 为日志、摘要、确认、通知和评估提供稳定接缝。 + +## 本地扩展加载 + +**实现阶段:P4** + +- 加载本地 Tool、Adapter、Hook 和 Behavior,并校验声明与依赖。 +- 支持安装、启用、禁用、重新加载和错误回退。 +- 内置模块与外部扩展使用相同的运行接缝,但 Application Runtime 领域不插件化。 + +## Profile 组合 + +**实现阶段:P4** + +- 声明每种 Space 默认使用的 Behavior、Tools、Hooks、模型和上下文策略。 +- 支持全局默认、Project/Task/System Evolution Profile 和 Space 级覆盖。 +- 运行前得到确定的能力集合,并能向用户解释最终配置来源。 + +## 扩展开发与版本管理 + +**实现阶段:P4** + +- 提供扩展模板、测试工具、调试输出和兼容性检查。 +- 将扩展变更纳入 System Evolution 的候选、测试和回退流程。 +- 当内部模块真实长大后,支持从目录提升为独立 package,而不要求一开始拆包。 + +## Behavior 与 Skill 能力包 + +**实现阶段:P5** + +- 将可复用的 Agent 角色、提示词、上下文策略和工作方法封装成能力包。 +- 支持 Behavior 组合、参数化和按 Profile 选择。 +- Skill 复用现有 Tool 和 Run 机制,不建立绕过 Microkernel 的第二套执行系统。 + +## 扩展分发 + +**实现阶段:P8** + +- 支持本地扩展打包、来源记录、版本锁定、更新和卸载。 +- 对不兼容或启动失败的扩展提供隔离和回退。 +- 分发机制服务个人设备与自举,不以建设公共插件市场为前提。 diff --git a/docs/features/memory-knowledge.md b/docs/features/memory-knowledge.md new file mode 100644 index 0000000..6c24504 --- /dev/null +++ b/docs/features/memory-knowledge.md @@ -0,0 +1,57 @@ +# 记忆、检索与知识 + +## Conversation 摘要与上下文记忆 + +**实现阶段:P6** + +- 从长会话中提炼目标、决策、约束、未完成事项和重要结果。 +- 摘要可查看并能追溯原始消息,更新时不覆盖用户显式纠正。 +- Context Builder 按当前需求选择摘要与必要原文。 + +## Task 与 Project 记忆 + +**实现阶段:P6** + +- 跨 Conversation 保存 Space 级事实、规则、架构、决策和经验。 +- Project 记忆与项目目录绑定,Task 记忆随 Task 升级迁移。 +- 新会话可以检索相关历史,而不是加载该 Space 的全部内容。 + +## 全局个人记忆 + +**实现阶段:P6** + +- 保存跨 Space 有效的个人偏好、习惯和长期事实。 +- 区分用户明确声明与 Agent 从历史中提炼的内容。 +- 三个客户端共享同一记忆服务和修改结果。 + +## 记忆管理与来源 + +**实现阶段:P6** + +- 支持查看、编辑、固定、合并、纠错、遗忘和恢复记忆。 +- 每条记忆保留来源、作用域、更新时间和相关 Conversation/Run。 +- 处理互相冲突、已过期和不再可信的信息。 + +## 全文、向量与混合检索 + +**实现阶段:P6** + +- 对消息、Run、项目文档和知识资料建立全文索引。 +- 在需要语义检索时增加向量索引,并通过混合排序提高准确性。 +- 返回可解释的来源片段,避免只给出不可核查的记忆结论。 + +## 文档导入与个人知识库 + +**实现阶段:P6** + +- 导入常见文本、代码、Markdown、PDF 和网页资料。 +- 管理解析、切分、索引、更新、删除和重复内容。 +- 回答时引用原始来源,并区分个人记忆与外部资料。 + +## 记忆提炼与维护 Hooks + +**实现阶段:P6** + +- 在 Conversation、Run 或 Project 事件后提出摘要与记忆更新。 +- 定期检测重复、冲突、过期和缺少来源的记忆。 +- 自动维护不得阻塞正常 Run,用户可以查看和撤销变更。 diff --git a/docs/features/model-agent.md b/docs/features/model-agent.md new file mode 100644 index 0000000..d6842c8 --- /dev/null +++ b/docs/features/model-agent.md @@ -0,0 +1,57 @@ +# 模型、聊天与 Agent + +## DeepSeek Adapter 与流式聊天 + +**实现阶段:P1** + +- 通过 Model Port 接入 DeepSeek API,支持流式文本生成和基础请求配置。 +- 将模型增量传递给客户端,并在完成后保存用户可见消息。 +- 处理网络失败、API 错误、中途取消和未完成回复。 + +## DefaultAgent 文本执行 + +**实现阶段:P1** + +- 实现单一 DefaultAgent Behavior,完成输入、上下文组装、模型调用和最终回答。 +- Behavior 运行在 Microkernel 中,通过端口使用模型,不直接写入 Repository。 +- 保持纯聊天 Run 与后续行动 Run 使用一致的执行入口。 + +## 系统提示词与上下文组装 + +**实现阶段:P1** + +- 组合系统提示词、当前 Space、Conversation 历史和用户输入。 +- 定义消息角色、顺序和客户端可见内容与模型内部消息的边界。 +- 为 Project 规则、记忆、附件和子 Agent 上下文预留明确装配位置。 + +## 单 Agent Tool Calling + +**实现阶段:P2** + +- 将可用 Tool 描述交给模型,解析 Tool Call 并通过 Tool Gateway 执行。 +- 将结构化 Tool 结果送回模型,循环直至生成最终回答。 +- 对未知 Tool、无效参数、执行失败和循环上限给出可恢复反馈。 + +## 上下文裁剪与压缩 + +**实现阶段:P4** + +- 在超过模型上下文限制前选择、裁剪或压缩历史内容。 +- 保留关键用户要求、Tool 结果和来源,避免摘要悄然改变任务意图。 +- 向用户展示发生过的上下文压缩,并允许查看原始历史。 + +## 多模型管理与路由 + +**实现阶段:P5** + +- 接入多个 Model Adapter,按 Space、Profile、Behavior 或具体 Run 选择模型。 +- 支持模型能力匹配、失败降级和角色级模型路由。 +- 统一记录模型标识、Token、延迟和调用结果。 + +## 多模态模型能力 + +**实现阶段:P7** + +- 支持图片、截图、文件等多模态输入和相应模型能力声明。 +- 将附件、屏幕内容和浏览器截图安全地组装进模型上下文。 +- 对不支持某种输入的模型进行能力降级或路由。 diff --git a/docs/features/observability-reliability.md b/docs/features/observability-reliability.md new file mode 100644 index 0000000..0b4be17 --- /dev/null +++ b/docs/features/observability-reliability.md @@ -0,0 +1,57 @@ +# 可观测性与可靠性 + +## Run 时间线与基础日志 + +**实现阶段:P1** + +- 记录 Run 状态、模型请求结果、错误和客户端输出时间线。 +- P2 加入 Tool、Shell、测试、Diff 和 Artifact 事件。 +- 日志保持可读、可关联且能够从 CLI 定位,不提前建设复杂遥测平台。 + +## Runtime 与功能诊断 + +**实现阶段:P4** + +- 生成包含 Runtime、配置、扩展、模型、数据目录和最近错误的诊断报告。 +- 查看 Hook 调用、Tool 耗时、连接状态和异常堆栈。 +- 支持脱敏后导出诊断信息用于排查或交给 Agent 自己分析。 + +## Run 暂停与恢复 + +**实现阶段:P5** + +- 在计划、子 Agent 或等待用户输入时持久化可恢复状态。 +- 区分可安全重放的步骤和已经产生外部副作用的步骤。 +- Runtime 重启后允许用户继续、跳过、回滚或终止未完成 Run。 + +## Runtime 崩溃恢复 + +**实现阶段:P8** + +- 检测异常退出、孤儿子进程、未完成写入和悬空 worktree。 +- 重启后恢复健康状态并列出需要处理的 Run。 +- 对自动恢复过程保留记录,避免隐藏数据或副作用不一致。 + +## 数据完整性与修复 + +**实现阶段:P8** + +- 检查注册表、JSON/JSONL、Artifact、索引和实际目录之间的一致性。 +- 修复可恢复问题,隔离损坏记录,并在操作前生成备份。 +- 定期验证备份可读取,而不是只确认备份文件存在。 + +## 资源、性能与成本监控 + +**实现阶段:P8** + +- 统计 Runtime CPU/内存、磁盘占用、队列长度、模型 Token、延迟和费用。 +- 定位过慢 Tool、异常循环、长期占用的进程和持续增长的数据。 +- 为清理、并发限制和模型路由提供真实依据。 + +## 资源回收 + +**实现阶段:P8** + +- 管理 Workspace 临时文件、worktree、日志、缓存、索引和 Artifact 的保留策略。 +- 在自动清理前保护用户固定内容和仍被 Run 引用的资源。 +- 提供空间预览、手动清理和可恢复删除。 diff --git a/docs/features/permissions-security.md b/docs/features/permissions-security.md new file mode 100644 index 0000000..8a21b0c --- /dev/null +++ b/docs/features/permissions-security.md @@ -0,0 +1,57 @@ +# 权限、确认与敏感数据 + +## API Key 与基础凭据配置 + +**实现阶段:P1** + +- 首版安全读取 DeepSeek API Key,避免写入源码和普通 Run 日志。 +- 明确环境变量、配置引用和错误提示。 +- 所有凭据访问集中经过统一接口,为后续 Keychain 做准备。 + +## System Evolution Git 确认 + +**实现阶段:P3** + +- 对 commit、merge、push 和活动版本切换展示具体对象与 Diff。 +- 用户明确确认后才能执行,拒绝不会导致候选数据丢失。 +- 确认结果关联到 Evolution Run 和发布记录。 + +## 自动化操作的就地确认 + +**实现阶段:P7** + +- 文件批量修改或删除、网页提交、消息发布、系统设置等高影响动作在对应功能中请求确认。 +- 确认内容描述即将发生的真实副作用,而不是只显示抽象 Tool 名称。 +- 支持本次允许、拒绝和转为人工接管。 + +## 统一 Tool 风险与确认协议 + +**实现阶段:P8** + +- 为 Tool 声明读取范围、写入副作用、外部提交和所需系统权限。 +- 按全局、Profile、Space 和具体 Tool 配置自动允许或询问。 +- 统一管理待确认、超时、拒绝、恢复和审计记录。 + +## Keychain 与凭据生命周期 + +**实现阶段:P8** + +- 将模型、网站和外部服务凭据接入系统安全存储。 +- 支持创建、更新、撤销、失效提示和按能力授权访问。 +- Agent 只获得调用凭据的能力,不在普通上下文中看到明文。 + +## 敏感内容与历史清理 + +**实现阶段:P8** + +- 对日志、模型输入输出和 Artifact 中的密钥、Cookie 与个人信息进行识别和脱敏。 +- 支持用户定位并清除已经写入历史的敏感内容。 +- 清理过程同步处理缓存、索引、备份策略和来源引用。 + +## 远程访问边界 + +**实现阶段:P9** + +- Web 远程访问默认关闭,本地访问与远程暴露使用不同配置。 +- 远程模式提供轻量认证、连接撤销、来源限制和敏感操作再确认。 +- 不引入多用户系统,但避免无认证地暴露本机 Agent 能力。 diff --git a/docs/features/planning-multi-agent.md b/docs/features/planning-multi-agent.md new file mode 100644 index 0000000..802c8b0 --- /dev/null +++ b/docs/features/planning-multi-agent.md @@ -0,0 +1,57 @@ +# 计划、工作流与多 Agent + +## Plan 创建、编辑与执行 + +**实现阶段:P5** + +- 将复杂目标拆成有顺序和依赖关系的 Step。 +- 在执行前查看、编辑、重新生成或直接批准计划。 +- 顺序执行 Step,并将每步的输入、状态、结果和产物关联到父 Run。 + +## Step 控制与动态重规划 + +**实现阶段:P5** + +- 支持暂停、继续、跳过、重试和修改尚未执行的步骤。 +- 步骤失败后根据实际结果修订后续计划,而不是机械重复原方案。 +- 允许 Agent 在关键点等待用户补充信息或确认方向。 + +## Child Run 与子 Agent 委派 + +**实现阶段:P5** + +- 主 Agent 创建 Child Run,传递清晰目标、上下文和可用能力范围。 +- 隔离不同子 Agent 的工作上下文并回收结构化结果。 +- 父 Run 可以观察、取消和处理子 Run 失败。 + +## Agent 角色、团队与 Reviewer + +**实现阶段:P5** + +- 支持 Planner、Worker、Reviewer 等 Behavior 角色,但不固定唯一协作模板。 +- 按任务动态选择角色、模型、Tool 和上下文。 +- Reviewer 依据测试、Diff 和完成条件给出通过、返工或人工处理结论。 + +## 并行与 DAG 调度 + +**实现阶段:P5** + +- 并行执行没有依赖且不会争用同一资源的步骤。 +- 管理 DAG 依赖、并发上限、取消传播、工作区冲突和结果合并。 +- 并行失败不会造成其他步骤结果丢失或状态不明。 + +## 可复用工作流 + +**实现阶段:P5** + +- 将稳定的计划和 Agent 协作方式保存为参数化工作流。 +- 支持从一次成功 Run 提炼模板、再次运行并查看版本变化。 +- 工作流调用统一的 Behavior、Tool 和 Run,不另建执行引擎。 + +## 任务验证与质量评估 + +**实现阶段:P5** + +- 为计划和步骤定义可检查的完成条件。 +- 综合自动测试、Reviewer、外部状态和用户反馈评估结果。 +- 记录部分完成、回退、返工和最终交付之间的关系。 diff --git a/docs/features/project-assistant.md b/docs/features/project-assistant.md new file mode 100644 index 0000000..d2f983c --- /dev/null +++ b/docs/features/project-assistant.md @@ -0,0 +1,57 @@ +# Project 开发助手 + +## 项目上下文与规则 + +**实现阶段:P2** + +- 读取项目说明、目录结构、已有开发规则和构建配置。 +- 将相关 Project 上下文装配给 Coding Behavior,而不是一次性塞入全部源码。 +- 支持用户维护项目专属指令,并在 Run 中显示实际采用的规则。 + +## 仓库理解与代码检索 + +**实现阶段:P2** + +- 浏览仓库结构、搜索符号与文本、定位入口和相关测试。 +- 形成面向当前需求的结构概览,避免为每次任务预先建立重型索引。 +- 在修改前识别影响范围、现有约定和可能的验证方式。 + +## 代码修改与 Diff + +**实现阶段:P2** + +- 使用文件与补丁 Tool 修改源码,处理多文件改动和冲突。 +- 持续展示工作区状态和 Diff,并关联每次修改的需求与 Run。 +- 保持用户已有未提交改动,不用破坏性 Git 操作覆盖现场。 + +## Build、Test、Lint 与验证 + +**实现阶段:P2** + +- 发现或配置项目验证命令,运行构建、测试、Lint 和类型检查。 +- 解析退出状态和关键失败,允许 Agent 修正后重新验证。 +- 汇总执行过的验证、未执行项和剩余风险。 + +## 单 Agent 开发闭环 + +**实现阶段:P2** + +- 跑通“理解需求 → 阅读源码 → 修改 → 验证 → 汇报”的完整流程。 +- 在普通非自身项目中验证该闭环后,才允许用于 System Evolution。 +- 最终回答包含改动摘要、验证结果、Diff 位置和需要用户决定的问题。 + +## Git 与 Worktree 工作流 + +**实现阶段:P2** + +- 支持 status、diff、log、branch 和隔离 worktree 的创建、使用、检查与清理。 +- 候选开发默认在 worktree 中进行,稳定工作区保持可用。 +- Git 写操作通过明确的工作流执行,不让模型随意拼接高影响命令。 + +## 远程仓库、PR 与 CI + +**实现阶段:P7** + +- 读取远程 Issue、PR、Review 和 CI 状态,将其转换为本地开发上下文。 +- 支持创建提交、推送分支、创建或更新 PR,并展示外部结果。 +- 所有对外写操作进入统一确认和审计流程。 diff --git a/docs/features/run-execution.md b/docs/features/run-execution.md new file mode 100644 index 0000000..a922dd5 --- /dev/null +++ b/docs/features/run-execution.md @@ -0,0 +1,49 @@ +# Run 与 Agent 执行 + +## Run Context、Record 与生命周期 + +**实现阶段:P1** + +- Microkernel 创建内存 Run Context,Application Runtime 保存同 ID 的 Run Record。 +- 覆盖创建、运行、完成、失败和取消等基础生命周期。 +- 纯聊天与 Tool 行动共享同一 Run 概念,并关联所属 Space、Conversation 和输入消息。 + +## Run Event 与实时输出 + +**实现阶段:P1** + +- 产生文本增量、状态变化、错误和完成结果等结构化 Run Event。 +- 将 Event 同时用于客户端实时展示和 Run 日志,不与 Hook 执行语义混淆。 +- 保证客户端断开不影响 Runtime 中 Run 的基本记录完整性。 + +## Tool 行动执行循环 + +**实现阶段:P2** + +- 记录模型请求 Tool、Tool 执行、结果返回模型和最终回答的完整时间线。 +- 支持一个 Run 内多次顺序 Tool 调用及其错误反馈。 +- 将 Run 结果、实际副作用和产物建立可追踪关联。 + +## Run 控制与人工介入 + +**实现阶段:P4** + +- 支持取消、重试、重新执行、暂停等待用户输入和继续运行。 +- 用户可以在长 Run 中回答 Agent 追问、修改约束或终止后续行动。 +- 重新执行时明确复用哪些输入、上下文和已经产生的副作用。 + +## 后台 Run 与运行队列 + +**实现阶段:P5** + +- 支持客户端退出后继续执行、排队、优先级和并发限制。 +- 恢复连接后可以重新订阅进度、查看结果或取消后台 Run。 +- 为 Child Run、多 Agent 和自动化任务提供统一调度入口。 + +## Run 结果验证 + +**实现阶段:P5** + +- 为 Run 定义可验证的完成条件,而不只依赖模型口头宣布完成。 +- 汇总测试、文件变化、Tool 结果和 Reviewer 结论形成最终结果。 +- 区分成功、部分完成、需要人工处理和不可继续的失败。 diff --git a/docs/features/runtime.md b/docs/features/runtime.md new file mode 100644 index 0000000..4e50426 --- /dev/null +++ b/docs/features/runtime.md @@ -0,0 +1,57 @@ +# Runtime 与系统生命周期 + +## 工程基座与依赖边界 + +**实现阶段:P1** + +- 建立 TypeScript + Node.js monorepo、统一构建、测试、类型检查和开发命令。 +- 落实 Supervisor、Runtime Host、Application Runtime、Microkernel、Extensions 和 Clients 的依赖方向。 +- 保证 Microkernel 不依赖产品领域或具体扩展,客户端不直接依赖 Runtime 内部实现。 + +## Runtime Host 与进程生命周期 + +**实现阶段:P1** + +- 提供常驻 Node.js Runtime、单实例检测、启动、停止、退出信号和基础健康检查。 +- 负责读取启动配置、组装 Application Runtime、Microkernel 与首批内置能力。 +- CLI 在 Runtime 未启动时能够拉起并连接,退出 CLI 不终止 Runtime。 + +## Application Runtime + +**实现阶段:P1** + +- 管理 Space、Conversation、持久化 Run Record、当前上下文和本地 Repository。 +- 作为所有本地数据的唯一写入者,协调客户端请求与 Agent 执行。 +- 提供基础 Profile 选择和首版串行 Run 策略。 + +## Microkernel + +**实现阶段:P1** + +- 创建内存 Run Context,调度 Behavior,传播取消信号并隔离错误。 +- 提供 Tool Gateway、最小 Hook Pipeline、运行事件和流式输出接缝。 +- 通过可测试的公共接口运行,不认识 Project、Task、DeepSeek 或具体存储。 + +## Runtime Interface + +**实现阶段:P1** + +- 提供 Command、Query、Event Stream 三类客户端交互。 +- 支持流式文本、Tool 进度、Run 状态和错误事件。 +- 首版完成本地传输、连接识别、断线处理和协议级错误返回。 + +## 运行诊断与环境信息 + +**实现阶段:P4** + +- 提供 Runtime 版本、进程、端口、数据目录、已加载能力和健康状态查询。 +- 支持诊断报告、连接恢复、配置来源追踪和调试模式。 +- 为 CLI/TUI、Web、Desktop 共享同一套诊断数据。 + +## 后台队列与并发运行 + +**实现阶段:P5** + +- 从首版串行执行发展为后台 Run、排队、按 Space 并发和资源占用控制。 +- 支持客户端断开后继续运行、重新订阅和跨子 Run 的取消传播。 +- 为多 Agent、自动化和长期任务提供统一运行基础。 diff --git a/docs/features/spaces.md b/docs/features/spaces.md new file mode 100644 index 0000000..ba83105 --- /dev/null +++ b/docs/features/spaces.md @@ -0,0 +1,49 @@ +# Space、Conversation 与消息 + +## Task 生命周期 + +**实现阶段:P1** + +- 创建、命名、列出、切换、重命名、归档和删除 Task。 +- 提供首次启动的默认 Task,并记录最近使用位置。 +- Task 的工作文件和 Agent 数据随整个 Task 目录移动。 + +## Conversation 生命周期 + +**实现阶段:P1** + +- 在每个 Space 内创建、命名、切换、重命名、归档和删除 Conversation。 +- 保存消息顺序、角色、可见内容和关联 Run。 +- 支持恢复最近 Conversation,并为自动标题保留实现入口。 + +## Project 绑定与发现 + +**实现阶段:P2** + +- 将本地目录注册为 Project,并初始化或复用根目录中的 `.agent/`。 +- 支持 Project 移动后的重新定位、解绑和重新发现。 +- 保证项目源码不复制到 Agent 数据目录,多个会话共享同一个 Project 上下文。 + +## Task 升级为 Project + +**实现阶段:P4** + +- 将整个 Task 目录移动至用户指定位置并改为 Project。 +- 更新 Space 类型、全局注册路径和相关上下文引用。 +- 处理目标目录冲突、Git 初始化与升级失败回退。 + +## Conversation 搜索、分支与引用 + +**实现阶段:P4** + +- 跨 Space 搜索 Conversation、消息和 Run 结果。 +- 支持编辑或重新执行历史输入,并通过会话分支保留原始对话。 +- 在新会话中引用其他 Conversation 或 Run,并保留来源导航。 + +## 会话附件 + +**实现阶段:P6** + +- 在消息中附加文件、图片、代码片段和其他本地资料。 +- 管理附件复制或引用策略、预览、上下文注入和删除。 +- 为 Web/Desktop 和多模态模型共享统一附件语义。 diff --git a/docs/features/system-evolution.md b/docs/features/system-evolution.md new file mode 100644 index 0000000..57596cf --- /dev/null +++ b/docs/features/system-evolution.md @@ -0,0 +1,49 @@ +# System Evolution + +## System Evolution Project + +**实现阶段:P3** + +- 将 Agent 自身源码仓库注册为特殊 Project,并应用 System Evolution Profile。 +- 用户在该 Project 的 Conversation 中提出自身需求和反馈,不要求 Agent 自动发现问题。 +- 复用 P2 已验证的单 Agent 开发能力,而不是建设第二套修改系统。 + +## Source、Candidate 与 Release 隔离 + +**实现阶段:P3** + +- 区分源码仓库、候选 worktree 和 Supervisor 实际启动的 Installed Release。 +- 当前运行版本不会被候选修改直接覆盖。 +- 候选失败、放弃或重新修改不会影响稳定 Runtime。 + +## 自身修改与验证流程 + +**实现阶段:P3** + +- 将用户需求转化为候选 worktree 中的代码修改。 +- 运行构建、测试、类型检查和最小启动验证。 +- 输出变更说明、完整 Diff、验证结果和已知风险。 + +## 发布提案与 Git 确认 + +**实现阶段:P3** + +- 将候选版本整理为可审核的发布提案。 +- commit、merge、push 等 Git 写操作必须由用户明确确认。 +- 拒绝发布时保留候选上下文,允许继续修改或安全清理。 + +## Supervisor 发布与回退 + +**实现阶段:P3** + +- 将通过确认的代码构建为不可变 Installed Release。 +- 原子切换活动版本、重启 Runtime、执行健康检查并完成客户端重连。 +- 新版本失败时自动切回旧版本,并支持用户主动回退演练。 + +## Evolution Record 与版本评估 + +**实现阶段:P4** + +- 关联需求、Conversation、Run、worktree、Diff、测试、Git 提交、Release 和回退结果。 +- 保存基准任务和回归验证,用于比较候选与稳定版本。 +- 从历史进化记录中查看某项能力为何加入、如何验证和何时发布。 diff --git a/docs/features/tools-execution.md b/docs/features/tools-execution.md new file mode 100644 index 0000000..2583179 --- /dev/null +++ b/docs/features/tools-execution.md @@ -0,0 +1,65 @@ +# Tools、Workspace 与产物 + +## Tool 契约与统一 Gateway + +**实现阶段:P2** + +- 定义 Tool 名称、说明、参数 Schema、结构化结果和错误协议。 +- 支持注册、列出、调用和取消 Tool,并将所有调用纳入 Run Context。 +- 在统一 Gateway 中触发基础 Hook、日志和输出事件。 + +## Workspace 作用域与路径解析 + +**实现阶段:P2** + +- 为 Task 和 Project 解析工作区根目录与当前工作目录。 +- 统一处理相对路径、绝对路径、符号链接和工作区外路径。 +- 将 Tool 产生的实际文件与 `.agent/` 元数据明确区分。 + +## 文件操作 Toolset + +**实现阶段:P2** + +- 支持目录浏览、文件读取、文本搜索、写入、补丁、移动、复制和创建目录。 +- 处理编码、大文件、二进制文件和修改冲突。 +- 返回适合模型继续工作的结构化摘要,同时保留完整结果入口。 + +## Shell 与进程 Toolset + +**实现阶段:P2** + +- 执行 Shell 命令,流式返回 stdout/stderr、退出码和耗时。 +- 支持超时、取消、工作目录、环境变量和基础后台进程处理。 +- 将模型临时生成的命令或脚本文本记录进 Run,不额外建立脚本库。 + +## Tool 进度与产物收集 + +**实现阶段:P2** + +- 统一表达 Tool 开始、进度、完成、失败和取消。 +- 自动识别测试报告、截图、下载文件和其他可预览产物并关联到 Run。 +- 支持产物查看、固定、导出和后续 Tool 引用。 + +## 交互式终端会话 + +**实现阶段:P4** + +- 使用 PTY 运行需要持续输入、终端控制序列或长时间驻留的命令。 +- 允许 Agent 与用户查看会话、发送输入、转入后台、重新连接和终止进程。 +- 将交互式会话与普通 Shell Tool、Run 日志和资源回收统一管理。 + +## Tool 能力范围与分组 + +**实现阶段:P4** + +- 按 Profile、Space 和 Behavior 选择向模型暴露的 Tool 集合。 +- 支持 Tool 别名、分组、描述优化和能力发现,避免一次向模型暴露过多接口。 +- 为权限策略和不同平台能力降级提供统一元数据。 + +## 外部 Tool 协议接入 + +**实现阶段:P4** + +- 通过 Adapter 接入 MCP 等外部 Tool 服务,将其映射到统一 Tool 契约。 +- 管理服务连接、能力同步、错误转换和生命周期。 +- 外部 Tool 与内置 Tool 使用一致的日志、确认和 Run 关联。 diff --git a/docs/features/web.md b/docs/features/web.md new file mode 100644 index 0000000..058049e --- /dev/null +++ b/docs/features/web.md @@ -0,0 +1,57 @@ +# Web + +## Web 客户端基础与 Runtime 连接 + +**实现阶段:P9** + +- 选择 Web 框架并建立路由、状态管理和 Runtime Client。 +- 使用 Runtime 已有的 Command、Query、Event Stream,不重复实现 Agent 逻辑。 +- 处理连接状态、断线重连、版本不匹配和实时事件恢复。 + +## Space、Conversation 与聊天 + +**实现阶段:P9** + +- 创建、切换和管理 Task、Project、Conversation 与消息附件。 +- 支持流式聊天、历史导航、搜索和会话分支。 +- 在桌面与移动尺寸下保持可用的响应式交互。 + +## Run 与 Agent 工作台 + +**实现阶段:P9** + +- 展示 Run 时间线、Tool、Plan、Step、Child Run、Agent 角色、日志和 Artifact。 +- 提供取消、暂停、继续、重试、人工回答和计划调整。 +- 长任务在页面刷新或断线后能够恢复观察。 + +## Project 开发与 Review + +**实现阶段:P9** + +- 展示项目文件、代码修改、Build/Test/Lint 结果和 Git Diff。 +- 完成候选修改 Review、评论、确认和返回 Agent 继续修改。 +- 查看远程 Issue、PR 和 CI 结果。 + +## System Evolution 控制台 + +**实现阶段:P9** + +- 查看自身候选版本、测试、Diff、Evolution Record 和 Release。 +- 执行 Git 确认、版本发布、健康检查、重启和回退。 +- 清晰区分稳定版本、当前运行版本和未发布候选版本。 + +## 记忆、扩展与自动化管理 + +**实现阶段:P9** + +- 查看和编辑记忆、知识来源、Profile、模型、Tool、Hook、Behavior 与扩展。 +- 管理提醒、周期任务、事件触发器和自动化执行历史。 +- 提供统一配置和确认中心。 + +## 本地与远程访问体验 + +**实现阶段:P9** + +- 默认服务本机使用,并提供明确的远程启用流程。 +- 支持会话过期、认证、设备连接管理和敏感操作保护。 +- 保持远程客户端不直接访问本地数据文件或 Tool 实现。 diff --git a/docs/foundation/cli.md b/docs/foundation/cli.md deleted file mode 100644 index 6471371..0000000 --- a/docs/foundation/cli.md +++ /dev/null @@ -1,45 +0,0 @@ -# CLI - -CLI 是第一条完整产品线,既要适合快速对话,也要能观察和管理复杂运行。 - -## 自然语言 REPL - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:普通输入发送给当前 Conversation;提示符展示当前 Task/Project 与 Conversation;回答支持流式输出。 -- 待确认点:启动时恢复最近上下文还是显示选择器;输入历史、多行输入与中断体验。 - -## 斜杠命令 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:斜杠命令只处理确定性控制操作;候选包括 `/new`、`/switch`、`/task`、`/project`、`/runs`、`/log`、`/cancel`、`/exit`。 -- 待确认点:第一批命令范围、参数格式、命令补全和错误提示。 - -## Space 与会话导航 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:CLI 能创建、查看、切换 Task/Project 和它们内部的多个 Conversation。 -- 待确认点:列表样式、编号/名称选择、最近使用记录与快速切换方式。 - -## Tool 与 Run 呈现 - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:显示 Tool 名称、关键参数、结果和失败,不展示模型内部推理;长输出保留完整日志入口。 -- 待确认点:流式事件样式、折叠规则、颜色、产物链接和后台 Run 展示。 - -## 全屏 TUI - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:TUI 属于 CLI 正式能力,用于浏览 Space、Conversation、Run、日志与产物;REPL 仍保留用于快速工作。 -- 待确认点:TUI 库、布局、快捷键、命令面板,以及与 REPL 的切换方式。 - -## CLI 诊断与配置 - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:CLI 应能查看 Runtime 状态、当前配置与日志位置。 -- 待确认点:配置修改入口、诊断报告和调试模式。 diff --git a/docs/foundation/runtime.md b/docs/foundation/runtime.md deleted file mode 100644 index 0a3023c..0000000 --- a/docs/foundation/runtime.md +++ /dev/null @@ -1,31 +0,0 @@ -# Runtime - -Runtime 是本地常驻的唯一运行宿主,负责状态写入、Agent 执行与 Tool 调用。CLI、Web、Desktop 都是它的客户端。 - -## 工程与包边界 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:TypeScript + Node.js + monorepo;Runtime、客户端与共享边界需要清晰。 -- 待确认点:Node 版本、包管理器、monorepo 工具、首批包的职责与依赖方向。 - -## 常驻进程 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:Runtime 从第一天起独立常驻;CLI 日常启动时自动连接,必要时自动拉起。 -- 待确认点:单实例检测、启动/停止、健康检查、日志位置和异常退出行为。 - -## 本地通信 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:客户端不直接读写 Agent 数据;协议不绑定 CLI,并应能支持流式输出。 -- 待确认点:HTTP + SSE、Unix Socket 或其他本地 RPC 方案。 - -## Runtime 服务边界 - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:Runtime 负责 Space、Conversation、Run、本地数据、Agent Behavior 与 Tool 执行。 -- 待确认点:首版内部模块边界,以及哪些能力进入微内核、哪些保留为可替换实现。 diff --git a/docs/foundation/spaces.md b/docs/foundation/spaces.md deleted file mode 100644 index 8aed449..0000000 --- a/docs/foundation/spaces.md +++ /dev/null @@ -1,31 +0,0 @@ -# Space 与 Conversation - -Project 与 Task 是并列的一级空间,两者内部都可以包含多个 Conversation。 - -## Task Space - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:用于闲聊与临时事务;数据存放于用户目录;每个 Task 拥有专属 Workspace。 -- 待确认点:默认 `inbox`、自动命名、归档、删除与最近使用策略。 - -## Project Space - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:用于长期开发工作;绑定本地项目目录;Agent 数据存放在项目的 `.agent/` 中。 -- 待确认点:项目初始化、路径移动、解绑与多工作区支持。 - -## Conversation - -- 期望实现阶段:P1 -- 实际开发状态:未开始 -- 已确认点:每个 Space 可以拥有多个 Conversation;会话承载消息与相关 Run。 -- 待确认点:标题生成、重命名、归档、搜索和跨会话引用。 - -## Task 升级为 Project - -- 期望实现阶段:P2 -- 实际开发状态:未开始 -- 已确认点:Task Workspace 结构尽量与 Project 工作区一致;升级时迁移到用户指定位置并补充 Project 元数据。 -- 待确认点:目录冲突、历史记录路径更新、Git 初始化与升级交互。 diff --git a/docs/intelligence/memory-knowledge.md b/docs/intelligence/memory-knowledge.md deleted file mode 100644 index a546e7d..0000000 --- a/docs/intelligence/memory-knowledge.md +++ /dev/null @@ -1,38 +0,0 @@ -# 记忆与知识 - -记忆能力为 CLI、Web、Desktop 共享个人、项目和会话上下文,同时保持数据本地、可读和可编辑。 - -## Conversation 记忆 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:会话可以生成摘要,降低长历史的上下文成本。 -- 待确认点:生成时机、更新方式、人工编辑和与原始消息的关系。 - -## Space 记忆 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:Task/Project 各自保存长期上下文;Project 可沉淀规则、架构和决策。 -- 待确认点:文件布局、跨会话提炼、失效信息和 Task/Project 差异。 - -## 全局个人记忆 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:保存跨 Space 有效的个人偏好与长期事实,供三端共享。 -- 待确认点:写入确认、隐私边界、冲突处理和从 Space 提升的规则。 - -## 记忆管理 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:记忆应能查看、编辑、固定、遗忘并追溯来源。 -- 待确认点:客户端体验、自动清理、可信度与过期策略。 - -## 检索与知识导入 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:先使用普通文件和全文检索;向量/混合检索、文档导入按真实需求增加。 -- 待确认点:索引方式、切分、引用来源、支持格式与向量存储。 diff --git a/docs/intelligence/planning-multi-agent.md b/docs/intelligence/planning-multi-agent.md deleted file mode 100644 index 08a11f0..0000000 --- a/docs/intelligence/planning-multi-agent.md +++ /dev/null @@ -1,38 +0,0 @@ -# 计划与多 Agent - -这一能力在单 Agent 出现真实瓶颈后引入,用于拆解复杂目标并协调多个执行者。 - -## Plan 与 Step - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:复杂目标可拆为可执行步骤并自动依序执行;它们属于 Run 之上的行为能力。 -- 待确认点:数据结构、编辑方式、完成条件、CLI/TUI 展示和与 Conversation 的关系。 - -## 步骤执行与重规划 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:未来需要失败重试、跳过和重新规划,但不在 MVP 中预设完整状态机。 -- 待确认点:失败分类、重试上限、人工介入点和上下文继承。 - -## 子 Agent 委派 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:主 Agent 可以创建子 Run,分配目标并回收结果。 -- 待确认点:上下文隔离、Tool 范围、并发限制、取消传播与结果可信度。 - -## Agent 角色与协作模板 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:Planner、Worker、Reviewer 是候选角色,不构成强制工作流。 -- 待确认点:角色声明、Behavior 组合、Reviewer 门槛和用户自定义方式。 - -## 并行与模型路由 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:并行、DAG、不同角色使用不同模型都属于后续增强。 -- 待确认点:调度策略、成本/Token 统计、并发冲突与结果合并。 diff --git a/docs/operations/reliability-safety.md b/docs/operations/reliability-safety.md deleted file mode 100644 index f1c6038..0000000 --- a/docs/operations/reliability-safety.md +++ /dev/null @@ -1,38 +0,0 @@ -# 可靠性与个人安全 - -安全能力不阻塞早期个人探索,但在 Agent 开始影响真实文件、网站和系统后,需要逐步补足恢复与确认能力。 - -## Run 恢复 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:长期需要取消、暂停、恢复与 Runtime 崩溃后的未完成 Run 处理。 -- 待确认点:恢复粒度、可重放 Tool、幂等性和异常进程清理。 - -## 数据备份与迁移 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:本地数据最终需要备份、导入导出和目录迁移;早期格式变化可使用一次性脚本。 -- 待确认点:备份位置、周期、保留数量、恢复验证和格式兼容范围。 - -## 高影响操作确认 - -- 期望实现阶段:P3 -- 实际开发状态:未开始 -- 已确认点:System Evolution 的 Git 写操作必须确认;删除文件、真实网页提交、系统设置等后续按实际风险加入。 -- 待确认点:确认粒度、默认允许范围、超时和拒绝后的 Run 行为。 - -## 密钥与敏感数据 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:项目是单用户、本地优先,不建设多租户权限;长期可接入系统 Keychain 并处理敏感日志。 -- 待确认点:首版 API Key 保存方式、脱敏规则和敏感 Tool 输出保留策略。 - -## Workspace 与产物清理 - -- 期望实现阶段:P4 -- 实际开发状态:未开始 -- 已确认点:需要管理临时文件、候选 worktree、执行产物和长期磁盘占用。 -- 待确认点:自动清理阈值、回收站、归档和用户固定机制。 diff --git a/docs/roadmap.md b/docs/roadmap.md index bc24c10..e69670d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,29 +1,75 @@ -# 路线图与管理规则 +# 实现阶段 -路线图只描述阶段目标。每个功能的具体拆分、提交顺序和实现方案以各自的功能文档为准。 +阶段只描述实际开发顺序,不等同于功能分类。大功能会跨越多个阶段,每个小功能点在对应功能文档中标记自己的实现阶段。 -| 阶段 | 目标 | 已知范围 | -| --- | --- | --- | -| P1 | 本地 Runtime 与终端产品基础 | Runtime、CLI(REPL 与全屏 TUI)、Space、会话、DeepSeek 聊天、Run、基础 Tool/Hook 接缝 | -| P2 | 终端行动能力与开发助手 | Workspace、文件/Shell、单 Agent Tool Calling、Project、代码与 Git Worktree 能力 | -| P3 | System Evolution 闭环 | 候选 worktree、自身修改与测试、确认发布、Supervisor 回退 | -| P4 | Runtime 成长与个人自动化 | 扩展、计划/多 Agent、记忆、浏览器/电脑控制、可靠性与按需安全能力 | -| P5 | Web 产品线 | Runtime 的 Web 客户端与管理界面 | -| P6 | Desktop 产品线 | 本地常驻控制台与系统集成 | +## P1:本地可交互基座 -## 功能记录格式 +建立 TypeScript + Node.js monorepo、常驻 Runtime、本地文件数据、Task、Conversation、基础 Run、DeepSeek 流式聊天和 CLI REPL。 -大功能本身不设置状态和阶段。大功能文档中的每个小功能点分别记录: +完成标准:Runtime 可独立启动,CLI 可连接并管理多个 Task/Conversation,退出重进后能够继续聊天。 -```text -期望实现阶段:P1 | P2 | ... -实际开发状态:未开始 | 设计中 | 开发中 | 已完成 | 暂停 -已确认点:当前已经达成共识的要求 -待确认点:实现前仍需讨论或通过实践决定的问题 -``` +## P2:可行动的开发助手 -新增需求先归类到已有大功能点;如果它确实是新的长期能力,再新建一个大功能文件。实现过程中允许新增、合并或拆分小功能点,不要求回头重排整份路线图。 +加入 Tool Gateway、文件与 Shell Tool、单 Agent Tool Calling、Project、源码修改、测试、Git/Worktree、Run 日志和终端执行观察。 -## 提交与 blog +完成标准:Agent 能在一个普通项目的隔离 worktree 中理解需求、修改源码、执行测试并展示可审核的 Diff。 -一次提交应交付一个可验证的纵向能力,而不是单独提交类型、枚举或预留接口。完成后在相关功能文档追加:提交链接、验证方式、实现取舍和对应 blog。 +## P3:最小自举闭环 + +建立 System Evolution Project、候选 worktree、构建测试、变更审核、Git 写操作确认、Installed Release、Supervisor 切换和失败回退。 + +完成标准:Agent 为自身增加一个完整的小能力,经用户确认发布后由新 Runtime 接管,并能够实际回退到旧版本。 + +P3 之后的功能默认优先通过该自举流程实现。 + +## P4:终端产品与扩展能力成熟 + +完善斜杠命令、非交互 CLI、全屏 TUI、Runtime 诊断、后台 Run、Hook Runtime、Profile、扩展加载与开发体验、多模型以及完整进化记录。 + +完成标准:终端可以承担日常工作和 System Evolution 的完整操作;新增能力通常能够落入清晰的 Adapter、Tool、Hook 或 Behavior 边界。 + +## P5:计划与多 Agent + +实现 Plan/Step、连续执行、失败重规划、Child Run、角色协作、并行/DAG 调度、结果验证和模型路由。 + +完成标准:复杂目标可以被拆解、连续执行、动态调整,并由多个 Agent 分工完成与验证。 + +## P6:分层记忆与个人知识 + +实现 Conversation、Task/Project、全局个人记忆,记忆管理、全文/向量检索、文档导入和项目知识沉淀。 + +完成标准:新会话能够准确找到相关历史和来源,用户可以查看、纠正或删除记忆。 + +## P7:电脑管家与个人自动化 + +实现浏览器、本机文件与应用、剪贴板、通知、屏幕理解和操作、定时/后台任务、多模态输入与可复用自动化流程。 + +完成标准:Agent 可以从终端完成开发之外的高频电脑事务,并沉淀重复工作。 + +## P8:可靠性、安全与数据治理 + +系统化建设崩溃恢复、数据备份和迁移、统一审批、风险策略、密钥与敏感数据、资源清理和版本维护。 + +完成标准:强能力不会因为进程崩溃、数据损坏、误操作或错误升级造成不可恢复的后果。 + +这不意味着早期阶段不处理错误和风险:每一阶段都必须具备支撑自身闭环的最小日志、取消、确认或回退;P8 负责将它们统一为完整体系。 + +## P9:Web 产品线 + +基于同一 Runtime 建设 Web 会话、运行观察、项目、记忆、扩展、审批和 System Evolution 管理,并支持受控远程访问。 + +完成标准:Web 能独立承担日常交互与执行观察,但不复制 Agent 逻辑和数据状态。 + +## P10:Desktop 产品线 + +复用 Web 界面并增加 Runtime 生命周期、托盘、快捷键、通知、原生确认、系统能力桥接、安装和更新体验。 + +完成标准:Desktop 提供 Web 无法自然提供的本地常驻和原生集成能力,而不是第三套 Agent 实现。 + +## 功能文档规则 + +- `docs/features/` 中每个文件对应一个大功能。 +- 大功能不设置阶段,因为其中的小功能可能分布在多个阶段。 +- 每个小功能必须有可独立验证的实质性交付;只需改动很少代码的细节应合并到相邻功能点。 +- 小功能只记录实现阶段和功能范围,不维护 todo/done、候选、已确认或待确认状态。 +- 进入某个阶段时,再围绕该阶段的小功能制定提交级实现计划。 From 49f333b668c916d28e98e165df831c7eb70587fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 24 Jul 2026 17:36:02 +0800 Subject: [PATCH 14/24] =?UTF-8?q?init:=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 32 ----- docs/architecture.md | 131 +++++---------------- docs/data-layout.md | 53 +++++++++ docs/directory-plan.md | 116 ------------------ docs/extensions.md | 54 +++++++++ docs/features/automation.md | 49 -------- docs/features/browser.md | 49 -------- docs/features/cli.md | 65 ---------- docs/features/data-config.md | 57 --------- docs/features/desktop-control.md | 51 -------- docs/features/desktop.md | 57 --------- docs/features/extensions.md | 65 ---------- docs/features/memory-knowledge.md | 57 --------- docs/features/model-agent.md | 57 --------- docs/features/observability-reliability.md | 57 --------- docs/features/permissions-security.md | 57 --------- docs/features/planning-multi-agent.md | 57 --------- docs/features/project-assistant.md | 57 --------- docs/features/run-execution.md | 49 -------- docs/features/runtime.md | 57 --------- docs/features/spaces.md | 49 -------- docs/features/system-evolution.md | 49 -------- docs/features/tools-execution.md | 65 ---------- docs/features/web.md | 57 --------- docs/roadmap.md | 79 ++++--------- docs/source-layout.md | 39 ++++++ 26 files changed, 201 insertions(+), 1364 deletions(-) delete mode 100644 docs/README.md create mode 100644 docs/data-layout.md delete mode 100644 docs/directory-plan.md create mode 100644 docs/extensions.md delete mode 100644 docs/features/automation.md delete mode 100644 docs/features/browser.md delete mode 100644 docs/features/cli.md delete mode 100644 docs/features/data-config.md delete mode 100644 docs/features/desktop-control.md delete mode 100644 docs/features/desktop.md delete mode 100644 docs/features/extensions.md delete mode 100644 docs/features/memory-knowledge.md delete mode 100644 docs/features/model-agent.md delete mode 100644 docs/features/observability-reliability.md delete mode 100644 docs/features/permissions-security.md delete mode 100644 docs/features/planning-multi-agent.md delete mode 100644 docs/features/project-assistant.md delete mode 100644 docs/features/run-execution.md delete mode 100644 docs/features/runtime.md delete mode 100644 docs/features/spaces.md delete mode 100644 docs/features/system-evolution.md delete mode 100644 docs/features/tools-execution.md delete mode 100644 docs/features/web.md create mode 100644 docs/source-layout.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1c3526f..0000000 --- a/docs/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# llm-to-agent 文档 - -文档分为三部分: - -- [架构总览](architecture.md):稳定的职责边界与依赖原则; -- [规划目录](directory-plan.md):当前建议的源码与运行数据布局,允许随实际开发调整; -- [实现阶段](roadmap.md):先完成基本能力,再建立自举,随后由 Agent 继续实现自己的开发顺序; -- `features/`:按整个项目的大功能分类,列出尽量完整且有实质性交付的小功能点。 - -## 功能目录 - -- [Runtime 与内核](features/runtime.md) -- [本地数据与配置](features/data-config.md) -- [Space、Conversation 与消息](features/spaces.md) -- [模型、聊天与 Agent](features/model-agent.md) -- [Run 与 Agent 执行](features/run-execution.md) -- [Tools、Workspace 与产物](features/tools-execution.md) -- [CLI](features/cli.md) -- [Project 开发助手](features/project-assistant.md) -- [System Evolution](features/system-evolution.md) -- [扩展、Hooks 与 Profile](features/extensions.md) -- [计划、工作流与多 Agent](features/planning-multi-agent.md) -- [记忆、检索与知识](features/memory-knowledge.md) -- [浏览器](features/browser.md) -- [本机与桌面控制](features/desktop-control.md) -- [自动化与后台任务](features/automation.md) -- [可观测性与可靠性](features/observability-reliability.md) -- [权限、确认与敏感数据](features/permissions-security.md) -- [Web](features/web.md) -- [Desktop](features/desktop.md) - -具体字段、技术库和交互细节在进入对应实现阶段时讨论,不用早期规划替代真实开发反馈。 diff --git a/docs/architecture.md b/docs/architecture.md index 9d10cfe..1440fff 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,125 +1,54 @@ # 架构总览 -## 定位 +## 目标 -`llm-to-agent` 是纯个人使用、本地优先的开发助手与电脑管家。它以一个常驻 Runtime 作为唯一运行宿主,并通过 CLI、Web、Desktop 三个客户端共享数据和执行状态。 - -系统采用四层结构: +llm-to-agent 是个人项目,架构优先级是: ```text -Launcher / Supervisor - ↓ -Runtime Host - ↓ -Application Runtime - ↓ -Microkernel + Extensions +简单易懂 > 容易修改 > 容易扩展 > 通用性 ``` -这四层表达职责和依赖方向,不要求第一版拆成四个独立进程。 +Runtime 内部长期只有: -## Launcher / Supervisor +```text +Kernel + Extensions +``` -Supervisor 位于 Agent 运行版本之外,只负责启动、版本切换、健康检查和失败回退。它不理解 Space、Conversation、Tool 或 Agent 推理。 +Kernel 管“Extension 怎样连接和运行”,Extension 管“系统具体能做什么”。 -Supervisor 在 P3 的 System Evolution 发布闭环中实现,P1/P2 开发阶段可以先由普通启动脚本承担进程拉起。 +## Kernel -## Runtime Host +Kernel 不认识 Workspace、Conversation、Run、Agent、模型或 Tool,只提供所有 Extension 共同需要的机制: -Runtime Host 是 Node.js 进程的组合入口,负责: +- 生命周期与资源清理; +- 单个能力的提供与调用; +- 多个实现的注册与选择; +- 事实事件的发布与监听; +- 隔离存储、取消和错误传播; +- 副作用能力共用的调用接缝。 -- 创建并启动 Application Runtime 与 Microkernel; -- 装配当前启用的扩展; -- 开启本地客户端连接; -- 处理进程信号、单实例和退出; -- 将配置与基础设施交给内部模块。 - -Host 不包含 Project、Conversation、Agent 工作流等业务规则。 - -## Application Runtime - -Application Runtime 负责这个产品不可缺少的应用能力: - -- Project / Task Space; -- Conversation 与持久化 Run Record; -- 本地数据 Repository 和唯一写入; -- 客户端 Command、Query 与 Event Stream; -- 当前 Space/Conversation 上下文; -- Profile 选择和基础并发策略。 - -它不包含 DeepSeek 请求、Shell、Git、浏览器驱动、提示词或具体 Agent 协作策略。 - -## Microkernel - -Microkernel 负责一次 Agent 执行的通用机制: - -- 创建内存中的 Run Context; -- 调用 Agent Behavior; -- 注册并调用 Tool; -- 执行 Hook Pipeline; -- 发出流式输出和运行事件; -- 传播取消信号并隔离执行错误。 - -Microkernel 不认识 Project、Task 或具体存储,也不依赖任何具体扩展。 +只有所有 Extension 都必须遵守的运行规则才进入 Kernel。 ## Extensions -扩展只放真正可替换、可按需增加的能力: +Extension 可以提供能力、调用其他能力、添加同类实现、发布或监听事实,并管理自己的数据与资源。 -- **Adapter**:DeepSeek、未来模型提供商、浏览器或检索驱动; -- **Tool**:文件、Shell、Git、浏览器、桌面控制; -- **Hook**:围绕 Tool/Run 生命周期观察、限制或响应; -- **Behavior**:DefaultAgent、CodingAgent、Planner、Reviewer。 +控制流程必须使用明确调用;事件只表示已经发生的事实,不能承担顺序、返回值、审批或回滚。 -Space、Conversation 和持久化 Run Record 是 Application Runtime 的产品领域,不作为可卸载插件。 +Extension 之间只依赖公开契约,不引用彼此的实现。 -## Profile +具体能力归属见 [Extension 目录](extensions.md)。 -Profile 是 Application Runtime 管理的声明式能力组合,不是独立运行层: +## Runtime 与产品 -```text -Profile -├── 默认 Behavior -├── 可用 Tools -├── 启用 Hooks -├── 模型设置 -└── 上下文/记忆策略 -``` +Runtime 持有唯一一套 Kernel、Extension 实例和本地数据。CLI、Web、Desktop 是不同产品入口,使用同一套能力和状态。 -Project 和 Task 是 Space 类型;System Evolution 是应用于 Project 的特殊 Profile。 +自进化阶段再增加 Runtime 外的 Supervisor,用于版本切换、健康检查和失败回退。 -## 依赖规则 +## 长期约束 -```text -Supervisor → Runtime Host -Runtime Host → Application Runtime / Microkernel / Extensions -Application Runtime → Microkernel 公共接口 -Extensions → Microkernel 扩展接口 -Microkernel ✕ Application Runtime / 具体 Extensions -Clients → Runtime Interface -``` - -目录隔离只是表现,以上依赖方向才是边界是否真实成立的判断依据。 - -## 客户端接口 - -CLI、Web、Desktop 逻辑上只使用三类交互: - -- **Command**:创建 Space、发送消息、取消 Run 等状态变更; -- **Query**:读取 Space、Conversation、Run 和日志; -- **Event Stream**:接收模型增量、Tool 过程和 Run 状态。 - -具体采用 HTTP/SSE、Unix Socket 或其他本地协议在实现时确定,不提前冻结字段。 - -## Event 与 Hook - -- **Run Event** 是发生过的事实,用于日志和客户端实时展示。 -- **Hook** 是事件前后实际执行的扩展代码。 - -两者不能混为同一个事件总线:记录了一个事件,不代表必须触发可修改流程的 Hook。 - -## 演化原则 - -任何一层都允许由 Agent 修改,包括 Application Runtime 和 Microkernel。Agent 在 System Evolution Project 的隔离 worktree 中修改源码、运行测试并展示 Diff;Git 写操作由用户确认,Supervisor 负责切换构建版本和失败回退。 - -自进化依赖的是源码可修改、候选版本隔离、测试、发布和回退,不要求把所有产品能力插件化。 +- 默认在现有 Extension 内增加普通代码,出现真实独立边界后再拆。 +- 第一版静态装配,不建设插件平台、热加载或依赖图。 +- 保持单进程、单用户和本地优先,直到真实需求要求改变。 +- 不提前建设 Command Bus、Middleware、事件溯源或分布式状态。 +- 新增 Kernel 机制必须有多个具体使用者。 diff --git a/docs/data-layout.md b/docs/data-layout.md new file mode 100644 index 0000000..0f17dcd --- /dev/null +++ b/docs/data-layout.md @@ -0,0 +1,53 @@ +# 数据目录 + +## 唯一数据根 + +所有 llm-to-agent 专属数据集中在 `LLM_TO_AGENT_HOME`。未设置时默认使用: + +```text +~/.llm-to-agent/ +``` + +开发环境可以指向另一个用户目录,测试使用临时目录。 + +## 物理目录 + +```text +/ +├── config/ +├── workspaces/ +│ └── / +│ ├── files/ +│ ├── conversations/ +│ ├── runs/ +│ └── extensions/ +├── extensions/ +├── releases/ +├── runtime/ +└── trash/ +``` + +目录按需要创建,不规定尚未实现的数据文件和格式。 + +## Workspace + +- `managed` Workspace 的工作文件位于自己的 `files/`。 +- `linked` Workspace 只记录用户已有目录的绑定关系。 +- 两种 Workspace 的 Conversation、Run、记忆和 Extension 数据都位于数据根目录。 + +## 项目目录零落地 + +绑定外部项目时,项目源码保留在原位置,所有 llm-to-agent 专属数据仍保存在: + +```text +/workspaces// +``` + +项目中不创建 `.agent/`、`.llm-to-agent/` 或其他专属元数据。项目移动时更新绑定关系;删除 linked Workspace 绝不能删除外部项目源码。 + +## 数据规则 + +- Kernel 只提供安全路径、隔离和基础写入机制,不理解业务数据。 +- 每个 Extension 管理自己的数据,不能直接修改其他 Extension 的私有内容。 +- 缓存和临时数据必须可以清理重建。 +- 删除重要数据默认先进入 `trash/`,迁移和修复前先保留可恢复副本。 diff --git a/docs/directory-plan.md b/docs/directory-plan.md deleted file mode 100644 index 1c5ac0d..0000000 --- a/docs/directory-plan.md +++ /dev/null @@ -1,116 +0,0 @@ -# 规划目录 - -本文描述当前架构下建议采用的源码与运行数据目录。它用于指导项目起步,不是不可修改的目录规范。 - -后续实现或 System Evolution 过程中,如果真实依赖、代码规模或开发体验证明某个划分不合理,可以移动、合并或拆分目录;调整时应优先保持职责和依赖方向,而不是机械维持路径兼容。 - -## 源码仓库 - -```text -llm-to-agent/ -├── apps/ -│ ├── runtime/ # Runtime Host / composition root -│ ├── supervisor/ # 启动、版本切换、健康检查、回退 -│ ├── cli/ # REPL、非交互 CLI、TUI -│ ├── web/ # Web 客户端 -│ └── desktop/ # Desktop Host 与原生桥接 -│ -├── packages/ -│ ├── kernel/ # Microkernel -│ ├── application/ # Application Runtime -│ └── client/ # 客户端共享的 Runtime Client -│ -├── extensions/ # 第一方扩展,按完整能力纵向划分 -│ ├── deepseek/ -│ ├── workspace/ -│ ├── shell/ -│ ├── git/ -│ └── .../ -│ -├── docs/ -│ ├── architecture.md -│ ├── directory-plan.md -│ ├── roadmap.md -│ └── features/ -│ -├── tests/ -│ └── e2e/ # 跨包、自举和版本切换测试 -│ -├── tooling/ # 构建、发布、开发辅助 -│ -├── package.json -├── tsconfig.json -└── workspace.yaml -``` - -## 架构对应关系 - -| 架构职责 | 规划目录 | -| --- | --- | -| Launcher / Supervisor | `apps/supervisor/` | -| Runtime Host | `apps/runtime/` | -| Application Runtime | `packages/application/` | -| Microkernel | `packages/kernel/` | -| Extensions | `extensions/` | -| Runtime Client | `packages/client/` | -| CLI / Web / Desktop | `apps/cli/`、`apps/web/`、`apps/desktop/` | - -## 目录原则 - -### Apps 是进程和产品入口 - -`apps/runtime/` 负责创建组件、安装扩展并开启本地连接,不承载 Space、Conversation、Tool 或 Agent Behavior 的具体实现。 - -CLI、Web、Desktop 通过同一个 Runtime Client 工作,不各自复制 Agent、存储和执行逻辑。 - -### Packages 固定核心依赖边界 - -- `kernel/` 只包含 Run Context、Behavior 调度、Tool Gateway、Hook Pipeline、Run Event、流式输出、取消和错误边界。 -- `application/` 承载 Project/Task、Conversation、Message、Run Record、Repository、Profile 以及 Command/Query 等产品领域。 -- `client/` 承载三个客户端共用的 Command、Query、Event Stream 和连接逻辑。 - -不建立泛化的 `shared/` 或 `types/` 大杂烩。类型应尽量由拥有该概念的模块导出,只有真正跨客户端传输的协议进入 `client/`。 - -### Extensions 按完整能力纵向划分 - -扩展不按 Adapter、Tool、Hook、Behavior 等技术类型横向拆目录,而是按能力组织。例如 `browser/` 可以同时包含浏览器 Adapter、相关 Tools、操作 Hook 和 Browser Behavior。 - -第一版不要求每个扩展都是独立 package。只有在依赖、测试、复用、版本或独立发布需求出现后,才将它提升为单独 workspace package。 - -### 功能文档不映射源码目录 - -`docs/features/` 按产品大功能组织,源码按职责和依赖组织,两者不要求一一对应。 - -例如 System Evolution 会同时涉及 Application Runtime、Git 扩展、Runtime Host、Supervisor 和 CLI,不应为了与功能文档对齐而把所有代码放入单个目录。 - -### 测试就近放置 - -单元测试和模块测试与所属源码放在一起。顶层 `tests/e2e/` 只保存真正跨包、跨进程或跨版本的场景,例如 CLI 到 Runtime、Project 开发、自举发布和版本回退。 - -## 运行数据目录 - -运行数据不进入源码仓库: - -```text -~/.agent/ -├── tasks/ -├── registry.json -├── extensions/ # 用户本地安装的扩展 -├── releases/ # Installed Releases -├── runtime/ -└── config/ -``` - -Project 专属 Agent 数据位于项目根目录的 `.agent/`。候选 worktree 和 Installed Release 由 `~/.agent/` 下的运行数据管理,不作为源码仓库中的固定目录。 - -## 允许调整的判断标准 - -满足以下任一情况时,可以调整目录: - -- 一个目录长期承载了多个不相关职责; -- 一项完整能力被迫跨越过多技术分类目录; -- 模块需要独立测试、复用、加载、版本或发布; -- 现有依赖方向导致循环依赖或核心反向依赖具体功能; -- System Evolution 的真实开发过程证明当前结构降低了可理解性或修改效率。 - -目录调整后应同步更新本文,但不需要为保持旧规划而保留无价值的兼容层。 diff --git a/docs/extensions.md b/docs/extensions.md new file mode 100644 index 0000000..272006a --- /dev/null +++ b/docs/extensions.md @@ -0,0 +1,54 @@ +# Extension 目录 + +具体能力全部由 Extension 实现。Tool、页面、命令和事件监听器只是 Extension 内部组成,不是新的架构类型。 + +## 第一版 + +```text +src/extensions/ +├── shared/ +│ ├── workspace/ +│ ├── agent/ +│ └── deepseek/ +└── cli/ +``` + +- `workspace`:Workspace、Conversation 和消息。 +- `agent`:Run、上下文和 Agent 执行。 +- `deepseek`:DeepSeek 模型接入。 +- `cli`:终端输入、命令和展示。 + +第一版只静态装配这四个 Extension。 + +## 协作 + +- 一个明确能力由 Extension 提供,调用方取得后直接调用。 +- 模型、Tool 等多个实现通过同一扩展点汇集,由业务拥有者选择。 +- 状态真正发生后再发布事件,监听器只做展示、日志或派生处理。 +- Extension 只引用其他 Extension 的公开契约。 + +## 后续方向 + +后续公共能力大致包括: + +```text +项目操作与 Git +自身进化 +计划与多 Agent +记忆与知识 +浏览器与本机控制 +自动化 +``` + +CLI、Web、Desktop 各自先保持一个产品 Extension。远程仓库、外部 Tool 协议、Keychain 等在实际接入时再决定是否独立。 + +## 何时拆分 + +只有出现以下真实边界时才创建新 Extension: + +- 需要独立启停; +- 拥有独立的长期资源或数据; +- 存在可替换实现; +- 有明确产品或平台边界。 + +否则继续留在现有 Extension 内。目录只在能力开始实现时创建,不预建占位代码。 diff --git a/docs/features/automation.md b/docs/features/automation.md deleted file mode 100644 index 72f3bee..0000000 --- a/docs/features/automation.md +++ /dev/null @@ -1,49 +0,0 @@ -# 自动化与后台任务 - -## 持久后台任务 - -**实现阶段:P7** - -- 将 Run 提交为客户端断开后仍能执行的持久任务。 -- 保存执行状态、下一步动作、关联 Space 和人工介入点。 -- Runtime 重启后识别未完成任务并按策略恢复或等待用户处理。 - -## 提醒与定时执行 - -**实现阶段:P7** - -- 创建一次性提醒和指定时间执行的 Agent 任务。 -- 正确处理时区、设备休眠、Runtime 未运行和错过执行时间。 -- 将执行结果通过 CLI、Web、Desktop 或系统通知反馈。 - -## 周期任务 - -**实现阶段:P7** - -- 支持每日、每周及可表达的周期规则。 -- 管理启停、下次执行、失败重试和避免重复运行。 -- 每次执行生成独立 Run,并保留周期任务的整体历史。 - -## 事件触发自动化 - -**实现阶段:P7** - -- 根据文件变化、下载完成、应用状态或其他本地事件触发 Run。 -- 对频繁事件去抖、合并并限制并发。 -- 显示触发来源,允许用户临时暂停或禁用自动化。 - -## 自动化模板与参数 - -**实现阶段:P7** - -- 将成功的脚本、Tool 组合或工作流保存为可复用自动化。 -- 定义输入参数、默认值、执行条件和所需能力。 -- 修改模板不会篡改既有执行记录,能够查看版本差异。 - -## 自动化执行历史与通知 - -**实现阶段:P7** - -- 汇总每个自动化的成功、失败、耗时、产物和人工介入记录。 -- 对连续失败或异常结果发送通知并暂停后续执行。 -- 从历史 Run 快速重试或进入对应 Conversation 排查。 diff --git a/docs/features/browser.md b/docs/features/browser.md deleted file mode 100644 index 19d3a2a..0000000 --- a/docs/features/browser.md +++ /dev/null @@ -1,49 +0,0 @@ -# 浏览器 - -## 搜索、导航与页面读取 - -**实现阶段:P7** - -- 搜索互联网、打开 URL、读取页面文本和获取页面基本结构。 -- 管理重定向、加载失败、分页和动态页面等待。 -- 将页面内容转为适合模型使用且保留来源的结构化结果。 - -## 页面提取与站内探索 - -**实现阶段:P7** - -- 提取列表、表格、链接、表单和指定区域内容。 -- 在站内跟随链接完成多页资料收集,并避免无界爬取。 -- 保存必要截图、页面快照或下载内容作为 Run 产物。 - -## DOM 交互与表单操作 - -**实现阶段:P7** - -- 点击、输入、选择、滚动、等待和提交表单。 -- 通过可访问性树、DOM 和稳定定位信息减少脆弱坐标操作。 -- 提交、购买、发布等真实外部写操作进入确认流程。 - -## 标签页、会话与登录态 - -**实现阶段:P7** - -- 管理浏览器实例、窗口、标签页、历史和页面间上下文。 -- 按 Profile 或任务复用已有登录态,同时保持不同任务之间的边界。 -- 处理登录过期、验证码和必须由用户接管的页面。 - -## 上传、下载与文件流转 - -**实现阶段:P7** - -- 将 Workspace 文件上传到网页,并将下载内容保存为可追踪 Artifact。 -- 观察下载状态、文件名、重复文件和失败重试。 -- 将浏览器文件与 Task/Project Workspace 建立明确关联。 - -## 页面视觉理解与混合定位 - -**实现阶段:P7** - -- 使用截图和视觉模型理解仅靠 DOM 难以操作的界面。 -- 组合 DOM、可访问性树、图像和坐标完成定位。 -- 视觉操作记录截图和动作序列,便于用户检查和失败恢复。 diff --git a/docs/features/cli.md b/docs/features/cli.md deleted file mode 100644 index 261444c..0000000 --- a/docs/features/cli.md +++ /dev/null @@ -1,65 +0,0 @@ -# CLI - -## Runtime 管理与首次配置 - -**实现阶段:P1** - -- 提供 Runtime 启动、连接、状态查询和停止命令。 -- Runtime 未启动时自动拉起,CLI 退出后 Runtime 保持运行。 -- 完成 DeepSeek Key、数据目录和首个 Task 的最小首次配置。 - -## 自然语言 REPL - -**实现阶段:P1** - -- 在当前 Conversation 中进行流式聊天。 -- 支持输入历史、多行输入、取消当前请求和清晰的当前上下文提示符。 -- 终端重开后恢复最近 Space、Conversation 和消息历史。 - -## Space 与 Conversation 控制 - -**实现阶段:P1** - -- 通过一组一致的斜杠命令创建、列出、切换、重命名和归档 Task/Conversation。 -- 为 Project 绑定和切换复用相同的导航体验。 -- 提供帮助、命令错误提示和最近使用列表。 - -## Run、Tool 与产物呈现 - -**实现阶段:P2** - -- 实时展示模型回复、Tool 调用、Shell 输出、测试结果、错误和最终状态。 -- 长输出默认摘要,并能查看完整 Run 日志和 Artifact。 -- 提供 Run 取消、重新执行以及 Project Diff 查看入口。 - -## System Evolution 终端流程 - -**实现阶段:P3** - -- 在 CLI 中查看候选版本、Diff、测试结果、发布说明和健康检查。 -- 完成 Git 写操作确认、版本切换和回退操作。 -- 确保拒绝发布或发布失败后仍能继续处理候选工作区。 - -## 非交互与管道模式 - -**实现阶段:P4** - -- 支持一次性命令、stdin 输入、stdout 结果、结构化 JSON 输出和可靠退出码。 -- 允许 Shell 脚本或其他程序创建 Run、等待结果或后台提交。 -- 交互模式和非交互模式使用同一 Runtime 接口。 - -## 全屏 TUI - -**实现阶段:P4** - -- 提供 Space/Conversation 导航、聊天、Run 时间线、日志、产物和 Diff 工作区。 -- 加入命令面板、搜索、补全、快捷键、主题和可调整布局。 -- 支持配置、扩展、System Evolution 和后台 Run 管理,不替代快速 REPL。 - -## 多 Agent 与记忆工作台 - -**实现阶段:P6** - -- 展示 Plan、Step、Child Run、Agent 分工、人工检查点和结果验证。 -- 管理 Conversation/Space/全局记忆,查看来源并进行编辑或遗忘。 -- 将 P5/P6 的复杂能力整合到一致的终端交互中。 diff --git a/docs/features/data-config.md b/docs/features/data-config.md deleted file mode 100644 index 04e084c..0000000 --- a/docs/features/data-config.md +++ /dev/null @@ -1,57 +0,0 @@ -# 本地数据与配置 - -## 全局数据根目录与 Space 注册表 - -**实现阶段:P1** - -- 建立 `~/.agent/` 全局目录、Runtime 数据和 Space 注册表。 -- 注册表保存 Space 的 ID、类型、名称、真实路径和最近访问,用于定位 Task 与分散的 Project。 -- 处理首次启动、目录缺失、重复注册和路径不可访问等情况。 - -## Space、Conversation 与 Run 文件仓库 - -**实现阶段:P1** - -- 使用普通 JSON 保存当前元信息,使用 JSONL 保存消息与追加式 Run 记录。 -- 支持创建、读取、更新和列出 Space、Conversation、Message、Run。 -- 采用临时文件替换等简单方式保证单次写入完整,异常文件能够被识别并报告。 - -## 统一工作区目录 - -**实现阶段:P1** - -- Task 根目录本身就是 Workspace,内部使用 `.agent/` 保存 Agent 数据。 -- Project 使用相同目录形状,使 Task 可以整体移动并升级为 Project。 -- 明确工作文件、Agent 元数据和全局注册信息各自的所有权。 - -## Run 日志与产物仓库 - -**实现阶段:P2** - -- 保存模型调用、Tool 调用、Shell 输出、错误、测试结果和 Diff 等执行记录。 -- 将日志中的一次性内容与需要长期保留、预览或下载的 Artifact 分开管理。 -- 支持通过 Conversation、Run 和 Tool 调用定位相关记录与文件。 - -## 配置体系与覆盖规则 - -**实现阶段:P4** - -- 管理全局、Space、Profile 和客户端配置,并定义清晰的覆盖顺序。 -- 区分普通配置、凭据引用和运行时临时选项。 -- 支持查看配置最终值、来源、修改和恢复默认值。 - -## 缓存、索引与临时数据 - -**实现阶段:P6** - -- 为代码索引、知识检索、网页内容和模型缓存提供统一存放位置。 -- 缓存可重建且不与原始数据混淆,支持容量统计和按类型清理。 -- 保持 Project 数据、全局数据和临时数据之间的边界。 - -## 数据导入、导出与修复 - -**实现阶段:P8** - -- 支持全局或按 Space 导出、导入、备份和恢复。 -- 提供目录迁移、格式升级、完整性检查和损坏数据修复工具。 -- 备份过程可验证,恢复前保留现有数据的回退副本。 diff --git a/docs/features/desktop-control.md b/docs/features/desktop-control.md deleted file mode 100644 index 2fb9fe0..0000000 --- a/docs/features/desktop-control.md +++ /dev/null @@ -1,51 +0,0 @@ -# 本机与桌面控制 - -本功能描述 Agent 面向电脑的 Tool;它不同于 P10 的 Desktop 客户端。 - -## 剪贴板、通知与系统信息 - -**实现阶段:P7** - -- 读取和写入剪贴板文本或文件引用。 -- 发送本地通知并关联触发它的 Run。 -- 查询时间、网络、磁盘、进程和基础系统状态。 - -## 本地文件整理 - -**实现阶段:P7** - -- 对用户指定目录执行分类、移动、重命名、去重和下载目录整理。 -- 先生成变更预览,再执行可能影响大量文件的操作。 -- 删除优先进入系统回收站,并记录可恢复位置。 - -## 应用生命周期与状态 - -**实现阶段:P7** - -- 启动、聚焦、退出应用并查询运行状态。 -- 打开指定文件、URL 或项目位置到对应应用。 -- 处理应用未安装、无响应和需要用户登录等情况。 - -## 窗口与文件定位 - -**实现阶段:P7** - -- 查询、切换、移动、调整大小和排列窗口。 -- 将 Workspace、终端、编辑器和浏览器定位到相关资源。 -- 在多屏幕和多个同名窗口环境中保持明确目标。 - -## 屏幕理解与键鼠操作 - -**实现阶段:P7** - -- 截取屏幕或窗口,使用视觉能力识别界面状态。 -- 执行键盘、鼠标、拖放和快捷键操作。 -- 提供紧急停止、动作记录和操作前后截图。 - -## 系统权限与能力降级 - -**实现阶段:P7** - -- 检测辅助功能、录屏、通知和自动化权限。 -- 指引用户完成必要授权,并在权限缺失时切换到可用能力。 -- 向 Agent 暴露真实平台能力,避免反复调用不可用 Tool。 diff --git a/docs/features/desktop.md b/docs/features/desktop.md deleted file mode 100644 index e5e71cb..0000000 --- a/docs/features/desktop.md +++ /dev/null @@ -1,57 +0,0 @@ -# Desktop - -## Desktop Host 与共享界面 - -**实现阶段:P10** - -- 选择 Desktop 框架,复用 Web 的主要界面、状态和 Runtime Client。 -- 管理应用窗口、深色模式、链接打开和本地导航。 -- Desktop 不包含第三套 Agent、存储或 Tool 执行逻辑。 - -## Runtime 生命周期管理 - -**实现阶段:P10** - -- 安装、启动、停止、重启并观察本地 Runtime。 -- 处理 Runtime 未安装、启动失败、版本不匹配和客户端重连。 -- 提供健康状态、日志入口和故障恢复操作。 - -## 托盘、快捷键与通知 - -**实现阶段:P10** - -- 支持系统托盘、开机启动、后台常驻和快速状态查看。 -- 使用全局快捷键快速唤起输入、当前任务或运行面板。 -- 将 Run 完成、失败、确认和自动化结果发送为原生通知。 - -## 原生确认与任务总览 - -**实现阶段:P10** - -- 在应用不位于前台时显示原生确认窗口。 -- 汇总后台 Run、定时任务、自动化、资源使用和待处理人工介入。 -- 从通知或托盘直接跳转到对应 Space、Conversation 或 Run。 - -## 快速输入与语音交互 - -**实现阶段:P10** - -- 通过全局快捷入口快速输入文本、粘贴剪贴板内容或发起语音请求。 -- 将语音转写、附件和当前前台应用上下文交给同一个 Conversation/Run 流程。 -- 支持录音状态、取消、转写确认和输出朗读,不建立独立语音会话系统。 - -## 系统能力桥接 - -**实现阶段:P10** - -- 为剪贴板、窗口、文件选择器、浏览器和系统权限提供原生 Adapter。 -- Agent 使用的 Tool 仍归本机与桌面控制功能,Desktop 只提供平台桥接。 -- 处理权限申请、平台差异和应用关闭后的能力可用性。 - -## 版本、安装与更新 - -**实现阶段:P10** - -- 打包 Desktop、Runtime 和必要资源,提供个人设备安装流程。 -- 展示 Installed Release、更新、重启、版本切换和回退状态。 -- 更新失败时保留可启动的旧版本,并与 Supervisor 的版本管理保持一致。 diff --git a/docs/features/extensions.md b/docs/features/extensions.md deleted file mode 100644 index ee5c4e7..0000000 --- a/docs/features/extensions.md +++ /dev/null @@ -1,65 +0,0 @@ -# 扩展、Hooks 与 Profile - -## 静态扩展装配 - -**实现阶段:P1** - -- Runtime Host 通过明确的 composition root 装配首批 Adapter 和 Behavior。 -- P2 在同一机制中加入 Tool 与基础 Hook,不使用动态扫描或 manifest。 -- 扩展只能通过 Microkernel 公共接口注册,不能反向依赖 Host 内部实现。 - -## 基础 Hook Pipeline - -**实现阶段:P2** - -- 围绕 Tool 调用和 Run 完成提供少量稳定 Hook 点。 -- Hook 与 Run Event 分离:Hook 执行附加逻辑,Event 记录已经发生的事实。 -- Hook 失败不会悄然破坏主流程,执行结果进入 Run 日志。 - -## Hook 管理与诊断 - -**实现阶段:P4** - -- 支持 Hook 顺序、启停、异常隔离、耗时和调用链查看。 -- 明确哪些 Hook 只观察、哪些可以拒绝或结构化修改调用。 -- 为日志、摘要、确认、通知和评估提供稳定接缝。 - -## 本地扩展加载 - -**实现阶段:P4** - -- 加载本地 Tool、Adapter、Hook 和 Behavior,并校验声明与依赖。 -- 支持安装、启用、禁用、重新加载和错误回退。 -- 内置模块与外部扩展使用相同的运行接缝,但 Application Runtime 领域不插件化。 - -## Profile 组合 - -**实现阶段:P4** - -- 声明每种 Space 默认使用的 Behavior、Tools、Hooks、模型和上下文策略。 -- 支持全局默认、Project/Task/System Evolution Profile 和 Space 级覆盖。 -- 运行前得到确定的能力集合,并能向用户解释最终配置来源。 - -## 扩展开发与版本管理 - -**实现阶段:P4** - -- 提供扩展模板、测试工具、调试输出和兼容性检查。 -- 将扩展变更纳入 System Evolution 的候选、测试和回退流程。 -- 当内部模块真实长大后,支持从目录提升为独立 package,而不要求一开始拆包。 - -## Behavior 与 Skill 能力包 - -**实现阶段:P5** - -- 将可复用的 Agent 角色、提示词、上下文策略和工作方法封装成能力包。 -- 支持 Behavior 组合、参数化和按 Profile 选择。 -- Skill 复用现有 Tool 和 Run 机制,不建立绕过 Microkernel 的第二套执行系统。 - -## 扩展分发 - -**实现阶段:P8** - -- 支持本地扩展打包、来源记录、版本锁定、更新和卸载。 -- 对不兼容或启动失败的扩展提供隔离和回退。 -- 分发机制服务个人设备与自举,不以建设公共插件市场为前提。 diff --git a/docs/features/memory-knowledge.md b/docs/features/memory-knowledge.md deleted file mode 100644 index 6c24504..0000000 --- a/docs/features/memory-knowledge.md +++ /dev/null @@ -1,57 +0,0 @@ -# 记忆、检索与知识 - -## Conversation 摘要与上下文记忆 - -**实现阶段:P6** - -- 从长会话中提炼目标、决策、约束、未完成事项和重要结果。 -- 摘要可查看并能追溯原始消息,更新时不覆盖用户显式纠正。 -- Context Builder 按当前需求选择摘要与必要原文。 - -## Task 与 Project 记忆 - -**实现阶段:P6** - -- 跨 Conversation 保存 Space 级事实、规则、架构、决策和经验。 -- Project 记忆与项目目录绑定,Task 记忆随 Task 升级迁移。 -- 新会话可以检索相关历史,而不是加载该 Space 的全部内容。 - -## 全局个人记忆 - -**实现阶段:P6** - -- 保存跨 Space 有效的个人偏好、习惯和长期事实。 -- 区分用户明确声明与 Agent 从历史中提炼的内容。 -- 三个客户端共享同一记忆服务和修改结果。 - -## 记忆管理与来源 - -**实现阶段:P6** - -- 支持查看、编辑、固定、合并、纠错、遗忘和恢复记忆。 -- 每条记忆保留来源、作用域、更新时间和相关 Conversation/Run。 -- 处理互相冲突、已过期和不再可信的信息。 - -## 全文、向量与混合检索 - -**实现阶段:P6** - -- 对消息、Run、项目文档和知识资料建立全文索引。 -- 在需要语义检索时增加向量索引,并通过混合排序提高准确性。 -- 返回可解释的来源片段,避免只给出不可核查的记忆结论。 - -## 文档导入与个人知识库 - -**实现阶段:P6** - -- 导入常见文本、代码、Markdown、PDF 和网页资料。 -- 管理解析、切分、索引、更新、删除和重复内容。 -- 回答时引用原始来源,并区分个人记忆与外部资料。 - -## 记忆提炼与维护 Hooks - -**实现阶段:P6** - -- 在 Conversation、Run 或 Project 事件后提出摘要与记忆更新。 -- 定期检测重复、冲突、过期和缺少来源的记忆。 -- 自动维护不得阻塞正常 Run,用户可以查看和撤销变更。 diff --git a/docs/features/model-agent.md b/docs/features/model-agent.md deleted file mode 100644 index d6842c8..0000000 --- a/docs/features/model-agent.md +++ /dev/null @@ -1,57 +0,0 @@ -# 模型、聊天与 Agent - -## DeepSeek Adapter 与流式聊天 - -**实现阶段:P1** - -- 通过 Model Port 接入 DeepSeek API,支持流式文本生成和基础请求配置。 -- 将模型增量传递给客户端,并在完成后保存用户可见消息。 -- 处理网络失败、API 错误、中途取消和未完成回复。 - -## DefaultAgent 文本执行 - -**实现阶段:P1** - -- 实现单一 DefaultAgent Behavior,完成输入、上下文组装、模型调用和最终回答。 -- Behavior 运行在 Microkernel 中,通过端口使用模型,不直接写入 Repository。 -- 保持纯聊天 Run 与后续行动 Run 使用一致的执行入口。 - -## 系统提示词与上下文组装 - -**实现阶段:P1** - -- 组合系统提示词、当前 Space、Conversation 历史和用户输入。 -- 定义消息角色、顺序和客户端可见内容与模型内部消息的边界。 -- 为 Project 规则、记忆、附件和子 Agent 上下文预留明确装配位置。 - -## 单 Agent Tool Calling - -**实现阶段:P2** - -- 将可用 Tool 描述交给模型,解析 Tool Call 并通过 Tool Gateway 执行。 -- 将结构化 Tool 结果送回模型,循环直至生成最终回答。 -- 对未知 Tool、无效参数、执行失败和循环上限给出可恢复反馈。 - -## 上下文裁剪与压缩 - -**实现阶段:P4** - -- 在超过模型上下文限制前选择、裁剪或压缩历史内容。 -- 保留关键用户要求、Tool 结果和来源,避免摘要悄然改变任务意图。 -- 向用户展示发生过的上下文压缩,并允许查看原始历史。 - -## 多模型管理与路由 - -**实现阶段:P5** - -- 接入多个 Model Adapter,按 Space、Profile、Behavior 或具体 Run 选择模型。 -- 支持模型能力匹配、失败降级和角色级模型路由。 -- 统一记录模型标识、Token、延迟和调用结果。 - -## 多模态模型能力 - -**实现阶段:P7** - -- 支持图片、截图、文件等多模态输入和相应模型能力声明。 -- 将附件、屏幕内容和浏览器截图安全地组装进模型上下文。 -- 对不支持某种输入的模型进行能力降级或路由。 diff --git a/docs/features/observability-reliability.md b/docs/features/observability-reliability.md deleted file mode 100644 index 0b4be17..0000000 --- a/docs/features/observability-reliability.md +++ /dev/null @@ -1,57 +0,0 @@ -# 可观测性与可靠性 - -## Run 时间线与基础日志 - -**实现阶段:P1** - -- 记录 Run 状态、模型请求结果、错误和客户端输出时间线。 -- P2 加入 Tool、Shell、测试、Diff 和 Artifact 事件。 -- 日志保持可读、可关联且能够从 CLI 定位,不提前建设复杂遥测平台。 - -## Runtime 与功能诊断 - -**实现阶段:P4** - -- 生成包含 Runtime、配置、扩展、模型、数据目录和最近错误的诊断报告。 -- 查看 Hook 调用、Tool 耗时、连接状态和异常堆栈。 -- 支持脱敏后导出诊断信息用于排查或交给 Agent 自己分析。 - -## Run 暂停与恢复 - -**实现阶段:P5** - -- 在计划、子 Agent 或等待用户输入时持久化可恢复状态。 -- 区分可安全重放的步骤和已经产生外部副作用的步骤。 -- Runtime 重启后允许用户继续、跳过、回滚或终止未完成 Run。 - -## Runtime 崩溃恢复 - -**实现阶段:P8** - -- 检测异常退出、孤儿子进程、未完成写入和悬空 worktree。 -- 重启后恢复健康状态并列出需要处理的 Run。 -- 对自动恢复过程保留记录,避免隐藏数据或副作用不一致。 - -## 数据完整性与修复 - -**实现阶段:P8** - -- 检查注册表、JSON/JSONL、Artifact、索引和实际目录之间的一致性。 -- 修复可恢复问题,隔离损坏记录,并在操作前生成备份。 -- 定期验证备份可读取,而不是只确认备份文件存在。 - -## 资源、性能与成本监控 - -**实现阶段:P8** - -- 统计 Runtime CPU/内存、磁盘占用、队列长度、模型 Token、延迟和费用。 -- 定位过慢 Tool、异常循环、长期占用的进程和持续增长的数据。 -- 为清理、并发限制和模型路由提供真实依据。 - -## 资源回收 - -**实现阶段:P8** - -- 管理 Workspace 临时文件、worktree、日志、缓存、索引和 Artifact 的保留策略。 -- 在自动清理前保护用户固定内容和仍被 Run 引用的资源。 -- 提供空间预览、手动清理和可恢复删除。 diff --git a/docs/features/permissions-security.md b/docs/features/permissions-security.md deleted file mode 100644 index 8a21b0c..0000000 --- a/docs/features/permissions-security.md +++ /dev/null @@ -1,57 +0,0 @@ -# 权限、确认与敏感数据 - -## API Key 与基础凭据配置 - -**实现阶段:P1** - -- 首版安全读取 DeepSeek API Key,避免写入源码和普通 Run 日志。 -- 明确环境变量、配置引用和错误提示。 -- 所有凭据访问集中经过统一接口,为后续 Keychain 做准备。 - -## System Evolution Git 确认 - -**实现阶段:P3** - -- 对 commit、merge、push 和活动版本切换展示具体对象与 Diff。 -- 用户明确确认后才能执行,拒绝不会导致候选数据丢失。 -- 确认结果关联到 Evolution Run 和发布记录。 - -## 自动化操作的就地确认 - -**实现阶段:P7** - -- 文件批量修改或删除、网页提交、消息发布、系统设置等高影响动作在对应功能中请求确认。 -- 确认内容描述即将发生的真实副作用,而不是只显示抽象 Tool 名称。 -- 支持本次允许、拒绝和转为人工接管。 - -## 统一 Tool 风险与确认协议 - -**实现阶段:P8** - -- 为 Tool 声明读取范围、写入副作用、外部提交和所需系统权限。 -- 按全局、Profile、Space 和具体 Tool 配置自动允许或询问。 -- 统一管理待确认、超时、拒绝、恢复和审计记录。 - -## Keychain 与凭据生命周期 - -**实现阶段:P8** - -- 将模型、网站和外部服务凭据接入系统安全存储。 -- 支持创建、更新、撤销、失效提示和按能力授权访问。 -- Agent 只获得调用凭据的能力,不在普通上下文中看到明文。 - -## 敏感内容与历史清理 - -**实现阶段:P8** - -- 对日志、模型输入输出和 Artifact 中的密钥、Cookie 与个人信息进行识别和脱敏。 -- 支持用户定位并清除已经写入历史的敏感内容。 -- 清理过程同步处理缓存、索引、备份策略和来源引用。 - -## 远程访问边界 - -**实现阶段:P9** - -- Web 远程访问默认关闭,本地访问与远程暴露使用不同配置。 -- 远程模式提供轻量认证、连接撤销、来源限制和敏感操作再确认。 -- 不引入多用户系统,但避免无认证地暴露本机 Agent 能力。 diff --git a/docs/features/planning-multi-agent.md b/docs/features/planning-multi-agent.md deleted file mode 100644 index 802c8b0..0000000 --- a/docs/features/planning-multi-agent.md +++ /dev/null @@ -1,57 +0,0 @@ -# 计划、工作流与多 Agent - -## Plan 创建、编辑与执行 - -**实现阶段:P5** - -- 将复杂目标拆成有顺序和依赖关系的 Step。 -- 在执行前查看、编辑、重新生成或直接批准计划。 -- 顺序执行 Step,并将每步的输入、状态、结果和产物关联到父 Run。 - -## Step 控制与动态重规划 - -**实现阶段:P5** - -- 支持暂停、继续、跳过、重试和修改尚未执行的步骤。 -- 步骤失败后根据实际结果修订后续计划,而不是机械重复原方案。 -- 允许 Agent 在关键点等待用户补充信息或确认方向。 - -## Child Run 与子 Agent 委派 - -**实现阶段:P5** - -- 主 Agent 创建 Child Run,传递清晰目标、上下文和可用能力范围。 -- 隔离不同子 Agent 的工作上下文并回收结构化结果。 -- 父 Run 可以观察、取消和处理子 Run 失败。 - -## Agent 角色、团队与 Reviewer - -**实现阶段:P5** - -- 支持 Planner、Worker、Reviewer 等 Behavior 角色,但不固定唯一协作模板。 -- 按任务动态选择角色、模型、Tool 和上下文。 -- Reviewer 依据测试、Diff 和完成条件给出通过、返工或人工处理结论。 - -## 并行与 DAG 调度 - -**实现阶段:P5** - -- 并行执行没有依赖且不会争用同一资源的步骤。 -- 管理 DAG 依赖、并发上限、取消传播、工作区冲突和结果合并。 -- 并行失败不会造成其他步骤结果丢失或状态不明。 - -## 可复用工作流 - -**实现阶段:P5** - -- 将稳定的计划和 Agent 协作方式保存为参数化工作流。 -- 支持从一次成功 Run 提炼模板、再次运行并查看版本变化。 -- 工作流调用统一的 Behavior、Tool 和 Run,不另建执行引擎。 - -## 任务验证与质量评估 - -**实现阶段:P5** - -- 为计划和步骤定义可检查的完成条件。 -- 综合自动测试、Reviewer、外部状态和用户反馈评估结果。 -- 记录部分完成、回退、返工和最终交付之间的关系。 diff --git a/docs/features/project-assistant.md b/docs/features/project-assistant.md deleted file mode 100644 index d2f983c..0000000 --- a/docs/features/project-assistant.md +++ /dev/null @@ -1,57 +0,0 @@ -# Project 开发助手 - -## 项目上下文与规则 - -**实现阶段:P2** - -- 读取项目说明、目录结构、已有开发规则和构建配置。 -- 将相关 Project 上下文装配给 Coding Behavior,而不是一次性塞入全部源码。 -- 支持用户维护项目专属指令,并在 Run 中显示实际采用的规则。 - -## 仓库理解与代码检索 - -**实现阶段:P2** - -- 浏览仓库结构、搜索符号与文本、定位入口和相关测试。 -- 形成面向当前需求的结构概览,避免为每次任务预先建立重型索引。 -- 在修改前识别影响范围、现有约定和可能的验证方式。 - -## 代码修改与 Diff - -**实现阶段:P2** - -- 使用文件与补丁 Tool 修改源码,处理多文件改动和冲突。 -- 持续展示工作区状态和 Diff,并关联每次修改的需求与 Run。 -- 保持用户已有未提交改动,不用破坏性 Git 操作覆盖现场。 - -## Build、Test、Lint 与验证 - -**实现阶段:P2** - -- 发现或配置项目验证命令,运行构建、测试、Lint 和类型检查。 -- 解析退出状态和关键失败,允许 Agent 修正后重新验证。 -- 汇总执行过的验证、未执行项和剩余风险。 - -## 单 Agent 开发闭环 - -**实现阶段:P2** - -- 跑通“理解需求 → 阅读源码 → 修改 → 验证 → 汇报”的完整流程。 -- 在普通非自身项目中验证该闭环后,才允许用于 System Evolution。 -- 最终回答包含改动摘要、验证结果、Diff 位置和需要用户决定的问题。 - -## Git 与 Worktree 工作流 - -**实现阶段:P2** - -- 支持 status、diff、log、branch 和隔离 worktree 的创建、使用、检查与清理。 -- 候选开发默认在 worktree 中进行,稳定工作区保持可用。 -- Git 写操作通过明确的工作流执行,不让模型随意拼接高影响命令。 - -## 远程仓库、PR 与 CI - -**实现阶段:P7** - -- 读取远程 Issue、PR、Review 和 CI 状态,将其转换为本地开发上下文。 -- 支持创建提交、推送分支、创建或更新 PR,并展示外部结果。 -- 所有对外写操作进入统一确认和审计流程。 diff --git a/docs/features/run-execution.md b/docs/features/run-execution.md deleted file mode 100644 index a922dd5..0000000 --- a/docs/features/run-execution.md +++ /dev/null @@ -1,49 +0,0 @@ -# Run 与 Agent 执行 - -## Run Context、Record 与生命周期 - -**实现阶段:P1** - -- Microkernel 创建内存 Run Context,Application Runtime 保存同 ID 的 Run Record。 -- 覆盖创建、运行、完成、失败和取消等基础生命周期。 -- 纯聊天与 Tool 行动共享同一 Run 概念,并关联所属 Space、Conversation 和输入消息。 - -## Run Event 与实时输出 - -**实现阶段:P1** - -- 产生文本增量、状态变化、错误和完成结果等结构化 Run Event。 -- 将 Event 同时用于客户端实时展示和 Run 日志,不与 Hook 执行语义混淆。 -- 保证客户端断开不影响 Runtime 中 Run 的基本记录完整性。 - -## Tool 行动执行循环 - -**实现阶段:P2** - -- 记录模型请求 Tool、Tool 执行、结果返回模型和最终回答的完整时间线。 -- 支持一个 Run 内多次顺序 Tool 调用及其错误反馈。 -- 将 Run 结果、实际副作用和产物建立可追踪关联。 - -## Run 控制与人工介入 - -**实现阶段:P4** - -- 支持取消、重试、重新执行、暂停等待用户输入和继续运行。 -- 用户可以在长 Run 中回答 Agent 追问、修改约束或终止后续行动。 -- 重新执行时明确复用哪些输入、上下文和已经产生的副作用。 - -## 后台 Run 与运行队列 - -**实现阶段:P5** - -- 支持客户端退出后继续执行、排队、优先级和并发限制。 -- 恢复连接后可以重新订阅进度、查看结果或取消后台 Run。 -- 为 Child Run、多 Agent 和自动化任务提供统一调度入口。 - -## Run 结果验证 - -**实现阶段:P5** - -- 为 Run 定义可验证的完成条件,而不只依赖模型口头宣布完成。 -- 汇总测试、文件变化、Tool 结果和 Reviewer 结论形成最终结果。 -- 区分成功、部分完成、需要人工处理和不可继续的失败。 diff --git a/docs/features/runtime.md b/docs/features/runtime.md deleted file mode 100644 index 4e50426..0000000 --- a/docs/features/runtime.md +++ /dev/null @@ -1,57 +0,0 @@ -# Runtime 与系统生命周期 - -## 工程基座与依赖边界 - -**实现阶段:P1** - -- 建立 TypeScript + Node.js monorepo、统一构建、测试、类型检查和开发命令。 -- 落实 Supervisor、Runtime Host、Application Runtime、Microkernel、Extensions 和 Clients 的依赖方向。 -- 保证 Microkernel 不依赖产品领域或具体扩展,客户端不直接依赖 Runtime 内部实现。 - -## Runtime Host 与进程生命周期 - -**实现阶段:P1** - -- 提供常驻 Node.js Runtime、单实例检测、启动、停止、退出信号和基础健康检查。 -- 负责读取启动配置、组装 Application Runtime、Microkernel 与首批内置能力。 -- CLI 在 Runtime 未启动时能够拉起并连接,退出 CLI 不终止 Runtime。 - -## Application Runtime - -**实现阶段:P1** - -- 管理 Space、Conversation、持久化 Run Record、当前上下文和本地 Repository。 -- 作为所有本地数据的唯一写入者,协调客户端请求与 Agent 执行。 -- 提供基础 Profile 选择和首版串行 Run 策略。 - -## Microkernel - -**实现阶段:P1** - -- 创建内存 Run Context,调度 Behavior,传播取消信号并隔离错误。 -- 提供 Tool Gateway、最小 Hook Pipeline、运行事件和流式输出接缝。 -- 通过可测试的公共接口运行,不认识 Project、Task、DeepSeek 或具体存储。 - -## Runtime Interface - -**实现阶段:P1** - -- 提供 Command、Query、Event Stream 三类客户端交互。 -- 支持流式文本、Tool 进度、Run 状态和错误事件。 -- 首版完成本地传输、连接识别、断线处理和协议级错误返回。 - -## 运行诊断与环境信息 - -**实现阶段:P4** - -- 提供 Runtime 版本、进程、端口、数据目录、已加载能力和健康状态查询。 -- 支持诊断报告、连接恢复、配置来源追踪和调试模式。 -- 为 CLI/TUI、Web、Desktop 共享同一套诊断数据。 - -## 后台队列与并发运行 - -**实现阶段:P5** - -- 从首版串行执行发展为后台 Run、排队、按 Space 并发和资源占用控制。 -- 支持客户端断开后继续运行、重新订阅和跨子 Run 的取消传播。 -- 为多 Agent、自动化和长期任务提供统一运行基础。 diff --git a/docs/features/spaces.md b/docs/features/spaces.md deleted file mode 100644 index ba83105..0000000 --- a/docs/features/spaces.md +++ /dev/null @@ -1,49 +0,0 @@ -# Space、Conversation 与消息 - -## Task 生命周期 - -**实现阶段:P1** - -- 创建、命名、列出、切换、重命名、归档和删除 Task。 -- 提供首次启动的默认 Task,并记录最近使用位置。 -- Task 的工作文件和 Agent 数据随整个 Task 目录移动。 - -## Conversation 生命周期 - -**实现阶段:P1** - -- 在每个 Space 内创建、命名、切换、重命名、归档和删除 Conversation。 -- 保存消息顺序、角色、可见内容和关联 Run。 -- 支持恢复最近 Conversation,并为自动标题保留实现入口。 - -## Project 绑定与发现 - -**实现阶段:P2** - -- 将本地目录注册为 Project,并初始化或复用根目录中的 `.agent/`。 -- 支持 Project 移动后的重新定位、解绑和重新发现。 -- 保证项目源码不复制到 Agent 数据目录,多个会话共享同一个 Project 上下文。 - -## Task 升级为 Project - -**实现阶段:P4** - -- 将整个 Task 目录移动至用户指定位置并改为 Project。 -- 更新 Space 类型、全局注册路径和相关上下文引用。 -- 处理目标目录冲突、Git 初始化与升级失败回退。 - -## Conversation 搜索、分支与引用 - -**实现阶段:P4** - -- 跨 Space 搜索 Conversation、消息和 Run 结果。 -- 支持编辑或重新执行历史输入,并通过会话分支保留原始对话。 -- 在新会话中引用其他 Conversation 或 Run,并保留来源导航。 - -## 会话附件 - -**实现阶段:P6** - -- 在消息中附加文件、图片、代码片段和其他本地资料。 -- 管理附件复制或引用策略、预览、上下文注入和删除。 -- 为 Web/Desktop 和多模态模型共享统一附件语义。 diff --git a/docs/features/system-evolution.md b/docs/features/system-evolution.md deleted file mode 100644 index 57596cf..0000000 --- a/docs/features/system-evolution.md +++ /dev/null @@ -1,49 +0,0 @@ -# System Evolution - -## System Evolution Project - -**实现阶段:P3** - -- 将 Agent 自身源码仓库注册为特殊 Project,并应用 System Evolution Profile。 -- 用户在该 Project 的 Conversation 中提出自身需求和反馈,不要求 Agent 自动发现问题。 -- 复用 P2 已验证的单 Agent 开发能力,而不是建设第二套修改系统。 - -## Source、Candidate 与 Release 隔离 - -**实现阶段:P3** - -- 区分源码仓库、候选 worktree 和 Supervisor 实际启动的 Installed Release。 -- 当前运行版本不会被候选修改直接覆盖。 -- 候选失败、放弃或重新修改不会影响稳定 Runtime。 - -## 自身修改与验证流程 - -**实现阶段:P3** - -- 将用户需求转化为候选 worktree 中的代码修改。 -- 运行构建、测试、类型检查和最小启动验证。 -- 输出变更说明、完整 Diff、验证结果和已知风险。 - -## 发布提案与 Git 确认 - -**实现阶段:P3** - -- 将候选版本整理为可审核的发布提案。 -- commit、merge、push 等 Git 写操作必须由用户明确确认。 -- 拒绝发布时保留候选上下文,允许继续修改或安全清理。 - -## Supervisor 发布与回退 - -**实现阶段:P3** - -- 将通过确认的代码构建为不可变 Installed Release。 -- 原子切换活动版本、重启 Runtime、执行健康检查并完成客户端重连。 -- 新版本失败时自动切回旧版本,并支持用户主动回退演练。 - -## Evolution Record 与版本评估 - -**实现阶段:P4** - -- 关联需求、Conversation、Run、worktree、Diff、测试、Git 提交、Release 和回退结果。 -- 保存基准任务和回归验证,用于比较候选与稳定版本。 -- 从历史进化记录中查看某项能力为何加入、如何验证和何时发布。 diff --git a/docs/features/tools-execution.md b/docs/features/tools-execution.md deleted file mode 100644 index 2583179..0000000 --- a/docs/features/tools-execution.md +++ /dev/null @@ -1,65 +0,0 @@ -# Tools、Workspace 与产物 - -## Tool 契约与统一 Gateway - -**实现阶段:P2** - -- 定义 Tool 名称、说明、参数 Schema、结构化结果和错误协议。 -- 支持注册、列出、调用和取消 Tool,并将所有调用纳入 Run Context。 -- 在统一 Gateway 中触发基础 Hook、日志和输出事件。 - -## Workspace 作用域与路径解析 - -**实现阶段:P2** - -- 为 Task 和 Project 解析工作区根目录与当前工作目录。 -- 统一处理相对路径、绝对路径、符号链接和工作区外路径。 -- 将 Tool 产生的实际文件与 `.agent/` 元数据明确区分。 - -## 文件操作 Toolset - -**实现阶段:P2** - -- 支持目录浏览、文件读取、文本搜索、写入、补丁、移动、复制和创建目录。 -- 处理编码、大文件、二进制文件和修改冲突。 -- 返回适合模型继续工作的结构化摘要,同时保留完整结果入口。 - -## Shell 与进程 Toolset - -**实现阶段:P2** - -- 执行 Shell 命令,流式返回 stdout/stderr、退出码和耗时。 -- 支持超时、取消、工作目录、环境变量和基础后台进程处理。 -- 将模型临时生成的命令或脚本文本记录进 Run,不额外建立脚本库。 - -## Tool 进度与产物收集 - -**实现阶段:P2** - -- 统一表达 Tool 开始、进度、完成、失败和取消。 -- 自动识别测试报告、截图、下载文件和其他可预览产物并关联到 Run。 -- 支持产物查看、固定、导出和后续 Tool 引用。 - -## 交互式终端会话 - -**实现阶段:P4** - -- 使用 PTY 运行需要持续输入、终端控制序列或长时间驻留的命令。 -- 允许 Agent 与用户查看会话、发送输入、转入后台、重新连接和终止进程。 -- 将交互式会话与普通 Shell Tool、Run 日志和资源回收统一管理。 - -## Tool 能力范围与分组 - -**实现阶段:P4** - -- 按 Profile、Space 和 Behavior 选择向模型暴露的 Tool 集合。 -- 支持 Tool 别名、分组、描述优化和能力发现,避免一次向模型暴露过多接口。 -- 为权限策略和不同平台能力降级提供统一元数据。 - -## 外部 Tool 协议接入 - -**实现阶段:P4** - -- 通过 Adapter 接入 MCP 等外部 Tool 服务,将其映射到统一 Tool 契约。 -- 管理服务连接、能力同步、错误转换和生命周期。 -- 外部 Tool 与内置 Tool 使用一致的日志、确认和 Run 关联。 diff --git a/docs/features/web.md b/docs/features/web.md deleted file mode 100644 index 058049e..0000000 --- a/docs/features/web.md +++ /dev/null @@ -1,57 +0,0 @@ -# Web - -## Web 客户端基础与 Runtime 连接 - -**实现阶段:P9** - -- 选择 Web 框架并建立路由、状态管理和 Runtime Client。 -- 使用 Runtime 已有的 Command、Query、Event Stream,不重复实现 Agent 逻辑。 -- 处理连接状态、断线重连、版本不匹配和实时事件恢复。 - -## Space、Conversation 与聊天 - -**实现阶段:P9** - -- 创建、切换和管理 Task、Project、Conversation 与消息附件。 -- 支持流式聊天、历史导航、搜索和会话分支。 -- 在桌面与移动尺寸下保持可用的响应式交互。 - -## Run 与 Agent 工作台 - -**实现阶段:P9** - -- 展示 Run 时间线、Tool、Plan、Step、Child Run、Agent 角色、日志和 Artifact。 -- 提供取消、暂停、继续、重试、人工回答和计划调整。 -- 长任务在页面刷新或断线后能够恢复观察。 - -## Project 开发与 Review - -**实现阶段:P9** - -- 展示项目文件、代码修改、Build/Test/Lint 结果和 Git Diff。 -- 完成候选修改 Review、评论、确认和返回 Agent 继续修改。 -- 查看远程 Issue、PR 和 CI 结果。 - -## System Evolution 控制台 - -**实现阶段:P9** - -- 查看自身候选版本、测试、Diff、Evolution Record 和 Release。 -- 执行 Git 确认、版本发布、健康检查、重启和回退。 -- 清晰区分稳定版本、当前运行版本和未发布候选版本。 - -## 记忆、扩展与自动化管理 - -**实现阶段:P9** - -- 查看和编辑记忆、知识来源、Profile、模型、Tool、Hook、Behavior 与扩展。 -- 管理提醒、周期任务、事件触发器和自动化执行历史。 -- 提供统一配置和确认中心。 - -## 本地与远程访问体验 - -**实现阶段:P9** - -- 默认服务本机使用,并提供明确的远程启用流程。 -- 支持会话过期、认证、设备连接管理和敏感操作保护。 -- 保持远程客户端不直接访问本地数据文件或 Tool 实现。 diff --git a/docs/roadmap.md b/docs/roadmap.md index e69670d..2612787 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,75 +1,46 @@ -# 实现阶段 +# 路线图 -阶段只描述实际开发顺序,不等同于功能分类。大功能会跨越多个阶段,每个小功能点在对应功能文档中标记自己的实现阶段。 +路线图只描述大的实现顺序。安全、日志、取消和数据保护从每个阶段开始就随能力一起实现。 -## P1:本地可交互基座 +## P1:能持续聊天 -建立 TypeScript + Node.js monorepo、常驻 Runtime、本地文件数据、Task、Conversation、基础 Run、DeepSeek 流式聊天和 CLI REPL。 +建立最小 Kernel、第一版 Extension 和集中式数据目录。 -完成标准:Runtime 可独立启动,CLI 可连接并管理多个 Task/Conversation,退出重进后能够继续聊天。 +完成标志:CLI 可以管理 Workspace 与 Conversation,流式聊天,并在重启后继续历史。 -## P2:可行动的开发助手 +## P2:能操作项目 -加入 Tool Gateway、文件与 Shell Tool、单 Agent Tool Calling、Project、源码修改、测试、Git/Worktree、Run 日志和终端执行观察。 +加入文件、Shell、Tool Calling、Git、Worktree、验证和最小权限确认。 -完成标准:Agent 能在一个普通项目的隔离 worktree 中理解需求、修改源码、执行测试并展示可审核的 Diff。 +完成标志:Agent 可以在普通代码项目中完成一次可审核的修改与测试闭环。 -## P3:最小自举闭环 +## P3:能修改自己 -建立 System Evolution Project、候选 worktree、构建测试、变更审核、Git 写操作确认、Installed Release、Supervisor 切换和失败回退。 +加入隔离候选、构建验证、发布确认、版本切换和失败回退。 -完成标准:Agent 为自身增加一个完整的小能力,经用户确认发布后由新 Runtime 接管,并能够实际回退到旧版本。 +完成标志:Agent 可以安全完成一次自身小能力的修改与发布。 -P3 之后的功能默认优先通过该自举流程实现。 +## P4:能处理复杂工作 -## P4:终端产品与扩展能力成熟 +加入多模型、计划、多 Agent、工作流、长期记忆和知识检索。 -完善斜杠命令、非交互 CLI、全屏 TUI、Runtime 诊断、后台 Run、Hook Runtime、Profile、扩展加载与开发体验、多模型以及完整进化记录。 +完成标志:复杂目标可以持续执行,跨 Conversation 找到相关上下文,并给出可验证结果。 -完成标准:终端可以承担日常工作和 System Evolution 的完整操作;新增能力通常能够落入清晰的 Adapter、Tool、Hook 或 Behavior 边界。 +## P5:能管理电脑 -## P5:计划与多 Agent +加入浏览器、本机控制、多模态、提醒、定时任务和自动化。 -实现 Plan/Step、连续执行、失败重规划、Child Run、角色协作、并行/DAG 调度、结果验证和模型路由。 +完成标志:Agent 可以完成开发之外的高频电脑事务,并可靠重复执行。 -完成标准:复杂目标可以被拆解、连续执行、动态调整,并由多个 Agent 分工完成与验证。 +## P6:适合长期使用 -## P6:分层记忆与个人知识 +实现 Web 和 Desktop,完善远程访问、诊断、备份迁移、资源清理、凭据保护和版本维护。 -实现 Conversation、Task/Project、全局个人记忆,记忆管理、全文/向量检索、文档导入和项目知识沉淀。 +完成标志:三个产品共用一套稳定 Runtime 和数据,能够长期日常使用。 -完成标准:新会话能够准确找到相关历史和来源,用户可以查看、纠正或删除记忆。 +## 执行原则 -## P7:电脑管家与个人自动化 - -实现浏览器、本机文件与应用、剪贴板、通知、屏幕理解和操作、定时/后台任务、多模态输入与可复用自动化流程。 - -完成标准:Agent 可以从终端完成开发之外的高频电脑事务,并沉淀重复工作。 - -## P8:可靠性、安全与数据治理 - -系统化建设崩溃恢复、数据备份和迁移、统一审批、风险策略、密钥与敏感数据、资源清理和版本维护。 - -完成标准:强能力不会因为进程崩溃、数据损坏、误操作或错误升级造成不可恢复的后果。 - -这不意味着早期阶段不处理错误和风险:每一阶段都必须具备支撑自身闭环的最小日志、取消、确认或回退;P8 负责将它们统一为完整体系。 - -## P9:Web 产品线 - -基于同一 Runtime 建设 Web 会话、运行观察、项目、记忆、扩展、审批和 System Evolution 管理,并支持受控远程访问。 - -完成标准:Web 能独立承担日常交互与执行观察,但不复制 Agent 逻辑和数据状态。 - -## P10:Desktop 产品线 - -复用 Web 界面并增加 Runtime 生命周期、托盘、快捷键、通知、原生确认、系统能力桥接、安装和更新体验。 - -完成标准:Desktop 提供 Web 无法自然提供的本地常驻和原生集成能力,而不是第三套 Agent 实现。 - -## 功能文档规则 - -- `docs/features/` 中每个文件对应一个大功能。 -- 大功能不设置阶段,因为其中的小功能可能分布在多个阶段。 -- 每个小功能必须有可独立验证的实质性交付;只需改动很少代码的细节应合并到相邻功能点。 -- 小功能只记录实现阶段和功能范围,不维护 todo/done、候选、已确认或待确认状态。 -- 进入某个阶段时,再围绕该阶段的小功能制定提交级实现计划。 +- 每个阶段开始时再拆提交级任务。 +- 先跑通最短链路,再根据真实压力抽象。 +- 新增普通能力默认修改 Extension;只有通用运行机制才修改 Kernel。 +- 路线图允许随实践调整,不维护小功能状态表。 diff --git a/docs/source-layout.md b/docs/source-layout.md new file mode 100644 index 0000000..6d85095 --- /dev/null +++ b/docs/source-layout.md @@ -0,0 +1,39 @@ +# 源码目录 + +项目保持一个 TypeScript package: + +```text +llm-to-agent/ +├── src/ +│ ├── kernel/ +│ ├── extensions/ +│ │ ├── shared/ +│ │ ├── cli/ +│ │ ├── web/ +│ │ └── desktop/ +│ ├── products/ +│ └── main.ts +├── docs/ +├── tests/ +├── tooling/ +├── package.json +└── tsconfig.json +``` + +## 目录职责 + +- `kernel/`:只保存 Extension 运行机制。 +- `extensions/shared/`:跨产品使用的具体能力。 +- `extensions/cli|web|desktop/`:三条产品线各自的输入、展示和平台集成。 +- `products/`:静态选择每个产品启用哪些 Extension,不写业务逻辑。 +- `tests/`:只保存跨 Extension、跨进程或跨版本测试;普通测试跟随源码。 +- `tooling/`:构建、开发和发布辅助。 + +Extension 的具体划分见 [Extension 目录](extensions.md)。 + +## 放置规则 + +- 业务概念和流程进入 Extension,不为了复用方便放进 Kernel。 +- 产品私有实现不能互相依赖,真实复用出现后再提升到 `shared/`。 +- 目录和文件只在真实代码出现时创建,不预建占位层级。 +- 当前保持单包;只有构建、平台依赖、独立分发或进程隔离造成实际问题时才拆包。 From 258de1a1cdaa5440153fa4badd53b2f521c58039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 24 Jul 2026 17:37:00 +0800 Subject: [PATCH 15/24] =?UTF-8?q?init:=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/write-agent-blog/SKILL.md | 125 +++++++ .../write-agent-blog/agents/openai.yaml | 4 + .../assets/article-template.md | 40 +++ .gitignore | 7 +- README.md | 21 ++ docs/architecture.md | 16 +- docs/data-layout.md | 5 +- docs/extensions.md | 12 + .../old/knowledge/llm-to-agent-guide.md | 0 {packages => legacy}/old/package.json | 0 {packages => legacy}/old/src/agents/agent.ts | 0 {packages => legacy}/old/src/agents/index.ts | 0 .../old/src/agents/manager.ts | 0 {packages => legacy}/old/src/agents/tools.ts | 0 {packages => legacy}/old/src/context.ts | 0 {packages => legacy}/old/src/hooks/index.ts | 0 .../old/src/hooks/log.hooks.ts | 0 .../old/src/hooks/registry.ts | 0 {packages => legacy}/old/src/index.ts | 0 .../old/src/knowledge/knowledge-base.ts | 0 .../old/src/knowledge/search-tool.ts | 0 {packages => legacy}/old/src/llm.ts | 0 .../old/src/prompts/orchestrator.ts | 0 {packages => legacy}/old/src/prompts/reAct.ts | 0 .../old/src/prompts/system.ts | 0 .../old/src/tools/calculator.ts | 0 {packages => legacy}/old/src/tools/guess.ts | 0 .../old/src/tools/registry.ts | 0 {packages => legacy}/old/src/tools/weather.ts | 0 {packages => legacy}/old/src/types/index.ts | 0 {packages/cli => legacy/old}/tsconfig.json | 0 package.json | 16 +- packages/cli/package.json | 19 - packages/cli/src/index.ts | 1 - packages/core/package.json | 19 - packages/core/src/index.ts | 6 - packages/core/tsconfig.json | 7 - packages/desktop/package.json | 19 - packages/desktop/src/main.ts | 1 - packages/desktop/tsconfig.json | 7 - packages/old/tsconfig.json | 7 - packages/plugins-builtin/package.json | 16 - packages/plugins-builtin/src/index.ts | 1 - packages/plugins-builtin/tsconfig.json | 7 - packages/server/package.json | 19 - packages/server/src/index.ts | 1 - packages/server/tsconfig.json | 7 - packages/tests/package.json | 19 - packages/tests/src/core.test.ts | 17 - packages/tests/src/plugins-builtin.test.ts | 11 - packages/tests/src/types.test.ts | 57 --- packages/tests/tsconfig.json | 7 - packages/types/package.json | 12 - packages/types/src/index.ts | 46 --- packages/types/tsconfig.json | 7 - packages/web/index.html | 14 - packages/web/package.json | 10 - pnpm-lock.yaml | 329 ++++++++++++++++++ pnpm-workspace.yaml | 2 - src/extensions/README.md | 10 + src/extensions/catalog.ts | 7 + src/extensions/cli/README.md | 3 + src/extensions/desktop/README.md | 3 + src/extensions/shared/README.md | 5 + src/extensions/web/README.md | 3 + src/kernel/events.ts | 35 ++ src/kernel/extension.ts | 24 ++ src/kernel/index.ts | 4 + src/kernel/kernel.test.ts | 136 ++++++++ src/kernel/kernel.ts | 156 +++++++++ src/kernel/registry.ts | 31 ++ src/main.ts | 31 ++ src/products/cli.ts | 6 + src/products/desktop.ts | 6 + src/products/index.ts | 22 ++ src/products/product.ts | 8 + src/products/shared.ts | 4 + src/products/web.ts | 6 + tests/e2e/.gitkeep | 1 + tooling/.gitkeep | 1 + tsconfig.json | 9 +- 81 files changed, 1063 insertions(+), 362 deletions(-) create mode 100644 .agents/skills/write-agent-blog/SKILL.md create mode 100644 .agents/skills/write-agent-blog/agents/openai.yaml create mode 100644 .agents/skills/write-agent-blog/assets/article-template.md create mode 100644 README.md rename {packages => legacy}/old/knowledge/llm-to-agent-guide.md (100%) rename {packages => legacy}/old/package.json (100%) rename {packages => legacy}/old/src/agents/agent.ts (100%) rename {packages => legacy}/old/src/agents/index.ts (100%) rename {packages => legacy}/old/src/agents/manager.ts (100%) rename {packages => legacy}/old/src/agents/tools.ts (100%) rename {packages => legacy}/old/src/context.ts (100%) rename {packages => legacy}/old/src/hooks/index.ts (100%) rename {packages => legacy}/old/src/hooks/log.hooks.ts (100%) rename {packages => legacy}/old/src/hooks/registry.ts (100%) rename {packages => legacy}/old/src/index.ts (100%) rename {packages => legacy}/old/src/knowledge/knowledge-base.ts (100%) rename {packages => legacy}/old/src/knowledge/search-tool.ts (100%) rename {packages => legacy}/old/src/llm.ts (100%) rename {packages => legacy}/old/src/prompts/orchestrator.ts (100%) rename {packages => legacy}/old/src/prompts/reAct.ts (100%) rename {packages => legacy}/old/src/prompts/system.ts (100%) rename {packages => legacy}/old/src/tools/calculator.ts (100%) rename {packages => legacy}/old/src/tools/guess.ts (100%) rename {packages => legacy}/old/src/tools/registry.ts (100%) rename {packages => legacy}/old/src/tools/weather.ts (100%) rename {packages => legacy}/old/src/types/index.ts (100%) rename {packages/cli => legacy/old}/tsconfig.json (100%) delete mode 100644 packages/cli/package.json delete mode 100644 packages/cli/src/index.ts delete mode 100644 packages/core/package.json delete mode 100644 packages/core/src/index.ts delete mode 100644 packages/core/tsconfig.json delete mode 100644 packages/desktop/package.json delete mode 100644 packages/desktop/src/main.ts delete mode 100644 packages/desktop/tsconfig.json delete mode 100644 packages/old/tsconfig.json delete mode 100644 packages/plugins-builtin/package.json delete mode 100644 packages/plugins-builtin/src/index.ts delete mode 100644 packages/plugins-builtin/tsconfig.json delete mode 100644 packages/server/package.json delete mode 100644 packages/server/src/index.ts delete mode 100644 packages/server/tsconfig.json delete mode 100644 packages/tests/package.json delete mode 100644 packages/tests/src/core.test.ts delete mode 100644 packages/tests/src/plugins-builtin.test.ts delete mode 100644 packages/tests/src/types.test.ts delete mode 100644 packages/tests/tsconfig.json delete mode 100644 packages/types/package.json delete mode 100644 packages/types/src/index.ts delete mode 100644 packages/types/tsconfig.json delete mode 100644 packages/web/index.html delete mode 100644 packages/web/package.json create mode 100644 pnpm-lock.yaml delete mode 100644 pnpm-workspace.yaml create mode 100644 src/extensions/README.md create mode 100644 src/extensions/catalog.ts create mode 100644 src/extensions/cli/README.md create mode 100644 src/extensions/desktop/README.md create mode 100644 src/extensions/shared/README.md create mode 100644 src/extensions/web/README.md create mode 100644 src/kernel/events.ts create mode 100644 src/kernel/extension.ts create mode 100644 src/kernel/index.ts create mode 100644 src/kernel/kernel.test.ts create mode 100644 src/kernel/kernel.ts create mode 100644 src/kernel/registry.ts create mode 100644 src/main.ts create mode 100644 src/products/cli.ts create mode 100644 src/products/desktop.ts create mode 100644 src/products/index.ts create mode 100644 src/products/product.ts create mode 100644 src/products/shared.ts create mode 100644 src/products/web.ts create mode 100644 tests/e2e/.gitkeep create mode 100644 tooling/.gitkeep diff --git a/.agents/skills/write-agent-blog/SKILL.md b/.agents/skills/write-agent-blog/SKILL.md new file mode 100644 index 0000000..ed953f7 --- /dev/null +++ b/.agents/skills/write-agent-blog/SKILL.md @@ -0,0 +1,125 @@ +--- +name: write-agent-blog +description: 为 llm-to-agent 的《Agent进阶专题》系列创作中文文章,并在项目 blog 目录输出单个 Markdown 文件。当用户要求为某次提交、代码变更、小功能点、路线图事项、里程碑或实现过程撰写、生成、修改博客或文章时使用,包括“为本次提交写篇文章”“给这个功能写博客”“记录这次实现”等表达。 +--- + +# 创作 Agent 系列博客 + +为《Agent进阶专题》系列创作一篇以项目事实为依据的 Hexo 文章,并将成果保存为 `<项目根目录>/blog/` 下的单个 Markdown 文件。 + +## 工作流程 + +1. 使用 `git rev-parse --show-toplevel` 定位项目根目录。 +2. 确定用户要求的写作范围。对于“本次提交”等表达,使用 `git show` 检查 `HEAD`;用户明确指向尚未提交的工作时,还要检查工作区变更。 +3. 阅读相关实现、测试、包脚本、路线图事项和功能文档。阅读已有的 `blog/*.md`,延续编号、术语和内容深度,并避免重复。 +4. 确定文章对应的单个小功能点、所属阶段(如 `P1`、`P2`)、阶段内篇号、标题和仅含 ASCII 小写字符的短横线式别名。 +5. 以 `assets/article-template.md` 为写作骨架。替换所有占位内容并删除模板说明。 +6. 如果 `blog/` 不存在则创建它。除非用户明确要求修改已有文章,否则只新建一个 `.md` 文件。 +7. 检查元信息、文件名、永久链接、``、事实陈述、代码片段和 Git 差异。除非用户明确要求,否则不要创建提交。 + +## 事实依据 + +- 将源代码、测试和实际差异视为事实依据;使用文档和提交信息理解设计意图与背景。 +- 只描述选定变更已经体现的行为。明确标注计划和未来工作,不得将其写成已经实现的能力。 +- 不得虚构调试经历、性能数据、设计争论、执行命令、命令输出或用户反馈。 +- 优先选用能够揭示核心思路的短代码片段。确保片段与仓库内容一致,并在附近正文中注明仓库相对路径。 +- 如果目标提交主要是脚手架、配置或文档,应解释这项基础工作的目的和后续价值,但不得夸大现有能力。 + +## 文件名与元信息 + +文件名使用 `2026-PX-0X-xxx.md` 格式: + +- 除非用户修改约定,否则本系列固定使用四位年份 `2026`。 +- 将 `PX` 替换为路线图阶段,例如 `P1`。 +- 将 `0X` 替换为该阶段内的两位文章序号,从 `01` 开始。 +- 将 `xxx` 替换为描述该功能的简短英文别名,只使用小写 ASCII 字母、数字和短横线。 +- 示例:`2026-P1-03-streaming-chat.md`。 +- 根据已有文章推断下一个未使用的序号。如果无法可靠判断所属阶段,应暂停并询问用户,不要自行猜测。 +- 不得覆盖已有文件。如果目标名称已经存在,先判断它是否对应同一功能;否则递增序号。 + +严格使用以下元信息结构: + +```yaml +--- +title: 【Agent进阶专题】中文标题 +tags: [AI, Agent] +categories: +- AI +date: YYYY-MM-DD HH:mm:ss +permalink: /agent/YYYY/MM/slug/ +--- +``` + +- 新建文件时使用当前本地日期和时间。 +- 标题应具体并体现结果,不使用章节编号,也不要使用“一些思考”之类含糊标题。 +- 文件名和永久链接使用相同的英文别名。 +- 永久链接中的 `YYYY/MM` 从 `date` 推导。 + +## 文章固定结构 + +保持以下三部分顺序: + +1. Hexo 元信息。 +2. 位于 `` 之前的简洁简介,用于列表页摘要。 +3. 位于 `` 之后的完整正文。 + +使用自然且能够说明内容的标题,不要直接使用“简介部分”或“正文部分”等模板化标题。简介由一至两个短段落组成,需要说明问题、此次交付的能力及其价值。 + +正文应围绕具体功能组织,不要机械套用完全相同的标题。根据主题只选择必要内容,不要求每篇全部覆盖: + +- 变更前存在的问题或限制; +- 当前小功能点的目标与边界; +- 核心设计或理解模型; +- 配合精选代码片段说明实现路径; +- 验证方式与可观察结果; +- 取舍、当前限制和下一项自然演进能力; +- 用简短结语将本篇内容连接到整个系列的演进主线。 + +- 导读、概念介绍和设计点题类文章建议控制在 1000 个中文字符左右;这是保持简洁的参考值,不是硬性上限。根据章节范围和必要信息调整篇幅,优先保证主题讲清且没有无关扩展。 +- 标题给出的主题就是内容边界。只讲清当前主题,不借机扩展相关协议、实现细节或远期能力。 + +## 读者视角与系列衔接 + +- 面向第一次接触本项目的外部读者写作,不要把作者已经掌握的项目背景当作读者常识。 +- 阶段编号和文章编号用于组织系列,不是正文的叙事前提。首次出现 `P0`、`P1` 等编号时必须用自然语言解释其含义;如果编号对理解当前内容没有帮助,就不要在正文中使用。 +- 每篇文章先从读者能够理解的场景、问题或一次自然的需求变化切入,再逐步引出项目术语和设计结论。 +- 用“原本有什么—遇到什么问题—做出什么选择”推进文章,但不要为了讲故事虚构场景或增加文学化铺垫。 +- 不得使用“如前所述”“大家已经知道”等措辞假设读者读过其他文章。即使文章位于系列中间,也应提供理解当前主题所需的最少背景。 +- 相邻文章应各守边界,避免提前讲完后续主题。结尾只需自然提出下一篇的问题,不要罗列尚未解释的阶段和术语。 +- 当前导读部分暂定为:`P0-00` 项目介绍与目录导航、`P0-01` 架构设计、`P0-02` 功能划分、`P0-03` 阶段规划、`P0-04` 目录规划。创作其中一篇时,不要侵占其他篇目的主要内容。 + +## 系列文风 + +- 使用清晰的简体中文,面向理解 TypeScript 和基本 LLM 概念、但可能刚接触 Agent 工程的开发者。 +- 直接、口语化、克制。先给结论,再补必要原因;能用一句话说清的内容不要扩成一段。 +- 删除不推动观点的过渡句、重复总结、设问和修饰语。每个段落只表达一个重点,通常不超过三句话。 +- 优先使用具体动词和短句,避免“我们需要先回答一个不那么显眼、却会影响整个项目的问题”一类绕弯表达。 +- 适度使用“我们”营造共同实践感,重点仍应放在技术推理和可复现的工程过程上。 +- 按照“动机—原理—实现—验证”的顺序展开。在进入密集实现细节前,先解释为什么这样做。 +- 保持段落简短、标题明确,并与项目文档使用一致的术语。 +- 技术术语首次出现时给出简要解释。代码中的英文标识符保持原样;不要为同一概念反复更换译名。 +- 避免营销语言、泛泛的 AI 背景介绍、夸张结论、填充式总结和流水账式叙述。 +- 每篇文章都应能够独立阅读,同时在概念上承接前一项能力并引出下一项能力。 +- 用最短的具体情境引出矛盾和选择,避免从项目内部术语或文档摘要直接起笔,也避免把技术文章写成散文。 +- 不为追求完整而增加章节。点题类文章通常使用两至四个短章节即可。 + +## Markdown 与代码 + +- 使用标准 Markdown,围栏代码块必须标注语言。 +- 正文标题从 `##` 开始;页面标题由 Hexo 元信息提供,不要在正文中重复一级标题。 +- 标题、列表、代码块和 `` 前后保留空行。 +- 代码片段应尽量短且与主题直接相关。只有在省略范围明显且不会造成误解时才使用 `...`。 +- 展示命令时,应区分命令和示例输出。只有命令确实存在或实际执行过,才能将其作为验证命令写入文章。 +- 除非用户明确要求,否则不要添加目录。 + +## 最终检查 + +回复用户前确认: + +- 只新增或修改了一篇目标 Markdown 文章。 +- 文件名符合 `^2026-P[0-9]+-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$`。 +- 元信息结构有效;除非用户要求,否则只包含本系列规定的字段。 +- 有意义的简介之后恰好出现一次 ``。 +- 没有遗留任何占位内容。 +- 事实陈述和代码片段符合当前仓库状态。 +- 最终回复包含文章文件链接,并用一句话说明文章主题。 diff --git a/.agents/skills/write-agent-blog/agents/openai.yaml b/.agents/skills/write-agent-blog/agents/openai.yaml new file mode 100644 index 0000000..4e4915a --- /dev/null +++ b/.agents/skills/write-agent-blog/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Agent 专题博客创作" + short_description: "根据提交与项目文档创作统一风格的 Hexo Markdown 文章" + default_prompt: "使用 $write-agent-blog 为本次提交写一篇文章。" diff --git a/.agents/skills/write-agent-blog/assets/article-template.md b/.agents/skills/write-agent-blog/assets/article-template.md new file mode 100644 index 0000000..5b32b80 --- /dev/null +++ b/.agents/skills/write-agent-blog/assets/article-template.md @@ -0,0 +1,40 @@ +--- +title: 【Agent进阶专题】{{中文标题}} +tags: [AI, Agent] +categories: +- AI +date: {{年-月-日 时:分:秒}} +permalink: /agent/{{四位年份}}/{{两位月份}}/{{英文别名}}/ +--- + +{{面向首次接触项目的读者,从一个具体场景或问题切入,用一至两个短段落说明本篇主题及其价值,作为 Hexo 列表页摘要。}} + + + +## {{问题或限制}} + +{{说明需要解决的具体问题或此前存在的限制。}} + +## {{目标与边界}} + +{{说明本篇交付什么,以及刻意暂不处理什么。}} + +## {{核心设计或理解模型}} + +{{先解释核心思路,再进入实现细节。}} + +## {{实现过程}} + +{{配合精选代码片段说明关键实现路径。}} + +## {{验证结果}} + +{{说明实际使用的验证方式和可观察结果。}} + +## {{取舍与下一步}} + +{{记录当前限制、实现取舍和下一项自然演进能力。}} + +## {{结语}} + +{{将这个小功能点连接到从 LLM 走向 Agent 的系列主线。}} diff --git a/.gitignore b/.gitignore index 4c5307c..04fae43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -node_modules +node_modules/ .env -pnpm-lock.yaml .DS_Store -tmp \ No newline at end of file +tmp/ +.agent/ +.llm-to-agent \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdbf8b3 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# llm-to-agent + +个人使用、本地优先的开发助手与电脑管家。 + +项目坚持简单的 `Kernel + Extensions`:Kernel 只提供运行机制,具体能力全部由 Extension 实现;所有专属数据集中在用户目录,不写入绑定的项目。 + +运行 CLI: + +```bash +pnpm dev +``` + +启动时自动加载项目根目录的 `.env`。 + +文档: + +- [架构总览](docs/architecture.md) +- [源码目录](docs/source-layout.md) +- [数据目录](docs/data-layout.md) +- [Extension 目录](docs/extensions.md) +- [路线图](docs/roadmap.md) diff --git a/docs/architecture.md b/docs/architecture.md index 1440fff..21115f6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -18,16 +18,16 @@ Kernel 管“Extension 怎样连接和运行”,Extension 管“系统具体 ## Kernel -Kernel 不认识 Workspace、Conversation、Run、Agent、模型或 Tool,只提供所有 Extension 共同需要的机制: +第一版 Kernel 只保留几件直观的事: -- 生命周期与资源清理; -- 单个能力的提供与调用; -- 多个实现的注册与选择; -- 事实事件的发布与监听; -- 隔离存储、取消和错误传播; -- 副作用能力共用的调用接缝。 +- 按顺序装入 Extension; +- 依次执行 `setup` 和 `start`,停止时倒序执行 `stop`; +- 通过命名抓手提供和取得能力; +- 通过命名事件发布和监听事实。 -只有所有 Extension 都必须遵守的运行规则才进入 Kernel。 +抓手和事件暂时使用带命名空间的字符串,不为它们建立复杂的 TypeScript 类型系统。Setup Context 负责注册,Runtime Context 负责使用;Kernel 信任 Extension 遵守生命周期,不增加 Context 失效检查和不可变包装。 + +Kernel 不认识 Workspace、Conversation、Run、Agent、模型或 Tool。只有所有 Extension 都必须遵守的运行规则才进入 Kernel。 ## Extensions diff --git a/docs/data-layout.md b/docs/data-layout.md index 0f17dcd..67f4861 100644 --- a/docs/data-layout.md +++ b/docs/data-layout.md @@ -29,6 +29,9 @@ 目录按需要创建,不规定尚未实现的数据文件和格式。 +当前对话链路只会创建 Workspace 绑定信息和 +`conversations/default/messages.jsonl`,后续能力再增加自己的数据。 + ## Workspace - `managed` Workspace 的工作文件位于自己的 `files/`。 @@ -43,7 +46,7 @@ /workspaces// ``` -项目中不创建 `.agent/`、`.llm-to-agent/` 或其他专属元数据。项目移动时更新绑定关系;删除 linked Workspace 绝不能删除外部项目源码。 +项目中不创建 `.llm-to-agent/` 或其他专属元数据。项目移动时更新绑定关系;删除 linked Workspace 绝不能删除外部项目源码。 ## 数据规则 diff --git a/docs/extensions.md b/docs/extensions.md index 272006a..6d82603 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -6,6 +6,7 @@ ```text src/extensions/ +├── catalog.ts ├── shared/ │ ├── workspace/ │ ├── agent/ @@ -20,6 +21,17 @@ src/extensions/ 第一版只静态装配这四个 Extension。 +`catalog.ts` 集中列出所有跨 Extension 使用的 Extension ID、能力抓手和事实事件;它只保存字符串,不保存类型或运行逻辑。 + +当前协作链路: + +```text +workspace -> 提供 workspace +deepseek -> 添加 model.providers +agent -> 使用二者并提供 agent +cli -> 调用 agent +``` + ## 协作 - 一个明确能力由 Extension 提供,调用方取得后直接调用。 diff --git a/packages/old/knowledge/llm-to-agent-guide.md b/legacy/old/knowledge/llm-to-agent-guide.md similarity index 100% rename from packages/old/knowledge/llm-to-agent-guide.md rename to legacy/old/knowledge/llm-to-agent-guide.md diff --git a/packages/old/package.json b/legacy/old/package.json similarity index 100% rename from packages/old/package.json rename to legacy/old/package.json diff --git a/packages/old/src/agents/agent.ts b/legacy/old/src/agents/agent.ts similarity index 100% rename from packages/old/src/agents/agent.ts rename to legacy/old/src/agents/agent.ts diff --git a/packages/old/src/agents/index.ts b/legacy/old/src/agents/index.ts similarity index 100% rename from packages/old/src/agents/index.ts rename to legacy/old/src/agents/index.ts diff --git a/packages/old/src/agents/manager.ts b/legacy/old/src/agents/manager.ts similarity index 100% rename from packages/old/src/agents/manager.ts rename to legacy/old/src/agents/manager.ts diff --git a/packages/old/src/agents/tools.ts b/legacy/old/src/agents/tools.ts similarity index 100% rename from packages/old/src/agents/tools.ts rename to legacy/old/src/agents/tools.ts diff --git a/packages/old/src/context.ts b/legacy/old/src/context.ts similarity index 100% rename from packages/old/src/context.ts rename to legacy/old/src/context.ts diff --git a/packages/old/src/hooks/index.ts b/legacy/old/src/hooks/index.ts similarity index 100% rename from packages/old/src/hooks/index.ts rename to legacy/old/src/hooks/index.ts diff --git a/packages/old/src/hooks/log.hooks.ts b/legacy/old/src/hooks/log.hooks.ts similarity index 100% rename from packages/old/src/hooks/log.hooks.ts rename to legacy/old/src/hooks/log.hooks.ts diff --git a/packages/old/src/hooks/registry.ts b/legacy/old/src/hooks/registry.ts similarity index 100% rename from packages/old/src/hooks/registry.ts rename to legacy/old/src/hooks/registry.ts diff --git a/packages/old/src/index.ts b/legacy/old/src/index.ts similarity index 100% rename from packages/old/src/index.ts rename to legacy/old/src/index.ts diff --git a/packages/old/src/knowledge/knowledge-base.ts b/legacy/old/src/knowledge/knowledge-base.ts similarity index 100% rename from packages/old/src/knowledge/knowledge-base.ts rename to legacy/old/src/knowledge/knowledge-base.ts diff --git a/packages/old/src/knowledge/search-tool.ts b/legacy/old/src/knowledge/search-tool.ts similarity index 100% rename from packages/old/src/knowledge/search-tool.ts rename to legacy/old/src/knowledge/search-tool.ts diff --git a/packages/old/src/llm.ts b/legacy/old/src/llm.ts similarity index 100% rename from packages/old/src/llm.ts rename to legacy/old/src/llm.ts diff --git a/packages/old/src/prompts/orchestrator.ts b/legacy/old/src/prompts/orchestrator.ts similarity index 100% rename from packages/old/src/prompts/orchestrator.ts rename to legacy/old/src/prompts/orchestrator.ts diff --git a/packages/old/src/prompts/reAct.ts b/legacy/old/src/prompts/reAct.ts similarity index 100% rename from packages/old/src/prompts/reAct.ts rename to legacy/old/src/prompts/reAct.ts diff --git a/packages/old/src/prompts/system.ts b/legacy/old/src/prompts/system.ts similarity index 100% rename from packages/old/src/prompts/system.ts rename to legacy/old/src/prompts/system.ts diff --git a/packages/old/src/tools/calculator.ts b/legacy/old/src/tools/calculator.ts similarity index 100% rename from packages/old/src/tools/calculator.ts rename to legacy/old/src/tools/calculator.ts diff --git a/packages/old/src/tools/guess.ts b/legacy/old/src/tools/guess.ts similarity index 100% rename from packages/old/src/tools/guess.ts rename to legacy/old/src/tools/guess.ts diff --git a/packages/old/src/tools/registry.ts b/legacy/old/src/tools/registry.ts similarity index 100% rename from packages/old/src/tools/registry.ts rename to legacy/old/src/tools/registry.ts diff --git a/packages/old/src/tools/weather.ts b/legacy/old/src/tools/weather.ts similarity index 100% rename from packages/old/src/tools/weather.ts rename to legacy/old/src/tools/weather.ts diff --git a/packages/old/src/types/index.ts b/legacy/old/src/types/index.ts similarity index 100% rename from packages/old/src/types/index.ts rename to legacy/old/src/types/index.ts diff --git a/packages/cli/tsconfig.json b/legacy/old/tsconfig.json similarity index 100% rename from packages/cli/tsconfig.json rename to legacy/old/tsconfig.json diff --git a/package.json b/package.json index 7597174..c21eb9c 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "private": true, "type": "module", "scripts": { - "dev": "pnpm --filter @llm-to-agent/cli dev", - "dev:old": "pnpm --filter @llm-to-agent/old dev", - "dev:core": "pnpm --filter @llm-to-agent/core dev", - "dev:server": "pnpm --filter @llm-to-agent/server dev", - "dev:web": "pnpm --filter @llm-to-agent/web dev", - "dev:desktop": "pnpm --filter @llm-to-agent/desktop dev", - "test": "pnpm --filter @llm-to-agent/tests test" + "dev": "node --import tsx src/main.ts cli", + "dev:runtime": "node --import tsx src/main.ts cli web desktop", + "dev:cli": "node --import tsx src/main.ts cli", + "dev:web": "node --import tsx src/main.ts web", + "dev:desktop": "node --import tsx src/main.ts desktop", + "typecheck": "tsc --noEmit", + "test": "node --import tsx --test \"src/**/*.test.ts\"" }, "devDependencies": { "@types/node": "^25.9.1", "tsx": "^4.x", "typescript": "^5.x" } -} \ No newline at end of file +} diff --git a/packages/cli/package.json b/packages/cli/package.json deleted file mode 100644 index 04314a7..0000000 --- a/packages/cli/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@llm-to-agent/cli", - "version": "0.1.0", - "type": "module", - "private": true, - "main": "./src/index.ts", - "scripts": { - "dev": "tsx src/index.ts" - }, - "dependencies": { - "@llm-to-agent/core": "workspace:*", - "@llm-to-agent/plugins-builtin": "workspace:*" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts deleted file mode 100644 index ad1964e..0000000 --- a/packages/cli/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -console.log('🚀 LLM-to-Agent CLI 初始化已完成,待开发。\n'); diff --git a/packages/core/package.json b/packages/core/package.json deleted file mode 100644 index 0a7ee4e..0000000 --- a/packages/core/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@llm-to-agent/core", - "version": "0.1.0", - "type": "module", - "private": true, - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "dev": "tsx src/index.ts" - }, - "dependencies": { - "@llm-to-agent/types": "workspace:*" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts deleted file mode 100644 index 817e4a2..0000000 --- a/packages/core/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from '@llm-to-agent/types'; - -export const init = () => { - console.log('🚀 Core 入口已初始化完成,待开发。'); -} - diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/packages/core/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} diff --git a/packages/desktop/package.json b/packages/desktop/package.json deleted file mode 100644 index a2f5e4e..0000000 --- a/packages/desktop/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@llm-to-agent/desktop", - "version": "0.1.0", - "type": "module", - "private": true, - "main": "./src/main.ts", - "scripts": { - "dev": "tsx './src/main.ts'" - }, - "dependencies": { - "@llm-to-agent/core": "workspace:*", - "@llm-to-agent/plugins-builtin": "workspace:*" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts deleted file mode 100644 index 1c84edc..0000000 --- a/packages/desktop/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -console.log('🚀 Desktop 入口已初始化完成,待开发。'); diff --git a/packages/desktop/tsconfig.json b/packages/desktop/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/packages/desktop/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} diff --git a/packages/old/tsconfig.json b/packages/old/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/packages/old/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} diff --git a/packages/plugins-builtin/package.json b/packages/plugins-builtin/package.json deleted file mode 100644 index b5dcd57..0000000 --- a/packages/plugins-builtin/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@llm-to-agent/plugins-builtin", - "version": "0.1.0", - "type": "module", - "private": true, - "main": "./src/index.ts", - "dependencies": { - "@llm-to-agent/core": "workspace:*", - "@llm-to-agent/types": "workspace:*" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/packages/plugins-builtin/src/index.ts b/packages/plugins-builtin/src/index.ts deleted file mode 100644 index 56004c9..0000000 --- a/packages/plugins-builtin/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export default {} \ No newline at end of file diff --git a/packages/plugins-builtin/tsconfig.json b/packages/plugins-builtin/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/packages/plugins-builtin/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} diff --git a/packages/server/package.json b/packages/server/package.json deleted file mode 100644 index 6e16493..0000000 --- a/packages/server/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@llm-to-agent/server", - "version": "0.1.0", - "type": "module", - "private": true, - "main": "./src/index.ts", - "scripts": { - "dev": "tsx src/index.ts" - }, - "dependencies": { - "@llm-to-agent/core": "workspace:*", - "@llm-to-agent/plugins-builtin": "workspace:*" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts deleted file mode 100644 index 49a6c64..0000000 --- a/packages/server/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -console.log('🚀 Server 入口已初始化完成,待开发。'); diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/packages/server/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} diff --git a/packages/tests/package.json b/packages/tests/package.json deleted file mode 100644 index a6ee9b9..0000000 --- a/packages/tests/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@llm-to-agent/tests", - "version": "0.1.0", - "type": "module", - "private": true, - "scripts": { - "test": "tsx --test src/**/*.test.ts" - }, - "dependencies": { - "@llm-to-agent/types": "workspace:*", - "@llm-to-agent/core": "workspace:*", - "@llm-to-agent/plugins-builtin": "workspace:*" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/packages/tests/src/core.test.ts b/packages/tests/src/core.test.ts deleted file mode 100644 index 716ded6..0000000 --- a/packages/tests/src/core.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -import { init } from '@llm-to-agent/core'; - -describe('@llm-to-agent/core', () => { - - it('init() 不抛异常', () => { - assert.doesNotThrow(() => init()); - }); - - it('core 通过 re-export 暴露 types', async () => { - const mod = await import('@llm-to-agent/core'); - assert.equal(typeof mod.init, 'function', '应导出 init 函数'); - }); - -}); diff --git a/packages/tests/src/plugins-builtin.test.ts b/packages/tests/src/plugins-builtin.test.ts deleted file mode 100644 index c7cc2fe..0000000 --- a/packages/tests/src/plugins-builtin.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -describe('@llm-to-agent/plugins-builtin', () => { - - it('入口文件可正常 import', async () => { - const mod = await import('@llm-to-agent/plugins-builtin'); - assert.ok(mod.default !== undefined); - }); - -}); diff --git a/packages/tests/src/types.test.ts b/packages/tests/src/types.test.ts deleted file mode 100644 index 54a4fb7..0000000 --- a/packages/tests/src/types.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -import { Message, ToolCall, ToolDef, PluginManifest, SchedulerConfig, EventName, Listener } from '@llm-to-agent/types'; - -describe('@llm-to-agent/types', () => { - - it('Message 类型可正常构造', () => { - const msg: Message = { role: 'user', content: 'hello' }; - assert.equal(msg.role, 'user'); - assert.equal(msg.content, 'hello'); - }); - - it('Message 支持 tool_calls 和 tool_call_id', () => { - const tc: ToolCall = { id: '1', function: { name: 'test', arguments: '{}' } }; - const msg: Message = { role: 'assistant', content: '', tool_calls: [tc] }; - assert.equal(msg.tool_calls![0].id, '1'); - }); - - it('ToolDef 可正常构造', () => { - const tool: ToolDef = { - type: 'function', - function: { - name: 'weather', - description: '查询天气', - parameters: { type: 'object', properties: {}, required: [] }, - }, - }; - assert.equal(tool.function.name, 'weather'); - }); - - it('PluginManifest 类型完整', () => { - const manifest: PluginManifest = { - name: '@agent/test', - version: '1.0.0', - type: 'tool', - provides: ['test-tool'], - entry: './index.ts', - platforms: ['cli', 'desktop'], - }; - assert.equal(manifest.type, 'tool'); - assert.deepEqual(manifest.platforms, ['cli', 'desktop']); - }); - - it('SchedulerConfig 可正常构造', () => { - const config: SchedulerConfig = { maxSteps: 5 }; - assert.equal(config.maxSteps, 5); - }); - - it('EventName 和 Listener 类型定义正确', () => { - const name: EventName = 'llm:call'; - const fn: Listener = (data: any) => data; - assert.equal(typeof fn, 'function'); - assert.equal(name, 'llm:call'); - }); - -}); diff --git a/packages/tests/tsconfig.json b/packages/tests/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/packages/tests/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} diff --git a/packages/types/package.json b/packages/types/package.json deleted file mode 100644 index 9844c38..0000000 --- a/packages/types/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@llm-to-agent/types", - "version": "0.1.0", - "type": "module", - "private": true, - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": {}, - "devDependencies": { - "typescript": "^5.x" - } -} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts deleted file mode 100644 index 99f958c..0000000 --- a/packages/types/src/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -// ===== Agent 消息 ===== - -export interface Message { - role: 'system' | 'user' | 'assistant' | 'tool'; - content: string; - tool_calls?: ToolCall[]; - tool_call_id?: string; -} - -export interface ToolCall { - id: string; - function: { name: string; arguments: string }; -} - -// ===== 工具 ===== - -export interface ToolDef { - type: 'function'; - function: { - name: string; - description: string; - parameters: Record; - }; -} - -// ===== 插件 ===== - -export interface PluginManifest { - name: string; - version: string; - type: 'provider' | 'tool' | 'hook' | 'prompt'; - provides: string | string[]; - entry: string; - platforms?: ('cli' | 'desktop' | 'web')[]; -} - -// ===== 事件总线 ===== - -export type EventName = string; -export type Listener = (data: any) => any | Promise; - -// ===== 调度器 ===== - -export interface SchedulerConfig { - maxSteps?: number; -} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json deleted file mode 100644 index efdc208..0000000 --- a/packages/types/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": [] - } -} diff --git a/packages/web/index.html b/packages/web/index.html deleted file mode 100644 index b2cd966..0000000 --- a/packages/web/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - LLM-to-Agent Web - - -
-

🚀 LLM-to-Agent Web

-

占位页面,待实现 React 聊天界面。

-
- - diff --git a/packages/web/package.json b/packages/web/package.json deleted file mode 100644 index cf96ca1..0000000 --- a/packages/web/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@llm-to-agent/web", - "version": "0.1.0", - "type": "module", - "private": true, - "scripts": { - "dev": "echo 'TODO: vite dev'", - "build": "echo 'TODO: vite build'" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..ea3bef0 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,329 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^25.9.1 + version: 25.9.5 + tsx: + specifier: ^4.x + version: 4.23.1 + typescript: + specifier: ^5.x + version: 5.9.3 + +packages: + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@types/node@25.9.5': + resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + +snapshots: + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@types/node@25.9.5': + dependencies: + undici-types: 7.24.6 + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + fsevents@2.3.3: + optional: true + + tsx@4.23.1: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.3: {} + + undici-types@7.24.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index 18ec407..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/src/extensions/README.md b/src/extensions/README.md new file mode 100644 index 0000000..077c884 --- /dev/null +++ b/src/extensions/README.md @@ -0,0 +1,10 @@ +# Extensions + +除 Kernel 的稳定运行机制外,所有具体能力都在这里实现。 + +- `shared/` 保存不依赖产品界面的公共能力; +- `cli/`、`web/`、`desktop/` 保存三条产品线各自的输入、展示、协议和系统集成; +- 产品线私有扩展之间不能互相依赖;两个产品需要的实现应提升到 `shared/`; +- Kernel 不导入本目录中的任何具体扩展。 + +新增扩展时直接实现 `Extension`。只有真实能力开始开发时才创建对应源码目录,不再预建空 package 或 `export {}` 文件。 diff --git a/src/extensions/catalog.ts b/src/extensions/catalog.ts new file mode 100644 index 0000000..41638b1 --- /dev/null +++ b/src/extensions/catalog.ts @@ -0,0 +1,7 @@ +export const Event = { + RuntimeStopRequested: "runtime.stop.requested", + RunStarted: "run.started", + RunCompleted: "run.completed", + RunFailed: "run.failed", + MessageAdded: "message.added", +}; diff --git a/src/extensions/cli/README.md b/src/extensions/cli/README.md new file mode 100644 index 0000000..748f59c --- /dev/null +++ b/src/extensions/cli/README.md @@ -0,0 +1,3 @@ +# CLI extensions + +这里保存 REPL、终端渲染、斜杠命令和 TUI 等 CLI 私有能力。CLI 扩展把终端输入提交给 Kernel,并把 Run 事件转换成终端输出。 diff --git a/src/extensions/desktop/README.md b/src/extensions/desktop/README.md new file mode 100644 index 0000000..5e6bb43 --- /dev/null +++ b/src/extensions/desktop/README.md @@ -0,0 +1,3 @@ +# Desktop extensions + +这里保存原生桥接、窗口、托盘、快捷键、通知与更新等 Desktop 私有能力。与 Web 共用的界面逻辑应放入 `extensions/shared/`。 diff --git a/src/extensions/shared/README.md b/src/extensions/shared/README.md new file mode 100644 index 0000000..79b7593 --- /dev/null +++ b/src/extensions/shared/README.md @@ -0,0 +1,5 @@ +# Shared extensions + +这里保存模型、Workspace、Shell、Git、记忆、规划、多 Agent、浏览器、自动化和 System Evolution 等跨产品能力。 + +共享扩展的源码只依赖 Kernel 契约;需要协作时通过其他共享扩展注册的公开能力运行,不能认识 CLI、Web、Desktop 的界面与传输细节。 diff --git a/src/extensions/web/README.md b/src/extensions/web/README.md new file mode 100644 index 0000000..275c7a5 --- /dev/null +++ b/src/extensions/web/README.md @@ -0,0 +1,3 @@ +# Web extensions + +这里保存 HTTP 接入、事件流、路由与 Web 页面等 Web 私有能力。Web 扩展负责在网络交互和 Kernel 运行接口之间转换。 diff --git a/src/kernel/events.ts b/src/kernel/events.ts new file mode 100644 index 0000000..339511e --- /dev/null +++ b/src/kernel/events.ts @@ -0,0 +1,35 @@ +export type EventHandler = (payload: T) => void | Promise; +export type Unsubscribe = () => void; + +export class EventBus { + #listeners = new Map>>(); + + on(event: string, handler: EventHandler): Unsubscribe { + const listeners = this.#listeners.get(event) ?? new Set(); + listeners.add(handler); + this.#listeners.set(event, listeners); + + return () => { + listeners.delete(handler); + if (listeners.size === 0) this.#listeners.delete(event); + }; + } + + async emit(event: string, payload: T): Promise { + const errors: unknown[] = []; + + for (const listener of this.#listeners.get(event) ?? []) { + try { + await listener(payload); + } catch (error) { + errors.push(error); + } + } + + return errors; + } + + clear(): void { + this.#listeners.clear(); + } +} diff --git a/src/kernel/extension.ts b/src/kernel/extension.ts new file mode 100644 index 0000000..d0c0b50 --- /dev/null +++ b/src/kernel/extension.ts @@ -0,0 +1,24 @@ +import type { EventHandler, Unsubscribe } from "./events"; + +export type Awaitable = T | Promise; + +export interface ExtensionSetupContext { + add(name: string, value: T): void; + on(event: string, handler: EventHandler): Unsubscribe; +} + +export interface ExtensionRuntimeContext { + get(name: string): T; + all(name: string): T[]; + on(event: string, handler: EventHandler): Unsubscribe; + emit(event: string, payload: T): Promise; +} + +export interface Extension { + id: string; + setup(context: ExtensionSetupContext): Awaitable; + start?(context: ExtensionRuntimeContext): Awaitable; + stop?(context: ExtensionRuntimeContext): Awaitable; +} + +export type ExtensionFactory = () => Extension; diff --git a/src/kernel/index.ts b/src/kernel/index.ts new file mode 100644 index 0000000..4d8ae2d --- /dev/null +++ b/src/kernel/index.ts @@ -0,0 +1,4 @@ +export * from "./events"; +export * from "./extension"; +export * from "./kernel"; +export * from "./registry"; diff --git a/src/kernel/kernel.test.ts b/src/kernel/kernel.test.ts new file mode 100644 index 0000000..d3fb59d --- /dev/null +++ b/src/kernel/kernel.test.ts @@ -0,0 +1,136 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { EventBus, Kernel, type Extension } from "./index"; + +test("sets up every extension before starting them and stops in reverse order", async () => { + const calls: string[] = []; + + const first: Extension = { + id: "first", + setup(context) { + calls.push("first.setup"); + context.add("test.values", "first"); + }, + start(context) { + calls.push(`first.start:${context.all("test.values").join(",")}`); + }, + stop() { + calls.push("first.stop"); + }, + }; + + const second: Extension = { + id: "second", + setup(context) { + calls.push("second.setup"); + context.add("test.values", "second"); + }, + start(context) { + calls.push(`second.start:${context.all("test.values").join(",")}`); + }, + stop() { + calls.push("second.stop"); + }, + }; + + const kernel = new Kernel().use(first, second); + await kernel.start(); + await kernel.stop(); + + assert.deepEqual(calls, [ + "first.setup", + "second.setup", + "first.start:first,second", + "second.start:first,second", + "second.stop", + "first.stop", + ]); +}); + +test("rejects duplicate extension ids without partially installing a batch", () => { + const first: Extension = { id: "first", setup() {} }; + const duplicate: Extension = { id: "first", setup() {} }; + const kernel = new Kernel(); + + assert.throws(() => kernel.use(first, duplicate), /already installed/); + assert.deepEqual(kernel.installedExtensionIds, []); +}); + +test("gets one value or collects multiple values", async () => { + const kernel = new Kernel().use({ + id: "values", + setup(context) { + context.add("single", "one"); + context.add("multiple", "one"); + context.add("multiple", "two"); + }, + }); + + await kernel.start(); + + assert.equal(kernel.get("single"), "one"); + assert.deepEqual(kernel.all("multiple"), ["one", "two"]); + assert.throws(() => kernel.get("missing"), /found 0/); + assert.throws(() => kernel.get("multiple"), /found 2/); +}); + +test("clears registrations when setup fails", async () => { + const kernel = new Kernel().use({ + id: "broken-setup", + setup(context) { + context.add("temporary", "value"); + throw new Error("setup failed"); + }, + }); + + await assert.rejects(kernel.start(), /setup failed/); + + assert.equal(kernel.state, "stopped"); + assert.deepEqual(kernel.all("temporary"), []); +}); + +test("stops every active extension after cleanup errors", async () => { + const calls: string[] = []; + const kernel = new Kernel().use( + { + id: "first", + setup() {}, + stop() { + calls.push("first.stop"); + }, + }, + { + id: "second", + setup() {}, + stop() { + calls.push("second.stop"); + throw new Error("cleanup failed"); + }, + }, + ); + + await kernel.start(); + await assert.rejects(kernel.stop(), /failed to stop/); + + assert.equal(kernel.state, "stopped"); + assert.deepEqual(calls, ["second.stop", "first.stop"]); +}); + +test("publishes events to every listener and reports listener errors", async () => { + const events = new EventBus(); + const received: string[] = []; + + events.on("test.delivery", () => { + throw new Error("listener failed"); + }); + events.on("test.delivery", (payload) => { + received.push(payload); + }); + + const errors = await events.emit("test.delivery", "delivered"); + + assert.deepEqual(received, ["delivered"]); + assert.equal(errors.length, 1); + assert.match(String(errors[0]), /listener failed/); +}); diff --git a/src/kernel/kernel.ts b/src/kernel/kernel.ts new file mode 100644 index 0000000..7faaef0 --- /dev/null +++ b/src/kernel/kernel.ts @@ -0,0 +1,156 @@ +import { EventBus, type EventHandler, type Unsubscribe } from "./events"; +import type { + Extension, + ExtensionRuntimeContext, + ExtensionSetupContext, +} from "./extension"; +import { ExtensionRegistry } from "./registry"; + +export class Kernel { + #extensions: Extension[] = []; + #activeExtensions: Extension[] = []; + #registry = new ExtensionRegistry(); + #events = new EventBus(); + #setupContext: ExtensionSetupContext; + #runtimeContext: ExtensionRuntimeContext; + #state = "created"; + + constructor() { + const registry = this.#registry; + const events = this.#events; + + this.#setupContext = { + add(name, value) { + registry.add(name, value); + }, + on(event, handler) { + return events.on(event, handler); + }, + }; + + this.#runtimeContext = { + get(name) { + return registry.get(name); + }, + all(name) { + return registry.all(name); + }, + on(event, handler) { + return events.on(event, handler); + }, + emit(event, payload) { + return events.emit(event, payload); + }, + }; + } + + get state(): string { + return this.#state; + } + + get installedExtensionIds(): string[] { + return this.#extensions.map((extension) => extension.id); + } + + use(...extensions: Extension[]): this { + if (this.#state !== "created") { + throw new Error(`Cannot install extensions while kernel is ${this.#state}.`); + } + + const ids = new Set(this.#extensions.map((extension) => extension.id)); + + for (const extension of extensions) { + if (ids.has(extension.id)) { + throw new Error(`Extension "${extension.id}" is already installed.`); + } + ids.add(extension.id); + } + + this.#extensions.push(...extensions); + return this; + } + + get(name: string): T { + return this.#registry.get(name); + } + + all(name: string): T[] { + return this.#registry.all(name); + } + + on(event: string, handler: EventHandler): Unsubscribe { + return this.#events.on(event, handler); + } + + emit(event: string, payload: T): Promise { + return this.#events.emit(event, payload); + } + + async start(): Promise { + if (this.#state !== "created") { + throw new Error(`Cannot start kernel while it is ${this.#state}.`); + } + + this.#state = "starting"; + + try { + for (const extension of this.#extensions) { + await extension.setup(this.#setupContext); + } + + for (const extension of this.#extensions) { + this.#activeExtensions.push(extension); + await extension.start?.(this.#runtimeContext); + } + + this.#state = "running"; + } catch (error) { + const cleanupErrors = await this.#stopActiveExtensions(); + this.#events.clear(); + this.#registry.clear(); + this.#state = "stopped"; + + if (cleanupErrors.length > 0) { + throw new AggregateError( + [error, ...cleanupErrors], + "Kernel failed to start and one or more extensions failed to clean up.", + ); + } + + throw error; + } + } + + async stop(): Promise { + if (this.#state === "stopped") return; + + if (this.#state !== "running") { + throw new Error(`Cannot stop kernel while it is ${this.#state}.`); + } + + this.#state = "stopping"; + const errors = await this.#stopActiveExtensions(); + this.#events.clear(); + this.#registry.clear(); + this.#state = "stopped"; + + if (errors.length > 0) { + throw new AggregateError(errors, "One or more extensions failed to stop."); + } + } + + async #stopActiveExtensions(): Promise { + const errors: unknown[] = []; + + for (const extension of [...this.#activeExtensions].reverse()) { + try { + await extension.stop?.(this.#runtimeContext); + } catch (error) { + errors.push(error); + } + } + + this.#activeExtensions = []; + return errors; + } +} diff --git a/src/kernel/registry.ts b/src/kernel/registry.ts new file mode 100644 index 0000000..db86037 --- /dev/null +++ b/src/kernel/registry.ts @@ -0,0 +1,31 @@ +export class ExtensionRegistry { + #values = new Map(); + + add(name: string, value: T): void { + const values = this.#values.get(name); + + if (values) { + values.push(value); + } else { + this.#values.set(name, [value]); + } + } + + get(name: string): T { + const values = this.all(name); + + if (values.length !== 1) { + throw new Error(`"${name}" needs exactly one value, found ${values.length}.`); + } + + return values[0] as T; + } + + all(name: string): T[] { + return [...(this.#values.get(name) ?? [])] as T[]; + } + + clear(): void { + this.#values.clear(); + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..9f79375 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,31 @@ +import { existsSync } from "node:fs"; +import { loadEnvFile } from "node:process"; + +import { Event } from "./extensions/catalog"; +import { Kernel } from "./kernel"; +import { getProduct, sharedExtensions } from "./products"; + +if (existsSync(".env")) { + loadEnvFile(); +} + +const productIds = process.argv.slice(2); +const products = (productIds.length > 0 ? productIds : ["cli"]).map(getProduct); +const factories = [ + ...sharedExtensions, + ...products.flatMap((product) => product.extensions), +]; +const kernel = new Kernel().use(...factories.map((factory) => factory())); + +const stopped = new Promise((resolve) => { + kernel.on(Event.RuntimeStopRequested, resolve); + process.once("SIGINT", resolve); + process.once("SIGTERM", resolve); +}); + +try { + await kernel.start(); + await stopped; +} finally { + await kernel.stop(); +} diff --git a/src/products/cli.ts b/src/products/cli.ts new file mode 100644 index 0000000..bc757d2 --- /dev/null +++ b/src/products/cli.ts @@ -0,0 +1,6 @@ +import type { ProductDefinition } from "./product"; + +export const cliProduct: ProductDefinition = { + id: "cli", + extensions: [], +}; diff --git a/src/products/desktop.ts b/src/products/desktop.ts new file mode 100644 index 0000000..c879471 --- /dev/null +++ b/src/products/desktop.ts @@ -0,0 +1,6 @@ +import type { ProductDefinition } from "./product"; + +export const desktopProduct: ProductDefinition = { + id: "desktop", + extensions: [], +}; diff --git a/src/products/index.ts b/src/products/index.ts new file mode 100644 index 0000000..466b5ec --- /dev/null +++ b/src/products/index.ts @@ -0,0 +1,22 @@ +import { cliProduct } from "./cli"; +import { desktopProduct } from "./desktop"; +import { webProduct } from "./web"; + +import type { ProductDefinition, ProductId } from "./product"; + +export * from "./product"; +export { sharedExtensions } from "./shared"; + +const products: Record = { + cli: cliProduct, + web: webProduct, + desktop: desktopProduct, +}; + +export function getProduct(id: string): ProductDefinition { + if (id === "cli" || id === "web" || id === "desktop") { + return products[id]; + } + + throw new Error(`Unknown product "${id}". Expected cli, web, or desktop.`); +} diff --git a/src/products/product.ts b/src/products/product.ts new file mode 100644 index 0000000..eef795d --- /dev/null +++ b/src/products/product.ts @@ -0,0 +1,8 @@ +import type { ExtensionFactory } from "../kernel"; + +export type ProductId = "cli" | "web" | "desktop"; + +export interface ProductDefinition { + id: ProductId; + extensions: ExtensionFactory[]; +} diff --git a/src/products/shared.ts b/src/products/shared.ts new file mode 100644 index 0000000..d7a2c3f --- /dev/null +++ b/src/products/shared.ts @@ -0,0 +1,4 @@ +import type { ExtensionFactory } from "../kernel"; + +// 公共扩展在实现真实能力时加入这里。 +export const sharedExtensions: ExtensionFactory[] = []; diff --git a/src/products/web.ts b/src/products/web.ts new file mode 100644 index 0000000..9b4c390 --- /dev/null +++ b/src/products/web.ts @@ -0,0 +1,6 @@ +import type { ProductDefinition } from "./product"; + +export const webProduct: ProductDefinition = { + id: "web", + extensions: [], +}; diff --git a/tests/e2e/.gitkeep b/tests/e2e/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/e2e/.gitkeep @@ -0,0 +1 @@ + diff --git a/tooling/.gitkeep b/tooling/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tooling/.gitkeep @@ -0,0 +1 @@ + diff --git a/tsconfig.json b/tsconfig.json index 2a77da6..c2832bc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,11 @@ "module": "esnext", "moduleResolution": "bundler", "strict": true, + "noUncheckedIndexedAccess": true, "esModuleInterop": true, "skipLibCheck": true, - "types": ["node"] - } -} \ No newline at end of file + "types": ["node"], + "noEmit": true + }, + "include": ["src/**/*.ts"] +} From d87dad27dbe694e7efb6e2b9bcdceb35a939e632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 30 Jul 2026 17:43:56 +0800 Subject: [PATCH 16/24] =?UTF-8?q?refactor:=20=E9=9B=86=E4=B8=AD=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20Extension=20=E8=BF=9E=E6=8E=A5=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/extensions/catalog.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/extensions/catalog.ts b/src/extensions/catalog.ts index 41638b1..736302d 100644 --- a/src/extensions/catalog.ts +++ b/src/extensions/catalog.ts @@ -1,3 +1,16 @@ +export const ExtensionId = { + Workspace: "workspace", + DeepSeek: "deepseek", + Agent: "agent", + Cli: "cli", +}; + +export const Hook = { + Workspace: "workspace", + Agent: "agent", + ModelProviders: "model.providers", +}; + export const Event = { RuntimeStopRequested: "runtime.stop.requested", RunStarted: "run.started", From a801f40bdcb9da1642bd77a6f6bdb6e88c3f3c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 30 Jul 2026 17:44:31 +0800 Subject: [PATCH 17/24] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Workspace=20?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/extensions/shared/workspace/index.ts | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/extensions/shared/workspace/index.ts diff --git a/src/extensions/shared/workspace/index.ts b/src/extensions/shared/workspace/index.ts new file mode 100644 index 0000000..a21a9fb --- /dev/null +++ b/src/extensions/shared/workspace/index.ts @@ -0,0 +1,97 @@ +import { createHash } from "node:crypto"; +import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises"; +import { homedir } from "node:os"; +import { join, resolve } from "node:path"; + +import type { Extension } from "../../../kernel"; +import { ExtensionId, Hook } from "../../catalog"; + +export type MessageRole = "system" | "user" | "assistant"; + +export interface ChatMessage { + role: MessageRole; + content: string; + createdAt: string; +} + +export interface WorkspaceService { + home: string; + projectPath: string; + workspaceId: string; + conversationId: string; + messages(): Promise; + append(role: MessageRole, content: string): Promise; +} + +export interface WorkspaceOptions { + home?: string; + projectPath?: string; + conversationId?: string; +} + +export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extension { + const home = resolve( + options.home ?? process.env.LLM_TO_AGENT_HOME ?? join(homedir(), ".llm-to-agent"), + ); + const projectPath = resolve(options.projectPath ?? process.cwd()); + const workspaceId = createHash("sha256").update(projectPath).digest("hex").slice(0, 16); + const conversationId = options.conversationId ?? "default"; + const workspaceDirectory = join(home, "workspaces", workspaceId); + const conversationDirectory = join(workspaceDirectory, "conversations", conversationId); + const messagesFile = join(conversationDirectory, "messages.jsonl"); + + const workspace: WorkspaceService = { + home, + projectPath, + workspaceId, + conversationId, + + async messages() { + try { + const content = await readFile(messagesFile, "utf8"); + return content + .split("\n") + .filter(Boolean) + .map((line) => JSON.parse(line) as ChatMessage); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; + throw error; + } + }, + + async append(role, content) { + const message = { + role, + content, + createdAt: new Date().toISOString(), + }; + + await appendFile(messagesFile, `${JSON.stringify(message)}\n`, "utf8"); + return message; + }, + }; + + return { + id: ExtensionId.Workspace, + + async setup(context) { + await mkdir(conversationDirectory, { recursive: true }); + await writeFile( + join(workspaceDirectory, "workspace.json"), + `${JSON.stringify( + { + id: workspaceId, + kind: "linked", + projectPath, + activeConversationId: conversationId, + }, + null, + 2, + )}\n`, + "utf8", + ); + + context.add(Hook.Workspace, workspace); + }, + }; +} From 13d2494208d4127e51d5b83fcb884ebaf9fef5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 30 Jul 2026 17:44:52 +0800 Subject: [PATCH 18/24] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Agent=20?= =?UTF-8?q?=E4=B8=8E=20DeepSeek=20=E5=AF=B9=E8=AF=9D=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + src/extensions/shared/agent/index.ts | 95 ++++++++++++++++++++++++ src/extensions/shared/deepseek/index.ts | 96 +++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 src/extensions/shared/agent/index.ts create mode 100644 src/extensions/shared/deepseek/index.ts diff --git a/README.md b/README.md index bdbf8b3..d173b6d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ pnpm dev 启动时自动加载项目根目录的 `.env`。 +其中设置 `DEEPSEEK_API_KEY`; +也可用 `LLM_TO_AGENT_HOME` 修改数据根目录,用 `DEEPSEEK_MODEL` 修改模型。 + 文档: - [架构总览](docs/architecture.md) diff --git a/src/extensions/shared/agent/index.ts b/src/extensions/shared/agent/index.ts new file mode 100644 index 0000000..ed244b4 --- /dev/null +++ b/src/extensions/shared/agent/index.ts @@ -0,0 +1,95 @@ +import { randomUUID } from "node:crypto"; + +import type { + Extension, + ExtensionRuntimeContext, +} from "../../../kernel"; +import { Event, ExtensionId, Hook } from "../../catalog"; +import { + type ChatMessage, + type WorkspaceService, +} from "../workspace"; + +export interface ModelProvider { + id: string; + chat(messages: ChatMessage[], signal?: AbortSignal): AsyncIterable; +} + +export interface AgentService { + chat(input: string, signal?: AbortSignal): AsyncIterable; +} + +export function createAgentExtension(): Extension { + let runtime: ExtensionRuntimeContext | undefined; + let workspace: WorkspaceService | undefined; + let model: ModelProvider | undefined; + + const agent: AgentService = { + async *chat(input, signal) { + if (!runtime || !workspace || !model) { + throw new Error("Agent is not running."); + } + + const activeRuntime = runtime; + const activeWorkspace = workspace; + const activeModel = model; + const runId = randomUUID(); + + const userMessage = await activeWorkspace.append("user", input); + await activeRuntime.emit(Event.MessageAdded, { + workspaceId: activeWorkspace.workspaceId, + conversationId: activeWorkspace.conversationId, + message: userMessage, + }); + await activeRuntime.emit(Event.RunStarted, { runId, input }); + + let answer = ""; + + try { + const messages = await activeWorkspace.messages(); + + for await (const chunk of activeModel.chat(messages, signal)) { + answer += chunk; + yield chunk; + } + + const assistantMessage = await activeWorkspace.append("assistant", answer); + await activeRuntime.emit(Event.MessageAdded, { + workspaceId: activeWorkspace.workspaceId, + conversationId: activeWorkspace.conversationId, + message: assistantMessage, + }); + await activeRuntime.emit(Event.RunCompleted, { runId }); + } catch (error) { + await activeRuntime.emit(Event.RunFailed, { runId, error: String(error) }); + throw error; + } + }, + }; + + return { + id: ExtensionId.Agent, + + setup(context) { + context.add(Hook.Agent, agent); + }, + + start(context) { + const providers = context.all(Hook.ModelProviders); + + if (providers.length === 0) { + throw new Error("Agent needs at least one model provider."); + } + + runtime = context; + workspace = context.get(Hook.Workspace); + model = providers[0]; + }, + + stop() { + runtime = undefined; + workspace = undefined; + model = undefined; + }, + }; +} diff --git a/src/extensions/shared/deepseek/index.ts b/src/extensions/shared/deepseek/index.ts new file mode 100644 index 0000000..4447489 --- /dev/null +++ b/src/extensions/shared/deepseek/index.ts @@ -0,0 +1,96 @@ +import type { Extension } from "../../../kernel"; +import { ExtensionId, Hook } from "../../catalog"; +import type { ModelProvider } from "../agent"; + +export interface DeepSeekOptions { + apiKey?: string; + baseUrl?: string; + model?: string; + request?: (url: string, init: RequestInit) => Promise; +} + +export function createDeepSeekExtension(options: DeepSeekOptions = {}): Extension { + const apiKey = options.apiKey ?? process.env.DEEPSEEK_API_KEY; + const baseUrl = ( + options.baseUrl ?? + process.env.DEEPSEEK_BASE_URL ?? + "https://api.deepseek.com" + ).replace(/\/+$/, ""); + const model = options.model ?? process.env.DEEPSEEK_MODEL ?? "deepseek-v4-flash"; + const request = options.request ?? fetch; + + const provider: ModelProvider = { + id: "deepseek", + + async *chat(messages, signal) { + const response = await request(`${baseUrl}/chat/completions`, { + method: "POST", + headers: { + authorization: `Bearer ${apiKey}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + model, + messages: messages.map(({ role, content }) => ({ role, content })), + thinking: { type: "disabled" }, + stream: true, + }), + signal, + }); + + if (!response.ok) { + throw new Error( + `DeepSeek request failed (${response.status}): ${await response.text()}`, + ); + } + + if (!response.body) { + throw new Error("DeepSeek returned an empty response body."); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + + try { + while (true) { + const { done, value } = await reader.read(); + buffer += decoder.decode(value, { stream: !done }); + + const events = buffer.split(/\r?\n\r?\n/); + buffer = events.pop() ?? ""; + + for (const entry of events) { + for (const line of entry.split(/\r?\n/)) { + if (!line.startsWith("data:")) continue; + + const data = line.slice(5).trim(); + if (data === "[DONE]") return; + if (!data) continue; + + const chunk = JSON.parse(data); + const content = chunk.choices?.[0]?.delta?.content; + if (content) yield content; + } + } + + if (done) break; + } + } finally { + reader.releaseLock(); + } + }, + }; + + return { + id: ExtensionId.DeepSeek, + + setup(context) { + if (!apiKey) { + throw new Error("DEEPSEEK_API_KEY is required."); + } + + context.add(Hook.ModelProviders, provider); + }, + }; +} From 8283ba604d0672ff717a0d4fc7a731568f85c5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 30 Jul 2026 17:45:51 +0800 Subject: [PATCH 19/24] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=20CLI=20?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/write-agent-blog/SKILL.md | 218 ++++++++++++++--------- src/extensions/chat.test.ts | 103 +++++++++++ src/extensions/cli/index.ts | 84 +++++++++ src/products/cli.ts | 3 +- src/products/shared.ts | 10 +- 5 files changed, 331 insertions(+), 87 deletions(-) create mode 100644 src/extensions/chat.test.ts create mode 100644 src/extensions/cli/index.ts diff --git a/.agents/skills/write-agent-blog/SKILL.md b/.agents/skills/write-agent-blog/SKILL.md index ed953f7..bcd5e6c 100644 --- a/.agents/skills/write-agent-blog/SKILL.md +++ b/.agents/skills/write-agent-blog/SKILL.md @@ -1,43 +1,31 @@ --- name: write-agent-blog -description: 为 llm-to-agent 的《Agent进阶专题》系列创作中文文章,并在项目 blog 目录输出单个 Markdown 文件。当用户要求为某次提交、代码变更、小功能点、路线图事项、里程碑或实现过程撰写、生成、修改博客或文章时使用,包括“为本次提交写篇文章”“给这个功能写博客”“记录这次实现”等表达。 +description: 为 llm-to-agent 的《Agent进阶专题》系列创作中文文章。触发:"为本次提交写篇文章""给这个功能写博客""记录这次实现"。不触发:修改 README、写代码注释、写 commit message。 --- # 创作 Agent 系列博客 -为《Agent进阶专题》系列创作一篇以项目事实为依据的 Hexo 文章,并将成果保存为 `<项目根目录>/blog/` 下的单个 Markdown 文件。 +为《Agent进阶专题》输出一篇 Hexo Markdown 文章到 `blog/` 下。**源码是唯一事实来源,只写已实现的行为。** -## 工作流程 +--- -1. 使用 `git rev-parse --show-toplevel` 定位项目根目录。 -2. 确定用户要求的写作范围。对于“本次提交”等表达,使用 `git show` 检查 `HEAD`;用户明确指向尚未提交的工作时,还要检查工作区变更。 -3. 阅读相关实现、测试、包脚本、路线图事项和功能文档。阅读已有的 `blog/*.md`,延续编号、术语和内容深度,并避免重复。 -4. 确定文章对应的单个小功能点、所属阶段(如 `P1`、`P2`)、阶段内篇号、标题和仅含 ASCII 小写字符的短横线式别名。 -5. 以 `assets/article-template.md` 为写作骨架。替换所有占位内容并删除模板说明。 -6. 如果 `blog/` 不存在则创建它。除非用户明确要求修改已有文章,否则只新建一个 `.md` 文件。 -7. 检查元信息、文件名、永久链接、``、事实陈述、代码片段和 Git 差异。除非用户明确要求,否则不要创建提交。 +## 必须(违反即错误) -## 事实依据 +### 工作流程 -- 将源代码、测试和实际差异视为事实依据;使用文档和提交信息理解设计意图与背景。 -- 只描述选定变更已经体现的行为。明确标注计划和未来工作,不得将其写成已经实现的能力。 -- 不得虚构调试经历、性能数据、设计争论、执行命令、命令输出或用户反馈。 -- 优先选用能够揭示核心思路的短代码片段。确保片段与仓库内容一致,并在附近正文中注明仓库相对路径。 -- 如果目标提交主要是脚手架、配置或文档,应解释这项基础工作的目的和后续价值,但不得夸大现有能力。 +1. `git rev-parse --show-toplevel` 定位项目根目录。 +2. 用 `git show` / `git diff` 读取目标变更;读已有 `blog/*.md` 确定编号、术语、避免重复。 +3. 确定:阶段(P0/P1/P2)、阶段内序号、中文标题、英文 slug。 +4. 只新建一个 `blog/2026-PX-0X-slug.md`,不覆盖已有文件。 -## 文件名与元信息 +### 文件名与元信息 -文件名使用 `2026-PX-0X-xxx.md` 格式: +``` +2026-PX-0X-slug.md +``` -- 除非用户修改约定,否则本系列固定使用四位年份 `2026`。 -- 将 `PX` 替换为路线图阶段,例如 `P1`。 -- 将 `0X` 替换为该阶段内的两位文章序号,从 `01` 开始。 -- 将 `xxx` 替换为描述该功能的简短英文别名,只使用小写 ASCII 字母、数字和短横线。 -- 示例:`2026-P1-03-streaming-chat.md`。 -- 根据已有文章推断下一个未使用的序号。如果无法可靠判断所属阶段,应暂停并询问用户,不要自行猜测。 -- 不得覆盖已有文件。如果目标名称已经存在,先判断它是否对应同一功能;否则递增序号。 - -严格使用以下元信息结构: +- `PX` = 路线图阶段,`0X` = 阶段内序号(01 起),`slug` = 小写 ASCII + 短横线。 +- 如果无法判断阶段或序号,暂停询问用户。 ```yaml --- @@ -46,80 +34,142 @@ tags: [AI, Agent] categories: - AI date: YYYY-MM-DD HH:mm:ss -permalink: /agent/YYYY/MM/slug/ +permalink: /YYYY/agent/slug/ --- ``` -- 新建文件时使用当前本地日期和时间。 -- 标题应具体并体现结果,不使用章节编号,也不要使用“一些思考”之类含糊标题。 -- 文件名和永久链接使用相同的英文别名。 -- 永久链接中的 `YYYY/MM` 从 `date` 推导。 +- date 用当前本地时间,适当加工避开工作时间,permalink的YYYY从date中提取。 +- 标题具体、体现结果,不用"一些思考"等模糊标题。 -## 文章固定结构 +### 事实约束 -保持以下三部分顺序: +- 只写代码中已存在的行为。未实现的能力标注"计划中",不写成已实现。 +- 禁止虚构:调试经历、性能数据、设计争论、命令输出、用户反馈。 +- 代码片段必须与仓库一致,附近标注仓库相对路径。 +- 脚手架/配置类提交:解释目的和后续价值,不夸大现有能力。 -1. Hexo 元信息。 -2. 位于 `` 之前的简洁简介,用于列表页摘要。 -3. 位于 `` 之后的完整正文。 +### 文章结构 -使用自然且能够说明内容的标题,不要直接使用“简介部分”或“正文部分”等模板化标题。简介由一至两个短段落组成,需要说明问题、此次交付的能力及其价值。 +``` +元信息 +摘要(1-2 句) + +正文 +``` -正文应围绕具体功能组织,不要机械套用完全相同的标题。根据主题只选择必要内容,不要求每篇全部覆盖: +- `` 恰好出现一次,前后各空一行。 +- 摘要说明"做了什么、为什么有价值"。 +- 正文标题从 `##` 开始,不重复一级标题。 -- 变更前存在的问题或限制; -- 当前小功能点的目标与边界; -- 核心设计或理解模型; -- 配合精选代码片段说明实现路径; -- 验证方式与可观察结果; -- 取舍、当前限制和下一项自然演进能力; -- 用简短结语将本篇内容连接到整个系列的演进主线。 +--- -- 导读、概念介绍和设计点题类文章建议控制在 1000 个中文字符左右;这是保持简洁的参考值,不是硬性上限。根据章节范围和必要信息调整篇幅,优先保证主题讲清且没有无关扩展。 -- 标题给出的主题就是内容边界。只讲清当前主题,不借机扩展相关协议、实现细节或远期能力。 +## 应该(尽量做到) -## 读者视角与系列衔接 +### 内容边界 -- 面向第一次接触本项目的外部读者写作,不要把作者已经掌握的项目背景当作读者常识。 -- 阶段编号和文章编号用于组织系列,不是正文的叙事前提。首次出现 `P0`、`P1` 等编号时必须用自然语言解释其含义;如果编号对理解当前内容没有帮助,就不要在正文中使用。 -- 每篇文章先从读者能够理解的场景、问题或一次自然的需求变化切入,再逐步引出项目术语和设计结论。 -- 用“原本有什么—遇到什么问题—做出什么选择”推进文章,但不要为了讲故事虚构场景或增加文学化铺垫。 -- 不得使用“如前所述”“大家已经知道”等措辞假设读者读过其他文章。即使文章位于系列中间,也应提供理解当前主题所需的最少背景。 -- 相邻文章应各守边界,避免提前讲完后续主题。结尾只需自然提出下一篇的问题,不要罗列尚未解释的阶段和术语。 -- 当前导读部分暂定为:`P0-00` 项目介绍与目录导航、`P0-01` 架构设计、`P0-02` 功能划分、`P0-03` 阶段规划、`P0-04` 目录规划。创作其中一篇时,不要侵占其他篇目的主要内容。 +- 标题就是边界。不讲无关协议、远期能力、实现细节。 +- 导读/概念类 ~1000 中文字符;实现类按需,讲清即止。 +- 2-4 个短章节即可,不为完整而堆章节。 -## 系列文风 +### 叙事方式 -- 使用清晰的简体中文,面向理解 TypeScript 和基本 LLM 概念、但可能刚接触 Agent 工程的开发者。 -- 直接、口语化、克制。先给结论,再补必要原因;能用一句话说清的内容不要扩成一段。 -- 删除不推动观点的过渡句、重复总结、设问和修饰语。每个段落只表达一个重点,通常不超过三句话。 -- 优先使用具体动词和短句,避免“我们需要先回答一个不那么显眼、却会影响整个项目的问题”一类绕弯表达。 -- 适度使用“我们”营造共同实践感,重点仍应放在技术推理和可复现的工程过程上。 -- 按照“动机—原理—实现—验证”的顺序展开。在进入密集实现细节前,先解释为什么这样做。 -- 保持段落简短、标题明确,并与项目文档使用一致的术语。 -- 技术术语首次出现时给出简要解释。代码中的英文标识符保持原样;不要为同一概念反复更换译名。 -- 避免营销语言、泛泛的 AI 背景介绍、夸张结论、填充式总结和流水账式叙述。 -- 每篇文章都应能够独立阅读,同时在概念上承接前一项能力并引出下一项能力。 -- 用最短的具体情境引出矛盾和选择,避免从项目内部术语或文档摘要直接起笔,也避免把技术文章写成散文。 -- 不为追求完整而增加章节。点题类文章通常使用两至四个短章节即可。 +- 从读者能理解的场景切入,不假定读者读过前文。 +- "原本有什么 → 遇到什么问题 → 做出什么选择"推进。 +- 读者已知的概念直接引用跳过,如"和 Express 中间件机制一致,略过"。 +- 结尾自然引出下一篇的问题,不罗列阶段和术语。 +- 首次出现 P0/P1 等编号时用自然语言解释。 -## Markdown 与代码 +### 文风 -- 使用标准 Markdown,围栏代码块必须标注语言。 -- 正文标题从 `##` 开始;页面标题由 Hexo 元信息提供,不要在正文中重复一级标题。 -- 标题、列表、代码块和 `` 前后保留空行。 -- 代码片段应尽量短且与主题直接相关。只有在省略范围明显且不会造成误解时才使用 `...`。 -- 展示命令时,应区分命令和示例输出。只有命令确实存在或实际执行过,才能将其作为验证命令写入文章。 -- 除非用户明确要求,否则不要添加目录。 +面向懂 TS 和基础 LLM、刚接触 Agent 工程的开发者。**笔记体**:结论先行,解释从简。 + +- ❌ "我们需要先回答一个不那么显眼、却会影响整个项目的问题" +- ✅ "先确定一件事:Kernel 不认业务概念。" + +- ❌ "经过反复思考和持续调整,我们最终选择了一种简洁优雅的方案" +- ✅ "架构收缩为两个核心概念:Kernel + Extensions。" + +- 每段一个重点,通常不超过三句话。 +- 删除过渡句、重复总结、设问、修饰语。 +- 可用"我们",重心在技术推理。 +- 不用营销语言、泛泛 AI 介绍、夸张结论。 +- 不写散文,不用文学化铺垫。 + +### 格式 + +- 围栏代码块标注语言;标题/列表/代码块/`` 前后空行。 +- 代码尽量短,展示核心思路即可。省略处用 `...` 且确保不会误解。 +- 不自行添加目录。 + +--- + +## 避免 + +- 假设读者读过系列其他文章("如前所述""大家已经知道")。 +- 提前讲完后续文章的主题。 +- 把计划/路线图写成已实现的能力。 +- 流水账式叙述或填充式总结。 +- 为同一概念反复更换中文译名。 + +--- ## 最终检查 -回复用户前确认: +- [ ] 只新增/修改了一篇 `.md` +- [ ] 文件名匹配 `^2026-P[0-9]+-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$` +- [ ] 元信息字段完整,`` 恰好一次 +- [ ] 无占位内容残留 +- [ ] 代码与仓库一致 +- [ ] 回复含文件链接 + 一句话主题说明 -- 只新增或修改了一篇目标 Markdown 文章。 -- 文件名符合 `^2026-P[0-9]+-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$`。 -- 元信息结构有效;除非用户要求,否则只包含本系列规定的字段。 -- 有意义的简介之后恰好出现一次 ``。 -- 没有遗留任何占位内容。 -- 事实陈述和代码片段符合当前仓库状态。 -- 最终回复包含文章文件链接,并用一句话说明文章主题。 +--- + +## 参考:本作者已上线系列的共性特征 + +以下从 Java 专题和 AI 基础专题中提取,是已被验证有效的写作习惯。 + +### 开头 + +一句话交代本篇做什么,不做铺垫: + +- "本篇简单记录常量和变量。" +- "开始之前,我们需要搭建一个基本的项目环境,基于 TypeScript 和 Node.js,且尽量简洁。" + +### 系列衔接 + +可引用前文建立上下文,但只提直接相关的一篇: + +- "上一节我们已经可以和大模型进行连续对话了,这节我们通过简单的例子来演示一下提示词管理的能力。" + +### 代码呈现 + +- 代码是正文主体,文字只是必要说明。 +- 按实现步骤拆分代码块,每块配一行解释。 +- 可在代码块之间穿插截图验证运行结果。 + +### 结尾 + +两种方式,按主题选择: + +- **引出下一篇**:"到此,我们的项目环境就搭建完成了,下一节我们将进行一个简单的对话。" +- **发散点列表**:列出 3-5 个后续可探索的方向,标注为"发散点"而非"计划"。 + ```md + 发散点: + * 上下文长度管理 - 监控上下文的长度,并在超过限制时进行裁剪。 + * 持久化 - 将上下文保存到文件或者数据库中。 + ``` + +### 跨知识引用 + +读者已知的概念直接跳过,不重复解释: + +- "`if` 语句和 `javascript` 完全一致,略过。" +- "和 Express 中间件机制一致,略过。" + +### 语气 + +- 用"我们"但不煽情,重心始终在技术本身。 +- 笔记语气:记录过程、防止遗忘,不假装客观权威。 +- [ ] 无占位内容残留 +- [ ] 代码与仓库一致 +- [ ] 回复含文件链接 + 一句话主题说明 diff --git a/src/extensions/chat.test.ts b/src/extensions/chat.test.ts new file mode 100644 index 0000000..9de3118 --- /dev/null +++ b/src/extensions/chat.test.ts @@ -0,0 +1,103 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, rm, readdir } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { Kernel } from "../kernel"; +import { Hook } from "./catalog"; +import { + createAgentExtension, + type AgentService, +} from "./shared/agent"; +import { createDeepSeekExtension } from "./shared/deepseek"; +import { + createWorkspaceExtension, + type WorkspaceService, +} from "./shared/workspace"; + +test("streams a reply, saves it, and restores the conversation after restart", async (t) => { + const temporaryDirectory = await mkdtemp(join(tmpdir(), "llm-to-agent-")); + const home = join(temporaryDirectory, "home"); + const projectPath = join(temporaryDirectory, "project"); + await mkdir(projectPath); + t.after(() => rm(temporaryDirectory, { recursive: true, force: true })); + + const requests: Array<{ + model: string; + messages: Array<{ role: string; content: string }>; + stream: boolean; + }> = []; + const answers = ["第一次回答", "第二次回答"]; + + const request = async (url: string, init: RequestInit) => { + assert.equal(url, "https://api.deepseek.com/chat/completions"); + assert.equal(init.method, "POST"); + + const body = JSON.parse(String(init.body)); + requests.push(body); + const answer = answers[requests.length - 1] as string; + const middle = Math.ceil(answer.length / 2); + const stream = [ + `data: ${JSON.stringify({ choices: [{ delta: { content: answer.slice(0, middle) } }] })}`, + `data: ${JSON.stringify({ choices: [{ delta: { content: answer.slice(middle) } }] })}`, + "data: [DONE]", + "", + ].join("\n\n"); + + return new Response(stream, { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); + }; + + const firstKernel = new Kernel().use( + createWorkspaceExtension({ home, projectPath }), + createDeepSeekExtension({ apiKey: "test-key", request }), + createAgentExtension(), + ); + + await firstKernel.start(); + + let firstAnswer = ""; + for await (const chunk of firstKernel.get(Hook.Agent).chat("第一次问题")) { + firstAnswer += chunk; + } + + assert.equal(firstAnswer, "第一次回答"); + await firstKernel.stop(); + + const secondKernel = new Kernel().use( + createWorkspaceExtension({ home, projectPath }), + createDeepSeekExtension({ apiKey: "test-key", request }), + createAgentExtension(), + ); + + await secondKernel.start(); + + let secondAnswer = ""; + for await (const chunk of secondKernel.get(Hook.Agent).chat("第二次问题")) { + secondAnswer += chunk; + } + + assert.equal(secondAnswer, "第二次回答"); + assert.deepEqual(requests[1]?.messages, [ + { role: "user", content: "第一次问题" }, + { role: "assistant", content: "第一次回答" }, + { role: "user", content: "第二次问题" }, + ]); + + const workspace = secondKernel.get(Hook.Workspace); + assert.deepEqual( + (await workspace.messages()).map(({ role, content }) => ({ role, content })), + [ + { role: "user", content: "第一次问题" }, + { role: "assistant", content: "第一次回答" }, + { role: "user", content: "第二次问题" }, + { role: "assistant", content: "第二次回答" }, + ], + ); + assert.deepEqual(await readdir(projectPath), []); + + await secondKernel.stop(); +}); diff --git a/src/extensions/cli/index.ts b/src/extensions/cli/index.ts new file mode 100644 index 0000000..6fbc4c8 --- /dev/null +++ b/src/extensions/cli/index.ts @@ -0,0 +1,84 @@ +import { + createInterface, + type Interface as ReadlineInterface, +} from "node:readline/promises"; + +import type { Extension } from "../../kernel"; +import { Event, ExtensionId, Hook } from "../catalog"; +import type { AgentService } from "../shared/agent"; + +export function createCliExtension(): Extension { + let terminal: ReadlineInterface | undefined; + let loop: Promise | undefined; + let currentRequest: AbortController | undefined; + + return { + id: ExtensionId.Cli, + + setup() {}, + + start(context) { + const agent = context.get(Hook.Agent); + terminal = createInterface({ + input: process.stdin, + output: process.stdout, + }); + + terminal.on("SIGINT", () => { + if (currentRequest) { + currentRequest.abort(); + } else { + terminal?.close(); + } + }); + + loop = (async () => { + try { + process.stdout.write( + `llm-to-agent\nWorkspace: ${process.cwd()}\n输入 /exit 退出,Ctrl+C 取消当前回复。\n\n`, + ); + terminal?.setPrompt("> "); + terminal?.prompt(); + + for await (const line of terminal!) { + const input = line.trim(); + + if (input === "/exit") break; + if (!input) { + terminal?.prompt(); + continue; + } + + currentRequest = new AbortController(); + + try { + for await (const chunk of agent.chat(input, currentRequest.signal)) { + process.stdout.write(chunk); + } + process.stdout.write("\n\n"); + } catch (error) { + if (currentRequest.signal.aborted) { + process.stdout.write("\n[已取消]\n\n"); + } else { + process.stderr.write(`\n${String(error)}\n\n`); + } + } finally { + currentRequest = undefined; + } + + terminal?.prompt(); + } + } finally { + terminal?.close(); + await context.emit(Event.RuntimeStopRequested, { source: "cli" }); + } + })(); + }, + + async stop() { + currentRequest?.abort(); + terminal?.close(); + await loop; + }, + }; +} diff --git a/src/products/cli.ts b/src/products/cli.ts index bc757d2..898f578 100644 --- a/src/products/cli.ts +++ b/src/products/cli.ts @@ -1,6 +1,7 @@ +import { createCliExtension } from "../extensions/cli"; import type { ProductDefinition } from "./product"; export const cliProduct: ProductDefinition = { id: "cli", - extensions: [], + extensions: [createCliExtension], }; diff --git a/src/products/shared.ts b/src/products/shared.ts index d7a2c3f..48e7a8d 100644 --- a/src/products/shared.ts +++ b/src/products/shared.ts @@ -1,4 +1,10 @@ import type { ExtensionFactory } from "../kernel"; +import { createAgentExtension } from "../extensions/shared/agent"; +import { createDeepSeekExtension } from "../extensions/shared/deepseek"; +import { createWorkspaceExtension } from "../extensions/shared/workspace"; -// 公共扩展在实现真实能力时加入这里。 -export const sharedExtensions: ExtensionFactory[] = []; +export const sharedExtensions: ExtensionFactory[] = [ + createWorkspaceExtension, + createDeepSeekExtension, + createAgentExtension, +]; From 5d13ae4c3ecab287fd6660857f5501fba9467e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 31 Jul 2026 09:05:17 +0800 Subject: [PATCH 20/24] =?UTF-8?q?del:=20=E5=8E=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E6=97=A7=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- legacy/old/knowledge/llm-to-agent-guide.md | 60 ---------- legacy/old/package.json | 18 --- legacy/old/src/agents/agent.ts | 92 --------------- legacy/old/src/agents/index.ts | 4 - legacy/old/src/agents/manager.ts | 90 --------------- legacy/old/src/agents/tools.ts | 57 --------- legacy/old/src/context.ts | 16 --- legacy/old/src/hooks/index.ts | 23 ---- legacy/old/src/hooks/log.hooks.ts | 30 ----- legacy/old/src/hooks/registry.ts | 29 ----- legacy/old/src/index.ts | 46 -------- legacy/old/src/knowledge/knowledge-base.ts | 128 --------------------- legacy/old/src/knowledge/search-tool.ts | 53 --------- legacy/old/src/llm.ts | 20 ---- legacy/old/src/prompts/orchestrator.ts | 18 --- legacy/old/src/prompts/reAct.ts | 16 --- legacy/old/src/prompts/system.ts | 17 --- legacy/old/src/tools/calculator.ts | 22 ---- legacy/old/src/tools/guess.ts | 20 ---- legacy/old/src/tools/registry.ts | 36 ------ legacy/old/src/tools/weather.ts | 19 --- legacy/old/src/types/index.ts | 14 --- legacy/old/tsconfig.json | 7 -- 23 files changed, 835 deletions(-) delete mode 100644 legacy/old/knowledge/llm-to-agent-guide.md delete mode 100644 legacy/old/package.json delete mode 100644 legacy/old/src/agents/agent.ts delete mode 100644 legacy/old/src/agents/index.ts delete mode 100644 legacy/old/src/agents/manager.ts delete mode 100644 legacy/old/src/agents/tools.ts delete mode 100644 legacy/old/src/context.ts delete mode 100644 legacy/old/src/hooks/index.ts delete mode 100644 legacy/old/src/hooks/log.hooks.ts delete mode 100644 legacy/old/src/hooks/registry.ts delete mode 100644 legacy/old/src/index.ts delete mode 100644 legacy/old/src/knowledge/knowledge-base.ts delete mode 100644 legacy/old/src/knowledge/search-tool.ts delete mode 100644 legacy/old/src/llm.ts delete mode 100644 legacy/old/src/prompts/orchestrator.ts delete mode 100644 legacy/old/src/prompts/reAct.ts delete mode 100644 legacy/old/src/prompts/system.ts delete mode 100644 legacy/old/src/tools/calculator.ts delete mode 100644 legacy/old/src/tools/guess.ts delete mode 100644 legacy/old/src/tools/registry.ts delete mode 100644 legacy/old/src/tools/weather.ts delete mode 100644 legacy/old/src/types/index.ts delete mode 100644 legacy/old/tsconfig.json diff --git a/legacy/old/knowledge/llm-to-agent-guide.md b/legacy/old/knowledge/llm-to-agent-guide.md deleted file mode 100644 index 9a41a5a..0000000 --- a/legacy/old/knowledge/llm-to-agent-guide.md +++ /dev/null @@ -1,60 +0,0 @@ -# LLM-to-Agent 框架知识库 - -## Agent 工具调用流程 - -Agent 的 `run()` 方法执行以下循环: -1. 将用户消息加入上下文 -2. 调用 LLM,传入可用工具列表 -3. 如果 LLM 返回 `tool_calls`,逐个执行工具,将结果加入上下文,回到步骤 2 -4. 如果 LLM 直接返回文本,结束循环,返回最终回复 - -工具定义需包含 `name`、`description`、`parameters`(JSON Schema)和 `execute` 函数。 - -## 子 Agent 管理 - -通过 `spawn_agent` 工具可以创建具有独立人设的子 Agent(如销售、顾客)。 -每个子 Agent 拥有独立的上下文和基础工具集(不含 sub-agent 管理工具,防止无限递归)。 -通过 `run_conversation` 工具可以在两个子 Agent 之间运行多轮对话。 - -## 知识库功能 - -知识库位于 `knowledge/` 目录,支持 `.md` 格式文档。 -文档按 `##` 二级标题自动分块,通过 `search_knowledge` 工具进行关键词检索。 -检索算法基于关键词命中次数,标题命中加权 ×3。 -也提供 `list_knowledge` 工具查看知识库全貌。 - -## 常用命令 - -- 启动框架:`pnpm dev` -- 指定提示词模式:`pnpm dev orchestrator` -- 可用模式:`default`、`toxic`、`json`、`agent`、`reAct`、`orchestrator` - -## 项目结构 - -``` -src/ - index.ts -- CLI 入口 - llm.ts -- LLM 客户端(OpenAI 兼容) - context.ts -- 上下文管理器 - types/index.ts -- 类型定义 - agents/ - agent.ts -- Agent 核心类 - manager.ts -- 子 Agent 管理器 - tools.ts -- spawn_agent / run_conversation 工具 - tools/ - registry.ts -- 工具注册表 - weather.ts -- 天气查询工具 - calculator.ts -- 计算器工具 - guess.ts -- 猜数字游戏工具 - knowledge/ - knowledge-base.ts -- 知识库类 - search-tool.ts -- 知识库搜索工具 - hooks/ - index.ts -- Hook 总线 - log.hooks.ts -- 日志 Hook - registry.ts -- Hook 注册 - prompts/ - system.ts -- 系统提示词 - orchestrator.ts -- Orchestrator 提示词 - reAct.ts -- ReAct 提示词 -``` diff --git a/legacy/old/package.json b/legacy/old/package.json deleted file mode 100644 index 92310d0..0000000 --- a/legacy/old/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@llm-to-agent/old", - "version": "0.2.0", - "type": "module", - "private": true, - "scripts": { - "dev": "tsx src/index.ts" - }, - "dependencies": { - "dotenv": "^16.x", - "openai": "^4.x" - }, - "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.x", - "typescript": "^5.x" - } -} diff --git a/legacy/old/src/agents/agent.ts b/legacy/old/src/agents/agent.ts deleted file mode 100644 index 61f5766..0000000 --- a/legacy/old/src/agents/agent.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ContextManager } from '../context.js'; -import { chat } from '../llm.js'; -import { getTools } from '../tools/registry.js'; -import { Tool } from '../types/index.js'; -import { hooks } from '../hooks/index.js'; - -export interface AgentConfig { - name: string; - systemPrompt: string; - /** 该 Agent 可用的工具列表,默认使用全局注册的所有工具 */ - tools?: Tool[]; -} - -export class Agent { - public name: string; - private context: ContextManager; - private tools: Tool[]; - - constructor(config: AgentConfig) { - this.name = config.name; - this.tools = config.tools ?? getTools(); - this.context = new ContextManager(); - this.context.add({ role: 'system', content: config.systemPrompt }); - } - - /** - * 运行 Agent 循环:发送消息 → 处理 tool_calls → 返回最终回复 - */ - async run(userMessage: string): Promise { - this.context.add({ role: 'user', content: userMessage }); - - const openaiTools = this.tools.length > 0 - ? this.tools.map((t) => ({ - type: 'function' as const, - function: { - name: t.name, - description: t.description, - parameters: t.parameters, - }, - })) - : undefined; - - let reply = await chat(this.context.getMessages(), openaiTools); - - // 工具调用循环 - while (reply.tool_calls && reply.tool_calls.length > 0) { - this.context.add({ - role: 'assistant', - content: '', - tool_calls: reply.tool_calls, - } as any); - - for (const tc of reply.tool_calls) { - const tool = this.tools.find((t) => t.name === tc.function.name); - if (!tool) { - this.context.add({ - role: 'tool', - tool_call_id: tc.id, - content: `错误: 未知工具 ${tc.function.name}`, - } as any); - continue; - } - - hooks.emit('tool:before', { toolCall: tc, agentName: this.name }); - - try { - const args = JSON.parse(tc.function.arguments); - const result = await tool.execute(args); - this.context.add({ - role: 'tool', - tool_call_id: tc.id, - content: result, - } as any); - hooks.emit('tool:after', { toolCall: tc, result, agentName: this.name }); - } catch (e: any) { - this.context.add({ - role: 'tool', - tool_call_id: tc.id, - content: `工具执行错误: ${e.message}`, - } as any); - hooks.emit('tool:after', { toolCall: tc, result: `错误: ${e.message}`, agentName: this.name }); - } - } - - reply = await chat(this.context.getMessages(), openaiTools); - } - - const content = reply.content!; - this.context.add({ role: 'assistant', content }); - return content; - } -} diff --git a/legacy/old/src/agents/index.ts b/legacy/old/src/agents/index.ts deleted file mode 100644 index f2bf247..0000000 --- a/legacy/old/src/agents/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { Agent } from './agent.js'; -export type { AgentConfig } from './agent.js'; -export { SubAgentManager, subAgentManager } from './manager.js'; -export { spawnAgentTool, runConversationTool, subAgentTools } from './tools.js'; diff --git a/legacy/old/src/agents/manager.ts b/legacy/old/src/agents/manager.ts deleted file mode 100644 index 57781ba..0000000 --- a/legacy/old/src/agents/manager.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Agent } from './agent.js'; -import { getTools } from '../tools/registry.js'; - -/** - * SubAgentManager 单例 — 管理所有子 Agent 的创建、查询和多轮对话编排 - */ -export class SubAgentManager { - private agents: Map = new Map(); - - /** - * 创建一个子 Agent(只有普通工具,没有 sub-agent 管理工具,防止无限递归) - */ - spawn(name: string, systemPrompt: string): Agent { - if (this.agents.has(name)) { - throw new Error(`子 Agent "${name}" 已存在`); - } - const agent = new Agent({ - name, - systemPrompt, - tools: getTools(), // 只有全局注册的普通工具 - }); - this.agents.set(name, agent); - return agent; - } - - get(name: string): Agent | undefined { - return this.agents.get(name); - } - - list(): Agent[] { - return Array.from(this.agents.values()); - } - - /** - * 运行两个 Agent 之间的多轮对话 - * @param agent1Name 发起方 Agent 名称 - * @param agent2Name 回应方 Agent 名称 - * @param maxTurns 最大对话轮次(一轮 = agent1 发言 + agent2 回应) - * @param topic 对话主题 - * @returns 完整对话记录 - */ - async runConversation( - agent1Name: string, - agent2Name: string, - maxTurns: number, - topic: string, - ): Promise { - const agent1 = this.agents.get(agent1Name); - const agent2 = this.agents.get(agent2Name); - - if (!agent1) { - return `错误: 子 Agent "${agent1Name}" 不存在。可用的 Agent: ${this.listNames()}`; - } - if (!agent2) { - return `错误: 子 Agent "${agent2Name}" 不存在。可用的 Agent: ${this.listNames()}`; - } - - const transcript: string[] = []; - transcript.push(`=== 对话开始: ${agent1Name} vs ${agent2Name},主题: ${topic},轮次: ${maxTurns} ===\n`); - - // 第一轮:agent1 发起对话 - let currentMessage = `请就以下话题开始对话:${topic}。你是对话的发起方,请先发言。`; - let speaker = agent1; - let listener = agent2; - - for (let turn = 1; turn <= maxTurns; turn++) { - // 当前发言者回复 - const response = await speaker.run(currentMessage); - const line = `[${speaker.name}]: ${response}`; - console.log(line); - transcript.push(line); - - // 将回复传给另一方 - currentMessage = `[${speaker.name}]: ${response}\n请回复。`; - - // 交换发言者 - [speaker, listener] = [listener, speaker]; - } - - transcript.push(`\n=== 对话结束 ===`); - return transcript.join('\n'); - } - - private listNames(): string { - return Array.from(this.agents.keys()).join(', ') || '(无)'; - } -} - -/** 全局单例 */ -export const subAgentManager = new SubAgentManager(); diff --git a/legacy/old/src/agents/tools.ts b/legacy/old/src/agents/tools.ts deleted file mode 100644 index 2d576ce..0000000 --- a/legacy/old/src/agents/tools.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Tool } from '../types/index.js'; -import { subAgentManager } from './manager.js'; - -/** - * spawn_agent — 创建一个指定角色和名称的子 Agent - */ -export const spawnAgentTool: Tool = { - name: 'spawn_agent', - description: '创建一个子Agent,指定其名称和角色/系统提示词。用于创建具有特定人设的对话角色(如销售、顾客等)。', - parameters: { - type: 'object', - properties: { - name: { type: 'string', description: '子Agent的唯一名称,如"sales"、"customer"' }, - role_prompt: { type: 'string', description: '子Agent的角色描述/系统提示词,如"你是一个热情的汽车销售"' }, - }, - required: ['name', 'role_prompt'], - }, - execute: async (args) => { - const { name, role_prompt } = args; - try { - const agent = subAgentManager.spawn(name as string, role_prompt as string); - return `子Agent "${agent.name}" 创建成功。`; - } catch (e: any) { - return `创建失败: ${e.message}`; - } - }, -}; - -/** - * run_conversation — 运行两个子 Agent 之间的多轮对话 - */ -export const runConversationTool: Tool = { - name: 'run_conversation', - description: '在两个已创建的子Agent之间运行多轮对话。一方先发起,另一方回应,交替进行。返回完整对话记录。', - parameters: { - type: 'object', - properties: { - agent1: { type: 'string', description: '发起方Agent名称(先说话的那个)' }, - agent2: { type: 'string', description: '回应方Agent名称' }, - max_turns: { type: 'number', description: '最大对话轮次,如5表示agent1发起 + 4轮交替 = 共5次发言' }, - topic: { type: 'string', description: '对话主题/场景描述,如"汽车购买谈判"' }, - }, - required: ['agent1', 'agent2', 'max_turns', 'topic'], - }, - execute: async (args) => { - const { agent1, agent2, max_turns, topic } = args; - return await subAgentManager.runConversation( - agent1 as string, - agent2 as string, - max_turns as number, - topic as string, - ); - }, -}; - -/** 所有 sub-agent 管理工具(仅供主 Agent 使用) */ -export const subAgentTools: Tool[] = [spawnAgentTool, runConversationTool]; diff --git a/legacy/old/src/context.ts b/legacy/old/src/context.ts deleted file mode 100644 index 9610f4f..0000000 --- a/legacy/old/src/context.ts +++ /dev/null @@ -1,16 +0,0 @@ -type Message = { role: 'system' | 'user' | 'assistant'; content: string }; - -export class ContextManager { - private messages: Message[] = []; - - constructor() { - } - - add(message: Message) { - this.messages.push(message); - } - - getMessages(): Message[] { - return [...this.messages]; - } -} \ No newline at end of file diff --git a/legacy/old/src/hooks/index.ts b/legacy/old/src/hooks/index.ts deleted file mode 100644 index a8c0c21..0000000 --- a/legacy/old/src/hooks/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -type EventName = 'process:start' | 'agent:start' | 'step:before' | 'tool:before' | 'tool:after' | 'agent:end'; -type Listener = (data: any) => void | Promise; - -export class HookBus { - private listeners = new Map(); - - on(event: EventName, fn: Listener) { - if (!this.listeners.has(event)) { - this.listeners.set(event, []); - } - this.listeners.get(event)!.push(fn); - } - - async emit(event: EventName, data: any) { - const fns = this.listeners.get(event); - if (!fns) return; - for (const fn of fns) { - await fn(data); - } - } -} - -export const hooks = new HookBus(); diff --git a/legacy/old/src/hooks/log.hooks.ts b/legacy/old/src/hooks/log.hooks.ts deleted file mode 100644 index 0da9020..0000000 --- a/legacy/old/src/hooks/log.hooks.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { HookBus } from './index.js'; - -export default (hooks: HookBus) => { - // hooks.on('agent:start', async (data) => { - // console.log('🚀 ~ agent:start ~ data:', data); - // }); - - // hooks.on('step:before', async (data) => { - // console.log('🚀 ~ step:before ~ data:', data); - // }); - - hooks.on('tool:before', async (data) => { - const { toolCall: tc, agentName } = data; - const prefix = agentName ? `[${agentName}] ` : ''; - console.log(` ${prefix}[工具调用] ${tc.function.name}(${tc.function.arguments})`); - }); - - hooks.on('tool:after', async (data) => { - const { toolCall: tc, result, agentName } = data; - const prefix = agentName ? `[${agentName}] ` : ''; - console.log(` ${prefix}[工具] ${tc.function.name}(${tc.function.arguments}) -> ${result}`); - }); - - hooks.on('agent:end', async (data) => { - const { reply } = data; - // reply 可能是字符串(Agent.run 返回值)或 OpenAI message 对象 - const content = typeof reply === 'string' ? reply : reply.content; - console.log('助手:', content); - }); -} \ No newline at end of file diff --git a/legacy/old/src/hooks/registry.ts b/legacy/old/src/hooks/registry.ts deleted file mode 100644 index 4ecc961..0000000 --- a/legacy/old/src/hooks/registry.ts +++ /dev/null @@ -1,29 +0,0 @@ -// 从内置hooks目录中获取所有hooks并注册到HookBus - -import path from 'node:path'; -import fs from 'node:fs'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { hooks } from './index.js'; - -// 这里可以自动扫描hooks目录下的所有文件并导入它们,假设每个文件都默认导出一个函数来注册hook - -const getAllHooks = async () => { - // 动态读取hooks目录下的所有 .hooks.ts 结尾的文件 - const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const hooksDir = path.join(__dirname); // hooks 目录即当前目录 - const hookFiles = fs.readdirSync(hooksDir).filter(file => file.endsWith('.hooks.ts')); - return Promise.all( - hookFiles.map(async (file) => { - const mod = await import(pathToFileURL(path.join(hooksDir, file)).href); - return mod.default; - }) - ); -} - -export const registerHooks = async () => { - const hookModules = await getAllHooks(); - for (const hookModule of hookModules) { - // 每个hook模块默认导出一个函数,调用它并传入hooks实例 - hookModule(hooks); - } -} \ No newline at end of file diff --git a/legacy/old/src/index.ts b/legacy/old/src/index.ts deleted file mode 100644 index 560954b..0000000 --- a/legacy/old/src/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Agent } from './agents/index.js'; -import { getOrchestratorTools } from './tools/registry.js'; -import { PROMPTS } from './prompts/system.js'; -import * as readline from 'node:readline/promises'; -import { hooks } from './hooks/index.js'; -import { registerHooks } from './hooks/registry.js'; -import { knowledgeBase } from './knowledge/knowledge-base.js'; - -await registerHooks(); -await knowledgeBase.load(); - -const promptName = process.argv[2] || 'orchestrator'; -const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; - -if (!systemPrompt) { - console.error(`未知提示词: ${promptName},可选: ${Object.keys(PROMPTS).join(', ')}`); - process.exit(1); -} - -// 主 orchestrator Agent,拥有全部工具(包括 spawn_agent / run_conversation) -const mainAgent = new Agent({ - name: 'Orchestrator', - systemPrompt, - tools: getOrchestratorTools(), -}); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, -}); - -console.log(`提示词模式: ${promptName},输入 "exit" 退出。\n`); - -while (true) { - const userInput = await rl.question('我: '); - if (userInput.toLowerCase() === 'exit') break; - - hooks.emit('agent:start', { userInput }); - - const reply = await mainAgent.run(userInput); - - hooks.emit('agent:end', { userInput, reply }); - // agent:end hook 会打印回复,这里不需要重复打印 -} - -rl.close(); diff --git a/legacy/old/src/knowledge/knowledge-base.ts b/legacy/old/src/knowledge/knowledge-base.ts deleted file mode 100644 index afd6b64..0000000 --- a/legacy/old/src/knowledge/knowledge-base.ts +++ /dev/null @@ -1,128 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; - -/** 知识库中的一个文档块 */ -interface Chunk { - /** 来源文件名 */ - source: string; - /** 块内文本 */ - content: string; -} - -/** - * 轻量知识库:从 knowledge/ 目录加载 .md 文件,按 ## 标题分块, - * 提供基于关键词匹配的检索能力。 - * - * 用法: - * const kb = new KnowledgeBase('./knowledge'); - * await kb.load(); - * const results = kb.search('Agent 工具调用'); - */ -export class KnowledgeBase { - private chunks: Chunk[] = []; - private knowledgeDir: string; - - constructor(knowledgeDir: string) { - this.knowledgeDir = knowledgeDir; - } - - /** 加载 knowledgeDir 下所有 .md 文件并分块 */ - async load(): Promise { - this.chunks = []; - - if (!fs.existsSync(this.knowledgeDir)) { - console.warn(`知识库目录不存在: ${this.knowledgeDir}`); - return; - } - - const files = fs - .readdirSync(this.knowledgeDir) - .filter((f) => f.endsWith('.md')); - - for (const file of files) { - const filePath = path.join(this.knowledgeDir, file); - const raw = fs.readFileSync(filePath, 'utf-8'); - const fileChunks = this.splitChunks(raw, file); - this.chunks.push(...fileChunks); - } - - console.log(`知识库已加载: ${this.chunks.length} 个块,来自 ${files.length} 个文件`); - } - - /** 按 ## 标题将文档拆分为块 */ - private splitChunks(raw: string, source: string): Chunk[] { - const blocks = raw.split(/(?=^## )/m); - return blocks - .map((b) => b.trim()) - .filter(Boolean) - .map((content) => ({ source, content })); - } - - /** - * 基于关键词匹配搜索,返回相关块(按相关性降序) - * - * 算法:将查询分词,统计每个块命中关键词的次数, - * 同时给标题匹配额外加权。 - */ - search(query: string, topK: number = 3): Chunk[] { - const keywords = this.tokenize(query); - if (keywords.length === 0) return []; - - const scored = this.chunks.map((chunk) => { - const lower = chunk.content.toLowerCase(); - let score = 0; - for (const kw of keywords) { - // 标题行命中加权 ×3 - const headlineRegex = /^## .+$/gm; - let match: RegExpExecArray | null; - while ((match = headlineRegex.exec(chunk.content)) !== null) { - if (match[0].toLowerCase().includes(kw)) { - score += 3; - } - } - // 正文命中 - const count = (lower.match(new RegExp(this.escapeRegex(kw), 'gi')) || []).length; - score += count; - } - // 标题匹配额外加分 - return { chunk, score }; - }); - - return scored - .filter((s) => s.score > 0) - .sort((a, b) => b.score - a.score) - .slice(0, topK) - .map((s) => s.chunk); - } - - /** 中文 + 英文简单分词 */ - private tokenize(text: string): string[] { - // 按空白/标点拆分,过滤长度 ≤1 的词 - return text - .split(/[\s,,。.!!??::;;、]+/) - .map((t) => t.toLowerCase().trim()) - .filter((t) => t.length > 1); - } - - private escapeRegex(s: string): string { - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - } - - /** 获取知识库摘要(供 Agent 概览) */ - summary(): string { - if (this.chunks.length === 0) return '知识库为空'; - const sources = [...new Set(this.chunks.map((c) => c.source))]; - const titles = this.chunks - .map((c) => { - const m = c.content.match(/^## (.+)$/m); - return m ? ` - ${m[1]} (${c.source})` : null; - }) - .filter(Boolean); - return `知识库包含 ${sources.length} 个文档:\n${titles.join('\n')}`; - } -} - -/** 全局单例 */ -export const knowledgeBase = new KnowledgeBase( - path.join(process.cwd(), 'knowledge'), -); diff --git a/legacy/old/src/knowledge/search-tool.ts b/legacy/old/src/knowledge/search-tool.ts deleted file mode 100644 index 8d4cdc8..0000000 --- a/legacy/old/src/knowledge/search-tool.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Tool } from '../types/index.js'; -import { knowledgeBase } from './knowledge-base.js'; - -/** - * search_knowledge — 在知识库中检索相关信息 - */ -export const searchKnowledgeTool: Tool = { - name: 'search_knowledge', - description: - '在本地知识库中搜索与查询相关的文档片段。当你需要查找项目文档、技术说明、业务规则等存储在知识库中的信息时使用此工具。', - parameters: { - type: 'object', - properties: { - query: { - type: 'string', - description: '搜索关键词或问题,如"Agent 工具调用流程"、"如何创建子Agent"', - }, - }, - required: ['query'], - }, - execute: async (args) => { - const { query } = args; - const results = knowledgeBase.search(query as string, 3); - if (results.length === 0) { - return `未找到与 "${query}" 相关的知识。当前知识库摘要:\n${knowledgeBase.summary()}`; - } - return results - .map( - (r, i) => - `--- 结果 ${i + 1} (来源: ${r.source}) ---\n${r.content}`, - ) - .join('\n\n'); - }, -}; - -/** - * list_knowledge — 列出知识库中所有文档和章节 - */ -export const listKnowledgeTool: Tool = { - name: 'list_knowledge', - description: '列出知识库中所有文档及其章节标题,用于了解知识库包含哪些内容。', - parameters: { - type: 'object', - properties: {}, - required: [], - }, - execute: async () => { - return knowledgeBase.summary(); - }, -}; - -/** 知识库相关工具集 */ -export const knowledgeTools: Tool[] = [searchKnowledgeTool, listKnowledgeTool]; diff --git a/legacy/old/src/llm.ts b/legacy/old/src/llm.ts deleted file mode 100644 index 8409ffa..0000000 --- a/legacy/old/src/llm.ts +++ /dev/null @@ -1,20 +0,0 @@ -import OpenAI from 'openai'; -import 'dotenv/config'; - -const client = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, - baseURL: process.env.OPENAI_BASE_URL, -}); - -export async function chat( - messages: { role: string; content: string }[], - tools?: any[] // OpenAI 格式的工具定义数组 -) { - const response = await client.chat.completions.create({ - model: 'deepseek-v4-pro', - messages: messages as any, - temperature: 0.7, - ...(tools && { tools }), - }); - return response.choices[0]!.message!; -} \ No newline at end of file diff --git a/legacy/old/src/prompts/orchestrator.ts b/legacy/old/src/prompts/orchestrator.ts deleted file mode 100644 index 993ed8b..0000000 --- a/legacy/old/src/prompts/orchestrator.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const ORCHESTRATOR_PROMPT = `你是一个智能编排助手,能够创建子Agent并编排它们之间的多轮对话。 - -## 你拥有的工具 -1. **spawn_agent** — 创建一个子Agent,需要指定名称和角色描述。用于根据用户需求创建具有特定人设的角色(如销售、顾客、谈判者等)。 -2. **run_conversation** — 在两个已创建的子Agent之间运行多轮对话。需要指定发起方、回应方、对话轮次和主题。 - -## 工作流程 -当用户要求创建Agent并进行对话时: -1. 分析用户需求,确定需要哪些角色 -2. 使用 spawn_agent 分别创建每个子Agent,为它们编写合适的角色描述/系统提示词 -3. 使用 run_conversation 运行对话,设定合理的轮次和主题 -4. 对话结束后,根据对话记录进行简要总结 - -## 重要规则 -- 子Agent的角色提示词要具体、生动,包含角色背景、性格特点和目标 -- 对话轮次根据用户要求设定,默认5-10轮 -- 总结时聚焦关键转折点、各方策略和最终结果 -- 使用中文回复`; diff --git a/legacy/old/src/prompts/reAct.ts b/legacy/old/src/prompts/reAct.ts deleted file mode 100644 index ad64baf..0000000 --- a/legacy/old/src/prompts/reAct.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const REACT_SYSTEM_PROMPT = `你是一个自主智能体,能够使用工具来完成目标。 - -## 工作方式 -你需要反复执行以下步骤,直到目标完成: - -1. **思考**:分析当前状态,决定下一步行动。 -2. **行动**:调用一个工具,或者给出最终答案。 - -## 行动格式 -- 如果需要调用工具,只返回工具调用的 JSON,不要其他内容。 - -## 重要规则 -- 如果工具返回了错误,分析错误并尝试修复,不要重复相同的错误调用。 -- 如果连续三次调用没有进展,给出最终答案并说明遇到困难。 -- 诚实:如果无法完成,直接说明,不要编造。 -- 使用中文回复。`; \ No newline at end of file diff --git a/legacy/old/src/prompts/system.ts b/legacy/old/src/prompts/system.ts deleted file mode 100644 index 29c6e81..0000000 --- a/legacy/old/src/prompts/system.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { REACT_SYSTEM_PROMPT } from './reAct.js'; -import { ORCHESTRATOR_PROMPT } from './orchestrator.js'; - -export const PROMPTS = { - default: '你是一个直爽的代码审查员,回答尽量简洁。', - toxic: '你是一个毒舌代码审查员,用讽刺的语气表达。', - json: `你是一个 API 格式化助手。你的回答必须是纯 JSON,不要加任何解释。 -{ - "answer": "你的回答", - "confidence": 0.0-1.0 之间的数字 -}`, - agent: `你是一个有工具调用能力的助手。当需要查询信息/执行计算或者玩猜谜游戏时,请使用提供的工具。 -如果调用了工具,根据工具执行结果给出答案。 -如果不需要工具,直接回答即可。回答简洁。`, - reAct: REACT_SYSTEM_PROMPT, - orchestrator: ORCHESTRATOR_PROMPT, -} as const; \ No newline at end of file diff --git a/legacy/old/src/tools/calculator.ts b/legacy/old/src/tools/calculator.ts deleted file mode 100644 index fb22ec0..0000000 --- a/legacy/old/src/tools/calculator.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Tool } from '../types/index.js'; - -export const calculatorTool: Tool = { - name: 'calculate', - description: '执行数学计算,支持加减乘除和括号', - parameters: { - type: 'object', - properties: { - expression: { type: 'string', description: '数学表达式,如"2+3*4"' }, - }, - required: ['expression'], - }, - execute: async (args) => { - try { - // 安全警告:生产环境绝不可以用 eval - const result = eval(args.expression); - return `${args.expression} = ${result}`; - } catch (e: any) { - return `计算错误: ${e.message}`; - } - }, -}; \ No newline at end of file diff --git a/legacy/old/src/tools/guess.ts b/legacy/old/src/tools/guess.ts deleted file mode 100644 index 49088ca..0000000 --- a/legacy/old/src/tools/guess.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Tool } from '../types/index.js'; - -export const guessTool: Tool = { - name: 'guess_number', - description: '猜一个1到100之间的整数。返回“大了”、“小了”或“猜对了”。', - parameters: { - type: 'object', - properties: { - number: { type: 'number', description: '你猜的数字' }, - }, - required: ['number'], - }, - execute: async (args) => { - // 答案写死在工具内部,模型绝对不知道 - const answer = 67; - const guess = args.number; - if (guess === answer) return '猜对了!'; - return guess > answer ? '大了' : '小了'; - }, -}; \ No newline at end of file diff --git a/legacy/old/src/tools/registry.ts b/legacy/old/src/tools/registry.ts deleted file mode 100644 index 381c425..0000000 --- a/legacy/old/src/tools/registry.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { weatherTool } from './weather.js'; -import { calculatorTool } from './calculator.js'; -import { guessTool } from './guess.js'; -import { Tool } from '../types/index.js'; -import { subAgentTools } from '../agents/tools.js'; -import { knowledgeTools } from '../knowledge/search-tool.js'; - -const baseTools: Tool[] = [weatherTool, calculatorTool, guessTool, ...knowledgeTools]; - -/** 所有工具(基础工具 + sub-agent 管理工具),供主 orchestrator Agent 使用 */ -const allTools: Tool[] = [...baseTools, ...subAgentTools]; - -/** 返回基础工具列表(供子 Agent 使用,不含 sub-agent 管理工具以防递归) */ -export function getTools(): Tool[] { - return baseTools; -} - -/** 返回全部工具列表(供主 orchestrator Agent 使用) */ -export function getOrchestratorTools(): Tool[] { - return allTools; -} - -export function getOpenAITools() { - return baseTools.map((t) => ({ - type: 'function' as const, - function: { - name: t.name, - description: t.description, - parameters: t.parameters, - }, - })); -} - -export function findTool(name: string): Tool | undefined { - return allTools.find((t) => t.name === name); -} \ No newline at end of file diff --git a/legacy/old/src/tools/weather.ts b/legacy/old/src/tools/weather.ts deleted file mode 100644 index 3dcf11b..0000000 --- a/legacy/old/src/tools/weather.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Tool } from '../types/index.js'; - -export const weatherTool: Tool = { - name: 'get_weather', - description: '获取指定城市的当前天气信息', - parameters: { - type: 'object', - properties: { - city: { type: 'string', description: '城市名称,如"北京"、"上海"' }, - }, - required: ['city'], - }, - execute: async (args) => { - // 模拟异步 API 调用 - const weathers = ['晴', '多云', '小雨', '阴天']; - const picked = weathers[Math.floor(Math.random() * weathers.length)]; - return `城市:${args.city},天气:${picked},温度:${Math.floor(Math.random() * 15 + 15)}°C`; - }, -}; \ No newline at end of file diff --git a/legacy/old/src/types/index.ts b/legacy/old/src/types/index.ts deleted file mode 100644 index 1c30214..0000000 --- a/legacy/old/src/types/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface Tool { - name: string; - description: string; - parameters: { - type: 'object'; - properties: Record; - required: string[]; - }; - execute: (args: Record) => Promise | string; -} \ No newline at end of file diff --git a/legacy/old/tsconfig.json b/legacy/old/tsconfig.json deleted file mode 100644 index f27ff83..0000000 --- a/legacy/old/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"] - } -} From d4b67283268144467ee4b935d3e25b0e6b12a436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 31 Jul 2026 15:45:50 +0800 Subject: [PATCH 21/24] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0write=20skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/write-agent-blog/SKILL.md | 29 ++++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/.agents/skills/write-agent-blog/SKILL.md b/.agents/skills/write-agent-blog/SKILL.md index bcd5e6c..0b71133 100644 --- a/.agents/skills/write-agent-blog/SKILL.md +++ b/.agents/skills/write-agent-blog/SKILL.md @@ -5,7 +5,7 @@ description: 为 llm-to-agent 的《Agent进阶专题》系列创作中文文章 # 创作 Agent 系列博客 -为《Agent进阶专题》输出一篇 Hexo Markdown 文章到 `blog/` 下。**源码是唯一事实来源,只写已实现的行为。** +为《Agent进阶专题》输出一篇 Hexo Markdown 文章到 `/Users/liyanyan/study/hexo-blog/source/_posts/agent/`。**源码是唯一事实来源,只写已实现的行为。** --- @@ -14,9 +14,9 @@ description: 为 llm-to-agent 的《Agent进阶专题》系列创作中文文章 ### 工作流程 1. `git rev-parse --show-toplevel` 定位项目根目录。 -2. 用 `git show` / `git diff` 读取目标变更;读已有 `blog/*.md` 确定编号、术语、避免重复。 +2. 用 `git show` / `git diff` 读取目标变更;读取 `/Users/liyanyan/study/hexo-blog/source/_posts/agent/*.md` 确定编号、术语、避免重复。 3. 确定:阶段(P0/P1/P2)、阶段内序号、中文标题、英文 slug。 -4. 只新建一个 `blog/2026-PX-0X-slug.md`,不覆盖已有文件。 +4. 只在 `/Users/liyanyan/study/hexo-blog/source/_posts/agent/` 新建一个 `2026-PX-0X-slug.md`,不覆盖已有文件。 ### 文件名与元信息 @@ -71,6 +71,25 @@ permalink: /YYYY/agent/slug/ - 导读/概念类 ~1000 中文字符;实现类按需,讲清即止。 - 2-4 个短章节即可,不为完整而堆章节。 +### Extension 实现类文章 + +默认按以下主线组织: + +```text +能力介绍 → 实现思路 → setup 行为 → start 行为 → stop 行为 → 抓手 → 下节引子 +``` + +- 这是写作顺序,不是固定模板;根据源码实际行为扩展、合并或删除小节。 +- 没有 `start` / `stop` 时直接省略,不创建空章节。 +- “能力介绍”说明当前插件解决什么问题、提供什么结果,以文字为主,不在这里罗列接口和方法。 +- “实现思路”说明数据模型、关键取舍和整体流程,以文字为主;路径、数据结构、内部方法等细节不要各自拆节。 +- 代码尽量集中在 `setup`、`start`、`stop` 和“抓手”中。前两节只在没有代码就难以说清时放一段短示意。 +- “抓手”专门说明当前插件**对外暴露**的命名能力。先用“对外暴露一个名为 `xxx` 的能力,它的契约是 `XxxService`”点题,再展示契约并逐项解释其中的字段和方法。 +- 不重复介绍所有插件通用的注册、获取和消费方式;除非某个插件的连接方式确有特殊之处,否则省略 `context.add/get` 等样板代码。 +- 解释契约时只写当前文章已经建立的概念。某个字段或方法涉及后续主题时,只说明它现在提供的结果,不点名尚未介绍的插件,也不展开后续流程。 +- 当前插件没有对外能力时,省略“抓手”,不要为了凑结构扩展概念。 +- 下节引子只承接一个最自然的后续能力。 + ### 叙事方式 - 从读者能理解的场景切入,不假定读者读过前文。 @@ -143,8 +162,8 @@ permalink: /YYYY/agent/slug/ ### 代码呈现 -- 代码是正文主体,文字只是必要说明。 -- 按实现步骤拆分代码块,每块配一行解释。 +- 实现类文章中,前两节以文字为主,代码集中到生命周期与“抓手”。 +- 按生命周期或对外能力拆分代码块,每块配一行解释。 - 可在代码块之间穿插截图验证运行结果。 ### 结尾 From f728132c8166a5dcbb23e4dd9a6f091ac05ea07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Tue, 4 Aug 2026 15:43:55 +0800 Subject: [PATCH 22/24] =?UTF-8?q?feat:=20=E6=8B=86=E5=88=86=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD=EF=BC=9B=E6=8B=93?= =?UTF-8?q?=E5=B1=95=E8=BE=93=E5=85=A5=E5=91=BD=E4=BB=A4=EF=BC=9B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=96=87=E6=A1=A3=E8=A7=84=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/architecture.md | 64 ++++++++---- docs/data-layout.md | 49 +++++---- docs/extensions.md | 126 +++++++++++++---------- docs/roadmap.md | 45 ++++---- docs/source-layout.md | 25 +++-- src/extensions/chat.test.ts | 43 ++++++++ src/extensions/cli/README.md | 2 +- src/extensions/cli/index.ts | 51 ++++++++- src/extensions/cli/input.test.ts | 26 +++++ src/extensions/cli/input.ts | 19 ++++ src/extensions/shared/workspace/index.ts | 97 ++++++++++++----- 12 files changed, 395 insertions(+), 156 deletions(-) create mode 100644 src/extensions/cli/input.test.ts create mode 100644 src/extensions/cli/input.ts diff --git a/README.md b/README.md index d173b6d..4c7c210 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,12 @@ pnpm dev 其中设置 `DEEPSEEK_API_KEY`; 也可用 `LLM_TO_AGENT_HOME` 修改数据根目录,用 `DEEPSEEK_MODEL` 修改模型。 +CLI 支持 `/new` 新建对话、`/switch ` 切换对话、`/history` 查看当前对话、`/exit` 退出;输入 `//` 可以发送以 `/` 开头的普通消息。 + 文档: - [架构总览](docs/architecture.md) - [源码目录](docs/source-layout.md) - [数据目录](docs/data-layout.md) -- [Extension 目录](docs/extensions.md) +- [Extension 规划](docs/extensions.md) - [路线图](docs/roadmap.md) diff --git a/docs/architecture.md b/docs/architecture.md index 21115f6..3b29ab1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,53 +2,81 @@ ## 目标 -llm-to-agent 是个人项目,架构优先级是: +llm-to-agent 是个人使用、本地优先的 Agent。架构优先级是: ```text -简单易懂 > 容易修改 > 容易扩展 > 通用性 +简单易懂 > 容易扩展和修改 > 架构完整 > 通用性 ``` -Runtime 内部长期只有: +Runtime 长期保持: ```text Kernel + Extensions ``` -Kernel 管“Extension 怎样连接和运行”,Extension 管“系统具体能做什么”。 +Kernel 管 Extension 怎样连接和运行,Extension 管系统具体能做什么。 ## Kernel -第一版 Kernel 只保留几件直观的事: +Kernel 只提供所有 Extension 共用的运行机制: - 按顺序装入 Extension; - 依次执行 `setup` 和 `start`,停止时倒序执行 `stop`; - 通过命名抓手提供和取得能力; - 通过命名事件发布和监听事实。 -抓手和事件暂时使用带命名空间的字符串,不为它们建立复杂的 TypeScript 类型系统。Setup Context 负责注册,Runtime Context 负责使用;Kernel 信任 Extension 遵守生命周期,不增加 Context 失效检查和不可变包装。 - -Kernel 不认识 Workspace、Conversation、Run、Agent、模型或 Tool。只有所有 Extension 都必须遵守的运行规则才进入 Kernel。 +Kernel 不认识 Workspace、Agent、模型、Tool 或其他业务概念。 ## Extensions -Extension 可以提供能力、调用其他能力、添加同类实现、发布或监听事实,并管理自己的数据与资源。 +Extension 是 Runtime 的独立装配单位,分为三类: -控制流程必须使用明确调用;事件只表示已经发生的事实,不能承担顺序、返回值、审批或回滚。 +- 共享能力:拥有明确状态、资源或业务流程; +- Provider:接入可替换的模型、协议或外部系统; +- 产品入口:处理 CLI、Web、Desktop 的输入、输出和平台资源。 -Extension 之间只依赖公开契约,不引用彼此的实现。 +规划可以列出尚未实现的 Extension,源码、Catalog 和产品装配只包含已经开始实现的部分。Extension 内部文件和目录不设统一结构。 -具体能力归属见 [Extension 目录](extensions.md)。 +最终能力归属见 [Extension 规划](extensions.md)。 + +## 扩展点 + +多个实现向能力所有者登记,不再为扩展点增加新的架构层: + +```text +models.providers +tools.providers +agent.contextContributors +automation.triggers +security.approvalChannels +health.checks +``` + +模型 Provider 由 `models` 选择,具体 Tool 由 `tools` 统一执行,上下文来源由 `agent` 组合。 + +## 协作 + +- 控制流程使用明确调用; +- 事件只表示已经发生的事实; +- Extension 只依赖其他 Extension 的公开契约; +- Provider 向扩展点注册实现,能力所有者不引用 Provider 实现; +- Tool 是能力 Extension 暴露给 Agent 的操作,不是新的 Extension; +- 高风险操作统一经过 `security`,凭据统一通过 `secrets` 的受控引用取得。 + +事件不能承担顺序、返回值、审批、事务或回滚。 ## Runtime 与产品 -Runtime 持有唯一一套 Kernel、Extension 实例和本地数据。CLI、Web、Desktop 是不同产品入口,使用同一套能力和状态。 +Runtime 持有一套 Kernel、Extension 实例和本地数据。CLI、Web、Desktop 是不同产品入口,共用共享能力和数据。 -自进化阶段再增加 Runtime 外的 Supervisor,用于版本切换、健康检查和失败回退。 +版本切换、进程守护、离线恢复和失败回退由 Runtime 外的 Supervisor 负责。 ## 长期约束 -- 默认在现有 Extension 内增加普通代码,出现真实独立边界后再拆。 -- 第一版静态装配,不建设插件平台、热加载或依赖图。 -- 保持单进程、单用户和本地优先,直到真实需求要求改变。 +- 保持单用户、本地优先和单 Runtime; +- 保持一个 TypeScript package,直到出现独立构建、发布或进程边界; +- 使用静态装配,不提前建设插件平台、热加载或依赖图; +- 规划高风险边界,但不创建占位目录、文件、ID 或 Hook; +- 普通函数、页面、命令、Parser、Prompt 和单个 Tool 不拆成 Extension; +- 新增 Kernel 机制必须有多个具体使用者; - 不提前建设 Command Bus、Middleware、事件溯源或分布式状态。 -- 新增 Kernel 机制必须有多个具体使用者。 diff --git a/docs/data-layout.md b/docs/data-layout.md index 67f4861..5b1b869 100644 --- a/docs/data-layout.md +++ b/docs/data-layout.md @@ -8,7 +8,7 @@ ~/.llm-to-agent/ ``` -开发环境可以指向另一个用户目录,测试使用临时目录。 +开发环境可以指定其他数据根,测试使用临时目录。 ## 物理目录 @@ -17,40 +17,51 @@ ├── config/ ├── workspaces/ │ └── / +│ ├── workspace.json │ ├── files/ │ ├── conversations/ -│ ├── runs/ +│ ├── artifacts/ │ └── extensions/ +│ └── / ├── extensions/ +│ └── / ├── releases/ ├── runtime/ └── trash/ ``` -目录按需要创建,不规定尚未实现的数据文件和格式。 +目录按需要创建,不预建尚未使用的层级。 -当前对话链路只会创建 Workspace 绑定信息和 -`conversations/default/messages.jsonl`,后续能力再增加自己的数据。 +## 数据归属 + +- `workspace` 管理 Workspace、Conversation、Message 和 Artifact; +- `agent` 管理 Agent Run; +- `tasks` 管理 Goal、Plan 和任务执行状态; +- `memory` 与 `knowledge` 分别管理自己的长期数据和索引; +- `automation` 管理 Workflow、Schedule 和执行历史; +- `security` 管理审批和安全记录; +- 每个 Extension 只能通过公开契约访问其他 Extension 的数据; +- 每个数据所有者负责自己的迁移、校验、导出和清理。 + +Workspace 范围的私有数据写入 `workspaces//extensions//`,全局私有数据写入 `extensions//`。 + +凭据明文不写入数据根,由 `secrets` 使用系统 Keychain 或受控加密存储管理。 ## Workspace -- `managed` Workspace 的工作文件位于自己的 `files/`。 -- `linked` Workspace 只记录用户已有目录的绑定关系。 -- 两种 Workspace 的 Conversation、Run、记忆和 Extension 数据都位于数据根目录。 +- `managed` Workspace 的工作文件位于自己的 `files/`; +- `linked` Workspace 只记录用户已有目录的绑定关系; +- 产品入口各自管理当前选择的 Workspace 和 Conversation; +- 删除 linked Workspace 绝不能删除外部项目源码。 ## 项目目录零落地 -绑定外部项目时,项目源码保留在原位置,所有 llm-to-agent 专属数据仍保存在: - -```text -/workspaces// -``` - -项目中不创建 `.llm-to-agent/` 或其他专属元数据。项目移动时更新绑定关系;删除 linked Workspace 绝不能删除外部项目源码。 +绑定外部项目时,项目源码保留在原位置,所有 llm-to-agent 专属数据仍保存在数据根中。项目中不创建 `.llm-to-agent/` 或其他专属元数据。 ## 数据规则 -- Kernel 只提供安全路径、隔离和基础写入机制,不理解业务数据。 -- 每个 Extension 管理自己的数据,不能直接修改其他 Extension 的私有内容。 -- 缓存和临时数据必须可以清理重建。 -- 删除重要数据默认先进入 `trash/`,迁移和修复前先保留可恢复副本。 +- Runtime 公共库只提供安全路径、原子写入和基础存储工具,不理解业务数据; +- 缓存、索引和临时数据必须可以清理重建; +- 删除重要数据默认先进入 `trash/`; +- 迁移、修复和恢复前保留可恢复副本; +- 全局备份、离线恢复和版本回退由 Supervisor 协调。 diff --git a/docs/extensions.md b/docs/extensions.md index 6d82603..669607c 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -1,66 +1,78 @@ -# Extension 目录 +# Extension 规划 -具体能力全部由 Extension 实现。Tool、页面、命令和事件监听器只是 Extension 内部组成,不是新的架构类型。 +## 共享能力 -## 第一版 +| Extension | 职责 | 公开能力与 Tool | +|---|---|---| +| `workspace` | Workspace、Conversation、Message、Artifact | `WorkspaceService`;默认不提供 Tool | +| `models` | 模型注册、选择、流式调用、Embedding 和 Fallback | `ModelService`;接收 `models.providers` | +| `tools` | Tool 注册、Schema 校验、执行、取消和安全拦截 | `ToolService`;可提供 `tools.search`、`tools.describe` | +| `agent` | 一次 Agent Run、上下文构建、模型与 Tool 循环 | `AgentService`;接收 `agent.contextContributors` | +| `tasks` | Goal、Plan、子 Agent、任务依赖、等待和恢复 | `TaskService`;`tasks.update_plan`、`tasks.delegate`、`tasks.wait` | +| `memory` | 用户偏好、事实、经验和长期记忆 | `MemoryService`;`memory.recall`、`memory.propose`、`memory.forget` | +| `knowledge` | 知识来源、索引、检索和引用 | `KnowledgeService`;`knowledge.search`、`knowledge.open_source` | +| `project` | 项目路径、文件、搜索、Patch、项目识别和验证 | `ProjectService`;`project.read`、`project.search`、`project.apply_patch`、`project.run_checks` | +| `shell` | 命令、PTY、输出流、超时和取消 | `ShellService`;`shell.run`、`shell.read`、`shell.cancel` | +| `git` | Repository、Diff、分支、Commit 和 Worktree | `GitService`;`git.status`、`git.diff`、`git.log`、`git.create_worktree` | +| `automation` | Workflow、Scheduler、提醒、重试和执行历史 | `AutomationService`;`automation.create`、`automation.schedule`、`automation.pause`、`automation.run_now` | +| `security` | 风险规则、授权、审批和安全记录 | `SecurityService`;接收 `security.approvalChannels`,不提供 Tool | +| `secrets` | Keychain、凭据引用、授权范围和轮换 | `SecretsService`;不向模型提供明文凭据 | +| `browser` | 浏览器 Session 和页面操作 | `BrowserService`;`browser.open`、`browser.read`、`browser.click`、`browser.type` | +| `computer` | 屏幕、窗口、应用、键鼠和剪贴板 | `ComputerService`;`computer.screenshot`、`computer.open_app`、`computer.click`、`computer.type` | +| `evolution` | 自身修改候选、验证和发布提案 | `EvolutionService`;`evolution.prepare`、`evolution.validate` | + +`browser`、`computer` 和 `evolution` 在实现对应能力时创建。 + +## Provider + +- 模型 Provider:`deepseek` 以及实际接入的其他模型; +- Tool Provider:GitHub、MCP 和其他实际接入的外部系统; +- Provider 管理自己的协议、配置、资源和故障,向所属扩展点注册实现; +- 一个 Provider 可以提供多个 Tool,一个 Tool 不对应一个 Extension。 + +## 产品入口 + +- `cli`:终端输入、命令、展示和审批通道; +- `web`:HTTP、WebSocket、页面和 Web 会话; +- `desktop`:窗口、快捷键、通知和原生平台集成。 + +产品入口不拥有共享业务能力。 + +## 协作关系 ```text -src/extensions/ -├── catalog.ts -├── shared/ -│ ├── workspace/ -│ ├── agent/ -│ └── deepseek/ -└── cli/ +模型 Provider ────────────────> models + +workspace / memory / knowledge ─> agent.contextContributors + +project / shell / git +browser / computer / 外部 Provider ─> tools.providers + +tasks ────────────────> agent +automation ───────────> tasks / agent / tools +agent ────────────────> workspace / models / tools / memory / knowledge +tools ────────────────> security +外部 Provider ────────> secrets +evolution ────────────> project / shell / git + +cli / web / desktop ──> workspace / agent / tasks / automation / security ``` -- `workspace`:Workspace、Conversation 和消息。 -- `agent`:Run、上下文和 Agent 执行。 -- `deepseek`:DeepSeek 模型接入。 -- `cli`:终端输入、命令和展示。 +## 不单独拆分 -第一版只静态装配这四个 Extension。 +- Conversation、Message、Artifact 归 `workspace`; +- Context、Agent Profile 和声明式 Skill 归 `agent`; +- Workflow、Scheduler 和 Notification 请求归 `automation`; +- Policy、Approval 和 Audit 归 `security`; +- 项目文件归 `project`,进程归 `shell`,仓库归 `git`; +- Run 由实际执行者拥有,不创建通用 Run Extension; +- 页面、命令、Parser、Prompt、模板和单个 Tool 不是 Extension; +- 数据迁移、校验和清理由数据所有者实现。 -`catalog.ts` 集中列出所有跨 Extension 使用的 Extension ID、能力抓手和事实事件;它只保存字符串,不保存类型或运行逻辑。 +## 创建规则 -当前协作链路: - -```text -workspace -> 提供 workspace -deepseek -> 添加 model.providers -agent -> 使用二者并提供 agent -cli -> 调用 agent -``` - -## 协作 - -- 一个明确能力由 Extension 提供,调用方取得后直接调用。 -- 模型、Tool 等多个实现通过同一扩展点汇集,由业务拥有者选择。 -- 状态真正发生后再发布事件,监听器只做展示、日志或派生处理。 -- Extension 只引用其他 Extension 的公开契约。 - -## 后续方向 - -后续公共能力大致包括: - -```text -项目操作与 Git -自身进化 -计划与多 Agent -记忆与知识 -浏览器与本机控制 -自动化 -``` - -CLI、Web、Desktop 各自先保持一个产品 Extension。远程仓库、外部 Tool 协议、Keychain 等在实际接入时再决定是否独立。 - -## 何时拆分 - -只有出现以下真实边界时才创建新 Extension: - -- 需要独立启停; -- 拥有独立的长期资源或数据; -- 存在可替换实现; -- 有明确产品或平台边界。 - -否则继续留在现有 Extension 内。目录只在能力开始实现时创建,不预建占位代码。 +- 规划中的 Extension 只有开始实现时才创建源码; +- `catalog.ts` 只登记已实现并发生跨 Extension 协作的 ID、Hook 和 Event; +- 不创建占位目录、文件、Factory、ID 或 Hook; +- Extension 内部文件划分不作统一限制; +- 当前保持单包,只有独立构建、发布或进程隔离时才拆包。 diff --git a/docs/roadmap.md b/docs/roadmap.md index 2612787..e69911b 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,46 +1,45 @@ # 路线图 -路线图只描述大的实现顺序。安全、日志、取消和数据保护从每个阶段开始就随能力一起实现。 +## P1:持续对话 -## P1:能持续聊天 - -建立最小 Kernel、第一版 Extension 和集中式数据目录。 +建立 Kernel、`workspace`、`models`、`secrets`、`agent`、模型 Provider 和 `cli`。 完成标志:CLI 可以管理 Workspace 与 Conversation,流式聊天,并在重启后继续历史。 -## P2:能操作项目 +## P2:项目闭环 -加入文件、Shell、Tool Calling、Git、Worktree、验证和最小权限确认。 +实现 `tools`、`security`、`project`、`shell` 和 `git`。 -完成标志:Agent 可以在普通代码项目中完成一次可审核的修改与测试闭环。 +完成标志:Agent 可以在受控范围内读取和修改项目、运行验证、检查 Git 变更,并处理审批。 -## P3:能修改自己 +## P3:自身演进 -加入隔离候选、构建验证、发布确认、版本切换和失败回退。 +实现 `evolution` 和 Runtime 外的 Supervisor。 -完成标志:Agent 可以安全完成一次自身小能力的修改与发布。 +完成标志:Agent 可以生成隔离候选、完成验证和发布提案,Supervisor 可以安全切换版本并失败回退。 -## P4:能处理复杂工作 +## P4:复杂任务 -加入多模型、计划、多 Agent、工作流、长期记忆和知识检索。 +实现 `tasks`、`memory`、`knowledge` 和更多需要的模型 Provider。 -完成标志:复杂目标可以持续执行,跨 Conversation 找到相关上下文,并给出可验证结果。 +完成标志:复杂目标可以持续执行、使用子 Agent、恢复任务并检索长期上下文。 -## P5:能管理电脑 +## P5:电脑与自动化 -加入浏览器、本机控制、多模态、提醒、定时任务和自动化。 +实现 `browser`、`computer` 和 `automation`,完善 `secrets` 的 Keychain、轮换和恢复能力。 -完成标志:Agent 可以完成开发之外的高频电脑事务,并可靠重复执行。 +完成标志:Agent 可以操作浏览器和本机应用,管理提醒与自动化,并安全使用外部凭据。 -## P6:适合长期使用 +## P6:长期使用 -实现 Web 和 Desktop,完善远程访问、诊断、备份迁移、资源清理、凭据保护和版本维护。 +实现 `web` 和 `desktop`,完善远程访问、诊断、备份迁移、资源清理和版本维护。 -完成标志:三个产品共用一套稳定 Runtime 和数据,能够长期日常使用。 +完成标志:CLI、Web、Desktop 共用一套稳定 Runtime 和数据,可以长期日常使用。 ## 执行原则 -- 每个阶段开始时再拆提交级任务。 -- 先跑通最短链路,再根据真实压力抽象。 -- 新增普通能力默认修改 Extension;只有通用运行机制才修改 Kernel。 -- 路线图允许随实践调整,不维护小功能状态表。 +- 每个阶段开始时再拆提交级任务; +- 只创建当前阶段实际使用的 Extension; +- 优先跑通最短闭环,再补充实现细节; +- 安全、取消、日志和数据保护随能力一起实现; +- 路线图只记录阶段和完成标志,不维护小功能状态表。 diff --git a/docs/source-layout.md b/docs/source-layout.md index 6d85095..715e7b2 100644 --- a/docs/source-layout.md +++ b/docs/source-layout.md @@ -7,6 +7,7 @@ llm-to-agent/ ├── src/ │ ├── kernel/ │ ├── extensions/ +│ │ ├── catalog.ts │ │ ├── shared/ │ │ ├── cli/ │ │ ├── web/ @@ -22,18 +23,22 @@ llm-to-agent/ ## 目录职责 -- `kernel/`:只保存 Extension 运行机制。 -- `extensions/shared/`:跨产品使用的具体能力。 -- `extensions/cli|web|desktop/`:三条产品线各自的输入、展示和平台集成。 -- `products/`:静态选择每个产品启用哪些 Extension,不写业务逻辑。 -- `tests/`:只保存跨 Extension、跨进程或跨版本测试;普通测试跟随源码。 +- `kernel/`:Extension 生命周期、能力注册和事实事件; +- `extensions/catalog.ts`:已经实现的跨 Extension ID、Hook 和 Event; +- `extensions/shared/`:跨产品使用的能力和 Provider; +- `extensions/cli|web|desktop/`:产品输入、展示和平台集成; +- `products/`:静态选择产品启用的 Extension,不写业务逻辑; +- `tests/`:跨 Extension、跨进程或跨版本测试; - `tooling/`:构建、开发和发布辅助。 -Extension 的具体划分见 [Extension 目录](extensions.md)。 +Extension 的最终职责见 [Extension 规划](extensions.md)。 ## 放置规则 -- 业务概念和流程进入 Extension,不为了复用方便放进 Kernel。 -- 产品私有实现不能互相依赖,真实复用出现后再提升到 `shared/`。 -- 目录和文件只在真实代码出现时创建,不预建占位层级。 -- 当前保持单包;只有构建、平台依赖、独立分发或进程隔离造成实际问题时才拆包。 +- 源码目录只在对应能力开始实现时创建; +- Extension 内部文件和目录不设统一模板; +- 业务概念和流程进入 Extension,不进入 Kernel; +- 产品私有实现不能互相依赖; +- Provider 只通过公开契约和扩展点接入能力所有者; +- 普通复用代码不因此升级为 Extension; +- 当前保持单包,只有独立构建、发布或进程隔离时才拆包。 diff --git a/src/extensions/chat.test.ts b/src/extensions/chat.test.ts index 9de3118..58fc61d 100644 --- a/src/extensions/chat.test.ts +++ b/src/extensions/chat.test.ts @@ -101,3 +101,46 @@ test("streams a reply, saves it, and restores the conversation after restart", a await secondKernel.stop(); }); + +test("creates a conversation and restores it after restart", async (t) => { + const temporaryDirectory = await mkdtemp(join(tmpdir(), "llm-to-agent-")); + const home = join(temporaryDirectory, "home"); + const projectPath = join(temporaryDirectory, "project"); + await mkdir(projectPath); + t.after(() => rm(temporaryDirectory, { recursive: true, force: true })); + + const firstKernel = new Kernel().use( + createWorkspaceExtension({ home, projectPath }), + ); + await firstKernel.start(); + + const firstWorkspace = firstKernel.get(Hook.Workspace); + await firstWorkspace.append("user", "旧对话消息"); + const conversationId = await firstWorkspace.newConversation(); + assert.deepEqual(await firstWorkspace.messages(), []); + await firstWorkspace.append("user", "新对话消息"); + assert.equal(await firstWorkspace.switchConversation("default"), true); + assert.deepEqual( + (await firstWorkspace.messages()).map(({ role, content }) => ({ role, content })), + [{ role: "user", content: "旧对话消息" }], + ); + assert.equal(await firstWorkspace.switchConversation("missing"), false); + assert.equal(firstWorkspace.conversationId, "default"); + assert.equal(await firstWorkspace.switchConversation(conversationId), true); + await firstKernel.stop(); + + const secondKernel = new Kernel().use( + createWorkspaceExtension({ home, projectPath }), + ); + await secondKernel.start(); + + const secondWorkspace = secondKernel.get(Hook.Workspace); + assert.equal(secondWorkspace.conversationId, conversationId); + assert.deepEqual( + (await secondWorkspace.messages()).map(({ role, content }) => ({ role, content })), + [{ role: "user", content: "新对话消息" }], + ); + assert.deepEqual(await readdir(projectPath), []); + + await secondKernel.stop(); +}); diff --git a/src/extensions/cli/README.md b/src/extensions/cli/README.md index 748f59c..f20334a 100644 --- a/src/extensions/cli/README.md +++ b/src/extensions/cli/README.md @@ -1,3 +1,3 @@ # CLI extensions -这里保存 REPL、终端渲染、斜杠命令和 TUI 等 CLI 私有能力。CLI 扩展把终端输入提交给 Kernel,并把 Run 事件转换成终端输出。 +这里保存终端输入解析、斜杠命令、展示和 TUI 等 CLI 私有能力。CLI Extension 通过公开抓手调用共享能力,并把运行结果转换成终端输出。 diff --git a/src/extensions/cli/index.ts b/src/extensions/cli/index.ts index 6fbc4c8..8f748c7 100644 --- a/src/extensions/cli/index.ts +++ b/src/extensions/cli/index.ts @@ -6,6 +6,8 @@ import { import type { Extension } from "../../kernel"; import { Event, ExtensionId, Hook } from "../catalog"; import type { AgentService } from "../shared/agent"; +import type { WorkspaceService } from "../shared/workspace"; +import { parseInput } from "./input"; export function createCliExtension(): Extension { let terminal: ReadlineInterface | undefined; @@ -19,6 +21,12 @@ export function createCliExtension(): Extension { start(context) { const agent = context.get(Hook.Agent); + const workspace = context.get(Hook.Workspace); + const roleNames = { + user: "用户", + assistant: "助手", + system: "系统", + }; terminal = createInterface({ input: process.stdin, output: process.stdout, @@ -35,24 +43,59 @@ export function createCliExtension(): Extension { loop = (async () => { try { process.stdout.write( - `llm-to-agent\nWorkspace: ${process.cwd()}\n输入 /exit 退出,Ctrl+C 取消当前回复。\n\n`, + `llm-to-agent\nWorkspace: ${process.cwd()}\n当前对话: ${workspace.conversationId}\n命令: /new、/switch 、/history、/exit,Ctrl+C 取消当前回复。\n\n`, ); terminal?.setPrompt("> "); terminal?.prompt(); for await (const line of terminal!) { - const input = line.trim(); + const input = parseInput(line); - if (input === "/exit") break; if (!input) { terminal?.prompt(); continue; } + if (input.type === "command") { + if (input.name === "exit") break; + + if (input.name === "new") { + const conversationId = await workspace.newConversation(); + process.stdout.write(`已新建对话: ${conversationId}\n\n`); + } else if (input.name === "switch") { + if (!input.argument) { + process.stdout.write("用法: /switch \n\n"); + } else if (await workspace.switchConversation(input.argument)) { + process.stdout.write(`已切换到对话: ${workspace.conversationId}\n\n`); + } else { + process.stdout.write(`对话不存在: ${input.argument}\n\n`); + } + } else if (input.name === "history") { + const messages = await workspace.messages(); + + if (messages.length === 0) { + process.stdout.write("当前对话暂无消息。\n\n"); + } else { + process.stdout.write(`对话 ${workspace.conversationId}\n`); + for (const message of messages) { + process.stdout.write( + `${roleNames[message.role]}: ${message.content}\n`, + ); + } + process.stdout.write("\n"); + } + } else { + process.stdout.write(`未知命令: /${input.name}\n\n`); + } + + terminal?.prompt(); + continue; + } + currentRequest = new AbortController(); try { - for await (const chunk of agent.chat(input, currentRequest.signal)) { + for await (const chunk of agent.chat(input.content, currentRequest.signal)) { process.stdout.write(chunk); } process.stdout.write("\n\n"); diff --git a/src/extensions/cli/input.test.ts b/src/extensions/cli/input.test.ts new file mode 100644 index 0000000..1f80d16 --- /dev/null +++ b/src/extensions/cli/input.test.ts @@ -0,0 +1,26 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { parseInput } from "./input"; + +test("parses CLI messages and commands", () => { + assert.equal(parseInput(" "), undefined); + assert.deepEqual(parseInput(" 你好 "), { + type: "message", + content: "你好", + }); + assert.deepEqual(parseInput("/new\tdemo"), { + type: "command", + name: "new", + argument: "demo", + }); + assert.deepEqual(parseInput("/switch default"), { + type: "command", + name: "switch", + argument: "default", + }); + assert.deepEqual(parseInput("//path"), { + type: "message", + content: "/path", + }); +}); diff --git a/src/extensions/cli/input.ts b/src/extensions/cli/input.ts new file mode 100644 index 0000000..3f46baf --- /dev/null +++ b/src/extensions/cli/input.ts @@ -0,0 +1,19 @@ +type ParsedInput = + | { type: "message"; content: string } + | { type: "command"; name: string; argument: string }; + +export function parseInput(value: string): ParsedInput | undefined { + const input = value.trim(); + + if (!input) return; + if (!input.startsWith("/")) return { type: "message", content: input }; + if (input.startsWith("//")) { + return { type: "message", content: input.slice(1) }; + } + + const separator = input.search(/\s/); + const name = input.slice(1, separator < 0 ? undefined : separator).toLowerCase(); + const argument = separator < 0 ? "" : input.slice(separator + 1).trim(); + + return { type: "command", name, argument }; +} diff --git a/src/extensions/shared/workspace/index.ts b/src/extensions/shared/workspace/index.ts index a21a9fb..c2a7075 100644 --- a/src/extensions/shared/workspace/index.ts +++ b/src/extensions/shared/workspace/index.ts @@ -1,5 +1,5 @@ -import { createHash } from "node:crypto"; -import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises"; +import { createHash, randomUUID } from "node:crypto"; +import { appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; @@ -21,6 +21,8 @@ export interface WorkspaceService { conversationId: string; messages(): Promise; append(role: MessageRole, content: string): Promise; + newConversation(): Promise; + switchConversation(conversationId: string): Promise; } export interface WorkspaceOptions { @@ -35,20 +37,42 @@ export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extens ); const projectPath = resolve(options.projectPath ?? process.cwd()); const workspaceId = createHash("sha256").update(projectPath).digest("hex").slice(0, 16); - const conversationId = options.conversationId ?? "default"; const workspaceDirectory = join(home, "workspaces", workspaceId); - const conversationDirectory = join(workspaceDirectory, "conversations", conversationId); - const messagesFile = join(conversationDirectory, "messages.jsonl"); + const conversationsDirectory = join(workspaceDirectory, "conversations"); + const workspaceFile = join(workspaceDirectory, "workspace.json"); + let conversationId = options.conversationId ?? "default"; + + async function saveWorkspace() { + await writeFile( + workspaceFile, + `${JSON.stringify( + { + id: workspaceId, + kind: "linked", + projectPath, + activeConversationId: conversationId, + }, + null, + 2, + )}\n`, + "utf8", + ); + } const workspace: WorkspaceService = { home, projectPath, workspaceId, - conversationId, + get conversationId() { + return conversationId; + }, async messages() { try { - const content = await readFile(messagesFile, "utf8"); + const content = await readFile( + join(conversationsDirectory, conversationId, "messages.jsonl"), + "utf8", + ); return content .split("\n") .filter(Boolean) @@ -66,30 +90,57 @@ export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extens createdAt: new Date().toISOString(), }; - await appendFile(messagesFile, `${JSON.stringify(message)}\n`, "utf8"); + await appendFile( + join(conversationsDirectory, conversationId, "messages.jsonl"), + `${JSON.stringify(message)}\n`, + "utf8", + ); return message; }, + + async newConversation() { + const newConversationId = randomUUID(); + await mkdir(join(conversationsDirectory, newConversationId), { recursive: true }); + conversationId = newConversationId; + await saveWorkspace(); + return conversationId; + }, + + async switchConversation(nextConversationId) { + const conversations = await readdir(conversationsDirectory, { + withFileTypes: true, + }); + const exists = conversations.some( + (entry) => entry.isDirectory() && entry.name === nextConversationId, + ); + + if (!exists) return false; + + conversationId = nextConversationId; + await saveWorkspace(); + return true; + }, }; return { id: ExtensionId.Workspace, async setup(context) { - await mkdir(conversationDirectory, { recursive: true }); - await writeFile( - join(workspaceDirectory, "workspace.json"), - `${JSON.stringify( - { - id: workspaceId, - kind: "linked", - projectPath, - activeConversationId: conversationId, - }, - null, - 2, - )}\n`, - "utf8", - ); + await mkdir(workspaceDirectory, { recursive: true }); + + if (!options.conversationId) { + try { + const savedWorkspace = JSON.parse(await readFile(workspaceFile, "utf8")); + if (savedWorkspace.activeConversationId) { + conversationId = savedWorkspace.activeConversationId; + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + } + + await mkdir(join(conversationsDirectory, conversationId), { recursive: true }); + await saveWorkspace(); context.add(Hook.Workspace, workspace); }, From 59903d6d5e95c670d323d39ec7e0fdb442655a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Tue, 4 Aug 2026 17:41:28 +0800 Subject: [PATCH 23/24] =?UTF-8?q?feat:=20=E6=8C=89=20Extension=20ID=20?= =?UTF-8?q?=E8=A3=85=E8=BD=BD=E5=88=9D=E5=A7=8B=E5=8C=96=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + docs/architecture.md | 12 +++ docs/data-layout.md | 23 ++++ docs/source-layout.md | 2 + src/config.test.ts | 79 ++++++++++++++ src/config.ts | 104 +++++++++++++++++++ src/extensions/chat.test.ts | 33 +++--- src/extensions/cli/index.ts | 4 +- src/extensions/shared/agent/index.ts | 4 +- src/extensions/shared/deepseek/index.test.ts | 60 +++++++++++ src/extensions/shared/deepseek/index.ts | 31 ++++-- src/extensions/shared/workspace/index.ts | 38 +++++-- src/kernel/extension.ts | 7 +- src/kernel/kernel.test.ts | 83 +++++++++++---- src/kernel/kernel.ts | 39 +++++-- src/main.ts | 14 ++- 16 files changed, 464 insertions(+), 71 deletions(-) create mode 100644 src/config.test.ts create mode 100644 src/config.ts create mode 100644 src/extensions/shared/deepseek/index.test.ts diff --git a/README.md b/README.md index 4c7c210..dbef823 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ pnpm dev CLI 支持 `/new` 新建对话、`/switch ` 切换对话、`/history` 查看当前对话、`/exit` 退出;输入 `//` 可以发送以 `/` 开头的普通消息。 +Runtime 会读取 `/config/extensions.json` 和当前 Workspace 下同结构的 `config/extensions.json`;文件缺失时继续使用默认值,不会自动创建。 + 文档: - [架构总览](docs/architecture.md) diff --git a/docs/architecture.md b/docs/architecture.md index 3b29ab1..a5d2d1d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -39,6 +39,18 @@ Extension 是 Runtime 的独立装配单位,分为三类: 最终能力归属见 [Extension 规划](extensions.md)。 +## 配置 + +Runtime 启动时读取全局和当前 Workspace 的可选配置,Workspace 配置按字段覆盖全局配置。产品装配向 Kernel 提供 Extension 工厂函数,每个工厂函数用自身的静态 `id` 表明身份: + +```ts +createDeepSeekExtension.id = ExtensionId.DeepSeek; +``` + +`kernel.use(createDeepSeekExtension)` 根据这个 `id` 取得对应配置,调用 `createDeepSeekExtension(options)`,再保存创建出的 Extension 实例。配置文件位置、作用域和合并规则都不进入 Kernel。 + +Extension 负责解释和校验自己的片段。第一版配置只在启动时读取,不创建缺失文件、不写入默认值,也不支持热更新。 + ## 扩展点 多个实现向能力所有者登记,不再为扩展点增加新的架构层: diff --git a/docs/data-layout.md b/docs/data-layout.md index 5b1b869..4c252e2 100644 --- a/docs/data-layout.md +++ b/docs/data-layout.md @@ -15,9 +15,12 @@ ```text / ├── config/ +│ └── extensions.json ├── workspaces/ │ └── / │ ├── workspace.json +│ ├── config/ +│ │ └── extensions.json │ ├── files/ │ ├── conversations/ │ ├── artifacts/ @@ -32,6 +35,26 @@ 目录按需要创建,不预建尚未使用的层级。 +## Extension 配置 + +全局和 Workspace 配置使用相同结构: + +```json +{ + "version": 1, + "extensions": { + "models": { + "defaultProvider": "deepseek" + }, + "deepseek": { + "model": "deepseek-v4-flash" + } + } +} +``` + +Runtime 先读取全局配置,再使用 Workspace 中同 Extension、同字段的值覆盖它。两个文件都是可选的;第一版只读,不自动创建、补全或修改配置文件。 + ## 数据归属 - `workspace` 管理 Workspace、Conversation、Message 和 Artifact; diff --git a/docs/source-layout.md b/docs/source-layout.md index 715e7b2..70aa3b6 100644 --- a/docs/source-layout.md +++ b/docs/source-layout.md @@ -13,6 +13,7 @@ llm-to-agent/ │ │ ├── web/ │ │ └── desktop/ │ ├── products/ +│ ├── config.ts │ └── main.ts ├── docs/ ├── tests/ @@ -28,6 +29,7 @@ llm-to-agent/ - `extensions/shared/`:跨产品使用的能力和 Provider; - `extensions/cli|web|desktop/`:产品输入、展示和平台集成; - `products/`:静态选择产品启用的 Extension,不写业务逻辑; +- `config.ts`:读取并合并全局与 Workspace Extension 配置; - `tests/`:跨 Extension、跨进程或跨版本测试; - `tooling/`:构建、开发和发布辅助。 diff --git a/src/config.test.ts b/src/config.test.ts new file mode 100644 index 0000000..96ca37f --- /dev/null +++ b/src/config.test.ts @@ -0,0 +1,79 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +import { loadRuntimeConfig, workspaceIdFor } from "./config"; + +test("merges global and workspace extension configs", async (t) => { + const temporaryDirectory = await mkdtemp(join(tmpdir(), "llm-to-agent-config-")); + const home = join(temporaryDirectory, "home"); + const projectPath = join(temporaryDirectory, "project"); + const workspaceId = workspaceIdFor(projectPath); + t.after(() => rm(temporaryDirectory, { recursive: true, force: true })); + + await mkdir(join(home, "config"), { recursive: true }); + await mkdir(join(home, "workspaces", workspaceId, "config"), { + recursive: true, + }); + await writeFile( + join(home, "config", "extensions.json"), + JSON.stringify({ + version: 1, + extensions: { + models: { defaultProvider: "deepseek" }, + shell: { timeoutMs: 30_000, maxOutputLength: 50_000 }, + }, + }), + ); + await writeFile( + join(home, "workspaces", workspaceId, "config", "extensions.json"), + JSON.stringify({ + version: 1, + extensions: { + shell: { timeoutMs: 120_000 }, + project: { checks: ["pnpm test"] }, + }, + }), + ); + + const config = await loadRuntimeConfig({ home, projectPath }); + + assert.equal(config.workspaceId, workspaceId); + assert.deepEqual(config.extensions, { + models: { defaultProvider: "deepseek" }, + shell: { timeoutMs: 120_000, maxOutputLength: 50_000 }, + project: { checks: ["pnpm test"] }, + }); +}); + +test("does not create missing config files", async (t) => { + const temporaryDirectory = await mkdtemp(join(tmpdir(), "llm-to-agent-config-")); + const home = join(temporaryDirectory, "home"); + const projectPath = join(temporaryDirectory, "project"); + t.after(() => rm(temporaryDirectory, { recursive: true, force: true })); + + const config = await loadRuntimeConfig({ home, projectPath }); + + assert.deepEqual(config.extensions, {}); + assert.deepEqual(await readdir(temporaryDirectory), []); +}); + +test("rejects invalid extension config sections", async (t) => { + const temporaryDirectory = await mkdtemp(join(tmpdir(), "llm-to-agent-config-")); + const home = join(temporaryDirectory, "home"); + const projectPath = join(temporaryDirectory, "project"); + t.after(() => rm(temporaryDirectory, { recursive: true, force: true })); + + await mkdir(join(home, "config"), { recursive: true }); + await writeFile( + join(home, "config", "extensions.json"), + JSON.stringify({ version: 1, extensions: { models: "deepseek" } }), + ); + + await assert.rejects( + loadRuntimeConfig({ home, projectPath }), + /invalid "models" section/, + ); +}); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..56f0d62 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,104 @@ +import { createHash } from "node:crypto"; +import { readFile } from "node:fs/promises"; +import { homedir } from "node:os"; +import { join, resolve } from "node:path"; + +import type { ExtensionConfig } from "./kernel"; + +export type ExtensionConfigs = Record; + +export interface RuntimeConfigOptions { + home?: string; + projectPath?: string; +} + +export interface RuntimeConfig { + home: string; + projectPath: string; + workspaceId: string; + extensions: ExtensionConfigs; +} + +export function workspaceIdFor(projectPath: string): string { + return createHash("sha256").update(resolve(projectPath)).digest("hex").slice(0, 16); +} + +export async function loadRuntimeConfig( + options: RuntimeConfigOptions = {}, +): Promise { + const home = resolve( + options.home ?? process.env.LLM_TO_AGENT_HOME ?? join(homedir(), ".llm-to-agent"), + ); + const projectPath = resolve(options.projectPath ?? process.cwd()); + const workspaceId = workspaceIdFor(projectPath); + const globalFile = join(home, "config", "extensions.json"); + const workspaceFile = join( + home, + "workspaces", + workspaceId, + "config", + "extensions.json", + ); + + async function readExtensions(file: string): Promise { + let content: string; + + try { + content = await readFile(file, "utf8"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return {}; + throw error; + } + + let parsed: unknown; + + try { + parsed = JSON.parse(content); + } catch (error) { + throw new Error(`Invalid JSON in extension config "${file}".`, { + cause: error, + }); + } + + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error(`Extension config "${file}" must be an object.`); + } + + const document = parsed as Record; + if (document.version !== 1) { + throw new Error(`Extension config "${file}" must use version 1.`); + } + if ( + !document.extensions || + typeof document.extensions !== "object" || + Array.isArray(document.extensions) + ) { + throw new Error(`Extension config "${file}" must contain extensions.`); + } + + const extensions: ExtensionConfigs = {}; + for (const [id, config] of Object.entries(document.extensions)) { + if (!config || typeof config !== "object" || Array.isArray(config)) { + throw new Error( + `Extension config "${file}" contains an invalid "${id}" section.`, + ); + } + extensions[id] = config as ExtensionConfig; + } + + return extensions; + } + + const globalExtensions = await readExtensions(globalFile); + const workspaceExtensions = await readExtensions(workspaceFile); + const extensions: ExtensionConfigs = {}; + + for (const [id, config] of Object.entries(globalExtensions)) { + extensions[id] = { ...config }; + } + for (const [id, config] of Object.entries(workspaceExtensions)) { + extensions[id] = { ...extensions[id], ...config }; + } + + return { home, projectPath, workspaceId, extensions }; +} diff --git a/src/extensions/chat.test.ts b/src/extensions/chat.test.ts index 58fc61d..4ba34c5 100644 --- a/src/extensions/chat.test.ts +++ b/src/extensions/chat.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import test from "node:test"; import { Kernel } from "../kernel"; -import { Hook } from "./catalog"; +import { ExtensionId, Hook } from "./catalog"; import { createAgentExtension, type AgentService, @@ -51,10 +51,14 @@ test("streams a reply, saves it, and restores the conversation after restart", a }); }; - const firstKernel = new Kernel().use( - createWorkspaceExtension({ home, projectPath }), - createDeepSeekExtension({ apiKey: "test-key", request }), - createAgentExtension(), + const extensionConfigs = { + [ExtensionId.Workspace]: { home, projectPath }, + [ExtensionId.DeepSeek]: { apiKey: "test-key", request }, + }; + const firstKernel = new Kernel({ extensionConfigs }).use( + createWorkspaceExtension, + createDeepSeekExtension, + createAgentExtension, ); await firstKernel.start(); @@ -67,10 +71,10 @@ test("streams a reply, saves it, and restores the conversation after restart", a assert.equal(firstAnswer, "第一次回答"); await firstKernel.stop(); - const secondKernel = new Kernel().use( - createWorkspaceExtension({ home, projectPath }), - createDeepSeekExtension({ apiKey: "test-key", request }), - createAgentExtension(), + const secondKernel = new Kernel({ extensionConfigs }).use( + createWorkspaceExtension, + createDeepSeekExtension, + createAgentExtension, ); await secondKernel.start(); @@ -109,8 +113,11 @@ test("creates a conversation and restores it after restart", async (t) => { await mkdir(projectPath); t.after(() => rm(temporaryDirectory, { recursive: true, force: true })); - const firstKernel = new Kernel().use( - createWorkspaceExtension({ home, projectPath }), + const extensionConfigs = { + [ExtensionId.Workspace]: { home, projectPath }, + }; + const firstKernel = new Kernel({ extensionConfigs }).use( + createWorkspaceExtension, ); await firstKernel.start(); @@ -129,8 +136,8 @@ test("creates a conversation and restores it after restart", async (t) => { assert.equal(await firstWorkspace.switchConversation(conversationId), true); await firstKernel.stop(); - const secondKernel = new Kernel().use( - createWorkspaceExtension({ home, projectPath }), + const secondKernel = new Kernel({ extensionConfigs }).use( + createWorkspaceExtension, ); await secondKernel.start(); diff --git a/src/extensions/cli/index.ts b/src/extensions/cli/index.ts index 8f748c7..d0e6483 100644 --- a/src/extensions/cli/index.ts +++ b/src/extensions/cli/index.ts @@ -15,8 +15,6 @@ export function createCliExtension(): Extension { let currentRequest: AbortController | undefined; return { - id: ExtensionId.Cli, - setup() {}, start(context) { @@ -125,3 +123,5 @@ export function createCliExtension(): Extension { }, }; } + +createCliExtension.id = ExtensionId.Cli; diff --git a/src/extensions/shared/agent/index.ts b/src/extensions/shared/agent/index.ts index ed244b4..32eb633 100644 --- a/src/extensions/shared/agent/index.ts +++ b/src/extensions/shared/agent/index.ts @@ -68,8 +68,6 @@ export function createAgentExtension(): Extension { }; return { - id: ExtensionId.Agent, - setup(context) { context.add(Hook.Agent, agent); }, @@ -93,3 +91,5 @@ export function createAgentExtension(): Extension { }, }; } + +createAgentExtension.id = ExtensionId.Agent; diff --git a/src/extensions/shared/deepseek/index.test.ts b/src/extensions/shared/deepseek/index.test.ts new file mode 100644 index 0000000..06d365c --- /dev/null +++ b/src/extensions/shared/deepseek/index.test.ts @@ -0,0 +1,60 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { Kernel } from "../../../kernel"; +import { ExtensionId, Hook } from "../../catalog"; +import type { ModelProvider } from "../agent"; +import { createDeepSeekExtension } from "."; + +test("reads DeepSeek settings from extension config", async (t) => { + const environment = { + DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY, + DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL, + DEEPSEEK_MODEL: process.env.DEEPSEEK_MODEL, + }; + delete process.env.DEEPSEEK_API_KEY; + delete process.env.DEEPSEEK_BASE_URL; + delete process.env.DEEPSEEK_MODEL; + t.after(() => { + for (const [name, value] of Object.entries(environment)) { + if (value === undefined) delete process.env[name]; + else process.env[name] = value; + } + }); + + let requestUrl = ""; + let requestInit: RequestInit | undefined; + const request = async (url: string, init: RequestInit) => { + requestUrl = url; + requestInit = init; + return new Response("data: [DONE]\n\n"); + }; + const kernel = new Kernel({ + extensionConfigs: { + [ExtensionId.DeepSeek]: { + apiKey: "config-key", + baseUrl: "https://example.test/v1/", + model: "config-model", + request, + }, + }, + }).use(createDeepSeekExtension); + await kernel.start(); + + const provider = kernel.all(Hook.ModelProviders)[0]; + assert.ok(provider); + + for await (const _ of provider.chat([ + { role: "user", content: "你好", createdAt: "2026-08-04T00:00:00.000Z" }, + ])) { + // The test response contains no text chunks. + } + + assert.equal(requestUrl, "https://example.test/v1/chat/completions"); + assert.equal( + (requestInit?.headers as Record).authorization, + "Bearer config-key", + ); + assert.equal(JSON.parse(String(requestInit?.body)).model, "config-model"); + await kernel.stop(); +}); diff --git a/src/extensions/shared/deepseek/index.ts b/src/extensions/shared/deepseek/index.ts index 4447489..a142642 100644 --- a/src/extensions/shared/deepseek/index.ts +++ b/src/extensions/shared/deepseek/index.ts @@ -1,4 +1,4 @@ -import type { Extension } from "../../../kernel"; +import type { Extension, ExtensionConfig } from "../../../kernel"; import { ExtensionId, Hook } from "../../catalog"; import type { ModelProvider } from "../agent"; @@ -9,15 +9,30 @@ export interface DeepSeekOptions { request?: (url: string, init: RequestInit) => Promise; } -export function createDeepSeekExtension(options: DeepSeekOptions = {}): Extension { - const apiKey = options.apiKey ?? process.env.DEEPSEEK_API_KEY; +export function createDeepSeekExtension(options: ExtensionConfig = {}): Extension { + if (options.apiKey !== undefined && typeof options.apiKey !== "string") { + throw new Error("deepseek.apiKey must be a string."); + } + if (options.baseUrl !== undefined && typeof options.baseUrl !== "string") { + throw new Error("deepseek.baseUrl must be a string."); + } + if (options.model !== undefined && typeof options.model !== "string") { + throw new Error("deepseek.model must be a string."); + } + if (options.request !== undefined && typeof options.request !== "function") { + throw new Error("deepseek.request must be a function."); + } + + const config = options as DeepSeekOptions; + const apiKey = process.env.DEEPSEEK_API_KEY ?? config.apiKey; const baseUrl = ( - options.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? + config.baseUrl ?? "https://api.deepseek.com" ).replace(/\/+$/, ""); - const model = options.model ?? process.env.DEEPSEEK_MODEL ?? "deepseek-v4-flash"; - const request = options.request ?? fetch; + const model = + process.env.DEEPSEEK_MODEL ?? config.model ?? "deepseek-v4-flash"; + const request = config.request ?? fetch; const provider: ModelProvider = { id: "deepseek", @@ -83,8 +98,6 @@ export function createDeepSeekExtension(options: DeepSeekOptions = {}): Extensio }; return { - id: ExtensionId.DeepSeek, - setup(context) { if (!apiKey) { throw new Error("DEEPSEEK_API_KEY is required."); @@ -94,3 +107,5 @@ export function createDeepSeekExtension(options: DeepSeekOptions = {}): Extensio }, }; } + +createDeepSeekExtension.id = ExtensionId.DeepSeek; diff --git a/src/extensions/shared/workspace/index.ts b/src/extensions/shared/workspace/index.ts index c2a7075..a0dad83 100644 --- a/src/extensions/shared/workspace/index.ts +++ b/src/extensions/shared/workspace/index.ts @@ -1,9 +1,10 @@ -import { createHash, randomUUID } from "node:crypto"; +import { randomUUID } from "node:crypto"; import { appendFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; -import type { Extension } from "../../../kernel"; +import { workspaceIdFor } from "../../../config"; +import type { Extension, ExtensionConfig } from "../../../kernel"; import { ExtensionId, Hook } from "../../catalog"; export type MessageRole = "system" | "user" | "assistant"; @@ -31,16 +32,33 @@ export interface WorkspaceOptions { conversationId?: string; } -export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extension { +export function createWorkspaceExtension(options: ExtensionConfig = {}): Extension { + if (options.home !== undefined && typeof options.home !== "string") { + throw new Error("workspace.home must be a string."); + } + if ( + options.projectPath !== undefined && + typeof options.projectPath !== "string" + ) { + throw new Error("workspace.projectPath must be a string."); + } + if ( + options.conversationId !== undefined && + typeof options.conversationId !== "string" + ) { + throw new Error("workspace.conversationId must be a string."); + } + + const config = options as WorkspaceOptions; const home = resolve( - options.home ?? process.env.LLM_TO_AGENT_HOME ?? join(homedir(), ".llm-to-agent"), + config.home ?? process.env.LLM_TO_AGENT_HOME ?? join(homedir(), ".llm-to-agent"), ); - const projectPath = resolve(options.projectPath ?? process.cwd()); - const workspaceId = createHash("sha256").update(projectPath).digest("hex").slice(0, 16); + const projectPath = resolve(config.projectPath ?? process.cwd()); + const workspaceId = workspaceIdFor(projectPath); const workspaceDirectory = join(home, "workspaces", workspaceId); const conversationsDirectory = join(workspaceDirectory, "conversations"); const workspaceFile = join(workspaceDirectory, "workspace.json"); - let conversationId = options.conversationId ?? "default"; + let conversationId = config.conversationId ?? "default"; async function saveWorkspace() { await writeFile( @@ -123,12 +141,10 @@ export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extens }; return { - id: ExtensionId.Workspace, - async setup(context) { await mkdir(workspaceDirectory, { recursive: true }); - if (!options.conversationId) { + if (!config.conversationId) { try { const savedWorkspace = JSON.parse(await readFile(workspaceFile, "utf8")); if (savedWorkspace.activeConversationId) { @@ -146,3 +162,5 @@ export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extens }, }; } + +createWorkspaceExtension.id = ExtensionId.Workspace; diff --git a/src/kernel/extension.ts b/src/kernel/extension.ts index d0c0b50..e0eb152 100644 --- a/src/kernel/extension.ts +++ b/src/kernel/extension.ts @@ -1,6 +1,7 @@ import type { EventHandler, Unsubscribe } from "./events"; export type Awaitable = T | Promise; +export type ExtensionConfig = Record; export interface ExtensionSetupContext { add(name: string, value: T): void; @@ -15,10 +16,12 @@ export interface ExtensionRuntimeContext { } export interface Extension { - id: string; setup(context: ExtensionSetupContext): Awaitable; start?(context: ExtensionRuntimeContext): Awaitable; stop?(context: ExtensionRuntimeContext): Awaitable; } -export type ExtensionFactory = () => Extension; +export interface ExtensionFactory { + id: string; + (options?: ExtensionConfig): Extension; +} diff --git a/src/kernel/kernel.test.ts b/src/kernel/kernel.test.ts index d3fb59d..ff4e06c 100644 --- a/src/kernel/kernel.test.ts +++ b/src/kernel/kernel.test.ts @@ -1,13 +1,28 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { EventBus, Kernel, type Extension } from "./index"; +import { + EventBus, + Kernel, + type Extension, + type ExtensionFactory, +} from "./index"; + +function extensionFactory( + id: string, + createExtension: () => Extension, +): ExtensionFactory { + function create() { + return createExtension(); + } + create.id = id; + return create; +} test("sets up every extension before starting them and stops in reverse order", async () => { const calls: string[] = []; - const first: Extension = { - id: "first", + const first = extensionFactory("first", () => ({ setup(context) { calls.push("first.setup"); context.add("test.values", "first"); @@ -18,10 +33,9 @@ test("sets up every extension before starting them and stops in reverse order", stop() { calls.push("first.stop"); }, - }; + })); - const second: Extension = { - id: "second", + const second = extensionFactory("second", () => ({ setup(context) { calls.push("second.setup"); context.add("test.values", "second"); @@ -32,7 +46,7 @@ test("sets up every extension before starting them and stops in reverse order", stop() { calls.push("second.stop"); }, - }; + })); const kernel = new Kernel().use(first, second); await kernel.start(); @@ -49,23 +63,53 @@ test("sets up every extension before starting them and stops in reverse order", }); test("rejects duplicate extension ids without partially installing a batch", () => { - const first: Extension = { id: "first", setup() {} }; - const duplicate: Extension = { id: "first", setup() {} }; + const first = extensionFactory("first", () => ({ setup() {} })); + const duplicate = extensionFactory("first", () => ({ setup() {} })); const kernel = new Kernel(); assert.throws(() => kernel.use(first, duplicate), /already installed/); assert.deepEqual(kernel.installedExtensionIds, []); }); +test("passes extension config by id when constructing", async () => { + const extensionConfigs = { + first: { value: "configured" }, + second: { enabled: true }, + }; + const received: Record[] = []; + + function createFirst(options: Record = {}) { + received.push(options); + return { setup() {} }; + } + createFirst.id = "first"; + + function createUnconfigured(options: Record = {}) { + received.push(options); + return { setup() {} }; + } + createUnconfigured.id = "unconfigured"; + + const kernel = new Kernel({ extensionConfigs }).use( + createFirst, + createUnconfigured, + ); + + extensionConfigs.first.value = "changed after use"; + await kernel.start(); + + assert.deepEqual(received, [{ value: "configured" }, {}]); + await kernel.stop(); +}); + test("gets one value or collects multiple values", async () => { - const kernel = new Kernel().use({ - id: "values", + const kernel = new Kernel().use(extensionFactory("values", () => ({ setup(context) { context.add("single", "one"); context.add("multiple", "one"); context.add("multiple", "two"); }, - }); + }))); await kernel.start(); @@ -76,13 +120,12 @@ test("gets one value or collects multiple values", async () => { }); test("clears registrations when setup fails", async () => { - const kernel = new Kernel().use({ - id: "broken-setup", + const kernel = new Kernel().use(extensionFactory("broken-setup", () => ({ setup(context) { context.add("temporary", "value"); throw new Error("setup failed"); }, - }); + }))); await assert.rejects(kernel.start(), /setup failed/); @@ -93,21 +136,19 @@ test("clears registrations when setup fails", async () => { test("stops every active extension after cleanup errors", async () => { const calls: string[] = []; const kernel = new Kernel().use( - { - id: "first", + extensionFactory("first", () => ({ setup() {}, stop() { calls.push("first.stop"); }, - }, - { - id: "second", + })), + extensionFactory("second", () => ({ setup() {}, stop() { calls.push("second.stop"); throw new Error("cleanup failed"); }, - }, + })), ); await kernel.start(); diff --git a/src/kernel/kernel.ts b/src/kernel/kernel.ts index 7faaef0..86bd6ec 100644 --- a/src/kernel/kernel.ts +++ b/src/kernel/kernel.ts @@ -1,23 +1,36 @@ import { EventBus, type EventHandler, type Unsubscribe } from "./events"; import type { Extension, + ExtensionConfig, + ExtensionFactory, ExtensionRuntimeContext, ExtensionSetupContext, } from "./extension"; import { ExtensionRegistry } from "./registry"; +export interface KernelOptions { + extensionConfigs?: Record; +} + +interface InstalledExtension { + id: string; + extension: Extension; +} + export class Kernel { - #extensions: Extension[] = []; + #extensions: InstalledExtension[] = []; #activeExtensions: Extension[] = []; #registry = new ExtensionRegistry(); #events = new EventBus(); #setupContext: ExtensionSetupContext; #runtimeContext: ExtensionRuntimeContext; + #extensionConfigs: Record; #state = "created"; - constructor() { + constructor(options: KernelOptions = {}) { const registry = this.#registry; const events = this.#events; + this.#extensionConfigs = options.extensionConfigs ?? {}; this.#setupContext = { add(name, value) { @@ -49,23 +62,27 @@ export class Kernel { } get installedExtensionIds(): string[] { - return this.#extensions.map((extension) => extension.id); + return this.#extensions.map(({ id }) => id); } - use(...extensions: Extension[]): this { + use(...factories: ExtensionFactory[]): this { if (this.#state !== "created") { throw new Error(`Cannot install extensions while kernel is ${this.#state}.`); } - const ids = new Set(this.#extensions.map((extension) => extension.id)); + const ids = new Set(this.#extensions.map(({ id }) => id)); - for (const extension of extensions) { - if (ids.has(extension.id)) { - throw new Error(`Extension "${extension.id}" is already installed.`); + for (const factory of factories) { + if (ids.has(factory.id)) { + throw new Error(`Extension "${factory.id}" is already installed.`); } - ids.add(extension.id); + ids.add(factory.id); } + const extensions = factories.map((factory) => ({ + id: factory.id, + extension: factory({ ...this.#extensionConfigs[factory.id] }), + })); this.#extensions.push(...extensions); return this; } @@ -94,11 +111,11 @@ export class Kernel { this.#state = "starting"; try { - for (const extension of this.#extensions) { + for (const { extension } of this.#extensions) { await extension.setup(this.#setupContext); } - for (const extension of this.#extensions) { + for (const { extension } of this.#extensions) { this.#activeExtensions.push(extension); await extension.start?.(this.#runtimeContext); } diff --git a/src/main.ts b/src/main.ts index 9f79375..f3d583b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,8 @@ import { existsSync } from "node:fs"; import { loadEnvFile } from "node:process"; -import { Event } from "./extensions/catalog"; +import { loadRuntimeConfig } from "./config"; +import { Event, ExtensionId } from "./extensions/catalog"; import { Kernel } from "./kernel"; import { getProduct, sharedExtensions } from "./products"; @@ -9,13 +10,22 @@ if (existsSync(".env")) { loadEnvFile(); } +const runtimeConfig = await loadRuntimeConfig(); const productIds = process.argv.slice(2); const products = (productIds.length > 0 ? productIds : ["cli"]).map(getProduct); const factories = [ ...sharedExtensions, ...products.flatMap((product) => product.extensions), ]; -const kernel = new Kernel().use(...factories.map((factory) => factory())); +const extensionConfigs = { + ...runtimeConfig.extensions, + [ExtensionId.Workspace]: { + ...runtimeConfig.extensions[ExtensionId.Workspace], + home: runtimeConfig.home, + projectPath: runtimeConfig.projectPath, + }, +}; +const kernel = new Kernel({ extensionConfigs }).use(...factories); const stopped = new Promise((resolve) => { kernel.on(Event.RuntimeStopRequested, resolve); From 1644731a5e8ebade066a11ef6dcbd5e5d96567d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Tue, 4 Aug 2026 17:44:47 +0800 Subject: [PATCH 24/24] =?UTF-8?q?feat:=20=E6=8B=86=E5=88=86=20Models=20Ext?= =?UTF-8?q?ension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/extensions/catalog.ts | 4 +- src/extensions/chat.test.ts | 3 + src/extensions/shared/agent/index.ts | 33 ++++------ src/extensions/shared/deepseek/index.test.ts | 13 ++-- src/extensions/shared/deepseek/index.ts | 2 +- src/extensions/shared/models/index.test.ts | 60 +++++++++++++++++++ src/extensions/shared/models/index.ts | 63 ++++++++++++++++++++ src/products/shared.ts | 2 + 8 files changed, 148 insertions(+), 32 deletions(-) create mode 100644 src/extensions/shared/models/index.test.ts create mode 100644 src/extensions/shared/models/index.ts diff --git a/src/extensions/catalog.ts b/src/extensions/catalog.ts index 736302d..95a4c6c 100644 --- a/src/extensions/catalog.ts +++ b/src/extensions/catalog.ts @@ -1,5 +1,6 @@ export const ExtensionId = { Workspace: "workspace", + Models: "models", DeepSeek: "deepseek", Agent: "agent", Cli: "cli", @@ -7,8 +8,9 @@ export const ExtensionId = { export const Hook = { Workspace: "workspace", + Models: "models", Agent: "agent", - ModelProviders: "model.providers", + ModelProviders: "models.providers", }; export const Event = { diff --git a/src/extensions/chat.test.ts b/src/extensions/chat.test.ts index 4ba34c5..380fe7b 100644 --- a/src/extensions/chat.test.ts +++ b/src/extensions/chat.test.ts @@ -11,6 +11,7 @@ import { type AgentService, } from "./shared/agent"; import { createDeepSeekExtension } from "./shared/deepseek"; +import { createModelsExtension } from "./shared/models"; import { createWorkspaceExtension, type WorkspaceService, @@ -57,6 +58,7 @@ test("streams a reply, saves it, and restores the conversation after restart", a }; const firstKernel = new Kernel({ extensionConfigs }).use( createWorkspaceExtension, + createModelsExtension, createDeepSeekExtension, createAgentExtension, ); @@ -73,6 +75,7 @@ test("streams a reply, saves it, and restores the conversation after restart", a const secondKernel = new Kernel({ extensionConfigs }).use( createWorkspaceExtension, + createModelsExtension, createDeepSeekExtension, createAgentExtension, ); diff --git a/src/extensions/shared/agent/index.ts b/src/extensions/shared/agent/index.ts index 32eb633..0ca560d 100644 --- a/src/extensions/shared/agent/index.ts +++ b/src/extensions/shared/agent/index.ts @@ -5,15 +5,8 @@ import type { ExtensionRuntimeContext, } from "../../../kernel"; import { Event, ExtensionId, Hook } from "../../catalog"; -import { - type ChatMessage, - type WorkspaceService, -} from "../workspace"; - -export interface ModelProvider { - id: string; - chat(messages: ChatMessage[], signal?: AbortSignal): AsyncIterable; -} +import type { ModelService } from "../models"; +import type { WorkspaceService } from "../workspace"; export interface AgentService { chat(input: string, signal?: AbortSignal): AsyncIterable; @@ -22,17 +15,17 @@ export interface AgentService { export function createAgentExtension(): Extension { let runtime: ExtensionRuntimeContext | undefined; let workspace: WorkspaceService | undefined; - let model: ModelProvider | undefined; + let models: ModelService | undefined; const agent: AgentService = { async *chat(input, signal) { - if (!runtime || !workspace || !model) { + if (!runtime || !workspace || !models) { throw new Error("Agent is not running."); } const activeRuntime = runtime; const activeWorkspace = workspace; - const activeModel = model; + const activeModels = models; const runId = randomUUID(); const userMessage = await activeWorkspace.append("user", input); @@ -46,9 +39,11 @@ export function createAgentExtension(): Extension { let answer = ""; try { - const messages = await activeWorkspace.messages(); + const messages = (await activeWorkspace.messages()).map( + ({ role, content }) => ({ role, content }), + ); - for await (const chunk of activeModel.chat(messages, signal)) { + for await (const chunk of activeModels.chat(messages, signal)) { answer += chunk; yield chunk; } @@ -73,21 +68,15 @@ export function createAgentExtension(): Extension { }, start(context) { - const providers = context.all(Hook.ModelProviders); - - if (providers.length === 0) { - throw new Error("Agent needs at least one model provider."); - } - runtime = context; workspace = context.get(Hook.Workspace); - model = providers[0]; + models = context.get(Hook.Models); }, stop() { runtime = undefined; workspace = undefined; - model = undefined; + models = undefined; }, }; } diff --git a/src/extensions/shared/deepseek/index.test.ts b/src/extensions/shared/deepseek/index.test.ts index 06d365c..0ed1648 100644 --- a/src/extensions/shared/deepseek/index.test.ts +++ b/src/extensions/shared/deepseek/index.test.ts @@ -3,7 +3,7 @@ import test from "node:test"; import { Kernel } from "../../../kernel"; import { ExtensionId, Hook } from "../../catalog"; -import type { ModelProvider } from "../agent"; +import { createModelsExtension, type ModelService } from "../models"; import { createDeepSeekExtension } from "."; test("reads DeepSeek settings from extension config", async (t) => { @@ -38,15 +38,12 @@ test("reads DeepSeek settings from extension config", async (t) => { request, }, }, - }).use(createDeepSeekExtension); + }).use(createModelsExtension, createDeepSeekExtension); await kernel.start(); - const provider = kernel.all(Hook.ModelProviders)[0]; - assert.ok(provider); - - for await (const _ of provider.chat([ - { role: "user", content: "你好", createdAt: "2026-08-04T00:00:00.000Z" }, - ])) { + for await (const _ of kernel + .get(Hook.Models) + .chat([{ role: "user", content: "你好" }])) { // The test response contains no text chunks. } diff --git a/src/extensions/shared/deepseek/index.ts b/src/extensions/shared/deepseek/index.ts index a142642..38b0401 100644 --- a/src/extensions/shared/deepseek/index.ts +++ b/src/extensions/shared/deepseek/index.ts @@ -1,6 +1,6 @@ import type { Extension, ExtensionConfig } from "../../../kernel"; import { ExtensionId, Hook } from "../../catalog"; -import type { ModelProvider } from "../agent"; +import type { ModelProvider } from "../models"; export interface DeepSeekOptions { apiKey?: string; diff --git a/src/extensions/shared/models/index.test.ts b/src/extensions/shared/models/index.test.ts new file mode 100644 index 0000000..fab7b7f --- /dev/null +++ b/src/extensions/shared/models/index.test.ts @@ -0,0 +1,60 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { Kernel, type ExtensionSetupContext } from "../../../kernel"; +import { ExtensionId, Hook } from "../../catalog"; +import { + createModelsExtension, + type ModelProvider, + type ModelService, +} from "."; + +test("selects a provider and streams its response", async () => { + const requests: Array<{ role: string; content: string }[]> = []; + const ignoredProvider: ModelProvider = { + id: "ignored", + async *chat() { + yield "错误"; + }, + }; + const provider: ModelProvider = { + id: "test", + async *chat(messages) { + requests.push(messages); + yield "你"; + yield "好"; + }, + }; + function createProviderExtension() { + return { + setup(context: ExtensionSetupContext) { + context.add(Hook.ModelProviders, ignoredProvider); + context.add(Hook.ModelProviders, provider); + }, + }; + } + createProviderExtension.id = "test-model-provider"; + + const kernel = new Kernel({ + extensionConfigs: { + [ExtensionId.Models]: { defaultProvider: "test" }, + }, + }).use(createModelsExtension, createProviderExtension); + await kernel.start(); + + let answer = ""; + for await (const chunk of kernel + .get(Hook.Models) + .chat([{ role: "user", content: "你好" }])) { + answer += chunk; + } + + assert.equal(answer, "你好"); + assert.deepEqual(requests, [[{ role: "user", content: "你好" }]]); + await kernel.stop(); +}); + +test("requires at least one model provider", async () => { + const kernel = new Kernel().use(createModelsExtension); + await assert.rejects(kernel.start(), /at least one provider/); +}); diff --git a/src/extensions/shared/models/index.ts b/src/extensions/shared/models/index.ts new file mode 100644 index 0000000..ba1e014 --- /dev/null +++ b/src/extensions/shared/models/index.ts @@ -0,0 +1,63 @@ +import type { Extension, ExtensionConfig } from "../../../kernel"; +import { ExtensionId, Hook } from "../../catalog"; + +export interface ModelMessage { + role: "system" | "user" | "assistant"; + content: string; +} + +export interface ModelProvider { + id: string; + chat(messages: ModelMessage[], signal?: AbortSignal): AsyncIterable; +} + +export interface ModelService { + chat(messages: ModelMessage[], signal?: AbortSignal): AsyncIterable; +} + +export function createModelsExtension(options: ExtensionConfig = {}): Extension { + if ( + options.defaultProvider !== undefined && + typeof options.defaultProvider !== "string" + ) { + throw new Error("models.defaultProvider must be a string."); + } + + const defaultProvider = options.defaultProvider; + let provider: ModelProvider | undefined; + + const models: ModelService = { + chat(messages, signal) { + if (!provider) throw new Error("Models is not running."); + return provider.chat(messages, signal); + }, + }; + + return { + setup(context) { + context.add(Hook.Models, models); + }, + + start(context) { + const providers = context.all(Hook.ModelProviders); + + if (providers.length === 0) { + throw new Error("Models needs at least one provider."); + } + + provider = defaultProvider + ? providers.find(({ id }) => id === defaultProvider) + : providers[0]; + + if (!provider) { + throw new Error(`Model provider "${defaultProvider}" is not registered.`); + } + }, + + stop() { + provider = undefined; + }, + }; +} + +createModelsExtension.id = ExtensionId.Models; diff --git a/src/products/shared.ts b/src/products/shared.ts index 48e7a8d..284df79 100644 --- a/src/products/shared.ts +++ b/src/products/shared.ts @@ -1,10 +1,12 @@ import type { ExtensionFactory } from "../kernel"; import { createAgentExtension } from "../extensions/shared/agent"; import { createDeepSeekExtension } from "../extensions/shared/deepseek"; +import { createModelsExtension } from "../extensions/shared/models"; import { createWorkspaceExtension } from "../extensions/shared/workspace"; export const sharedExtensions: ExtensionFactory[] = [ createWorkspaceExtension, + createModelsExtension, createDeepSeekExtension, createAgentExtension, ];