ai-worktool 1.0.17 → 1.0.19

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/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ## 1.0.17 (2025-08-06)
1
+ ## 1.0.19 (2025-08-10)
2
2
 
3
3
 
4
4
  ### Bug Fixes
@@ -78,6 +78,7 @@
78
78
  * JEST无法输出colors ([3a1bd24](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/3a1bd24ba0b9b6d78dc9cdb5958f3262d08d9ded))
79
79
  * **login:** 更新登录地址 ([36ba18d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/36ba18db3f2acb42f4964de2e93a6a093909bec2))
80
80
  * **login:** 更新客户端 ID ([bfeac59](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/bfeac590f8bae37c1a123c8356dd600ed9add4ad))
81
+ * **user:** 优化 JWT 解析并添加过期检查 ([33edb60](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/33edb600bd0a114b152e631d2a9259e4b04858bd))
81
82
  * **webview:** 新增文件无法观察问题 ([8f18813](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/8f188134861f5e151d2bcd0e7a1dda5d6a1e0268))
82
83
  * writeFile内部可以覆盖 ([ea4ee44](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/ea4ee442e7f3ec5cdfef5cf9142c8036772aaa11))
83
84
 
@@ -138,6 +139,8 @@
138
139
  * **agent:** 增加工具调用回调并优化测试流程 ([1cae77e](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/1cae77e7bf05c3f72771d29568034eaecfea3c8e))
139
140
  * **agent:** 增加智能体增量回复功能 ([edd406b](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/edd406b905b930a7943c9697248150f9a4505e08))
140
141
  * **ai-worktool:** 重构 Webview 交互逻辑 ([d118dec](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/d118dec14d3ca3644f41a34db4a5b028987df2db))
142
+ * **commands:** 在打开订单和使用页面时添加 token 参数 ([f52c7a3](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/f52c7a349988eeec08370b4fb673bc479256ab72))
143
+ * **commands:** 在打开订单和使用页面时添加 token 参数 ([c20e809](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/c20e809cec5a0c2c1ca886c5414730ce4893fabd))
141
144
  * **configuration:** 增加包管理器、测试框架和编程语言选择配置项 ([ec3cceb](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/ec3cceba695485010c5c710894ea2e49f57737f8))
142
145
  * **dify-plugin:** 新增重命名文件或目录的功能 ([9bb6961](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/9bb69613a28aba6859f72b14b2aa1a586ac9ca70))
143
146
  * **extension:** 重构 Webview 并添加覆盖率报告功能 ([fd8a62b](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/fd8a62b79aff4c03af8b56779d607f01d65c175a))
@@ -147,6 +150,7 @@
147
150
  * **program:** 增加代码覆盖率报告展示功能 ([60da9b5](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/60da9b59e4b913695daa8697dd9aeec16cf78a5f))
148
151
  * **program:** 增加代码覆盖率报告展示功能 ([f693e27](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/f693e272ef0fe7e58083d2589339af17e45cff95))
149
152
  * **project:** 优化项目类型检测逻辑 ([9bc024d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/9bc024deeed239c05c9cab810f9f12a6a3306aab))
153
+ * **project:** 增加加载 aicoder 配置文件功能 ([b4dc70b](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/b4dc70b3afa88cf45135cf56e63cbb2ffa2fed82))
150
154
  * Refactor CLI and program structure, update commands for testing and fixing code ([8dcbb19](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/8dcbb191fc8a95a40e07647de337452935926b10))
151
155
  * **test/demo:** 添加用户成绩 API 路由 ([6f838c7](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/6f838c7fda9924b1e5a7556d0a429427a652b985))
152
156
  * **test:** 添加 demo ([403c8fe](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/403c8fe37d39a21936561e743087d6b25ab6ad56))
package/README.md CHANGED
@@ -1,67 +1,67 @@
1
- # 🎮 吃豆豆:单测智能体
2
-
3
- <p align="center">
4
- <img src="https://aicoder.jianguoke.cn/assets/images/logo.png" alt="吃豆豆logo" width="120" height="120">
5
- </p>
6
-
7
- <p align="center">
8
- <strong>自动生成高质量单元测试,让代码覆盖率轻松达到100% 🚀</strong>
9
- </p>
10
-
11
- <p align="center">
12
- <em>你负责编写源代码,我负责编写测试代码,解放双手,专注核心业务逻辑开发 💻</em>
13
- </p>
14
-
15
- ---
16
-
17
- ## 目录
18
- - [🎮 吃豆豆:单测智能体](#-吃豆豆单测智能体)
19
- - [目录](#目录)
20
- - [🌟 特性亮点](#-特性亮点)
21
- - [🚀 核心功能](#-核心功能)
22
- - [💎 核心价值](#-核心价值)
23
- - [📝 使用指南](#-使用指南)
24
- - [🔧 适用范围](#-适用范围)
25
- - [🚀 开始使用](#-开始使用)
26
- - [💬 联系我们](#-联系我们)
27
-
28
- ## 🌟 特性亮点
29
- - 🔍 **智能代码分析**:深入理解代码结构和逻辑关系
30
- - 📝 **精准测试生成**:针对不同代码场景生成最佳测试策略
31
- - 🔄 **持续优化**:根据测试结果不断完善测试用例
32
- - 💡 **AI驱动**:利用先进的大语言模型提升测试质量
33
-
34
- ## 🚀 核心功能
35
- - 🤖 **自动化测试生成**:自动识别未覆盖代码,生成完整测试用例,直到100%代码覆盖通过
36
- - 🛠️ **智能错误修复**:检测并修复单元测试中的语法错误、逻辑错误及断言失败
37
- - 📊 **覆盖率可视化**:实时展示行/语句/函数/分支覆盖率,通过颜色编码直观呈现测试状态
38
- - 🔄 **全流程自动化**:从环境初始化、依赖安装到持续测试优化,全程无需人工干预
39
-
40
- ## 💎 核心价值
41
- - ⚡ **提升开发效率**:再也**不用手写**单测代码,专注核心业务逻辑开发
42
- - 🛡️ **保障代码质量**:确保测试覆盖率达标,降低线上bug风险
43
- - 🧩 **简化测试流程**:无需深入掌握测试框架细节,一键启动全自动化测试流程
44
- - 👁️ **可视化反馈**:通过直观的覆盖率报表和进度展示,让测试状态一目了然
45
-
46
- ## 📝 使用指南
47
- 1. 📂 打开项目文件夹
48
- 2. ▶️ 启动吃豆豆智能体
49
- 3. 📈 查看实时测试进度和覆盖率报告
50
- 4. 🧠 智能体自动编写代码
51
-
52
- ## 🔧 适用范围
53
- - 🔌 支持多种调用方式:VSCode插件、CLI命令
54
- - 💻 支持多种编程语言:TypeScript、JavaScript等
55
- - 🧪 支持主流测试框架:Jest、Vitest等
56
-
57
- ---
58
-
59
- ## 🚀 开始使用
60
- 准备好提升你的开发效率了吗?立即安装吃豆豆单测智能体,让测试工作变得轻松简单!
61
-
62
- ## 💬 联系我们
63
- 如有任何问题或建议,可以给我发邮件 9160294@qq.com。
64
-
65
- <p align="center">
66
- <strong>© 2025 吃豆豆单测智能体 - 让单元测试自动化、智能化</strong>
1
+ # 🎮 吃豆豆:单测智能体
2
+
3
+ <p align="center">
4
+ <img src="https://aicoder.jianguoke.cn/assets/images/logo.png" alt="吃豆豆logo" width="120" height="120">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>自动生成高质量单元测试,让代码覆盖率轻松达到100% 🚀</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <em>你负责编写源代码,我负责编写测试代码,解放双手,专注核心业务逻辑开发 💻</em>
13
+ </p>
14
+
15
+ ---
16
+
17
+ ## 目录
18
+ - [🎮 吃豆豆:单测智能体](#-吃豆豆单测智能体)
19
+ - [目录](#目录)
20
+ - [🌟 特性亮点](#-特性亮点)
21
+ - [🚀 核心功能](#-核心功能)
22
+ - [💎 核心价值](#-核心价值)
23
+ - [📝 使用指南](#-使用指南)
24
+ - [🔧 适用范围](#-适用范围)
25
+ - [🚀 开始使用](#-开始使用)
26
+ - [💬 联系我们](#-联系我们)
27
+
28
+ ## 🌟 特性亮点
29
+ - 🔍 **智能代码分析**:深入理解代码结构和逻辑关系
30
+ - 📝 **精准测试生成**:针对不同代码场景生成最佳测试策略
31
+ - 🔄 **持续优化**:根据测试结果不断完善测试用例
32
+ - 💡 **AI驱动**:利用先进的大语言模型提升测试质量
33
+
34
+ ## 🚀 核心功能
35
+ - 🤖 **自动化测试生成**:自动识别未覆盖代码,生成完整测试用例,直到100%代码覆盖通过
36
+ - 🛠️ **智能错误修复**:检测并修复单元测试中的语法错误、逻辑错误及断言失败
37
+ - 📊 **覆盖率可视化**:实时展示行/语句/函数/分支覆盖率,通过颜色编码直观呈现测试状态
38
+ - 🔄 **全流程自动化**:从环境初始化、依赖安装到持续测试优化,全程无需人工干预
39
+
40
+ ## 💎 核心价值
41
+ - ⚡ **提升开发效率**:再也**不用手写**单测代码,专注核心业务逻辑开发
42
+ - 🛡️ **保障代码质量**:确保测试覆盖率达标,降低线上bug风险
43
+ - 🧩 **简化测试流程**:无需深入掌握测试框架细节,一键启动全自动化测试流程
44
+ - 👁️ **可视化反馈**:通过直观的覆盖率报表和进度展示,让测试状态一目了然
45
+
46
+ ## 📝 使用指南
47
+ 1. 📂 打开项目文件夹
48
+ 2. ▶️ 启动吃豆豆智能体
49
+ 3. 📈 查看实时测试进度和覆盖率报告
50
+ 4. 🧠 智能体自动编写代码
51
+
52
+ ## 🔧 适用范围
53
+ - 🔌 支持多种调用方式:VSCode插件、CLI命令
54
+ - 💻 支持多种编程语言:TypeScript、JavaScript等
55
+ - 🧪 支持主流测试框架:Jest、Vitest等
56
+
57
+ ---
58
+
59
+ ## 🚀 开始使用
60
+ 准备好提升你的开发效率了吗?立即安装吃豆豆单测智能体,让测试工作变得轻松简单!
61
+
62
+ ## 💬 联系我们
63
+ 如有任何问题或建议,可以给我发邮件 9160294@qq.com。
64
+
65
+ <p align="center">
66
+ <strong>© 2025 吃豆豆单测智能体 - 让单元测试自动化、智能化</strong>
67
67
  </p>
Binary file
@@ -207,7 +207,7 @@ const stopAgent = async (taskId) => {
207
207
  };
208
208
  exports.stopAgent = stopAgent;
209
209
  // 获取机器人响应
210
- const startAgent = async (name, userMessage, vars, withConversation, ctrl, onMessage, onToolCall) => {
210
+ const startAgent = async (name, userMessage, vars, withConversation, ctrl, mode, onMessage, onToolCall) => {
211
211
  if (withConversation !== true) {
212
212
  (0, exports.setConversationId)(''); // 每次重来
213
213
  }
@@ -353,7 +353,12 @@ const startAgent = async (name, userMessage, vars, withConversation, ctrl, onMes
353
353
  break;
354
354
  case 'text':
355
355
  if (contentObj.type === 'text' && contentObj.text) {
356
- botResponse += contentObj.text;
356
+ if (mode === 'full') {
357
+ botResponse += contentObj.text;
358
+ }
359
+ else {
360
+ botResponse = contentObj.text;
361
+ }
357
362
  await onMessage?.(botResponse, name + botId);
358
363
  // addMessage(botResponse, 'bot');
359
364
  }
@@ -62,12 +62,12 @@ const setConversationId = (id) => {
62
62
  platform.setConversationId(id);
63
63
  };
64
64
  exports.setConversationId = setConversationId;
65
- async function startAgent(currentPlatform, name, question, vars, withConversation, ctrl, onMessage, onToolCall) {
65
+ async function startAgent(currentPlatform, name, question, vars, withConversation, ctrl, mode, onMessage, onToolCall) {
66
66
  const platform = platforms[currentPlatform] || getPlatform();
67
67
  if (!platform) {
68
68
  throw new Error(`智能体 "${currentPlatform}" 不支持`);
69
69
  }
70
- await platform.startAgent.apply(null, [name, question, vars, withConversation, ctrl, onMessage, onToolCall]);
70
+ await platform.startAgent.apply(null, [name, question, vars, withConversation, ctrl, mode, onMessage, onToolCall]);
71
71
  console.log(`智能体 "${currentPlatform}" 调用完成`);
72
72
  }
73
73
  //# sourceMappingURL=index.js.map
@@ -91,26 +91,31 @@ async function handleToolCall(toolCall) {
91
91
  if (!tool) {
92
92
  throw new Error(`Tool ${toolCall.toolName} not found`);
93
93
  }
94
+ console.log(toolCall.toolName, toolCall.params);
94
95
  data = (await (0, toolCall_1.callWithObject)(tool, toolCall.params)) || '执行成功';
95
96
  if (toolCall.toolName === 'readFile') {
96
97
  data = `\n\`\`\`${(0, toolCall_1.inferCodeTypeFromExtension)(toolCall.params.filePath)}\n${data}\n\`\`\``;
97
98
  }
99
+ console.log(toolCall.toolName, data);
98
100
  success = true;
99
101
  }
100
102
  catch (err) {
101
103
  success = false;
102
104
  message = err.message || '执行失败';
105
+ console.error(err);
103
106
  }
104
107
  await toolCallback(toolCall.toolName, toolCall.callId, data || message).catch(console.error);
105
108
  return { data, message, success };
106
109
  }
107
- async function startAgent(name, question, vars, withConversation, ctrl, onMessage, onToolCall) {
110
+ async function startAgent(name, question, vars, withConversation, ctrl, mode, onMessage, onToolCall) {
108
111
  let err = null;
109
112
  let msg = '';
110
113
  let lastMsgNo = 0;
111
114
  const baseURL = process.env.AI_SERVER || DEFAULT_SERVER;
112
115
  const token = await (0, user_1.getTokenStorage)().get();
113
116
  console.log(`Starting agent: ${baseURL}, token: ${token}`);
117
+ console.log(question);
118
+ let lastToolId = null;
114
119
  await (0, fetch_sse_1.fetchEventData)(`${baseURL}/api/agent/exec/aicoder`, {
115
120
  method: 'POST',
116
121
  signal: ctrl?.signal,
@@ -139,22 +144,37 @@ async function startAgent(name, question, vars, withConversation, ctrl, onMessag
139
144
  },
140
145
  async onMessage(ev) {
141
146
  const data = JSON.parse(ev.data);
142
- // console.log(ev?.event, data);
147
+ // console.log('Agent Message:', ev?.event, data);
143
148
  if (ev?.event === 'MESSAGE') {
144
149
  if (data.no !== lastMsgNo) {
145
150
  lastMsgNo = data.no;
146
151
  msg = '';
147
152
  }
148
- msg += data.content;
153
+ if (mode === 'full') {
154
+ msg += data.content;
155
+ }
156
+ else {
157
+ msg = data.content;
158
+ }
149
159
  await onMessage?.(msg, 'msg-' + data.id + data.no);
150
160
  }
161
+ if (ev?.event === 'TOOL' && lastToolId !== 'tool-' + data.id + data.no) {
162
+ lastToolId = 'tool-' + data.id + data.no;
163
+ await onMessage?.((0, toolCall_1.formatToolCallInfo)({
164
+ // eslint-disable-next-line @typescript-eslint/naming-convention
165
+ function_name: data.name,
166
+ params: data.params
167
+ }, vars.projectRoot), lastToolId);
168
+ }
151
169
  if (ev?.event === 'TOOLCALL') {
152
- const id = 'tool-' + data.callId;
170
+ if (!lastToolId) {
171
+ console.warn('TOOLCALL without TOOL!');
172
+ }
153
173
  await onMessage?.((0, toolCall_1.formatToolCallInfo)({
154
174
  // eslint-disable-next-line @typescript-eslint/naming-convention
155
175
  function_name: data.toolName,
156
176
  params: data.params
157
- }, vars.projectRoot), id);
177
+ }, vars.projectRoot), lastToolId);
158
178
  const result = await handleToolCall(data);
159
179
  await onToolCall?.({
160
180
  functionName: data.toolName,
@@ -165,13 +185,13 @@ async function startAgent(name, question, vars, withConversation, ctrl, onMessag
165
185
  // eslint-disable-next-line @typescript-eslint/naming-convention
166
186
  function_name: data.toolName,
167
187
  params: data.params
168
- }, vars.projectRoot, result), id);
188
+ }, vars.projectRoot, result), lastToolId);
169
189
  }
170
190
  if (ev?.event === 'DONE') {
171
191
  console.log(msg);
172
192
  }
173
193
  if (ev?.event === 'ERROR') {
174
- err = data.message;
194
+ err = data.error;
175
195
  }
176
196
  }
177
197
  });
@@ -10,32 +10,32 @@ const path_1 = __importDefault(require("path"));
10
10
  const tools_1 = require("../tools");
11
11
  const MAXLEN = 10000;
12
12
  function fixErrorPrompt(error) {
13
- return `单测执行失败,日志如下:
14
- \`\`\`
15
- ${error.slice(0, MAXLEN)}
16
- \`\`\`
13
+ return `单测执行失败,日志如下:
14
+ \`\`\`
15
+ ${error.slice(0, MAXLEN)}
16
+ \`\`\`
17
17
  `;
18
18
  }
19
19
  // 提示词管理,根据任务合成特定提示词
20
20
  async function fixCodePrompt(failedTest) {
21
- return `测试文件"${path_1.default.resolve(failedTest.testFilePath)}"${failedTest.testName ? '里的"' + failedTest.testName + '"' : ''}执行失败.
22
-
23
- 错误信息为:
24
- \`\`\`
25
- ${failedTest.errorMessages.join('\n')}
26
- \`\`\`
27
-
28
- 测试文件内容如下:
29
- \`\`\`
30
- ${await (0, tools_1.readFile)(failedTest.testFilePath, 'utf8', true)}
21
+ return `测试文件"${path_1.default.resolve(failedTest.testFilePath)}"${failedTest.testName ? '里的"' + failedTest.testName + '"' : ''}执行失败.
22
+
23
+ 错误信息为:
24
+ \`\`\`
25
+ ${failedTest.errorMessages.join('\n')}
26
+ \`\`\`
27
+
28
+ 测试文件内容如下:
29
+ \`\`\`
30
+ ${await (0, tools_1.readFile)(failedTest.testFilePath, 'utf8', true)}
31
31
  \`\`\``;
32
32
  }
33
33
  async function addUncoveredLinePrompt(srcPath, report) {
34
- return `源文件"${path_1.default.resolve(srcPath)}"的单元测试覆盖率是${report.coverage.lines.pct}%, 还有"${report.uncoveredLines.join(',')}"行未覆盖单测。
35
-
36
- 源文件内容如下:
37
- \`\`\`
38
- ${await (0, tools_1.readFile)(srcPath, 'utf8', true)}
34
+ return `源文件"${path_1.default.resolve(srcPath)}"的单元测试覆盖率是${report.coverage.lines.pct}%, 还有"${report.uncoveredLines.join(',')}"行未覆盖单测。
35
+
36
+ 源文件内容如下:
37
+ \`\`\`
38
+ ${await (0, tools_1.readFile)(srcPath, 'utf8', true)}
39
39
  \`\`\``;
40
40
  }
41
41
  //# sourceMappingURL=prompt.js.map
@@ -106,28 +106,33 @@ function getParamNames(fn) {
106
106
  }
107
107
  return paramNames;
108
108
  }
109
+ function formatDisplayText(txt) {
110
+ return txt.length > 50 ? txt.slice(0, 50) + '...' : txt;
111
+ }
109
112
  function formatToolCallInfo(
110
113
  // eslint-disable-next-line @typescript-eslint/naming-convention
111
114
  toolCall, root, result) {
112
115
  root = root || '/';
116
+ const params = toolCall.params || {};
113
117
  if (toolCall.function_name === 'readFile') {
114
118
  const outputResult = result ? (result?.success ? ` ✅` : result?.message || '❌') : '';
115
- return `读取文件:${path_1.default.relative(root, toolCall.params[0] || toolCall.params.filePath)}${outputResult}`;
119
+ const filePath = params[0] || params.filePath;
120
+ return `读取文件:${formatDisplayText(filePath ? path_1.default.relative(root, filePath) : '')}${outputResult}`;
116
121
  }
117
122
  if (toolCall.function_name === 'writeFile') {
118
123
  const outputResult = result ? (result?.success ? ` ✅` : result?.message || '❌') : '';
119
- return `修改文件:${path_1.default.relative(root, toolCall.params[0] || toolCall.params.filePath)}${outputResult}`;
124
+ const filePath = params[0] || params.filePath;
125
+ return `修改文件:${formatDisplayText(filePath ? path_1.default.relative(root, filePath) : '')}${outputResult}`;
120
126
  }
121
127
  if (toolCall.function_name === 'searchFilesByExtension') {
122
128
  const outputResult = result
123
129
  ? result?.success
124
- ? ` => ${result.data
125
- .map((filePath) => path_1.default.relative(root, filePath))
126
- .join(', ')
127
- .slice(0, 50)} ✅`
130
+ ? ` => ${formatDisplayText(result.data.map((filePath) => path_1.default.relative(root, filePath)).join(', '))} ✅`
128
131
  : result?.message || '❌'
129
132
  : '';
130
- return `检索文件:${path_1.default.relative(root, toolCall.params[0] || toolCall.params.directory)}/*${toolCall.params[1] || toolCall.params.extensions}${outputResult}`;
133
+ const exts = params[1] || params.extensions;
134
+ const directory = path_1.default.relative(root, params[0] || params.directory);
135
+ return `检索文件:${directory ? directory : ''}${exts ? '/*' : ''}${exts}${outputResult}`;
131
136
  }
132
137
  const outputResult = result ? (result?.success ? ` ✅` : result?.message || '❌') : '';
133
138
  return toolCall.function_name + outputResult;
package/dist/cli.js CHANGED
File without changes
package/dist/program.js CHANGED
@@ -58,34 +58,48 @@ program
58
58
  program
59
59
  .command('start [projectRoot]')
60
60
  .description('启动单测智能体,根据源代码文件开始补全单测代码,直到覆盖率100%')
61
- .option('-p, --platform <string>', '智能体平台', 'jianguoke')
62
- .option('-r, --projectRoot <directory>', '源代码文件夹', (txt) => path_1.default.resolve(txt), process.cwd())
63
- .option('-t, --testDir <string>', '测试用例文件夹', 'test')
64
- .option('-s, --srcDir <string>', '测试用例文件夹', 'src')
65
- .option('-c, --coverageDir <string>', '覆盖率报告文件夹', 'coverage')
66
- .option('--autoCommit <boolean>', '覆盖率报告文件夹', (txt) => txt === 'true', true)
61
+ .option('--platform <string>', '智能体平台', 'jianguoke')
62
+ .option('--projectRoot <directory>', '源代码文件夹', (txt) => path_1.default.resolve(txt), process.cwd())
63
+ .option('--testDir <string>', '测试用例文件夹', 'test')
64
+ .option('--srcDir <string>', '测试用例文件夹', 'src')
65
+ .option('--coverageDir <string>', '覆盖率报告文件夹', 'coverage')
66
+ .option('-p, --packageManager <string>', '包管理器', 'npm')
67
+ .option('-f, --testFramework <string>', '测试框架', 'jest')
68
+ .option('-l, --language <string>', '开发语言', 'javascript')
69
+ .option('-a, --autoCommit <boolean>', '覆盖率报告文件夹', (txt) => txt === 'true', true)
67
70
  .option('--fullCoverageEnd <boolean>', '是否在达到100%覆盖率后结束', (txt) => txt === 'true', true)
68
71
  .action(async (projectRoot, options) => {
69
72
  let projectConfig;
73
+ const tasks = [];
70
74
  await (0, testAgent_1.startTestAgent)({
71
75
  ...options,
72
76
  projectRoot: projectRoot || options.projectRoot,
77
+ mode: 'increment',
73
78
  onReady: async (project) => {
74
79
  projectConfig = project;
75
80
  },
76
- onMessage: async (message) => {
81
+ onMessage: (message) => {
77
82
  logHandler(message);
78
83
  if (message.includes('当前代码总覆盖率')) {
79
- await (0, view_1.loadAndDisplayCoverage)(path_1.default.join(projectConfig.projectRoot, projectConfig.coverageDir, view_1.COVERAGE_FILE_PATH), 0);
84
+ const task = (0, view_1.loadAndDisplayCoverage)(path_1.default.join(projectConfig.projectRoot, projectConfig.coverageDir, view_1.COVERAGE_FILE_PATH), 0);
85
+ tasks.push(task);
86
+ task
87
+ .then(() => {
88
+ tasks.splice(tasks.indexOf(task), 1);
89
+ })
90
+ .catch(() => {
91
+ tasks.splice(tasks.indexOf(task), 1);
92
+ });
80
93
  }
81
94
  }
82
95
  });
96
+ await Promise.all(tasks);
83
97
  });
84
98
  program
85
99
  .command('show [projectRoot]')
86
100
  .description('监控代码覆盖率报告')
87
- .option('-r, --projectRoot <directory>', '源代码文件夹', (txt) => path_1.default.resolve(txt), process.cwd())
88
- .option('-c, --coverageDir <string>', '覆盖率报告文件夹', 'coverage')
101
+ .option('--projectRoot <directory>', '源代码文件夹', (txt) => path_1.default.resolve(txt), process.cwd())
102
+ .option('--coverageDir <string>', '覆盖率报告文件夹', 'coverage')
89
103
  .option('-w watch <boolean>', '是否监控代码覆盖率报告变化', false)
90
104
  .action(async (projectRoot, options) => {
91
105
  await (0, tools_1.runJestTests)(projectRoot || options.projectRoot);
package/dist/testAgent.js CHANGED
@@ -37,17 +37,16 @@ async function startTestAgent(options) {
37
37
  break;
38
38
  }
39
39
  if (!result.success) {
40
- // 单测执行失败,没有发现任何需要测试的代码
41
- if (result.noTestsFound) {
42
- log('没有发现任何需要测试的源代码...');
43
- if (!options.fullCoverageEnd) {
44
- await (0, tools_1.wait)(options.idleTimeout || 5000);
45
- continue;
46
- }
47
- else {
48
- break;
49
- }
50
- }
40
+ // // 单测执行失败,没有发现任何需要测试的代码
41
+ // if (result.noTestsFound) {
42
+ // log('没有发现任何需要测试的源代码...');
43
+ // if (!options.fullCoverageEnd) {
44
+ // await wait(options.idleTimeout || 5000);
45
+ // continue;
46
+ // } else {
47
+ // break;
48
+ // }
49
+ // }
51
50
  // 上一次执行必须有写入类操作,否则不会有新的单测覆盖率变化
52
51
  if (lastTestResult?.stderr === result.stderr &&
53
52
  toolCalls.every((toolCall) => !tools_1.modifyTools.includes(toolCall.functionName))) {
@@ -58,7 +57,7 @@ async function startTestAgent(options) {
58
57
  lastTestResult = result;
59
58
  // 单测执行失败,比如代码编译不通过
60
59
  log('单测执行失败,开始修复...');
61
- await (0, agents_1.startAgent)(options.platform, 'fixconfig', (0, agents_1.fixErrorPrompt)(result.stderr), projectConfig, options.withConversation, ctrl, options.onMessage, async (tool) => {
60
+ await (0, agents_1.startAgent)(options.platform, 'fixconfig', (0, agents_1.fixErrorPrompt)(result.stderr), projectConfig, options.withConversation, ctrl, options.mode, options.onMessage, async (tool) => {
62
61
  toolCalls.push(tool);
63
62
  });
64
63
  if (ctrl?.signal.aborted) {
@@ -86,7 +85,7 @@ async function startTestAgent(options) {
86
85
  log(`发现有失败的单测${path_1.default.basename(failedTest.testFilePath)},开始修复...`);
87
86
  await (0, agents_1.startAgent)(options.platform,
88
87
  // 没有testName说明是修复单测代码本身问题
89
- failedTest.testName ? 'fixbug' : 'fixconfig', await (0, agents_1.fixCodePrompt)(failedTest), projectConfig, options.withConversation, ctrl, options.onMessage, async (tool) => {
88
+ failedTest.testName ? 'fixbug' : 'fixconfig', await (0, agents_1.fixCodePrompt)(failedTest), projectConfig, options.withConversation, ctrl, options.mode, options.onMessage, async (tool) => {
90
89
  toolCalls.push(tool);
91
90
  });
92
91
  if (ctrl?.signal.aborted) {
@@ -111,7 +110,7 @@ async function startTestAgent(options) {
111
110
  continue;
112
111
  }
113
112
  log(`发现有未覆盖行,开始补充...`, filePath);
114
- await (0, agents_1.startAgent)(options.platform, 'addtest', await (0, agents_1.addUncoveredLinePrompt)(filePath, uncoveredLineFile), projectConfig, options.withConversation, ctrl, options.onMessage, async (tool) => {
113
+ await (0, agents_1.startAgent)(options.platform, 'addtest', await (0, agents_1.addUncoveredLinePrompt)(filePath, uncoveredLineFile), projectConfig, options.withConversation, ctrl, options.mode, options.onMessage, async (tool) => {
115
114
  toolCalls.push(tool);
116
115
  });
117
116
  if (ctrl?.signal.aborted) {
@@ -67,7 +67,8 @@ function parseJavaScriptFile(filePath) {
67
67
  * @param ast AST 对象
68
68
  * @returns 导出函数名称数组
69
69
  */
70
- function extractExportedFunctions(ast) {
70
+ function extractExportedFunctions(filePath) {
71
+ const ast = typeof filePath === 'string' ? parseJavaScriptFile(filePath) : filePath;
71
72
  const exportedFunctions = [];
72
73
  walk.simple(ast, {
73
74
  // 处理命名函数导出
@@ -101,7 +102,8 @@ function extractExportedFunctions(ast) {
101
102
  * @param ast AST 对象
102
103
  * @returns 导出类名称数组
103
104
  */
104
- function extractExportedClasses(ast) {
105
+ function extractExportedClasses(filePath) {
106
+ const ast = typeof filePath === 'string' ? parseJavaScriptFile(filePath) : filePath;
105
107
  const exportedClasses = [];
106
108
  walk.simple(ast, {
107
109
  // 处理命名类导出
@@ -135,7 +137,8 @@ function extractExportedClasses(ast) {
135
137
  * @param ast AST 对象
136
138
  * @returns 导入模块路径数组
137
139
  */
138
- function extractImportedModules(ast) {
140
+ function extractImportedModules(filePath) {
141
+ const ast = typeof filePath === 'string' ? parseJavaScriptFile(filePath) : filePath;
139
142
  const importedModules = [];
140
143
  walk.simple(ast, {
141
144
  ImportDeclaration(node) {
@@ -42,7 +42,6 @@ exports.getFailedTests = getFailedTests;
42
42
  exports.parseLcovForUncoveredLines = parseLcovForUncoveredLines;
43
43
  exports.readCoverageSummary = readCoverageSummary;
44
44
  exports.generateUncoveredLinesReport = generateUncoveredLinesReport;
45
- exports.extractFailedTests = extractFailedTests;
46
45
  exports.getFailedTestSummaries = getFailedTestSummaries;
47
46
  exports.findTestErrorDetails = findTestErrorDetails;
48
47
  exports.clearJsonReportCache = clearJsonReportCache;
@@ -90,7 +89,7 @@ async function createJestConfigFile(configPath, options = {}) {
90
89
  'src/**/*.{js,jsx,ts,tsx}', // 例如:src 目录下所有 JS/TS 文件
91
90
  '!src/**/*.d.ts', // 排除类型定义文件(可选)
92
91
  '!src/mocks/**', // 排除 mock 文件(可选)
93
- // '!src/**/index.{js,ts}', // 排除入口文件(可选,根据需求调整)
92
+ '!src/**/index.{js,ts}', // 排除入口文件(可选,根据需求调整)
94
93
  '!**/node_modules/**', // 排除 node_modules
95
94
  '!**/vendor/**' // 排除第三方依赖(如有)
96
95
  ],
@@ -129,6 +128,9 @@ async function getTestResultsState(projectRoot, coverageDir = 'coverage') {
129
128
  */
130
129
  async function runJestTests(projectRoot, coverageDir = 'coverage', jestConfigPath) {
131
130
  coverageDir = coverageDir || 'coverage';
131
+ if (!(await fileExists(path.join(projectRoot, 'package.json')))) {
132
+ throw new Error('当前目录下不存在 package.json 文件,请确保当前目录为项目根目录。');
133
+ }
132
134
  return new Promise((resolve, reject) => {
133
135
  const now = Date.now();
134
136
  const testResultsFile = path.join(projectRoot, coverageDir, 'test-results.json');
@@ -185,6 +185,7 @@ async function addScript(projectPath, scriptName, scriptCommand) {
185
185
  */
186
186
  async function runTest(projectPath, args = [], manager = 'yarn') {
187
187
  try {
188
+ args = Array.isArray(args) ? args : args.split(' ');
188
189
  const resolvedPath = path_1.default.resolve(projectPath);
189
190
  const runCmd = getRunScriptCommand('test', args, manager);
190
191
  const [cmd, ...cmdArgs] = runCmd.split(' ').filter(Boolean);
@@ -343,16 +344,19 @@ async function setupTestEnvironment(projectRoot, packageManager, devLanguage, te
343
344
  if (packagesToInstall.length > 0) {
344
345
  const installCmd = getInstallCommand(packagesToInstall, true, packageManager);
345
346
  await executeCommand(projectRoot, installCmd, `安装${testFramework}及相关依赖`);
346
- // 框架特定的配置
347
- let exists = false;
348
- try {
349
- await promises_1.default.access(path_1.default.join(projectRoot, 'jest.config.js'));
350
- exists = true;
351
- }
352
- catch { }
353
- if (testFramework === 'jest' && !exists) {
354
- await executeCommand(projectRoot, 'npx ts-jest config:init', '配置Jest TypeScript支持');
355
- }
347
+ // // 框架特定的配置
348
+ // let exists = false;
349
+ // try {
350
+ // await fs.access(path.join(projectRoot, 'jest.config.js'));
351
+ // exists = true;
352
+ // } catch {}
353
+ // if (testFramework === 'jest') {
354
+ // if (!exists) {
355
+ // }
356
+ // if (devLanguage === 'typescript') {
357
+ // await executeCommand(projectRoot, 'npx ts-jest config:init', '配置Jest TypeScript支持');
358
+ // }
359
+ // }
356
360
  console.log('✅ 测试环境搭建完成!');
357
361
  }
358
362
  // 检查并添加test脚本
@@ -20,7 +20,7 @@ const path_1 = __importDefault(require("path"));
20
20
  */
21
21
  async function detectProjectConfig(projectRoot, defaultConfig) {
22
22
  defaultConfig = defaultConfig || {};
23
- const config = {
23
+ let config = {
24
24
  projectRoot: defaultConfig.projectRoot || projectRoot,
25
25
  projectType: defaultConfig.projectType || 'unknown',
26
26
  packageManager: defaultConfig.packageManager || 'unknown',
@@ -33,22 +33,32 @@ async function detectProjectConfig(projectRoot, defaultConfig) {
33
33
  testConfigFile: defaultConfig.testConfigFile || undefined,
34
34
  isInstalled: defaultConfig.isInstalled || undefined
35
35
  };
36
+ if (!projectRoot) {
37
+ throw new Error('项目文件夹未知.');
38
+ }
39
+ // 检查文件夹是否存在
40
+ if (!(await directoryExists(projectRoot))) {
41
+ throw new Error(`项目文件夹 "${projectRoot}" 不存在.`);
42
+ }
43
+ // 检测包管理工具
44
+ await detectPackageManager(projectRoot, config);
36
45
  // 首先检查项目是否已安装
37
46
  await checkProjectInstalled(projectRoot, config);
38
- // 只有项目已安装的情况下才进行其他检测
39
- if (config.isInstalled) {
40
- // 检测包管理工具
41
- await detectPackageManager(projectRoot, config);
42
- // 检测使用的开发语言
43
- await detectLanguages(projectRoot, config);
44
- // 检测源代码目录
45
- await detectSrcDir(projectRoot, config);
46
- // 检测测试相关配置
47
- await detectTestConfig(projectRoot, config);
48
- // 检测项目类型(前端/后端/全栈)
49
- await detectProjectType(projectRoot, config);
50
- // 检测框架信息
51
- await detectFramework(projectRoot, config);
47
+ // 检测使用的开发语言
48
+ await detectLanguages(projectRoot, config);
49
+ // 检测源代码目录
50
+ await detectSrcDir(projectRoot, config);
51
+ // 检测测试相关配置
52
+ await detectTestConfig(projectRoot, config);
53
+ // 检测项目类型(前端/后端/全栈)
54
+ await detectProjectType(projectRoot, config);
55
+ // 检测框架信息
56
+ await detectFramework(projectRoot, config);
57
+ // 检查项目目录中是否有aicoder.config.json有的直接加载作为配置信息
58
+ if (await fileExists(path_1.default.join(projectRoot, 'aicoder.config.json'))) {
59
+ const aicoderConfig = JSON.parse(await promises_1.default.readFile(path_1.default.join(projectRoot, 'aicoder.config.json'), 'utf8'));
60
+ // 合并配置信息
61
+ config = { ...config, ...aicoderConfig };
52
62
  }
53
63
  return config;
54
64
  }
@@ -81,7 +91,7 @@ async function detectPackageManager(projectRoot, config) {
81
91
  return;
82
92
  }
83
93
  }
84
- // config.packageManager = 'unknown';
94
+ config.packageManager = 'npm';
85
95
  }
86
96
  /**
87
97
  * 检测开发语言 - 排除node_modules和隐藏目录
@@ -257,7 +267,7 @@ async function detectTestConfig(projectRoot, config) {
257
267
  const testDirPath = path_1.default.join(projectRoot, config.testDir);
258
268
  if (await directoryExists(testDirPath)) {
259
269
  // 检测测试目录中实际存在的文件模式
260
- const detectedPatterns = await detectExistingTestPatterns(testDirPath, projectRoot, extensions);
270
+ const detectedPatterns = await detectExistingTestPatterns(testDirPath, projectRoot, Array.from(extensions));
261
271
  config.testFilePatterns = detectedPatterns;
262
272
  }
263
273
  }
@@ -271,6 +281,7 @@ async function detectTestConfig(projectRoot, config) {
271
281
  */
272
282
  async function detectExistingTestPatterns(testDir, projectRoot, extensions) {
273
283
  const patterns = new Set();
284
+ const extensionSets = new Set(Array.isArray(extensions) ? extensions : extensions.split(','));
274
285
  const relativeTestDir = path_1.default.relative(projectRoot, testDir);
275
286
  // 递归检查测试目录中的文件
276
287
  async function checkDir(currentDir) {
@@ -283,7 +294,7 @@ async function detectExistingTestPatterns(testDir, projectRoot, extensions) {
283
294
  else if (entry.isFile()) {
284
295
  // 检查文件是否匹配测试文件命名规范
285
296
  const extMatch = entry.name.match(/\.([^.]+)$/);
286
- if (extMatch && extensions.has(extMatch[1])) {
297
+ if (extMatch && extensionSets.has(extMatch[1])) {
287
298
  const fileName = entry.name.replace(/\.[^.]+$/, '');
288
299
  if (fileName.endsWith('.test') || fileName.endsWith('.spec')) {
289
300
  // 生成对应的glob模式
package/dist/user.js CHANGED
@@ -115,54 +115,54 @@ function startCallbackServer(state, port) {
115
115
  if (callbackState !== state) {
116
116
  // eslint-disable-next-line @typescript-eslint/naming-convention
117
117
  res.writeHead(403, { 'Content-Type': 'text/html; charset=utf-8' });
118
- res.end(`
119
- <html>
120
- <head>
121
- <style>
122
- body {
123
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
124
- display: flex;
125
- flex-direction: column;
126
- align-items: center;
127
- justify-content: center;
128
- height: 100vh;
129
- margin: 0;
130
- background-color: #f8f9fa;
131
- color: #333;
132
- }
133
- .card {
134
- background-color: white;
135
- border-radius: 12px;
136
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
137
- padding: 2rem;
138
- text-align: center;
139
- max-width: 400px;
140
- width: 90%;
141
- border-left: 4px solid #dc3545;
142
- }
143
- h1 {
144
- color: #dc3545;
145
- margin-bottom: 1rem;
146
- font-size: 1.8rem;
147
- }
148
- p {
149
- margin: 0.5rem 0;
150
- color: #6c757d;
151
- }
152
- .countdown {
153
- font-weight: bold;
154
- color: #dc3545;
155
- font-size: 1.2rem;
156
- }
157
- </style>
158
- </head>
159
- <body>
160
- <div class="card">
161
- <h1>登录验证失败</h1>
162
- <p>无效的状态参数,可能存在安全风险</p>
163
- </div>
164
- </body>
165
- </html>
118
+ res.end(`
119
+ <html>
120
+ <head>
121
+ <style>
122
+ body {
123
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
124
+ display: flex;
125
+ flex-direction: column;
126
+ align-items: center;
127
+ justify-content: center;
128
+ height: 100vh;
129
+ margin: 0;
130
+ background-color: #f8f9fa;
131
+ color: #333;
132
+ }
133
+ .card {
134
+ background-color: white;
135
+ border-radius: 12px;
136
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
137
+ padding: 2rem;
138
+ text-align: center;
139
+ max-width: 400px;
140
+ width: 90%;
141
+ border-left: 4px solid #dc3545;
142
+ }
143
+ h1 {
144
+ color: #dc3545;
145
+ margin-bottom: 1rem;
146
+ font-size: 1.8rem;
147
+ }
148
+ p {
149
+ margin: 0.5rem 0;
150
+ color: #6c757d;
151
+ }
152
+ .countdown {
153
+ font-weight: bold;
154
+ color: #dc3545;
155
+ font-size: 1.2rem;
156
+ }
157
+ </style>
158
+ </head>
159
+ <body>
160
+ <div class="card">
161
+ <h1>登录验证失败</h1>
162
+ <p>无效的状态参数,可能存在安全风险</p>
163
+ </div>
164
+ </body>
165
+ </html>
166
166
  `);
167
167
  reject(new Error('登录验证失败:无效的状态参数'));
168
168
  server.close();
@@ -177,121 +177,121 @@ function startCallbackServer(state, port) {
177
177
  // 返回包含自动刷新和关闭逻辑的HTML
178
178
  // eslint-disable-next-line @typescript-eslint/naming-convention
179
179
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
180
- res.end(`
181
- <html>
182
- <head>
183
- <title>登录成功</title>
184
- <style>
185
- body {
186
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
187
- display: flex;
188
- flex-direction: column;
189
- align-items: center;
190
- justify-content: center;
191
- height: 100vh;
192
- margin: 0;
193
- background-color: #f8f9fa;
194
- color: #333;
195
- }
196
- .card {
197
- background-color: white;
198
- border-radius: 12px;
199
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
200
- padding: 2rem;
201
- text-align: center;
202
- max-width: 400px;
203
- width: 90%;
204
- border-left: 4px solid #28a745;
205
- }
206
- h1 {
207
- color: #28a745;
208
- margin-bottom: 1rem;
209
- font-size: 1.8rem;
210
- }
211
- p {
212
- margin: 0.5rem 0;
213
- color: #6c757d;
214
- }
215
- .countdown {
216
- font-weight: bold;
217
- color: #28a745;
218
- font-size: 1.2rem;
219
- }
220
- .checkmark {
221
- font-size: 3rem;
222
- color: #28a745;
223
- margin-bottom: 1rem;
224
- }
225
- </style>
226
- </head>
227
- <body>
228
- <div class="card">
229
- <div class="checkmark">✓</div>
230
- <h1>登录成功!</h1>
231
- <p>您可以关闭此窗口返回应用。</p>
232
- </div>
233
- </body>
234
- </html>
180
+ res.end(`
181
+ <html>
182
+ <head>
183
+ <title>登录成功</title>
184
+ <style>
185
+ body {
186
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
187
+ display: flex;
188
+ flex-direction: column;
189
+ align-items: center;
190
+ justify-content: center;
191
+ height: 100vh;
192
+ margin: 0;
193
+ background-color: #f8f9fa;
194
+ color: #333;
195
+ }
196
+ .card {
197
+ background-color: white;
198
+ border-radius: 12px;
199
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
200
+ padding: 2rem;
201
+ text-align: center;
202
+ max-width: 400px;
203
+ width: 90%;
204
+ border-left: 4px solid #28a745;
205
+ }
206
+ h1 {
207
+ color: #28a745;
208
+ margin-bottom: 1rem;
209
+ font-size: 1.8rem;
210
+ }
211
+ p {
212
+ margin: 0.5rem 0;
213
+ color: #6c757d;
214
+ }
215
+ .countdown {
216
+ font-weight: bold;
217
+ color: #28a745;
218
+ font-size: 1.2rem;
219
+ }
220
+ .checkmark {
221
+ font-size: 3rem;
222
+ color: #28a745;
223
+ margin-bottom: 1rem;
224
+ }
225
+ </style>
226
+ </head>
227
+ <body>
228
+ <div class="card">
229
+ <div class="checkmark">✓</div>
230
+ <h1>登录成功!</h1>
231
+ <p>您可以关闭此窗口返回应用。</p>
232
+ </div>
233
+ </body>
234
+ </html>
235
235
  `);
236
236
  resolve(token);
237
237
  }
238
238
  else {
239
239
  // eslint-disable-next-line @typescript-eslint/naming-convention
240
240
  res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
241
- res.end(`
242
- <html>
243
- <head>
244
- <style>
245
- body {
246
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
247
- display: flex;
248
- flex-direction: column;
249
- align-items: center;
250
- justify-content: center;
251
- height: 100vh;
252
- margin: 0;
253
- background-color: #f8f9fa;
254
- color: #333;
255
- }
256
- .card {
257
- background-color: white;
258
- border-radius: 12px;
259
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
260
- padding: 2rem;
261
- text-align: center;
262
- max-width: 400px;
263
- width: 90%;
264
- border-left: 4px solid #dc3545;
265
- }
266
- h1 {
267
- color: #dc3545;
268
- margin-bottom: 1rem;
269
- font-size: 1.8rem;
270
- }
271
- p {
272
- margin: 0.5rem 0;
273
- color: #6c757d;
274
- }
275
- .countdown {
276
- font-weight: bold;
277
- color: #dc3545;
278
- font-size: 1.2rem;
279
- }
280
- .error-icon {
281
- font-size: 3rem;
282
- color: #dc3545;
283
- margin-bottom: 1rem;
284
- }
285
- </style>
286
- </head>
287
- <body>
288
- <div class="card">
289
- <div class="error-icon">✗</div>
290
- <h1>登录失败</h1>
291
- <p>未收到有效的认证信息</p>
292
- </div>
293
- </body>
294
- </html>
241
+ res.end(`
242
+ <html>
243
+ <head>
244
+ <style>
245
+ body {
246
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
247
+ display: flex;
248
+ flex-direction: column;
249
+ align-items: center;
250
+ justify-content: center;
251
+ height: 100vh;
252
+ margin: 0;
253
+ background-color: #f8f9fa;
254
+ color: #333;
255
+ }
256
+ .card {
257
+ background-color: white;
258
+ border-radius: 12px;
259
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
260
+ padding: 2rem;
261
+ text-align: center;
262
+ max-width: 400px;
263
+ width: 90%;
264
+ border-left: 4px solid #dc3545;
265
+ }
266
+ h1 {
267
+ color: #dc3545;
268
+ margin-bottom: 1rem;
269
+ font-size: 1.8rem;
270
+ }
271
+ p {
272
+ margin: 0.5rem 0;
273
+ color: #6c757d;
274
+ }
275
+ .countdown {
276
+ font-weight: bold;
277
+ color: #dc3545;
278
+ font-size: 1.2rem;
279
+ }
280
+ .error-icon {
281
+ font-size: 3rem;
282
+ color: #dc3545;
283
+ margin-bottom: 1rem;
284
+ }
285
+ </style>
286
+ </head>
287
+ <body>
288
+ <div class="card">
289
+ <div class="error-icon">✗</div>
290
+ <h1>登录失败</h1>
291
+ <p>未收到有效的认证信息</p>
292
+ </div>
293
+ </body>
294
+ </html>
295
295
  `);
296
296
  reject(new Error('登录失败:未收到token'));
297
297
  }
@@ -440,6 +440,17 @@ async function getUserData() {
440
440
  }
441
441
  return null;
442
442
  }
443
+ function isTokenExpired(payload) {
444
+ if (!payload || !payload.exp) {
445
+ // 没有过期时间字段,视为过期
446
+ return true;
447
+ }
448
+ // JWT的exp是秒级时间戳,需要转换为毫秒
449
+ const expirationTime = payload.exp * 1000;
450
+ const currentTime = Date.now();
451
+ // 检查是否已过期(提前30秒过期,避免网络延迟问题)
452
+ return currentTime >= expirationTime - 30000;
453
+ }
443
454
  /**
444
455
  * 解析JWT获取其中的信息
445
456
  * @param token JWT字符串
@@ -449,6 +460,9 @@ function decodeJwt(token) {
449
460
  try {
450
461
  // 不验证签名,仅解码获取信息
451
462
  const decoded = jsonwebtoken_1.default.decode(token);
463
+ if (isTokenExpired(decoded)) {
464
+ return null;
465
+ }
452
466
  return decoded;
453
467
  }
454
468
  catch (error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-worktool",
3
3
  "displayName": "吃豆豆:单测智能体",
4
- "version": "1.0.17",
4
+ "version": "1.0.19",
5
5
  "description": "单元测试智能体,帮你开发单元测试用例代码直到100%单测覆盖通过。",
6
6
  "author": "jianguoke.cn",
7
7
  "license": "MIT",
@@ -14,7 +14,8 @@
14
14
  "build": "tsc",
15
15
  "test": "jest test/tools",
16
16
  "coverage": "jest --coverage test/tools",
17
- "plugin": "ts-node ./tools.ts && dify plugin package plugins/dify-plugin",
17
+ "tools": "ts-node ./tools.ts",
18
+ "plugin": "yarn tools && dify plugin package plugins/dify-plugin",
18
19
  "package": "cross-env PKG_CACHE_PATH=./binaries pkg . --targets node20-win-x64 --out-path aicoder ",
19
20
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
20
21
  "ver": "node ./updatever.js",
@@ -276,6 +277,7 @@
276
277
  "adm-zip": "^0.5.16",
277
278
  "conventional-changelog-cli": "^2.2.2",
278
279
  "cross-env": "^10.0.0",
280
+ "dotenv": "^17.2.1",
279
281
  "eslint": "^7.32.0",
280
282
  "husky": "^9.1.7",
281
283
  "jest": "^29.7.0",
@@ -288,6 +290,9 @@
288
290
  "vsce": "^2.6.4"
289
291
  },
290
292
  "jest": {
293
+ "setupFiles": [
294
+ "dotenv/config"
295
+ ],
291
296
  "preset": "ts-jest",
292
297
  "testEnvironment": "node"
293
298
  }