closer-code 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +83 -0
- package/API_GUIDE.md +1411 -0
- package/AUTO_MKDIR_IMPROVEMENT.md +354 -0
- package/CLAUDE.md +55 -0
- package/CTRL_C_EXPERIMENT.md +90 -0
- package/PROJECT_CLEANUP_SUMMARY.md +121 -0
- package/README.md +686 -0
- package/cloco.md +51 -0
- package/config.example.json +116 -0
- package/dist/bash-runner.js +128 -0
- package/dist/batch-cli.js +20736 -0
- package/dist/closer-cli.js +21190 -0
- package/dist/index.js +31228 -0
- package/docs/EXPORT_COMMAND.md +152 -0
- package/docs/FILE_NAMING_IMPROVEMENT.md +168 -0
- package/docs/GLOBAL_CONFIG.md +128 -0
- package/docs/LONG_MESSAGE_DISPLAY_FIX.md +202 -0
- package/docs/PROJECT_HISTORY_ISOLATION.md +315 -0
- package/docs/QUICK_START_HISTORY.md +207 -0
- package/docs/TASK_PROGRESS_FEATURE.md +190 -0
- package/docs/THINKING_CONTENT_RESEARCH.md +267 -0
- package/docs/THINKING_FEATURE.md +187 -0
- package/docs/THINKING_IMPROVEMENT_COMPARISON.md +193 -0
- package/docs/THINKING_OPTIMIZATION_SUMMARY.md +242 -0
- package/docs/UI_IMPROVEMENTS_2025-01-18.md +256 -0
- package/docs/WHY_THINKING_SHORT.md +201 -0
- package/package.json +49 -0
- package/scenarios/README.md +234 -0
- package/scenarios/run-all-scenarios.js +342 -0
- package/scenarios/scenario1-batch-converter.js +247 -0
- package/scenarios/scenario2-code-analyzer.js +375 -0
- package/scenarios/scenario3-doc-generator.js +371 -0
- package/scenarios/scenario4-log-analyzer.js +496 -0
- package/scenarios/scenario5-tdd-helper.js +681 -0
- package/src/ai-client-legacy.js +171 -0
- package/src/ai-client.js +221 -0
- package/src/bash-runner.js +148 -0
- package/src/batch-cli.js +327 -0
- package/src/cli.jsx +166 -0
- package/src/closer-cli.jsx +1103 -0
- package/src/closer-cli.jsx.backup +948 -0
- package/src/commands/batch.js +62 -0
- package/src/commands/chat.js +10 -0
- package/src/commands/config.js +154 -0
- package/src/commands/help.js +76 -0
- package/src/commands/history.js +192 -0
- package/src/commands/setup.js +17 -0
- package/src/commands/upgrade.js +101 -0
- package/src/commands/workflow-tests.js +125 -0
- package/src/config.js +343 -0
- package/src/conversation.js +962 -0
- package/src/git-helper.js +349 -0
- package/src/index.js +88 -0
- package/src/logger.js +347 -0
- package/src/plan.js +193 -0
- package/src/planner.js +397 -0
- package/src/search.js +195 -0
- package/src/setup.js +147 -0
- package/src/shortcuts.js +269 -0
- package/src/snippets.js +430 -0
- package/src/test-modules.js +118 -0
- package/src/tools.js +398 -0
- package/src/utils/cli.js +124 -0
- package/src/utils/validator.js +184 -0
- package/src/utils/version.js +33 -0
- package/src/utils/workflow-test.js +271 -0
- package/src/utils/workflow.js +268 -0
- package/test/demo-file-naming.js +92 -0
- package/test/demo-thinking.js +124 -0
- package/test/final-verification-report.md +303 -0
- package/test/research-thinking.js +130 -0
- package/test/test-auto-mkdir.js +123 -0
- package/test/test-e2e-empty-dir.md +108 -0
- package/test/test-export-logic.js +119 -0
- package/test/test-global-cloco.js +126 -0
- package/test/test-history-isolation.js +291 -0
- package/test/test-improved-thinking.js +43 -0
- package/test/test-long-message.js +65 -0
- package/test/test-plan-functionality.js +95 -0
- package/test/test-real-scenario.js +216 -0
- package/test/test-thinking-display.js +65 -0
- package/test/ui-verification-test.js +203 -0
- package/test/verify-history-isolation.sh +71 -0
- package/test/verify-thinking.js +339 -0
- package/test/workflows/empty-dir-creation.md +51 -0
- package/test/workflows/inventor/ascii-teacup.js +199 -0
- package/test/workflows/inventor/ascii-teacup.mjs +199 -0
- package/test/workflows/inventor/ascii_apple.hs +84 -0
- package/test/workflows/inventor/ascii_apple.py +91 -0
- package/test/workflows/inventor/cloco.md +3 -0
- package/test/workflows/longtalk/cloco.md +19 -0
- package/test/workflows/longtalk/emoji_500.txt +63 -0
- package/test/workflows/longtalk/emoji_list.txt +20 -0
- package/test/workflows/programmer/adder.md +33 -0
- package/test/workflows/programmer/expect.md +2 -0
- package/test/workflows/programmer/prompt.md +3 -0
- package/test/workflows/test-empty-dir-creation.js +113 -0
- package/test-ctrl-c.jsx +126 -0
- package/test-manual-file-creation.js +151 -0
- package/winfix.md +3 -0
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对话管理器(使用 SDK)
|
|
3
|
+
*
|
|
4
|
+
* 使用 @anthropic-ai/sdk 的 toolRunner 自动处理工具调用循环
|
|
5
|
+
* 优势:
|
|
6
|
+
* - 无需手工解析工具调用
|
|
7
|
+
* - 无需清理工具调用标记
|
|
8
|
+
* - 无需复杂的正则表达式
|
|
9
|
+
* - 代码量减少 80%
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
13
|
+
import { createAIClient } from './ai-client.js';
|
|
14
|
+
import { setToolExecutorContext, getToolDefinitions } from './tools.js';
|
|
15
|
+
import { loadHistory, saveHistory, loadMemory } from './config.js';
|
|
16
|
+
import { Plan, PlanType, PlanStatus, StepStatus } from './plan.js';
|
|
17
|
+
import {
|
|
18
|
+
initLogger,
|
|
19
|
+
logConfig,
|
|
20
|
+
logUserMessage,
|
|
21
|
+
logAIRequest,
|
|
22
|
+
logAIResponse,
|
|
23
|
+
logAIError,
|
|
24
|
+
logToolCall,
|
|
25
|
+
logSessionSummary
|
|
26
|
+
} from './logger.js';
|
|
27
|
+
|
|
28
|
+
// 消息类型
|
|
29
|
+
export const MessageType = {
|
|
30
|
+
USER: 'user',
|
|
31
|
+
ASSISTANT: 'assistant',
|
|
32
|
+
TOOL: 'tool',
|
|
33
|
+
SYSTEM: 'system',
|
|
34
|
+
ERROR: 'error'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Workflow 测试模式的前置提示词(添加到每轮对话开头)
|
|
38
|
+
// SDK 版本无需特殊格式说明,工具调用自动处理
|
|
39
|
+
export const WORKFLOW_PROMPT_PREFIX = `
|
|
40
|
+
【Workflow 测试模式】
|
|
41
|
+
你现在处于 workflow 测试模式。这个测试分为两轮:
|
|
42
|
+
**第1轮(执行任务)**:
|
|
43
|
+
- 你会收到一个任务描述
|
|
44
|
+
- 你必须使用工具完成任务(如 readFile、writeFile、bash 等)
|
|
45
|
+
- 不能只说"我会做",而要立即调用工具
|
|
46
|
+
- 必须实际创建/修改文件,而不仅仅是输出代码
|
|
47
|
+
**第2轮(验证结果)**:
|
|
48
|
+
- 你会收到验收标准
|
|
49
|
+
- 使用工具验证结果是否符合预期
|
|
50
|
+
- 验证完成后,如果结果完全符合预期,你必须回复:WORKFLOW TEST AS EXPECTED
|
|
51
|
+
- 如果结果不符合预期,请详细说明哪些方面不符合
|
|
52
|
+
重要:你必须使用工具来实际操作文件和系统,而不是仅仅输出代码或描述。
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
// Workflow 测试模式的系统提示词
|
|
56
|
+
const WORKFLOW_SYSTEM_PROMPT = `
|
|
57
|
+
## Workflow 测试模式
|
|
58
|
+
你当前处于 workflow 测试模式。这是一个两轮对话测试:
|
|
59
|
+
1. **执行任务阶段(第1轮)**:
|
|
60
|
+
- 你会收到一个任务描述
|
|
61
|
+
- 必须使用工具完成该任务(readFile、writeFile、bash 等)
|
|
62
|
+
- 必须实际创建/修改文件,不能只输出代码
|
|
63
|
+
- 任务完成后对话会中止
|
|
64
|
+
2. **验证阶段(第2轮)**:
|
|
65
|
+
- 你会收到验收标准
|
|
66
|
+
- 使用工具验证结果是否符合预期
|
|
67
|
+
- 验证完成后对话会中止
|
|
68
|
+
**验证结果回复格式**:
|
|
69
|
+
- 如果结果完全符合预期:必须回复 "WORKFLOW TEST AS EXPECTED"
|
|
70
|
+
- 如果结果不符合预期:详细说明哪些方面不符合
|
|
71
|
+
**关键规则**:
|
|
72
|
+
- 第1轮必须使用工具实际操作文件
|
|
73
|
+
- 第2轮验证完成后必须给出明确的验收结论
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 对话会话(使用 SDK)
|
|
78
|
+
*/
|
|
79
|
+
export class Conversation {
|
|
80
|
+
constructor(config, workflowTest = false) {
|
|
81
|
+
this.config = config;
|
|
82
|
+
this.workflowTest = workflowTest;
|
|
83
|
+
this.messages = [];
|
|
84
|
+
this.currentPlan = null;
|
|
85
|
+
this.isProcessing = false;
|
|
86
|
+
|
|
87
|
+
// 初始化工具执行器上下文
|
|
88
|
+
setToolExecutorContext(config);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 初始化对话
|
|
93
|
+
*/
|
|
94
|
+
async initialize() {
|
|
95
|
+
// 初始化日志
|
|
96
|
+
await initLogger();
|
|
97
|
+
await logConfig(this.config);
|
|
98
|
+
|
|
99
|
+
// 加载历史
|
|
100
|
+
const history = loadHistory();
|
|
101
|
+
// SDK 不接受 role: 'tool' 的消息,只保留 user 和 assistant 消息
|
|
102
|
+
this.messages = history
|
|
103
|
+
.filter(msg => msg.role === 'user' || msg.role === 'assistant')
|
|
104
|
+
.map(msg => ({
|
|
105
|
+
role: msg.role,
|
|
106
|
+
content: msg.content
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// 构建系统提示(现在是异步的)
|
|
110
|
+
await this.buildSystemPrompt();
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 构建系统提示
|
|
116
|
+
*/
|
|
117
|
+
async buildSystemPrompt() {
|
|
118
|
+
const memory = loadMemory();
|
|
119
|
+
const projectKey = this.config.behavior.workingDir || 'default';
|
|
120
|
+
const projectInfo = memory.projects?.[projectKey];
|
|
121
|
+
|
|
122
|
+
// 读取全局 cloco.md 文件内容
|
|
123
|
+
let globalClocoContent = '';
|
|
124
|
+
try {
|
|
125
|
+
const fs = await import('fs/promises');
|
|
126
|
+
const path = await import('path');
|
|
127
|
+
const os = await import('os');
|
|
128
|
+
|
|
129
|
+
// 获取用户主目录
|
|
130
|
+
const homeDir = os.homedir();
|
|
131
|
+
const globalClocoPath = path.join(homeDir, '.closer-code', 'cloco.md');
|
|
132
|
+
|
|
133
|
+
globalClocoContent = await fs.readFile(globalClocoPath, 'utf-8');
|
|
134
|
+
console.log('✅ 已加载全局行为规范: ~/.closer-code/cloco.md');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// 全局配置不存在是正常情况,不报错
|
|
137
|
+
if (error.code !== 'ENOENT') {
|
|
138
|
+
console.error('读取全局 cloco.md 失败:', error.message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 读取项目级 cloco.md 文件内容
|
|
143
|
+
let projectClocoContent = '';
|
|
144
|
+
try {
|
|
145
|
+
const fs = await import('fs/promises');
|
|
146
|
+
const path = await import('path');
|
|
147
|
+
const clocoPath = path.join(process.cwd(), 'cloco.md');
|
|
148
|
+
projectClocoContent = await fs.readFile(clocoPath, 'utf-8');
|
|
149
|
+
console.log('✅ 已加载项目行为规范: ./cloco.md');
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// 项目配置不存在是正常情况,不报错
|
|
152
|
+
if (error.code !== 'ENOENT') {
|
|
153
|
+
console.error('读取项目 cloco.md 失败:', error.message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// SDK 版本的系统提示 - 移除了工具调用格式的说明
|
|
158
|
+
// SDK 会自动处理工具调用,无需告诉 AI 特殊格式
|
|
159
|
+
this.systemPrompt = `You are Closer, an AI programming assistant designed to help developers with coding tasks, debugging, and project management.
|
|
160
|
+
|
|
161
|
+
## Tool Use Requirements (CRITICAL)
|
|
162
|
+
**YOU MUST USE TOOLS TO EXECUTE ACTIONS.** This is not optional.
|
|
163
|
+
- When user asks you to "show", "list", "check", "see", "view" directory contents → **MUST** call bash tool with "ls" or "dir" command
|
|
164
|
+
- When user asks about files → **MUST** call readFile, searchFiles, or searchCode tools
|
|
165
|
+
- When user asks to run commands/tests → **MUST** call bash tool
|
|
166
|
+
- When user asks to make changes → **MUST** call writeFile or editFile tools
|
|
167
|
+
|
|
168
|
+
**DO NOT** just say "I'll check", "Let me see", "I'll look into it" - **IMMEDIATELY CALL THE APPROPRIATE TOOL**.
|
|
169
|
+
|
|
170
|
+
Examples of CORRECT behavior:
|
|
171
|
+
- User: "What's in this directory?" → You: Immediately call bash tool
|
|
172
|
+
- User: "Show me the config" → You: Immediately call readFile tool
|
|
173
|
+
- User: "Run the tests" → You: Immediately call bash tool
|
|
174
|
+
|
|
175
|
+
## Error Handling and Self-Correction (CRITICAL) 🆕
|
|
176
|
+
|
|
177
|
+
**When a tool returns an error, you MUST analyze and attempt to fix it.** Do not give up after the first failure.
|
|
178
|
+
|
|
179
|
+
### Error Analysis Process
|
|
180
|
+
1. **Read the error message carefully** - Look for error codes like ENOENT, EACCES, etc.
|
|
181
|
+
2. **Identify the root cause** - Understand why the operation failed
|
|
182
|
+
3. **Devise a solution** - Determine what needs to be fixed
|
|
183
|
+
4. **Execute the fix** - Use appropriate tools to resolve the issue
|
|
184
|
+
5. **Retry the original operation** - Attempt the failed operation again
|
|
185
|
+
|
|
186
|
+
### Common Error Patterns
|
|
187
|
+
|
|
188
|
+
#### Directory Not Found (ENOENT)
|
|
189
|
+
**Error**: "Parent directory does not exist"
|
|
190
|
+
**Solution**: Create the directory first
|
|
191
|
+
\`\`\`javascript
|
|
192
|
+
// Example error response:
|
|
193
|
+
{
|
|
194
|
+
"success": false,
|
|
195
|
+
"error": "ENOENT",
|
|
196
|
+
"suggestion": "Create it first using: bash tool with 'mkdir -p chapters/'"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Your response:
|
|
200
|
+
1. Call bash tool: "mkdir -p chapters/"
|
|
201
|
+
2. Retry writeFile with the original path
|
|
202
|
+
\`\`\`
|
|
203
|
+
|
|
204
|
+
#### Permission Denied (EACCES)
|
|
205
|
+
**Error**: "Permission denied"
|
|
206
|
+
**Solution**: Check permissions or use a different location
|
|
207
|
+
|
|
208
|
+
#### File Not Found for Editing
|
|
209
|
+
**Error**: "Old text not found in file"
|
|
210
|
+
**Solution**: Use readFile to check the actual content first, then adjust the oldText
|
|
211
|
+
|
|
212
|
+
### Retry Strategy
|
|
213
|
+
- **Maximum retries**: 3 attempts per operation
|
|
214
|
+
- **Wait time**: No delay needed for tool operations
|
|
215
|
+
- **Different approach**: If the same fix fails twice, try an alternative solution
|
|
216
|
+
|
|
217
|
+
### Example: Self-Correction in Action
|
|
218
|
+
|
|
219
|
+
**User Request**: "Create a file at src/components/Button.tsx"
|
|
220
|
+
|
|
221
|
+
**Attempt 1** (fails):
|
|
222
|
+
\`\`\`
|
|
223
|
+
You: Call writeFile with "src/components/Button.tsx"
|
|
224
|
+
Tool: {"success": false, "error": "ENOENT", "suggestion": "mkdir -p src/components/"}
|
|
225
|
+
\`\`\`
|
|
226
|
+
|
|
227
|
+
**Your Analysis**:
|
|
228
|
+
- Error: ENOENT means directory doesn't exist
|
|
229
|
+
- Root cause: src/components/ directory is missing
|
|
230
|
+
- Solution: Create the directory first
|
|
231
|
+
|
|
232
|
+
**Attempt 2** (fix):
|
|
233
|
+
\`\`\`
|
|
234
|
+
You: Call bash with "mkdir -p src/components/"
|
|
235
|
+
Tool: {"success": true}
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
**Attempt 3** (retry):
|
|
239
|
+
\`\`\`
|
|
240
|
+
You: Call writeFile with "src/components/Button.tsx"
|
|
241
|
+
Tool: {"success": true, "path": ".../src/components/Button.tsx"}
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
**Result**: ✅ Success through self-correction!
|
|
245
|
+
|
|
246
|
+
### Important Notes
|
|
247
|
+
- **Always read error suggestions** - Tools often provide hints on how to fix errors
|
|
248
|
+
- **Be persistent** - Up to 3 retries are acceptable for complex operations
|
|
249
|
+
- **Learn from errors** - If a pattern emerges, adapt your approach
|
|
250
|
+
- **Ask for help if needed** - After 3 failed attempts, explain the issue to the user
|
|
251
|
+
|
|
252
|
+
**Remember**: Errors are opportunities to demonstrate problem-solving skills. Analyze, fix, retry!
|
|
253
|
+
|
|
254
|
+
## Planning and Documentation Behavior (CRITICAL)
|
|
255
|
+
**YOU MUST DOCUMENT YOUR PLANNING PROCESS.** When analyzing complex tasks or projects:
|
|
256
|
+
1. **Save planning documents to .closer_plan directory**
|
|
257
|
+
- Copy relevant .md files that inform your understanding
|
|
258
|
+
- Document your analysis process and findings
|
|
259
|
+
- Keep track of context files you've reviewed
|
|
260
|
+
2. **Why this matters:**
|
|
261
|
+
- Creates a traceable record of your thought process
|
|
262
|
+
- Helps maintain context across sessions
|
|
263
|
+
- Enables better project understanding over time
|
|
264
|
+
3. **When to do this:**
|
|
265
|
+
- Before starting complex multi-step tasks
|
|
266
|
+
- When analyzing project architecture
|
|
267
|
+
- When reviewing documentation for context
|
|
268
|
+
- Before making significant changes
|
|
269
|
+
|
|
270
|
+
**DO NOT** skip this step for complex tasks. It's essential for maintaining project intelligence.
|
|
271
|
+
|
|
272
|
+
## Multi-Step Task Execution Guide
|
|
273
|
+
When users request complex tasks that require multiple tool calls, you MUST complete ALL steps before providing a summary.
|
|
274
|
+
|
|
275
|
+
### Task: "Read the entire project" / "Analyze the whole project" / "Read all the code"
|
|
276
|
+
**Required Steps (Do ALL of them):**
|
|
277
|
+
1. List the src/ directory to see all source files
|
|
278
|
+
2. Read README.md, package.json, and config files to understand the project
|
|
279
|
+
3. **Read ALL source code files** (.js, .jsx, .ts, .tsx) in src/ directory
|
|
280
|
+
4. Analyze the code architecture, module relationships, and data flow
|
|
281
|
+
5. Identify performance bottlenecks, security issues, or design problems
|
|
282
|
+
6. Provide a comprehensive summary including:
|
|
283
|
+
- Project purpose and functionality
|
|
284
|
+
- Technical architecture
|
|
285
|
+
- Code quality assessment
|
|
286
|
+
- Performance concerns
|
|
287
|
+
- Design issues or improvements
|
|
288
|
+
|
|
289
|
+
### Task: "Search for X in the codebase"
|
|
290
|
+
**Required Steps:**
|
|
291
|
+
1. Use searchFiles to find relevant files
|
|
292
|
+
2. Use searchCode to search within file contents
|
|
293
|
+
3. Read the matching files to understand context
|
|
294
|
+
4. Provide specific findings with file names and line numbers
|
|
295
|
+
|
|
296
|
+
## Your Capabilities
|
|
297
|
+
You have access to tools that allow you to:
|
|
298
|
+
- **bash**: Execute bash commands (ls, cat, grep, npm, git, etc.)
|
|
299
|
+
- **readFile**: Read file contents
|
|
300
|
+
- **writeFile**: Create or modify files
|
|
301
|
+
- **editFile**: Replace text in files
|
|
302
|
+
- **searchFiles**: Find files by pattern
|
|
303
|
+
- **searchCode**: Search within file contents
|
|
304
|
+
|
|
305
|
+
## Current Context
|
|
306
|
+
Working Directory: ${this.config.behavior.workingDir}
|
|
307
|
+
Available Tools: ${this.config.tools.enabled.join(', ')}
|
|
308
|
+
${projectInfo ? `
|
|
309
|
+
## Project Patterns
|
|
310
|
+
This is a familiar project. Remember these patterns:
|
|
311
|
+
${JSON.stringify(projectInfo.patterns, null, 2)}
|
|
312
|
+
` : ''}
|
|
313
|
+
|
|
314
|
+
## Behavior Configuration
|
|
315
|
+
- Auto Plan: ${this.config.behavior.autoPlan ? 'Enabled' : 'Disabled'}
|
|
316
|
+
- Auto Execute: ${this.config.behavior.autoExecute ? 'Enabled (low-risk operations only)' : 'Disabled'}
|
|
317
|
+
- Confirm Destructive: ${this.config.behavior.confirmDestructive ? 'Enabled' : 'Disabled'}
|
|
318
|
+
|
|
319
|
+
**Remember: Use tools proactively. Complete ALL steps of multi-step tasks before reporting results.**
|
|
320
|
+
|
|
321
|
+
${globalClocoContent ? `
|
|
322
|
+
## 📋 Global Behavior Guidelines (CRITICAL)
|
|
323
|
+
**The following global guidelines from ~/.closer-code/cloco.md are EXTREMELY IMPORTANT and MUST be followed:**
|
|
324
|
+
|
|
325
|
+
${globalClocoContent}
|
|
326
|
+
|
|
327
|
+
**These global guidelines take precedence over general instructions. Follow them carefully**
|
|
328
|
+
` : ''}
|
|
329
|
+
|
|
330
|
+
${projectClocoContent ? `
|
|
331
|
+
## 📋 Project Behavior Guidelines (CRITICAL)
|
|
332
|
+
**The following project-specific guidelines from ./cloco.md are EXTREMELY IMPORTANT and MUST be followed:**
|
|
333
|
+
|
|
334
|
+
${projectClocoContent}
|
|
335
|
+
|
|
336
|
+
**These project guidelines take precedence over general instructions. Follow them carefully**
|
|
337
|
+
` : ''}
|
|
338
|
+
|
|
339
|
+
${!globalClocoContent && !projectClocoContent ? `
|
|
340
|
+
## 📋 Behavior Guidelines
|
|
341
|
+
No custom behavior guidelines found. You can add them by:
|
|
342
|
+
- Creating ~/.closer-code/cloco.md for global guidelines
|
|
343
|
+
- Creating ./cloco.md for project-specific guidelines
|
|
344
|
+
` : ''}`
|
|
345
|
+
+ (this.workflowTest ? WORKFLOW_SYSTEM_PROMPT : '');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 发送消息并获取响应(使用 SDK,手动处理工具调用循环以支持进度)
|
|
350
|
+
*
|
|
351
|
+
* 工具调用循环:
|
|
352
|
+
* 1. 发送消息给 AI
|
|
353
|
+
* 2. 如果 AI 调用工具,执行工具并发送结果回 AI
|
|
354
|
+
* 3. 重复直到 AI 完成响应
|
|
355
|
+
*/
|
|
356
|
+
async sendMessage(userMessage, onProgress = null, options = {}) {
|
|
357
|
+
if (this.isProcessing) {
|
|
358
|
+
throw new Error('Already processing a message');
|
|
359
|
+
}
|
|
360
|
+
this.isProcessing = true;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
// 记录用户消息
|
|
364
|
+
await logUserMessage(userMessage);
|
|
365
|
+
|
|
366
|
+
// 添加用户消息
|
|
367
|
+
this.messages.push({
|
|
368
|
+
role: MessageType.USER,
|
|
369
|
+
content: userMessage
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// 获取 AI 客户端
|
|
373
|
+
const aiClient = createAIClient(this.config);
|
|
374
|
+
|
|
375
|
+
// 获取工具定义(使用 Zod 工具)
|
|
376
|
+
const tools = getToolDefinitions(this.config.tools.enabled);
|
|
377
|
+
|
|
378
|
+
// 手动处理工具调用循环 - 全部使用流式 API
|
|
379
|
+
let currentMessages = [...this.messages];
|
|
380
|
+
let fullTextContent = '';
|
|
381
|
+
let hasToolCalls = false;
|
|
382
|
+
|
|
383
|
+
await logAIRequest(currentMessages, { system: this.systemPrompt, tools: tools.map(t => t.name) });
|
|
384
|
+
|
|
385
|
+
// 工具调用循环
|
|
386
|
+
while (true) {
|
|
387
|
+
// 使用流式 API 发送消息
|
|
388
|
+
const response = await aiClient.chatStream(
|
|
389
|
+
currentMessages,
|
|
390
|
+
{
|
|
391
|
+
system: this.systemPrompt,
|
|
392
|
+
tools: tools,
|
|
393
|
+
temperature: 0.7,
|
|
394
|
+
thinking: process.env.CLOSER_THINKING_ENABLED !== '0' ? { type: 'enabled', budget_tokens: 20000 } : { type: 'disabled' }
|
|
395
|
+
},
|
|
396
|
+
(chunk) => {
|
|
397
|
+
// 处理流式事件
|
|
398
|
+
if (chunk.type === 'thinking') {
|
|
399
|
+
if (typeof onProgress === 'function') {
|
|
400
|
+
onProgress({
|
|
401
|
+
type: 'thinking',
|
|
402
|
+
delta: chunk.delta, // 增量内容
|
|
403
|
+
snapshot: chunk.snapshot // 完整快照
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
} else if (chunk.type === 'signature') {
|
|
407
|
+
if (typeof onProgress === 'function') {
|
|
408
|
+
onProgress({
|
|
409
|
+
type: 'thinking_signature',
|
|
410
|
+
signature: chunk.signature
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
} else if (chunk.type === 'text') {
|
|
414
|
+
// 真正的流式文本
|
|
415
|
+
if (typeof onProgress === 'function') {
|
|
416
|
+
onProgress({
|
|
417
|
+
type: 'token',
|
|
418
|
+
content: chunk.delta
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
} else if (chunk.type === 'content_block_start') {
|
|
422
|
+
// 检测到工具调用块开始
|
|
423
|
+
if (chunk.blockType === 'tool_use') {
|
|
424
|
+
if (typeof onProgress === 'function') {
|
|
425
|
+
onProgress({
|
|
426
|
+
type: 'tool_use_start',
|
|
427
|
+
toolName: chunk.block.name,
|
|
428
|
+
toolId: chunk.block.id
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// 检查是否有工具调用
|
|
437
|
+
const toolUseBlocks = response.content.filter(block => block.type === 'tool_use');
|
|
438
|
+
|
|
439
|
+
if (toolUseBlocks.length === 0) {
|
|
440
|
+
// 没有工具调用,提取最终文本内容(用于保存历史)
|
|
441
|
+
fullTextContent = response.content
|
|
442
|
+
.filter(block => block.type === 'text')
|
|
443
|
+
.map(block => block.text)
|
|
444
|
+
.join('\n');
|
|
445
|
+
|
|
446
|
+
// 更新消息历史
|
|
447
|
+
this.messages.push({
|
|
448
|
+
role: MessageType.ASSISTANT,
|
|
449
|
+
content: response.content
|
|
450
|
+
});
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
hasToolCalls = true;
|
|
455
|
+
|
|
456
|
+
// 添加助手响应(包含工具调用)到消息历史
|
|
457
|
+
this.messages.push({
|
|
458
|
+
role: MessageType.ASSISTANT,
|
|
459
|
+
content: response.content
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// 处理工具调用
|
|
463
|
+
for (const block of toolUseBlocks) {
|
|
464
|
+
if (typeof onProgress === 'function') {
|
|
465
|
+
onProgress({
|
|
466
|
+
type: 'tool_start',
|
|
467
|
+
tool: block.name,
|
|
468
|
+
input: block.input
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 检测 AI Planning
|
|
473
|
+
const detectedPlan = this.detectAIPlanning(block.name, block.input);
|
|
474
|
+
if (detectedPlan && typeof onProgress === 'function') {
|
|
475
|
+
onProgress({
|
|
476
|
+
type: 'plan_created',
|
|
477
|
+
plan: detectedPlan
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 查找对应的 betaZodTool
|
|
482
|
+
const tool = tools.find(t => t.name === block.name);
|
|
483
|
+
if (!tool) {
|
|
484
|
+
throw new Error(`Tool ${block.name} not found`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// 执行工具
|
|
488
|
+
const result = await tool.run(block.input);
|
|
489
|
+
|
|
490
|
+
// 记录工具调用
|
|
491
|
+
await logToolCall(block.name, block.input, result);
|
|
492
|
+
|
|
493
|
+
// 更新 AI Planning 步骤
|
|
494
|
+
const planUpdated = this.updateAIPlanningStep(block.name, result);
|
|
495
|
+
if (planUpdated && typeof onProgress === 'function') {
|
|
496
|
+
onProgress({
|
|
497
|
+
type: 'plan_progress',
|
|
498
|
+
plan: this.currentPlan
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (typeof onProgress === 'function') {
|
|
503
|
+
onProgress({
|
|
504
|
+
type: 'tool_complete',
|
|
505
|
+
tool: block.name,
|
|
506
|
+
result: JSON.parse(result)
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// 添加工具结果到 currentMessages(用于下一次 AI 请求)
|
|
511
|
+
currentMessages.push({
|
|
512
|
+
role: MessageType.ASSISTANT,
|
|
513
|
+
content: response.content
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
currentMessages.push({
|
|
517
|
+
role: 'user',
|
|
518
|
+
content: [{
|
|
519
|
+
type: 'tool_result',
|
|
520
|
+
tool_use_id: block.id,
|
|
521
|
+
content: result
|
|
522
|
+
}]
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// 同时添加到 this.messages(用于保存历史)
|
|
526
|
+
this.messages.push({
|
|
527
|
+
role: 'user',
|
|
528
|
+
content: [{
|
|
529
|
+
type: 'tool_result',
|
|
530
|
+
tool_use_id: block.id,
|
|
531
|
+
content: result
|
|
532
|
+
}]
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 提取最终文本内容
|
|
538
|
+
const lastMessage = this.messages[this.messages.length - 1];
|
|
539
|
+
let textContent = fullTextContent;
|
|
540
|
+
|
|
541
|
+
if (!textContent && lastMessage?.content) {
|
|
542
|
+
// 先提取 thinking 相关内容(包括 thinking 和 redacted_thinking)
|
|
543
|
+
const thinkingBlocks = lastMessage.content.filter(block =>
|
|
544
|
+
block.type === 'thinking' || block.type === 'redacted_thinking'
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
if (thinkingBlocks.length > 0 && typeof onProgress === 'function') {
|
|
548
|
+
for (const block of thinkingBlocks) {
|
|
549
|
+
if (block.type === 'thinking') {
|
|
550
|
+
onProgress({
|
|
551
|
+
type: 'thinking',
|
|
552
|
+
content: block.thinking,
|
|
553
|
+
signature: block.signature
|
|
554
|
+
});
|
|
555
|
+
} else if (block.type === 'redacted_thinking') {
|
|
556
|
+
onProgress({
|
|
557
|
+
type: 'thinking_redacted',
|
|
558
|
+
content: block.data
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 再提取文本内容
|
|
565
|
+
textContent = lastMessage.content
|
|
566
|
+
.filter(block => block.type === 'text')
|
|
567
|
+
.map(block => block.text)
|
|
568
|
+
.join('\n') || '';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 记录 AI 响应
|
|
572
|
+
await logAIResponse({ content: [{ type: 'text', text: textContent }] });
|
|
573
|
+
|
|
574
|
+
// 保存历史
|
|
575
|
+
saveHistory(this.messages);
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
content: textContent,
|
|
579
|
+
toolCalls: hasToolCalls ? ['executed'] : []
|
|
580
|
+
};
|
|
581
|
+
} catch (error) {
|
|
582
|
+
await logAIError(error);
|
|
583
|
+
throw error;
|
|
584
|
+
} finally {
|
|
585
|
+
this.isProcessing = false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* 发送消息(流式响应)
|
|
591
|
+
* 注意:toolRunner 不支持流式,所以这里使用普通的 chatStream
|
|
592
|
+
*/
|
|
593
|
+
async sendMessageStream(userMessage, onProgress = null) {
|
|
594
|
+
if (this.isProcessing) {
|
|
595
|
+
throw new Error('Already processing a message');
|
|
596
|
+
}
|
|
597
|
+
this.isProcessing = true;
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
// 记录用户消息
|
|
601
|
+
await logUserMessage(userMessage);
|
|
602
|
+
|
|
603
|
+
// 添加用户消息
|
|
604
|
+
this.messages.push({
|
|
605
|
+
role: MessageType.USER,
|
|
606
|
+
content: userMessage
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
// 获取 AI 客户端
|
|
610
|
+
const aiClient = createAIClient(this.config);
|
|
611
|
+
|
|
612
|
+
// 获取工具定义
|
|
613
|
+
const tools = getToolDefinitions(this.config.tools.enabled);
|
|
614
|
+
|
|
615
|
+
// 流式响应处理
|
|
616
|
+
let fullResponse = {
|
|
617
|
+
role: 'assistant',
|
|
618
|
+
content: []
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
await logAIRequest(this.messages, { system: this.systemPrompt });
|
|
622
|
+
|
|
623
|
+
await aiClient.chatStream(
|
|
624
|
+
this.messages,
|
|
625
|
+
{
|
|
626
|
+
system: this.systemPrompt,
|
|
627
|
+
tools: tools,
|
|
628
|
+
thinking: options.thinking || (process.env.CLOSER_THINKING_ENABLED !== '0' ? { type: 'enabled', budget_tokens: 20000 } : { type: 'disabled' })
|
|
629
|
+
},
|
|
630
|
+
(chunk) => {
|
|
631
|
+
// 处理 thinking 事件(使用 SDK 事件监听器 API)
|
|
632
|
+
if (chunk.type === 'thinking') {
|
|
633
|
+
if (typeof onProgress === 'function') {
|
|
634
|
+
onProgress({
|
|
635
|
+
type: 'thinking',
|
|
636
|
+
content: chunk.delta, // 增量内容
|
|
637
|
+
snapshot: chunk.snapshot // 完整快照
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// 处理 signature 事件(thinking 签名)
|
|
642
|
+
else if (chunk.type === 'signature') {
|
|
643
|
+
if (typeof onProgress === 'function') {
|
|
644
|
+
onProgress({
|
|
645
|
+
type: 'thinking_signature',
|
|
646
|
+
signature: chunk.signature
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// 处理文本事件
|
|
651
|
+
else if (chunk.type === 'text') {
|
|
652
|
+
if (typeof onProgress === 'function') {
|
|
653
|
+
onProgress({
|
|
654
|
+
type: 'token',
|
|
655
|
+
content: chunk.delta
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
return fullResponse;
|
|
663
|
+
} catch (error) {
|
|
664
|
+
await logAIError(error);
|
|
665
|
+
throw error;
|
|
666
|
+
} finally {
|
|
667
|
+
this.isProcessing = false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* 清除对话历史
|
|
673
|
+
*/
|
|
674
|
+
clearHistory() {
|
|
675
|
+
this.messages = [];
|
|
676
|
+
saveHistory([]);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* 获取对话摘要
|
|
681
|
+
*/
|
|
682
|
+
getSummary() {
|
|
683
|
+
return {
|
|
684
|
+
messageCount: this.messages.length,
|
|
685
|
+
hasPlan: !!this.currentPlan,
|
|
686
|
+
planStatus: this.currentPlan?.status,
|
|
687
|
+
lastMessage: this.messages[this.messages.length - 1]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* 导出对话
|
|
693
|
+
*/
|
|
694
|
+
export() {
|
|
695
|
+
return {
|
|
696
|
+
messages: this.messages,
|
|
697
|
+
plan: this.currentPlan,
|
|
698
|
+
summary: this.getSummary()
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* 导入对话
|
|
704
|
+
*/
|
|
705
|
+
import(data) {
|
|
706
|
+
this.messages = data.messages || [];
|
|
707
|
+
this.currentPlan = data.plan || null;
|
|
708
|
+
saveHistory(this.messages);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* 创建计划
|
|
713
|
+
*/
|
|
714
|
+
createPlan(description, type = PlanType.AUTO) {
|
|
715
|
+
const plan = new Plan(description, type);
|
|
716
|
+
this.currentPlan = plan;
|
|
717
|
+
return plan;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* 执行计划(/plan 命令)
|
|
722
|
+
*/
|
|
723
|
+
async planAndExecute(taskDescription, onProgress) {
|
|
724
|
+
try {
|
|
725
|
+
// 创建计划
|
|
726
|
+
const plan = this.createPlan(taskDescription, PlanType.COMMAND);
|
|
727
|
+
|
|
728
|
+
if (typeof onProgress === 'function') {
|
|
729
|
+
onProgress({
|
|
730
|
+
type: 'plan_created',
|
|
731
|
+
plan
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 让 AI 分析任务并生成步骤
|
|
736
|
+
plan.start();
|
|
737
|
+
|
|
738
|
+
// 添加分析步骤
|
|
739
|
+
const analysisStep = plan.addStep('分析任务需求');
|
|
740
|
+
plan.updateStep(analysisStep.id, StepStatus.IN_PROGRESS);
|
|
741
|
+
|
|
742
|
+
// 发送任务给 AI,让它生成执行步骤
|
|
743
|
+
const prompt = `请分析以下任务,并生成详细的执行步骤列表。每个步骤应该是一个具体的、可执行的操作。
|
|
744
|
+
|
|
745
|
+
任务:${taskDescription}
|
|
746
|
+
|
|
747
|
+
请以 JSON 格式返回步骤列表,格式如下:
|
|
748
|
+
[
|
|
749
|
+
{"description": "步骤1描述"},
|
|
750
|
+
{"description": "步骤2描述"},
|
|
751
|
+
...
|
|
752
|
+
]
|
|
753
|
+
|
|
754
|
+
只返回 JSON,不要其他内容。`;
|
|
755
|
+
|
|
756
|
+
const analysis = await this.sendMessage(prompt);
|
|
757
|
+
|
|
758
|
+
// 解析 AI 返回的步骤
|
|
759
|
+
let steps = [];
|
|
760
|
+
try {
|
|
761
|
+
// 尝试从响应中提取 JSON
|
|
762
|
+
const jsonMatch = analysis.content.match(/\[[\s\S]*\]/);
|
|
763
|
+
if (jsonMatch) {
|
|
764
|
+
steps = JSON.parse(jsonMatch[0]);
|
|
765
|
+
}
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error('Failed to parse steps:', error);
|
|
768
|
+
// 如果解析失败,使用默认步骤
|
|
769
|
+
steps = [{ description: taskDescription }];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
plan.updateStep(analysisStep.id, StepStatus.COMPLETED);
|
|
773
|
+
|
|
774
|
+
// 添加解析出的步骤
|
|
775
|
+
steps.forEach(step => {
|
|
776
|
+
plan.addStep(step.description);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
if (typeof onProgress === 'function') {
|
|
780
|
+
onProgress({
|
|
781
|
+
type: 'plan_ready',
|
|
782
|
+
plan
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// 逐步执行
|
|
787
|
+
for (const step of plan.steps) {
|
|
788
|
+
if (step.status === StepStatus.PENDING) {
|
|
789
|
+
plan.updateStep(step.id, StepStatus.IN_PROGRESS);
|
|
790
|
+
|
|
791
|
+
if (typeof onProgress === 'function') {
|
|
792
|
+
onProgress({
|
|
793
|
+
type: 'step_start',
|
|
794
|
+
plan,
|
|
795
|
+
step
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// 让 AI 执行这个步骤
|
|
800
|
+
try {
|
|
801
|
+
const result = await this.sendMessage(`执行步骤:${step.description}`);
|
|
802
|
+
plan.updateStep(step.id, StepStatus.COMPLETED, result);
|
|
803
|
+
|
|
804
|
+
if (typeof onProgress === 'function') {
|
|
805
|
+
onProgress({
|
|
806
|
+
type: 'step_complete',
|
|
807
|
+
plan,
|
|
808
|
+
step
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
} catch (error) {
|
|
812
|
+
plan.updateStep(step.id, StepStatus.FAILED, error.message);
|
|
813
|
+
|
|
814
|
+
if (typeof onProgress === 'function') {
|
|
815
|
+
onProgress({
|
|
816
|
+
type: 'step_failed',
|
|
817
|
+
plan,
|
|
818
|
+
step,
|
|
819
|
+
error
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// 失败后停止执行
|
|
824
|
+
plan.fail(error.message);
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return {
|
|
831
|
+
success: plan.status !== PlanStatus.FAILED,
|
|
832
|
+
plan
|
|
833
|
+
};
|
|
834
|
+
} catch (error) {
|
|
835
|
+
console.error('planAndExecute error:', error);
|
|
836
|
+
if (this.currentPlan) {
|
|
837
|
+
this.currentPlan.fail(error.message);
|
|
838
|
+
}
|
|
839
|
+
throw error;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* 学习项目模式(/learn 命令)
|
|
845
|
+
*/
|
|
846
|
+
async learnProject() {
|
|
847
|
+
const plan = this.createPlan('学习项目模式和代码结构', PlanType.COMMAND);
|
|
848
|
+
plan.start();
|
|
849
|
+
|
|
850
|
+
// 添加学习步骤
|
|
851
|
+
const steps = [
|
|
852
|
+
'读取项目配置文件 (package.json, README.md)',
|
|
853
|
+
'分析源代码目录结构',
|
|
854
|
+
'识别主要模块和依赖关系',
|
|
855
|
+
'总结项目模式和最佳实践'
|
|
856
|
+
];
|
|
857
|
+
|
|
858
|
+
steps.forEach(desc => plan.addStep(desc));
|
|
859
|
+
|
|
860
|
+
// 执行学习
|
|
861
|
+
for (const step of plan.steps) {
|
|
862
|
+
step.status = StepStatus.IN_PROGRESS;
|
|
863
|
+
|
|
864
|
+
try {
|
|
865
|
+
// 根据步骤描述执行相应的操作
|
|
866
|
+
if (step.description.includes('package.json')) {
|
|
867
|
+
await this.sendMessage('读取并分析 package.json 文件');
|
|
868
|
+
} else if (step.description.includes('README')) {
|
|
869
|
+
await this.sendMessage('读取并分析 README.md 文件');
|
|
870
|
+
} else if (step.description.includes('目录结构')) {
|
|
871
|
+
await this.sendMessage('列出并分析项目的目录结构');
|
|
872
|
+
} else if (step.description.includes('模块')) {
|
|
873
|
+
await this.sendMessage('分析项目的主要模块和依赖关系');
|
|
874
|
+
} else if (step.description.includes('总结')) {
|
|
875
|
+
await this.sendMessage('总结这个项目的模式和最佳实践');
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
step.status = StepStatus.COMPLETED;
|
|
879
|
+
} catch (error) {
|
|
880
|
+
step.status = StepStatus.FAILED;
|
|
881
|
+
step.result = error.message;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
plan.complete();
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
plan
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* 检测并创建 AI Planning(自动检测)
|
|
895
|
+
*/
|
|
896
|
+
detectAIPlanning(toolName, toolInput) {
|
|
897
|
+
// 检测是否在写入 .closer_plan/ 目录
|
|
898
|
+
if (toolName === 'writeFile' && toolInput.filePath) {
|
|
899
|
+
const filePath = toolInput.filePath;
|
|
900
|
+
if (filePath.includes('.closer_plan/') || filePath.includes('.closer_plan\\')) {
|
|
901
|
+
// 提取文件名作为任务描述
|
|
902
|
+
const fileName = filePath.split('/').pop().split('\\').pop();
|
|
903
|
+
const description = `AI Planning: ${fileName.replace('.md', '')}`;
|
|
904
|
+
|
|
905
|
+
// 如果当前没有 plan,或者 plan 类型不匹配,创建新的
|
|
906
|
+
if (!this.currentPlan || this.currentPlan.type !== PlanType.AUTO) {
|
|
907
|
+
const plan = this.createPlan(description, PlanType.AUTO);
|
|
908
|
+
plan.start();
|
|
909
|
+
|
|
910
|
+
// 添加步骤
|
|
911
|
+
plan.addStep('分析任务需求');
|
|
912
|
+
plan.addStep('执行操作');
|
|
913
|
+
plan.addStep('生成规划文档');
|
|
914
|
+
|
|
915
|
+
// 标记第一个步骤为进行中
|
|
916
|
+
const firstStep = plan.steps[0];
|
|
917
|
+
plan.updateStep(firstStep.id, StepStatus.IN_PROGRESS);
|
|
918
|
+
|
|
919
|
+
return plan;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* 更新 AI Planning 步骤
|
|
929
|
+
*/
|
|
930
|
+
updateAIPlanningStep(toolName, result) {
|
|
931
|
+
if (this.currentPlan && this.currentPlan.type === PlanType.AUTO) {
|
|
932
|
+
const currentStep = this.currentPlan.getCurrentStep();
|
|
933
|
+
if (currentStep) {
|
|
934
|
+
// 标记当前步骤完成
|
|
935
|
+
this.currentPlan.updateStep(currentStep.id, StepStatus.COMPLETED, result);
|
|
936
|
+
|
|
937
|
+
// 开始下一个步骤
|
|
938
|
+
const nextStep = this.currentPlan.getNextStep();
|
|
939
|
+
if (nextStep) {
|
|
940
|
+
this.currentPlan.updateStep(nextStep.id, StepStatus.IN_PROGRESS);
|
|
941
|
+
} else {
|
|
942
|
+
// 所有步骤完成
|
|
943
|
+
this.currentPlan.complete();
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* 创建对话会话(使用 SDK)
|
|
955
|
+
* @param {Object} config - 配置对象
|
|
956
|
+
* @param {boolean} workflowTest - 是否为 workflow 测试模式
|
|
957
|
+
*/
|
|
958
|
+
export async function createConversation(config, workflowTest = false) {
|
|
959
|
+
const conversation = new Conversation(config, workflowTest);
|
|
960
|
+
await conversation.initialize();
|
|
961
|
+
return conversation;
|
|
962
|
+
}
|