@bangdao-ai/acw-tools 1.2.7 → 1.2.8-beta.1
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/cursorConversationParser.js +111 -4
- package/index.js +37 -9
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -95,6 +95,89 @@ function extractFilePath(toolResult, rawArgs) {
|
|
|
95
95
|
return filePath;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* 解析write工具的代码变更统计
|
|
100
|
+
* @param {Object} bubble - bubble数据
|
|
101
|
+
* @returns {Object|null} 按语言聚合的代码变更统计,格式:{ languageId: { added: number, removed: number } }
|
|
102
|
+
*/
|
|
103
|
+
function parseCodeChanges(bubble) {
|
|
104
|
+
// 1. 检查是否是accepted的write操作
|
|
105
|
+
const toolFormerData = bubble.toolFormerData;
|
|
106
|
+
if (!toolFormerData || toolFormerData.name !== 'write' || toolFormerData.userDecision !== 'accepted') {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 2. 解析result获取diff chunks
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = typeof toolFormerData.result === 'string'
|
|
114
|
+
? JSON.parse(toolFormerData.result)
|
|
115
|
+
: toolFormerData.result;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!result || !result.diff || !Array.isArray(result.diff.chunks)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. 从rawArgs获取file_path
|
|
125
|
+
let filePath = null;
|
|
126
|
+
try {
|
|
127
|
+
const rawArgs = typeof toolFormerData.rawArgs === 'string'
|
|
128
|
+
? JSON.parse(toolFormerData.rawArgs)
|
|
129
|
+
: toolFormerData.rawArgs;
|
|
130
|
+
filePath = rawArgs?.file_path;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// 忽略解析错误
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!filePath) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 4. 在codeBlocks中查找对应的languageId
|
|
140
|
+
let languageId = 'unknown';
|
|
141
|
+
if (bubble.codeBlocks && Array.isArray(bubble.codeBlocks)) {
|
|
142
|
+
const matchedBlock = bubble.codeBlocks.find(block => {
|
|
143
|
+
if (!block.uri || !block.uri.path) return false;
|
|
144
|
+
|
|
145
|
+
// 尝试精确匹配
|
|
146
|
+
if (block.uri.path === filePath) return true;
|
|
147
|
+
|
|
148
|
+
// 尝试路径结尾匹配(处理相对路径 vs 绝对路径的情况)
|
|
149
|
+
// 例如: filePath="frontend/README.md" 可以匹配 uri.path="/path/to/frontend/README.md"
|
|
150
|
+
if (block.uri.path.endsWith(filePath)) return true;
|
|
151
|
+
if (filePath.endsWith(block.uri.path)) return true;
|
|
152
|
+
|
|
153
|
+
return false;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (matchedBlock && matchedBlock.languageId) {
|
|
157
|
+
languageId = matchedBlock.languageId;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 5. 统计所有chunks的增删行数
|
|
162
|
+
let totalAdded = 0;
|
|
163
|
+
let totalRemoved = 0;
|
|
164
|
+
|
|
165
|
+
for (const chunk of result.diff.chunks) {
|
|
166
|
+
// 直接使用chunk中的linesAdded和linesRemoved字段
|
|
167
|
+
totalAdded += chunk.linesAdded || 0;
|
|
168
|
+
totalRemoved += chunk.linesRemoved || 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 6. 返回按语言聚合的结果(增加文件路径信息)
|
|
172
|
+
return {
|
|
173
|
+
[languageId]: {
|
|
174
|
+
added: totalAdded,
|
|
175
|
+
removed: totalRemoved,
|
|
176
|
+
filePath: filePath // 新增:文件路径,用于后端识别真实语言
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
98
181
|
/**
|
|
99
182
|
* 提取附加信息(性能指标和会话元数据)
|
|
100
183
|
* @param {Object} composerData - Composer数据
|
|
@@ -146,12 +229,19 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
146
229
|
for (const bubble of bubbles) {
|
|
147
230
|
const bubbleType = bubble.type || 0;
|
|
148
231
|
|
|
232
|
+
// 跳过没有bubbleId的bubble(不应该发生,但作为保护措施)
|
|
233
|
+
if (!bubble.bubbleId) {
|
|
234
|
+
console.error('[WARN] bubble缺少bubbleId,跳过:', { type: bubbleType, createdAt: bubble.createdAt });
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
149
238
|
if (bubbleType === 1) {
|
|
150
239
|
// User消息
|
|
151
240
|
additionalInfo.performance.userMessageCount++;
|
|
152
241
|
|
|
153
242
|
// 记录用户消息的时间戳和提示词内容
|
|
154
243
|
const execution = {
|
|
244
|
+
bubbleId: bubble.bubbleId, // 添加bubbleId作为主键
|
|
155
245
|
type: 'user',
|
|
156
246
|
timestamp: bubble.createdAt || null,
|
|
157
247
|
modelName: null,
|
|
@@ -166,7 +256,15 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
166
256
|
// 提取用户提示词内容并添加到 execution 中
|
|
167
257
|
const userText = bubble.text || bubble.richText || '';
|
|
168
258
|
if (userText && userText.trim() && userText.trim() !== '*(无文本内容)*') {
|
|
169
|
-
|
|
259
|
+
const trimmedText = userText.trim();
|
|
260
|
+
// 限制prompt最大长度为10MB,避免超大内容导致上传失败
|
|
261
|
+
const MAX_PROMPT_LENGTH = 10 * 1024 * 1024; // 10MB
|
|
262
|
+
if (trimmedText.length > MAX_PROMPT_LENGTH) {
|
|
263
|
+
execution.prompt = trimmedText.substring(0, MAX_PROMPT_LENGTH) +
|
|
264
|
+
'\n\n...[内容过长已截断,原始长度: ' + trimmedText.length + ' 字符]';
|
|
265
|
+
} else {
|
|
266
|
+
execution.prompt = trimmedText;
|
|
267
|
+
}
|
|
170
268
|
}
|
|
171
269
|
|
|
172
270
|
// 新增: 记录 cursorCommands (只在数组不为空时添加)
|
|
@@ -193,6 +291,7 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
193
291
|
|
|
194
292
|
// 构建execution对象
|
|
195
293
|
const execution = {
|
|
294
|
+
bubbleId: bubble.bubbleId, // 添加bubbleId作为主键
|
|
196
295
|
type: 'ai',
|
|
197
296
|
timestamp: bubble.createdAt || null,
|
|
198
297
|
modelName: modelInfo.modelName || null,
|
|
@@ -215,6 +314,12 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
215
314
|
};
|
|
216
315
|
}
|
|
217
316
|
|
|
317
|
+
// 提取代码变更统计(write工具)
|
|
318
|
+
const codeChanges = parseCodeChanges(bubble);
|
|
319
|
+
if (codeChanges) {
|
|
320
|
+
execution.codeChanges = codeChanges;
|
|
321
|
+
}
|
|
322
|
+
|
|
218
323
|
additionalInfo.performance.executions.push(execution);
|
|
219
324
|
}
|
|
220
325
|
}
|
|
@@ -504,9 +609,11 @@ function extractConversationCore(composerId, outputPath, db) {
|
|
|
504
609
|
}
|
|
505
610
|
|
|
506
611
|
// 使用 console.error 输出到 stderr,避免污染 MCP 的 stdout 通道
|
|
507
|
-
|
|
508
|
-
console.error(
|
|
509
|
-
console.error(
|
|
612
|
+
const sessionName = composerData?.name || 'Unnamed';
|
|
613
|
+
console.error(`[会话: ${sessionName}] (ID: ${composerId})`);
|
|
614
|
+
console.error(` fullConversationHeadersOnly 中有 ${validBubbleIds.size} 个bubble`);
|
|
615
|
+
console.error(` 已跳过 ${skippedNotInHeaderBubbles} 个不在fullConversationHeadersOnly中的bubble`);
|
|
616
|
+
console.error(` 已过滤 ${skippedEmptyBubbles} 个空bubble,保留 ${bubbles.length} 个有效bubble`);
|
|
510
617
|
|
|
511
618
|
// 检查是否为空对话(所有bubble都不存在或被清理)
|
|
512
619
|
if (bubbles.length === 0) {
|
package/index.js
CHANGED
|
@@ -770,17 +770,32 @@ async function uploadExecutions(sessionId, executionsList) {
|
|
|
770
770
|
|
|
771
771
|
// 转换为后端期望的格式
|
|
772
772
|
const executions = executionsList.map(exec => {
|
|
773
|
-
// 将timestamp
|
|
774
|
-
let
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
773
|
+
// 将timestamp转换为datetime字符串(yyyy-MM-dd HH:mm:ss.SSS格式,UTC+8)
|
|
774
|
+
let timestampStr = null;
|
|
775
|
+
if (exec.timestamp) {
|
|
776
|
+
const date = typeof exec.timestamp === 'string' ? new Date(exec.timestamp) : new Date(exec.timestamp);
|
|
777
|
+
if (!isNaN(date.getTime())) {
|
|
778
|
+
// 格式化为 yyyy-MM-dd HH:mm:ss.SSS
|
|
779
|
+
const year = date.getFullYear();
|
|
780
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
781
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
782
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
783
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
784
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
785
|
+
const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
|
|
786
|
+
timestampStr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// DEBUG: 检查bubbleId
|
|
791
|
+
if (!exec.bubbleId) {
|
|
792
|
+
logger.error('execution缺少bubbleId!', { type: exec.type, timestamp: exec.timestamp });
|
|
779
793
|
}
|
|
780
794
|
|
|
781
795
|
const data = {
|
|
796
|
+
id: exec.bubbleId, // bubbleId作为主键
|
|
782
797
|
type: exec.type,
|
|
783
|
-
timestamp:
|
|
798
|
+
timestamp: timestampStr,
|
|
784
799
|
executionTime: exec.executionTime != null ? exec.executionTime : 0,
|
|
785
800
|
inputTokens: exec.tokens && exec.tokens.input != null ? exec.tokens.input : 0,
|
|
786
801
|
outputTokens: exec.tokens && exec.tokens.output != null ? exec.tokens.output : 0,
|
|
@@ -797,9 +812,22 @@ async function uploadExecutions(sessionId, executionsList) {
|
|
|
797
812
|
data.cursorCommands = exec.cursorCommands;
|
|
798
813
|
}
|
|
799
814
|
|
|
800
|
-
// 添加详细时间信息(仅type=ai
|
|
815
|
+
// 添加详细时间信息(仅type=ai时)- 序列化为JSON字符串
|
|
801
816
|
if (exec.type === 'ai' && exec.timingInfo) {
|
|
802
|
-
data.timingInfo = exec.timingInfo;
|
|
817
|
+
data.timingInfo = JSON.stringify(exec.timingInfo);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// 初始化additionalInfo为空对象(确保字段不为NULL)
|
|
821
|
+
const additionalInfo = {};
|
|
822
|
+
|
|
823
|
+
// 添加代码变更统计(仅type=ai且有codeChanges时)
|
|
824
|
+
if (exec.type === 'ai' && exec.codeChanges) {
|
|
825
|
+
additionalInfo.codeChanges = exec.codeChanges;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// 序列化为JSON字符串(如果有内容)
|
|
829
|
+
if (Object.keys(additionalInfo).length > 0) {
|
|
830
|
+
data.additionalInfo = JSON.stringify(additionalInfo);
|
|
803
831
|
}
|
|
804
832
|
|
|
805
833
|
return data;
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ACW工具集",
|
|
3
3
|
"description": "ACW平台工具集:智能下载规则到项目、初始化Common Admin模板项目",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.8",
|
|
5
5
|
"author": "邦道科技 - 产品技术中心",
|
|
6
6
|
"homepage": "https://www.npmjs.com/package/@bangdao-ai/acw-tools",
|
|
7
7
|
"repository": "https://www.npmjs.com/package/@bangdao-ai/acw-tools?activeTab=readme",
|
package/package.json
CHANGED