deepfish-ai 1.0.16 → 1.0.18

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/README.md CHANGED
@@ -10,13 +10,13 @@
10
10
  alt="WeChat"
11
11
  src="https://img.shields.io/badge/WeChat-MrRoman_123-green.svg"
12
12
  />
13
- <a href="https://github.com/qq306863030/deepfish">
13
+ <a href="https://github.com/qq306863030/deepfish-ai">
14
14
  <img
15
15
  alt="GitHub"
16
- src="https://img.shields.io/badge/GitHub-DeepFish-blue.svg"
16
+ src="https://img.shields.io/badge/GitHub-DeepFish AI-blue.svg"
17
17
  /></a>
18
- <a href="https://www.npmjs.com/package/deepfish">
19
- <img alt="NPM" src="https://img.shields.io/badge/NPM-DeepFish-blue.svg"
18
+ <a href="https://www.npmjs.com/package/deepfish-ai">
19
+ <img alt="NPM" src="https://img.shields.io/badge/NPM-DeepFish AI-blue.svg"
20
20
  /></a>
21
21
  <img
22
22
  alt="Code License"
@@ -258,7 +258,18 @@ ai ext add weather.js
258
258
 
259
259
  ```bash
260
260
  ai skill install https://clawhub.ai/TheSethRose/agent-browser
261
+ ai skill install https://clawhub.ai/steipete/weather
261
262
  ai skill ls
263
+ ai skill enable 1
264
+ ai skill disable 0
265
+ ```
266
+
267
+ **OpenClaw Skill Generation:**
268
+
269
+ ```bash
270
+ ai "Create a weather query skill"
271
+ ai skill add weather-query
272
+ ai skill enable weather-query
262
273
  ```
263
274
 
264
275
  **Media Processing:**
@@ -273,6 +284,16 @@ ai "I have ffmpeg5 installed on my system, help me convert all MP4 files in the
273
284
  ai "Classify all files under the 'model' directory into the 'model2' directory by month, date format YYYY-MM"
274
285
  ```
275
286
 
287
+ **Task List Execution:**
288
+
289
+ ```bash
290
+ ai "Create a task list: 1.xxxx; 2.xxxx; ..."
291
+ ai "Execute task list" # Start execution
292
+
293
+ ai "I want to implement an extension tool for long-form novel writing that supports large-scale writing, maintains contextual logic coherence, and avoids AI context explosion issues. This extension tool may be a bit complex to implement. You need to carefully read the extension tool generation rules first, then create a task list"
294
+ ai "Execute task list" # Start execution
295
+ ```
296
+
276
297
  ## 6. Extension Development
277
298
 
278
299
  Extensions allow you to add custom functions that AI can use in its workflows. For complex processes, you can develop them yourself or try generating extensions using this program, then register the extension with the program and use the command line to complete tasks.
@@ -346,7 +367,7 @@ module.exports = {
346
367
  Rules for automatic scanning of extension modules upon program startup:
347
368
 
348
369
  1. Scanning locations:
349
- - node_modules in the root directory
370
+ - node_modules in the npm root directory
350
371
  - node_modules in the command execution directory
351
372
  - the command execution directory itself
352
373
 
package/README_CN.md CHANGED
@@ -10,13 +10,13 @@
10
10
  alt="WeChat"
11
11
  src="https://img.shields.io/badge/WeChat-MrRoman_123-green.svg"
12
12
  />
13
- <a href="https://github.com/qq306863030/deepfish">
13
+ <a href="https://github.com/qq306863030/deepfish-ai">
14
14
  <img
15
15
  alt="GitHub"
16
- src="https://img.shields.io/badge/GitHub-DeepFish-blue.svg"
16
+ src="https://img.shields.io/badge/GitHub-DeepFish AI-blue.svg"
17
17
  /></a>
18
- <a href="https://www.npmjs.com/package/deepfish">
19
- <img alt="NPM" src="https://img.shields.io/badge/NPM-DeepFish-blue.svg"
18
+ <a href="https://www.npmjs.com/package/deepfish-ai">
19
+ <img alt="NPM" src="https://img.shields.io/badge/NPM-DeepFish AI-blue.svg"
20
20
  /></a>
21
21
  <img
22
22
  alt="Code License"
@@ -55,6 +55,7 @@
55
55
  - [AI服务选择](#ai服务选择)
56
56
  - [8. 使用说明](#8-使用说明)
57
57
  - [使用相对路径](#使用相对路径)
58
+ - [对话历史](#对话历史)
58
59
  - [9. 故障排除](#9-故障排除)
59
60
  - [配置问题](#配置问题)
60
61
  - [AI服务连接](#ai服务连接)
@@ -255,7 +256,18 @@ ai ext add weather.js
255
256
 
256
257
  ```bash
257
258
  ai skill install https://clawhub.ai/TheSethRose/agent-browser
259
+ ai skill install https://clawhub.ai/steipete/weather
258
260
  ai skill ls
261
+ ai skill enable 1
262
+ ai skill disable 0
263
+ ```
264
+
265
+ **OpenClaw Skill 生成:**
266
+
267
+ ```bash
268
+ ai "创建一个查询天气的skill"
269
+ ai skill add weather-query
270
+ ai skill enable weather-query
259
271
  ```
260
272
 
261
273
  **媒体处理:**
@@ -270,6 +282,16 @@ ai "我的系统上安装了ffmpeg5,帮我将目录中的所有MP4文件转换
270
282
  ai "将model目录下的所有文件按月份分类到model2目录中,日期格式为YYYY-MM"
271
283
  ```
272
284
 
285
+ **任务列表执行:**
286
+
287
+ ```bash
288
+ ai "创建一个任务列表,1.xxxx;2.xxxx;..."
289
+ ai "执行任务列表" # 开始执行
290
+
291
+ ai "我要实现一个用于长篇小说创作的扩展工具,支持大篇幅写作,保持上下文逻辑连贯,避免AI上下文爆炸问题。这个扩展工具实现起来可能有点复杂,你需要先仔细阅读扩展工具生成规则,然后创建一个任务列表"
292
+ ai "执行任务列表" # 开始执行
293
+ ```
294
+
273
295
  ## 6. 扩展开发
274
296
 
275
297
  扩展允许您添加AI可以在其工作流中使用的自定义函数, 对于复杂的流程也可以自行开发或尝试使用本程序生成扩展,然后将扩展注册到程序中,在使用命令行来完成任务。
@@ -342,7 +364,7 @@ module.exports = {
342
364
 
343
365
  程序启动时自动扫描扩展模块的规则:
344
366
  1. 扫描位置:
345
- - 根目录的node_modules
367
+ - npm根目录的node_modules
346
368
  - 命令执行目录的node_modules
347
369
  - 命令执行目录
348
370
  2. 扫描文件:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepfish-ai",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "This is an AI-driven command-line tool built on Node.js, equipped with AI agent and workflow capabilities. It is compatible with a wide range of AI models, can convert natural language into cross-system terminal and file operation commands, and features high extensibility. It supports complex tasks such as translation, content creation, and format conversion, while allowing custom extensions to be automatically generated via AI.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
  * @Author: Roman 306863030@qq.com
3
3
  * @Date: 2026-03-16 09:18:05
4
4
  * @LastEditors: Roman 306863030@qq.com
5
- * @LastEditTime: 2026-03-25 18:33:13
5
+ * @LastEditTime: 2026-03-26 15:19:36
6
6
  * @FilePath: \deepfish\src\cli\HistoryManager.js
7
7
  * @Description: 对话历史记录、恢复
8
8
  * @
@@ -15,12 +15,16 @@ const { v4: uuidv4 } = require('uuid')
15
15
  const { logSuccess, logError } = require('../core/utils/log')
16
16
  const { openDirectory } = require('../core/utils/normal')
17
17
  // cache => [history.json, id => [message.json, logs => [log.txt]]]
18
+ // messageType:1.主会话 2.子会话(每次开始前自动清空上下文) 3.子任务会话(任务开始前,自动加载会话历史,或加载主会话历史)
18
19
  class HistoryManager {
19
20
  constructor() {
20
21
  this.configManager = GlobalVariable.configManager
21
22
  this.cacheDir = null
22
23
  this.historyFilePath = null
23
24
  this.history = null
25
+ this.messagePath = null // 主会话历史记录
26
+ this.subMessagePath = null // 子会话历史记录
27
+ this.taskMessagePath = null // 任务会话历史记录
24
28
  this.id = null
25
29
  this.logDir = null
26
30
  GlobalVariable.historyManager = this
@@ -55,15 +59,21 @@ class HistoryManager {
55
59
  }
56
60
  // 根据id创建目录,再创建一个message.json文件
57
61
  const recordDir = path.join(this.cacheDir, id)
58
- const messageFile = path.join(recordDir, 'message.json')
62
+ this.messagePath = path.join(recordDir, 'message.json')
63
+ this.subMessagePath = path.join(recordDir,'subMessage.json')
64
+ this.taskMessagePath = path.join(recordDir,'taskMessage.json')
59
65
  fs.ensureDirSync(recordDir)
60
- fs.writeJsonSync(messageFile, [], { spaces: 2 })
66
+ fs.writeJsonSync(this.messagePath, [], { spaces: 2 })
61
67
  this.history.push(newHistoryItem)
62
68
  this.updateHistory(this.history)
63
69
  this.id = newHistoryItem.id
64
70
  } else {
65
71
  historyItem.execTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
66
72
  this.id = historyItem.id
73
+ const recordDir = path.join(this.cacheDir, this.id)
74
+ this.messagePath = path.join(recordDir, 'message.json')
75
+ this.subMessagePath = path.join(recordDir,'subMessage.json')
76
+ this.taskMessagePath = path.join(recordDir,'taskMessage.json')
67
77
  }
68
78
  const logDir = path.join(this.cacheDir, this.id, 'logs')
69
79
  fs.ensureDirSync(logDir)
@@ -125,25 +135,86 @@ class HistoryManager {
125
135
  }
126
136
  }
127
137
 
128
- clearMessage() {
129
- const messageFile = path.join(this.cacheDir, this.id, 'message.json')
130
- if (fs.existsSync(messageFile)) {
131
- fs.writeJsonSync(messageFile, [], { spaces: 2 })
138
+ // 清除主会话
139
+ clearMainMessage() {
140
+ if (fs.existsSync(this.messagePath)) {
141
+ fs.writeJsonSync(this.messagePath, [], { spaces: 2 })
132
142
  logSuccess('History messages have been cleared.')
133
143
  return
134
144
  }
135
145
  logError('No history messages found to clear.')
136
146
  }
147
+
137
148
 
138
- updateMessage(message) {
139
- const messageFile = path.join(this.cacheDir, this.id, 'message.json')
140
- if (fs.pathExistsSync(messageFile)) {
141
- fs.writeJsonSync(messageFile, message, { spaces: 2 })
149
+ // 清除子会话
150
+ clearSubMessage() {
151
+ if (fs.existsSync(this.subMessagePath)) {
152
+ fs.writeJsonSync(this.subMessagePath, [], { spaces: 2 })
153
+ return
154
+ }
155
+ }
156
+
157
+ // 清除任务会话
158
+ clearTaskMessage() {
159
+ if (fs.existsSync(this.taskMessagePath)) {
160
+ fs.writeJsonSync(this.taskMessagePath, [], { spaces: 2 })
161
+ return
162
+ }
163
+ }
164
+
165
+ // 更新主会话
166
+ updateMainMessage(message) {
167
+ if (fs.pathExistsSync(this.messagePath)) {
168
+ fs.writeJsonSync(this.messagePath, message, { spaces: 2 })
169
+ }
170
+ }
171
+
172
+ // 更新子会话
173
+ updateSubMessage(message) {
174
+ fs.writeJsonSync(this.subMessagePath, message, { spaces: 2 })
175
+ }
176
+
177
+ // 更新任务会话
178
+ updateTaskMessage(message) {
179
+ fs.writeJsonSync(this.taskMessagePath, message, { spaces: 2 })
180
+ }
181
+
182
+
183
+ clearMessage(messageType = 1) {
184
+ switch (messageType) {
185
+ case 1:
186
+ this.clearMainMessage()
187
+ break
188
+ case 2:
189
+ this.clearSubMessage()
190
+ break
191
+ case 3:
192
+ this.clearTaskMessage()
193
+ break
142
194
  }
143
195
  }
144
196
 
145
- getMessage() {
146
- const messageFile = path.join(this.cacheDir, this.id, 'message.json')
197
+ updateMessage(messageType = 1, message) {
198
+ switch (messageType) {
199
+ case 1:
200
+ this.updateMainMessage(message)
201
+ break
202
+ case 2:
203
+ this.updateSubMessage(message)
204
+ break
205
+ case 3:
206
+ this.updateTaskMessage(message)
207
+ break
208
+ }
209
+ }
210
+
211
+ getMessage(messageType = 1) {
212
+ let messageFile = this.messagePath
213
+ if (messageType === 2) {
214
+ messageFile = this.subMessagePath
215
+ } else if (messageType === 3) {
216
+ messageFile = this.taskMessagePath
217
+ }
147
218
  if (!fs.pathExistsSync(messageFile)) {
148
219
  return []
149
220
  }
@@ -174,13 +245,14 @@ class HistoryManager {
174
245
  fs.writeJsonSync(this.historyFilePath, this.history, { spaces: 2 })
175
246
  }
176
247
 
177
- record(messages) {
248
+
249
+ record(messages, messageType = 1) {
178
250
  try {
179
251
  const config = this.configManager.getConfig()
180
252
  if (config.maxHistoryExpireTime === 0) {
181
253
  return false
182
254
  }
183
- this.updateMessage(messages)
255
+ this.updateMessage(messageType, messages)
184
256
  return true
185
257
  } catch (error) {
186
258
  console.error('Failed to record:', error.message)
@@ -200,10 +272,14 @@ class HistoryManager {
200
272
  )
201
273
  try {
202
274
  let logEntry = ''
203
- if (isCompress) {
204
- logEntry = `[${new Date().toISOString()}][compress] ${message.content}\n`
275
+ if (typeof message === 'string') {
276
+ logEntry = `[${new Date().toISOString()}][###############] ${message}\n`
205
277
  } else {
206
- logEntry = `[${new Date().toISOString()}][${message.role}] ${message.content}\n`
278
+ if (isCompress) {
279
+ logEntry = `[${new Date().toISOString()}][***compress***] ${message.content}\n`
280
+ } else {
281
+ logEntry = `[${new Date().toISOString()}][${message.role}] ${message.content}\n`
282
+ }
207
283
  }
208
284
  fs.appendFileSync(logFile, logEntry)
209
285
  return true
@@ -2,7 +2,7 @@
2
2
  * @Author: Roman 306863030@qq.com
3
3
  * @Date: 2026-03-23 15:23:42
4
4
  * @LastEditors: Roman 306863030@qq.com
5
- * @LastEditTime: 2026-03-25 20:43:02
5
+ * @LastEditTime: 2026-03-27 10:30:40
6
6
  * @FilePath: \deepfish\src\cli\SkillConfigManager.js
7
7
  * @Description: Skill configuration manager
8
8
  */
@@ -46,7 +46,7 @@ class SkillConfigManager {
46
46
  preLoadSkills() {
47
47
  const skills = this.configManager.config.skills.filter((skill) => skill.enable)
48
48
  if (skills.length === 0) {
49
- return ''
49
+ return '### 暂无可以使用的Skill'
50
50
  }
51
51
  const table = skills
52
52
  .map((s) => `| ${s.name} | ${s.description} | ${s.location} | ${s.skillFilePath} |`)
@@ -268,7 +268,10 @@ ${table}
268
268
  return
269
269
  }
270
270
  const { skill, index } = skillObj
271
- const skillPath = skill.location
271
+ let skillPath = skill.location
272
+ if (!skillPath) {
273
+ skillPath = path.join(this.skillDir, skill.skillDirName)
274
+ }
272
275
  userConfig.skills = userConfig.skills.filter((_, i) => i !== index)
273
276
  this.configManager.writeConfig(userConfig)
274
277
  if (fs.existsSync(skillPath)) {
@@ -9,7 +9,9 @@ extCommand
9
9
  .command("clear")
10
10
  .description("Clear the history messages for the current directory")
11
11
  .action(() => {
12
- historyManager.clearMessage();
12
+ historyManager.clearMessage(1);
13
+ historyManager.clearMessage(2);
14
+ historyManager.clearMessage(3);
13
15
  });
14
16
 
15
17
  extCommand
package/src/core/AICLI.js CHANGED
@@ -3,10 +3,22 @@ const readline = require('readline')
3
3
  const { logError } = require('./utils/log')
4
4
  const { GlobalVariable } = require('./globalVariable')
5
5
  const AIService = require('./ai-services')
6
+ const ConfigManager = require('../cli/ConfigManager')
7
+ const SkillConfigManager = require('../cli/SkillConfigManager')
8
+ const HistoryManager = require('../cli/HistoryManager')
6
9
 
7
10
  class AICLI {
8
11
  constructor(config) {
9
- this.config = config
12
+ if (!GlobalVariable.configManager) {
13
+ GlobalVariable.configManager = new ConfigManager()
14
+ }
15
+ if (!GlobalVariable.skillConfigManager) {
16
+ GlobalVariable.skillConfigManager = new SkillConfigManager()
17
+ }
18
+ if (!GlobalVariable.historyManager) {
19
+ GlobalVariable.historyManager = new HistoryManager()
20
+ }
21
+ this.config = config || GlobalVariable.configManager.getConfig()
10
22
  this.aiConfig = GlobalVariable.configManager.getCurrentAiConfig()
11
23
  this.skillConfigManager = GlobalVariable.skillConfigManager
12
24
  this.historyManager = GlobalVariable.historyManager
@@ -2,7 +2,7 @@
2
2
  * @Author: Roman 306863030@qq.com
3
3
  * @Date: 2026-03-16 09:18:05
4
4
  * @LastEditors: Roman 306863030@qq.com
5
- * @LastEditTime: 2026-03-25 15:27:58
5
+ * @LastEditTime: 2026-03-26 10:17:59
6
6
  * @FilePath: \deepfish\src\core\ai-services\AiWorker\AIMessageManager.js
7
7
  * @Description: 上下文管理-添加、自动压缩
8
8
  * @
@@ -15,11 +15,12 @@ class AIMessageManager {
15
15
  aiClient
16
16
  aiConfig
17
17
  config
18
- constructor(aiClient, config, aiConfig, messages) {
18
+ constructor(aiClient, config, aiConfig, messages, messageType = 1) {
19
19
  this.aiClient = aiClient
20
20
  this.aiConfig = aiConfig
21
21
  this.config = config
22
22
  this.messages = messages
23
+ this.messageType = messageType
23
24
  }
24
25
  reLinkMsgs(messages) {
25
26
  this.messages = messages
@@ -27,7 +28,7 @@ class AIMessageManager {
27
28
  // 添加消息
28
29
  addMsg(message) {
29
30
  this.messages.push(message)
30
- GlobalVariable.historyManager.record(this.messages)
31
+ GlobalVariable.historyManager.record(this.messages, this.messageType)
31
32
  GlobalVariable.historyManager.log(message)
32
33
  }
33
34
  // 添加tool
@@ -41,7 +42,7 @@ class AIMessageManager {
41
42
  content: content,
42
43
  }
43
44
  this.messages.push(message)
44
- GlobalVariable.historyManager.record(this.messages)
45
+ GlobalVariable.historyManager.record(this.messages, this.messageType)
45
46
  GlobalVariable.historyManager.log(message)
46
47
  }
47
48
  /**
@@ -91,7 +92,7 @@ class AIMessageManager {
91
92
  } else if (lastUserMessageIndex === messages.length - 1) {
92
93
  newMessages.push(messages[lastUserMessageIndex])
93
94
  }
94
- GlobalVariable.historyManager.record(newMessages)
95
+ GlobalVariable.historyManager.record(newMessages, this.messageType)
95
96
  } else if (messages.length === 2) {
96
97
  const summary = await this._getSummary([messages[1]])
97
98
  newMessages.push([messages[0], summary])
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @Author: Roman 306863030@qq.com
3
3
  * @Date: 2026-03-16 09:18:05
4
- * @LastEditors: roman_123 306863030@qq.com
5
- * @LastEditTime: 2026-03-26 01:04:45
4
+ * @LastEditors: Roman 306863030@qq.com
5
+ * @LastEditTime: 2026-03-26 19:32:11
6
6
  * @FilePath: \deepfish\src\core\ai-services\AiWorker\AiAgent.js
7
7
  * @Description: 工作流循环
8
8
  * @
@@ -22,6 +22,7 @@ class AiAgent {
22
22
  config,
23
23
  aiConfig,
24
24
  extensionTools = { descriptions: [], functions: {} },
25
+ messageType = 1
25
26
  ) {
26
27
  this.aiClient = aiClient
27
28
  this.config = config
@@ -29,7 +30,8 @@ class AiAgent {
29
30
  this.maxIterations =
30
31
  config.maxIterations === -1 ? Infinity : config.maxIterations
31
32
  this.maxBlockFileSize = this.config.maxBlockFileSize || 20 // 默认20KB
32
- this.aiMessageManager = new AIMessageManager(aiClient, config, aiConfig, [])
33
+ this.aiMessageManager = new AIMessageManager(aiClient, config, aiConfig, [], messageType)
34
+ this.messageType = messageType
33
35
  this.extensionTools = extensionTools
34
36
  this.name = config.name
35
37
  }
@@ -60,12 +62,16 @@ class AiAgent {
60
62
  loadingStop(`${this.name} have finished thinking.`)
61
63
  loadingStop = null
62
64
  }
63
- logInfo(content)
64
65
  // 检查是否是任务完成的总结响应(没有工具调用且有内容)
65
66
  if (tool_calls) {
66
67
  // 执行函数
68
+ logInfo(content)
67
69
  await this.execTools(tool_calls)
68
70
  } else {
71
+ if (this.messageType === 1) {
72
+ // 只有主任务输出最后总结
73
+ logInfo(content)
74
+ }
69
75
  // 没有工具调用,结束
70
76
  break
71
77
  }
@@ -89,15 +95,12 @@ class AiAgent {
89
95
  for (const toolCall of tool_calls) {
90
96
  const { id, function: func } = toolCall
91
97
  const { name, arguments: args } = func
92
- let toolFunction = this.extensionTools.functions[name]
98
+ const toolFunctions = this.extensionTools.functions
93
99
  logInfo(`Calling tool ${toolCall.function.name}`)
94
- if (toolFunction) {
100
+ if (toolFunctions[name]) {
95
101
  try {
96
102
  const parsedArgs = typeof args === 'string' ? JSON.parse(args) : args
97
- if (name === 'readFile') {
98
- const fileInfo = await this.extensionTools.functions['getFileInfo'](
99
- parsedArgs.filePath,
100
- )
103
+ const fileInfo = await toolFunctions['getFileInfo'](parsedArgs.filePath)
101
104
  if (fileInfo && fileInfo.isFile && fileInfo.size > this.maxBlockFileSize * 1024) {
102
105
  this.aiMessageManager.addTool(id, {
103
106
  error:
@@ -107,8 +110,7 @@ class AiAgent {
107
110
  })
108
111
  continue
109
112
  }
110
- }
111
- let result = await toolFunction(...Object.values(parsedArgs))
113
+ let result = await toolFunctions[name](...Object.values(parsedArgs))
112
114
  let toolContent = JSON.stringify(result)
113
115
  if (name !== 'requestAI') {
114
116
  const MAX_CONTENT_SIZE = this.maxBlockFileSize * 1024