deepfish-ai 1.0.13 → 1.0.16

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
@@ -54,6 +54,7 @@
54
54
  - [AI Service Selection](#ai-service-selection)
55
55
  - [8. Usage Notes](#8-usage-notes)
56
56
  - [Using Relative Paths](#using-relative-paths)
57
+ - [Conversation History](#conversation-history)
57
58
  - [9. Troubleshooting](#9-troubleshooting)
58
59
  - [Configuration Issues](#configuration-issues)
59
60
  - [AI Service Connection](#ai-service-connection)
@@ -71,6 +72,8 @@ Core Features:
71
72
 
72
73
  - Multi-model Compatibility: Seamlessly supports DeepSeek, Ollama, and all AI models that comply with the OpenAI API specification. It can be flexibly switched according to needs to adapt to instruction generation requirements in different scenarios.
73
74
 
75
+ - OpenClaw Skill Compatibility: Supports the OpenClaw Skill ecosystem. Skills can be installed, enabled, and managed through the existing Skill commands to quickly expand workflow capabilities.
76
+
74
77
  - Natural Language to Instructions: Precisely parses natural language requirements and automatically converts them into corresponding operating system commands (such as Linux, Windows, and macOS terminal commands) and file operation instructions (such as creating, deleting, and modifying files/directories), eliminating the need to manually write complex commands.
75
78
 
76
79
  - Highly Extensible: Supports expanding functional boundaries through an extension mechanism. In addition to basic terminal and file operations, it can easily implement complex tasks such as translation, novel writing, file format conversion, and data processing to meet diverse usage needs.
@@ -140,6 +143,7 @@ ai config use <name> # Set the specified AI configuration as the current one
140
143
  ai config del <name> # Delete the specified AI configuration
141
144
  ai config view [name] # View details of the specified AI configuration
142
145
  ai config edit # Edit the configuration file manually
146
+ ai config dir # Open the configuration file directory
143
147
  ai config reset # Reset configuration
144
148
  ai config clear # Delete the configuration file
145
149
 
@@ -148,6 +152,21 @@ ai ext add <filename> # Add an extension tool
148
152
  ai ext del <filepath> # Remove an extension tool by file path
149
153
  ai ext del <index> # Remove an extension tool by index
150
154
  ai ext ls # List all extension tools
155
+
156
+ # Skill commands
157
+ ai skill ls # List all registered skills
158
+ ai skill add <name> # Add a local skill directory or zip file from the current directory
159
+ ai skill del <name|index> # Remove a skill by name or index, exp: ai skill del 1
160
+ ai skill install <url> # Install a skill from ClawHub,exp: ai skill install https://clawhub.ai/TheSethRose/agent-browser
161
+ ai skill enable <name|index> # Enable a skill by name or index, exp: ai skill enable 1
162
+ ai skill disable <name|index> # Disable a skill by name or index, exp: ai skill disable 1
163
+ ai skill dir # Open the skill directory
164
+
165
+ # History commands
166
+ ai history clear # Clear the history messages for the current directory
167
+ ai history output # Output the history messages to current directory
168
+ ai history dir # Open the history directory
169
+ ai history reset # Reset all history for all directories
151
170
  ```
152
171
 
153
172
  ### Configuration File Structure
@@ -169,12 +188,15 @@ module.exports = {
169
188
  }
170
189
  ],
171
190
  currentAi: "default", // Name of the currently active AI configuration
172
- maxIterations: -1, // Maximum iterations for agent workflow, -1 for unlimited
173
- maxMessagesLength: 50000, // Maximum compression length
174
- maxMessagesCount: 40, // Maximum compression count
191
+ maxIterations: -1, // Maximum iterations for AI to complete the workflow, -1 for unlimited
192
+ maxMessagesLength: 150000, // Maximum compression length, -1 for unlimited
193
+ maxMessagesCount: 100, // Maximum compression count, -1 for unlimited
194
+ maxHistoryExpireTime: 30, // Maximum session expiration time in days, -1 for unlimited, 0 to disable recording
195
+ maxLogExpireTime: 3, // Log expiration time in days, -1 for unlimited, 0 to disable recording
196
+ maxBlockFileSize: 20, // Maximum block file size in KB; files exceeding this size need to be processed in blocks
175
197
  extensions: [], // List of extension file paths
176
- isRecordHistory: false, // Whether to create workflow execution record files
177
- isLog: false // Whether to create workflow execution logs
198
+ skills: [], // List of skill configurations
199
+ encoding: "utf-8", // Command line encoding format, can be set to utf-8, gbk, etc., or auto/empty for auto-detection
178
200
  };
179
201
  ```
180
202
 
@@ -232,6 +254,13 @@ ai "Create a weather.js extension tool for querying weather"
232
254
  ai ext add weather.js
233
255
  ```
234
256
 
257
+ **Skill Management:**
258
+
259
+ ```bash
260
+ ai skill install https://clawhub.ai/TheSethRose/agent-browser
261
+ ai skill ls
262
+ ```
263
+
235
264
  **Media Processing:**
236
265
 
237
266
  ```bash
@@ -347,6 +376,17 @@ For production environments or complex tasks, we recommend using DeepSeek, OpenA
347
376
 
348
377
  AI always uses paths relative to the current working directory.
349
378
 
379
+ ### Conversation History
380
+
381
+ Conversation history is created on a per-directory basis — each execution directory corresponds to its own Agent context. This means that conversations started in different directories are independent of each other.
382
+
383
+ Conversation history will be automatically cleared after a configurable period (controlled by the `maxHistoryExpireTime` field in the configuration file, default is 30 days). You can also manage it manually:
384
+
385
+ - `ai history dir` — Open the history directory to view stored conversation contexts
386
+ - `ai history clear` — Manually clear the conversation history for the current directory
387
+ - `ai history output` — Export the conversation history to the current directory
388
+ - `ai history reset` — Reset all conversation history for all directories
389
+
350
390
  ## 9. Troubleshooting
351
391
 
352
392
  ### Configuration Issues
package/README_CN.md CHANGED
@@ -70,6 +70,8 @@
70
70
 
71
71
  - 多模型兼容:无缝支持DeepSeek、Ollama,以及所有遵循OpenAI API规范的AI模型,可根据需求灵活切换,适配不同场景下的指令生成需求。
72
72
 
73
+ - OpenClaw Skill 兼容:支持适配 OpenClaw 的 Skill 生态,可通过现有 Skill 命令进行安装、启用与管理,快速扩展工作流能力。
74
+
73
75
  - 自然语言转指令:精准解析自然语言需求,自动转换为对应的操作系统命令(如Linux、Windows、macOS终端指令)和文件操作指令(如创建、删除、修改文件/目录),无需手动编写复杂命令。
74
76
 
75
77
  - 高度可扩展:支持通过扩展机制拓展功能边界,除基础的终端、文件操作外,可轻松实现翻译、小说创作、文件格式转换、数据处理等复杂任务,满足多样化使用需求。
@@ -138,6 +140,7 @@ ai config use <name> # 设置指定的AI配置为当前配置
138
140
  ai config del <name> # 删除指定的AI配置
139
141
  ai config view [name] # 查看指定AI配置的详细信息
140
142
  ai config edit # 编辑配置文件手动编辑配置文件
143
+ ai config dir # 打开配置文件所在目录
141
144
  ai config reset # 重置配置
142
145
  ai config clear # 删除配置文件
143
146
 
@@ -146,6 +149,21 @@ ai ext add <filename> # 添加扩展工具
146
149
  ai ext del <filepath> # 通过文件路径移除扩展工具
147
150
  ai ext del <index> # 通过索引移除扩展工具
148
151
  ai ext ls # 列出所有扩展工具
152
+
153
+ # Skill 命令
154
+ ai skill ls # 列出所有已注册的 skill
155
+ ai skill add <name> # 从当前目录添加本地 skill 目录或 zip 文件
156
+ ai skill del <name|index> # 通过名称或索引删除 skill, exp: ai skill del 1
157
+ ai skill install <url> # 从 ClawHub 安装 skill,exp: ai skill install https://clawhub.ai/TheSethRose/agent-browser
158
+ ai skill enable <name|index> # 通过名称或索引启用 skill, exp: ai skill enable 1
159
+ ai skill disable <name|index> # 通过名称或索引禁用 skill, exp: ai skill disable 1
160
+ ai skill dir # 打开 skill 目录
161
+
162
+ # 历史记录命令
163
+ ai history clear # 清除当前目录的对话历史
164
+ ai history output # 将历史消息输出到当前目录
165
+ ai history dir # 打开历史记录目录
166
+ ai history reset # 清除所有目录的对话历史
149
167
  ```
150
168
 
151
169
  ### 配置文件结构
@@ -167,12 +185,15 @@ module.exports = {
167
185
  }
168
186
  ],
169
187
  currentAi: "default", // 当前活动的AI配置名称
170
- maxIterations: -1, // 代理工作流的最大迭代次数,-1为不限制迭代次数
171
- maxMessagesLength: 50000, // 最大压缩长度
172
- maxMessagesCount: 40, // 最大压缩数量
188
+ maxIterations: -1, // ai完成工作流的最大迭代次数,-1表示无限制
189
+ maxMessagesLength: 150000, // 最大压缩长度,-1表示无限制
190
+ maxMessagesCount: 100, // 最大压缩数量,-1表示无限制
191
+ maxHistoryExpireTime: 30, // 整个会话的最大过期时间,单位天,-1表示无限制,0表示不记录
192
+ maxLogExpireTime: 3, // 日志过期时间,单位天,-1表示无限制,0表示不记录
193
+ maxBlockFileSize: 20, // 最大分块文件大小,单位KB;超过该大小的文件需要分块处理
173
194
  extensions: [], // 扩展文件路径列表
174
- isRecordHistory: false, // 是否创建工作流执行记录文件
175
- isLog: false // 是否创建工作流执行日志
195
+ skills: [], // 技能配置列表
196
+ encoding: "utf-8", // 命令行编码格式,可设置为utf-8、gbk等,也可以设置成auto或空值自动判断
176
197
  };
177
198
  ```
178
199
 
@@ -230,6 +251,13 @@ ai "创建一个用于查询天气的扩展工具weather.js"
230
251
  ai ext add weather.js
231
252
  ```
232
253
 
254
+ **Skill 管理:**
255
+
256
+ ```bash
257
+ ai skill install https://clawhub.ai/TheSethRose/agent-browser
258
+ ai skill ls
259
+ ```
260
+
233
261
  **媒体处理:**
234
262
 
235
263
  ```bash
@@ -343,6 +371,17 @@ module.exports = {
343
371
 
344
372
  AI始终使用相对于当前工作目录的相对路径。
345
373
 
374
+ ### 对话历史
375
+
376
+ 对话历史是以程序执行目录为单位创建的,每个程序的执行目录会对应一个独立的 Agent 上下文。这意味着在不同目录下启动的对话是相互独立的。
377
+
378
+ 对话历史会在一定时间内自动清除(通过配置文件中的 `maxHistoryExpireTime` 字段控制,默认为 30 天)。您也可以手动管理对话历史:
379
+
380
+ - `ai history dir` — 打开历史记录目录,查看已存储的对话上下文
381
+ - `ai history clear` — 清除当前目录的对话历史
382
+ - `ai history output` — 将对话历史导出到当前目录
383
+ - `ai history reset` — 清除所有目录的对话历史
384
+
346
385
  ## 9. 故障排除
347
386
 
348
387
  ### 配置问题
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepfish-ai",
3
- "version": "1.0.13",
3
+ "version": "1.0.16",
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": {
@@ -17,6 +17,7 @@
17
17
  "cli",
18
18
  "command-line",
19
19
  "cmd",
20
+ "openclaw",
20
21
  "openai",
21
22
  "ollama",
22
23
  "deepfish"
@@ -34,14 +35,18 @@
34
35
  "dependencies": {
35
36
  "axios": "^1.13.5",
36
37
  "chalk": "^4.1.0",
38
+ "chardet": "^2.1.1",
39
+ "cheerio": "^1.2.0",
37
40
  "commander": "^11.0.0",
38
41
  "dayjs": "^1.11.19",
42
+ "extract-zip": "^2.0.1",
39
43
  "fs-extra": "^11.3.3",
40
44
  "iconv-lite": "^0.7.2",
41
45
  "inquirer": "^9.0.0",
46
+ "js-yaml": "^4.1.1",
42
47
  "lodash": "^4.17.23",
43
48
  "openai": "^6.18.0",
44
- "shelljs": "^0.10.0"
49
+ "uuid": "^13.0.0"
45
50
  },
46
51
  "devDependencies": {
47
52
  "@eslint/js": "^10.0.1",
@@ -1,9 +1,11 @@
1
1
  const path = require('path')
2
2
  const os = require('os')
3
3
  const fs = require('fs-extra')
4
- const { defaultConfig } = require('./configTools')
4
+ const { defaultConfig } = require('./DefaultConfig')
5
5
  const { logSuccess, logError, logInfo } = require('../core/utils/log')
6
6
  const { GlobalVariable } = require('../core/globalVariable')
7
+ const { merge } = require('lodash')
8
+ const { openDirectory } = require('../core/utils/normal')
7
9
 
8
10
  class ConfigManager {
9
11
  config = null
@@ -24,9 +26,14 @@ class ConfigManager {
24
26
  ) {
25
27
  fs.moveSync(path.join(os.homedir(), '.ai-cmd.config.js'), this.configPath)
26
28
  } else if (!isConfigExists) {
27
- this._writeConfig()
29
+ this.writeConfig()
28
30
  }
29
- this.config = this._getConfig()
31
+ this.config = this.getConfig()
32
+ this.writeConfig(this.config)
33
+ }
34
+
35
+ dir() {
36
+ openDirectory(this.configDir)
30
37
  }
31
38
 
32
39
  edit() {
@@ -70,8 +77,9 @@ class ConfigManager {
70
77
  // 添加一个aiConfig
71
78
  addAiConfig(aiConfig) {
72
79
  this.config.ai.push(aiConfig)
73
- this._writeConfig(this.config)
80
+ this.writeConfig(this.config)
74
81
  logSuccess(`AI configuration "${aiConfig.name}" added successfully!`)
82
+ return aiConfig
75
83
  }
76
84
 
77
85
  // 删除一个aiConfig
@@ -91,7 +99,7 @@ class ConfigManager {
91
99
  }
92
100
  // Remove the configuration
93
101
  this.config.ai.splice(existingIndex, 1)
94
- this._writeConfig(this.config)
102
+ this.writeConfig(this.config)
95
103
  logSuccess(`AI configuration "${aiName}" deleted successfully!`)
96
104
  }
97
105
 
@@ -106,7 +114,7 @@ class ConfigManager {
106
114
  return
107
115
  }
108
116
  this.config.currentAi = aiName
109
- this._writeConfig(this.config)
117
+ this.writeConfig(this.config)
110
118
  logSuccess(`Current AI configuration set to "${aiName}" successfully!`)
111
119
  }
112
120
 
@@ -144,8 +152,8 @@ class ConfigManager {
144
152
  // 重置config
145
153
  resetConfig() {
146
154
  console.log('Resetting configuration file:', this.configPath)
147
- this._writeConfig()
148
- this.config = this._getConfig()
155
+ this.writeConfig()
156
+ this.config = this.getConfig()
149
157
  logError('Configuration file has been reset to default settings.')
150
158
  }
151
159
 
@@ -189,14 +197,14 @@ class ConfigManager {
189
197
  // 更新扩展
190
198
  updateExtensions(extensions) {
191
199
  this.config.extensions = extensions
192
- this._writeConfig(this.config)
200
+ this.writeConfig(this.config)
193
201
  logSuccess('Extensions updated successfully!')
194
202
  }
195
203
 
196
204
  // 删除扩展
197
205
  removeExtensionByIndex(extIndex) {
198
206
  const filePath = this.config.extensions.splice(extIndex, 1)
199
- this._writeConfig(this.config)
207
+ this.writeConfig(this.config)
200
208
  logSuccess(
201
209
  `Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
202
210
  )
@@ -206,18 +214,19 @@ class ConfigManager {
206
214
  this.config.extensions = this.config.extensions.filter(
207
215
  (ext) => ext !== filePath,
208
216
  )
209
- this._writeConfig(this.config)
217
+ this.writeConfig(this.config)
210
218
  logSuccess(
211
219
  `Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
212
220
  )
213
221
  }
214
222
 
215
- _getConfig() {
216
- return require(this.configPath)
223
+ getConfig() {
224
+ const config = require(this.configPath)
225
+ return merge(defaultConfig, config)
217
226
  }
218
227
 
219
228
  // 写入配置
220
- _writeConfig(config) {
229
+ writeConfig(config) {
221
230
  if (!config) {
222
231
  config = defaultConfig
223
232
  }
@@ -225,6 +234,7 @@ class ConfigManager {
225
234
  this.configPath,
226
235
  `module.exports = ${JSON.stringify(config, null, 2)}`,
227
236
  )
237
+ this.config = config
228
238
  }
229
239
  }
230
240
 
@@ -0,0 +1,59 @@
1
+ const defaultConfig = {
2
+ ai: [],
3
+ currentAi: "",
4
+ maxIterations: -1, // ai完成工作流的最大迭代次数,-1表示无限制
5
+ maxMessagesLength: 150000, // 最大压缩长度,-1表示无限制
6
+ maxMessagesCount: 100, // 最大压缩数量,-1表示无限制
7
+ maxHistoryExpireTime: 30, // 整个会话的最大过期时间,单位天,-1表示无限制, 0表示不记录
8
+ maxLogExpireTime: 3, // 日志过期时间,单位天,-1表示无限制,0表示不记录
9
+ maxBlockFileSize: 20, // 最大分块文件大小,单位KB;超过该大小的文件需要分块处理
10
+ extensions: [],
11
+ skills:[],
12
+ encoding: "auto", // 命令行编码格式, 可设置为utf-8、gbk等, 也可以设置成auto或空值自动判断
13
+ };
14
+
15
+ const aiCliConfig = {
16
+ DeepSeek: {
17
+ baseUrl: "https://api.deepseek.com",
18
+ model: {
19
+ list: ["deepseek-chat", "deepseek-reasoner", "other"],
20
+ defaultValue: "",
21
+ },
22
+ type: 'deepseek',
23
+ apiKey: "",
24
+ temperature: 0.7,
25
+ maxTokens: 8192,
26
+ stream: true,
27
+ },
28
+ Ollama: {
29
+ baseUrl: "http://localhost:11434/v1",
30
+ model: {
31
+ list: [],
32
+ defaultValue: "deepseek-v3.2:cloud",
33
+ },
34
+ type: 'ollama',
35
+ apiKey: "ollama",
36
+ temperature: 0.7,
37
+ maxTokens: 8192,
38
+ stream: true,
39
+ },
40
+ OpenAI: {
41
+ baseUrl: "https://api.openai.com/v1",
42
+ model: {
43
+ list: [],
44
+ defaultValue: "gpt-4",
45
+ },
46
+ type: "openai",
47
+ apiKey: "",
48
+ temperature: 0.7,
49
+ maxTokens: 8192,
50
+ stream: true,
51
+ },
52
+ };
53
+
54
+
55
+
56
+ module.exports = {
57
+ aiCliConfig,
58
+ defaultConfig,
59
+ };
@@ -2,7 +2,7 @@ const path = require("path");
2
2
  const fs = require("fs-extra");
3
3
  const { GlobalVariable } = require('../core/globalVariable')
4
4
  const { logError, logSuccess } = require('../core/utils/log');
5
- const { traverseFiles } = require("./configTools");
5
+ const { traverseFiles } = require("../core/utils/normal");
6
6
 
7
7
  class ExtConfigManager {
8
8
  constructor() {
@@ -17,6 +17,11 @@ class ExtConfigManager {
17
17
  return
18
18
  }
19
19
  const filePath = path.resolve(process.cwd(), fileName)
20
+ // 判断文件是否存在
21
+ if (!fs.existsSync(filePath)) {
22
+ logError(`File not found: ${filePath}`)
23
+ return
24
+ }
20
25
  // 判断是否路径是文件还是目录
21
26
  if (fs.statSync(filePath).isDirectory()) {
22
27
  // 扫描目录和子目录下所有js、cjs文件
@@ -37,11 +42,6 @@ class ExtConfigManager {
37
42
  })
38
43
  return
39
44
  }
40
- // 判断文件是否存在
41
- if (!fs.existsSync(filePath)) {
42
- logError(`File not found: ${filePath}`)
43
- return
44
- }
45
45
  const userConfig = this.configManager.config
46
46
  userConfig.extensions.push(filePath)
47
47
  // 数组去重
@@ -81,7 +81,7 @@ class ExtConfigManager {
81
81
  }
82
82
  console.log('='.repeat(50))
83
83
  } else {
84
- logSuccess(`No extensions in config.`)
84
+ logError(`No extensions in config.`)
85
85
  }
86
86
  }
87
87
 
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @Author: Roman 306863030@qq.com
3
+ * @Date: 2026-03-16 09:18:05
4
+ * @LastEditors: Roman 306863030@qq.com
5
+ * @LastEditTime: 2026-03-25 18:33:13
6
+ * @FilePath: \deepfish\src\cli\HistoryManager.js
7
+ * @Description: 对话历史记录、恢复
8
+ * @
9
+ */
10
+ const fs = require('fs-extra')
11
+ const path = require('path')
12
+ const dayjs = require('dayjs')
13
+ const { GlobalVariable } = require('../core/globalVariable')
14
+ const { v4: uuidv4 } = require('uuid')
15
+ const { logSuccess, logError } = require('../core/utils/log')
16
+ const { openDirectory } = require('../core/utils/normal')
17
+ // cache => [history.json, id => [message.json, logs => [log.txt]]]
18
+ class HistoryManager {
19
+ constructor() {
20
+ this.configManager = GlobalVariable.configManager
21
+ this.cacheDir = null
22
+ this.historyFilePath = null
23
+ this.history = null
24
+ this.id = null
25
+ this.logDir = null
26
+ GlobalVariable.historyManager = this
27
+ this.initRecord()
28
+ }
29
+
30
+ reset() {
31
+ // 删除缓存目录
32
+ if (this.cacheDir && fs.existsSync(this.cacheDir)) {
33
+ fs.removeSync(this.cacheDir)
34
+ }
35
+ this.initRecord()
36
+ logSuccess('History has been reset.')
37
+ }
38
+
39
+ initRecord() {
40
+ this.cacheDir = path.join(this.configManager.configDir, './cache')
41
+ fs.ensureDirSync(this.cacheDir)
42
+ this.historyFilePath = path.join(this.cacheDir, 'history.json')
43
+ this.history = this.getHistory()
44
+ this.autoClearRecord()
45
+ const currentPath = process.cwd()
46
+ const historyItem = this.history.find(
47
+ (item) => item.execPath === currentPath,
48
+ )
49
+ if (!historyItem) {
50
+ const id = uuidv4()
51
+ const newHistoryItem = {
52
+ id: id,
53
+ execPath: currentPath,
54
+ execTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
55
+ }
56
+ // 根据id创建目录,再创建一个message.json文件
57
+ const recordDir = path.join(this.cacheDir, id)
58
+ const messageFile = path.join(recordDir, 'message.json')
59
+ fs.ensureDirSync(recordDir)
60
+ fs.writeJsonSync(messageFile, [], { spaces: 2 })
61
+ this.history.push(newHistoryItem)
62
+ this.updateHistory(this.history)
63
+ this.id = newHistoryItem.id
64
+ } else {
65
+ historyItem.execTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
66
+ this.id = historyItem.id
67
+ }
68
+ const logDir = path.join(this.cacheDir, this.id, 'logs')
69
+ fs.ensureDirSync(logDir)
70
+ this.logDir = logDir
71
+ this.autoClearLog()
72
+ }
73
+
74
+ openDirectory() {
75
+ const dir = path.join(this.cacheDir, this.id)
76
+ openDirectory(dir)
77
+ }
78
+
79
+ autoClearRecord() {
80
+ const config = this.configManager.getConfig()
81
+ const retentionDays = config.maxHistoryExpireTime || 30
82
+ if (retentionDays === -1) {
83
+ return
84
+ } else if (retentionDays === 0) {
85
+ this.clearMessage()
86
+ }
87
+ const currentDate = dayjs()
88
+ const history = this.history.filter(
89
+ (item) => currentDate.diff(dayjs(item.execTime), 'day') > retentionDays,
90
+ )
91
+ if (history.length > 0) {
92
+ history.forEach((item) => {
93
+ const recordDir = path.join(this.cacheDir, item.id)
94
+ fs.removeSync(recordDir)
95
+ })
96
+ this.history = this.history.filter(
97
+ (item) =>
98
+ currentDate.diff(dayjs(item.execTime), 'day') <= retentionDays,
99
+ )
100
+ this.updateHistory(this.history)
101
+ }
102
+ }
103
+
104
+ autoClearLog() {
105
+ const config = this.configManager.getConfig()
106
+ const retentionDays = config.maxLogExpireTime || 3
107
+ if (retentionDays === -1) {
108
+ return
109
+ }
110
+ if (this.history.length > 0) {
111
+ this.history.forEach((item) => {
112
+ const currentDate = dayjs()
113
+ const logDir = path.join(this.cacheDir, item.id, 'logs')
114
+ const logFiles = fs.readdirSync(logDir)
115
+ logFiles.forEach((logFile) => {
116
+ // 解析日志文件名中的日期
117
+ if (logFile.startsWith('log-') && logFile.endsWith('.txt')) {
118
+ const logDate = dayjs(logFile.slice(4, -4))
119
+ if (currentDate.diff(logDate, 'day') > retentionDays) {
120
+ fs.removeSync(path.join(logDir, logFile))
121
+ }
122
+ }
123
+ })
124
+ })
125
+ }
126
+ }
127
+
128
+ clearMessage() {
129
+ const messageFile = path.join(this.cacheDir, this.id, 'message.json')
130
+ if (fs.existsSync(messageFile)) {
131
+ fs.writeJsonSync(messageFile, [], { spaces: 2 })
132
+ logSuccess('History messages have been cleared.')
133
+ return
134
+ }
135
+ logError('No history messages found to clear.')
136
+ }
137
+
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 })
142
+ }
143
+ }
144
+
145
+ getMessage() {
146
+ const messageFile = path.join(this.cacheDir, this.id, 'message.json')
147
+ if (!fs.pathExistsSync(messageFile)) {
148
+ return []
149
+ }
150
+ return fs.readJsonSync(messageFile, { throws: false }) || []
151
+ }
152
+
153
+ outputMessage() {
154
+ const message = this.getMessage()
155
+ const outputFile = path.join(process.cwd(), 'message.json')
156
+ fs.writeJsonSync(outputFile, message, { spaces: 2 })
157
+ logSuccess(`History messages have been output to ${outputFile}`)
158
+ }
159
+
160
+ getHistory() {
161
+ const isExists = fs.existsSync(this.historyFilePath)
162
+ if (isExists) {
163
+ return fs.readJsonSync(this.historyFilePath, { throws: false })
164
+ } else {
165
+ // 创建一个文件
166
+ fs.writeJsonSync(this.historyFilePath, [], { spaces: 2 })
167
+ return []
168
+ }
169
+ }
170
+
171
+ // 更新history文件
172
+ updateHistory(history) {
173
+ this.history = history
174
+ fs.writeJsonSync(this.historyFilePath, this.history, { spaces: 2 })
175
+ }
176
+
177
+ record(messages) {
178
+ try {
179
+ const config = this.configManager.getConfig()
180
+ if (config.maxHistoryExpireTime === 0) {
181
+ return false
182
+ }
183
+ this.updateMessage(messages)
184
+ return true
185
+ } catch (error) {
186
+ console.error('Failed to record:', error.message)
187
+ return false
188
+ }
189
+ }
190
+
191
+ // 记录message以及压缩后的messages
192
+ log(message, isCompress = false) {
193
+ const config = this.configManager.getConfig()
194
+ if (config.maxLogExpireTime === 0) {
195
+ return false
196
+ }
197
+ const logFile = path.join(
198
+ this.logDir,
199
+ `log-${dayjs().format('YYYY-MM-DD HH')}.txt`,
200
+ )
201
+ try {
202
+ let logEntry = ''
203
+ if (isCompress) {
204
+ logEntry = `[${new Date().toISOString()}][compress] ${message.content}\n`
205
+ } else {
206
+ logEntry = `[${new Date().toISOString()}][${message.role}] ${message.content}\n`
207
+ }
208
+ fs.appendFileSync(logFile, logEntry)
209
+ return true
210
+ } catch (error) {
211
+ console.error('Failed to log:', error.message)
212
+ return false
213
+ }
214
+ }
215
+ }
216
+
217
+ module.exports = HistoryManager