@bangdao-ai/acw-tools 1.3.7 → 1.3.9-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 +203 -43
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -23,6 +23,7 @@ if (DEBUG_MODE && !fs.existsSync(DEBUG_LOG_DIR)) {
|
|
|
23
23
|
/**
|
|
24
24
|
* DEBUG 日志函数
|
|
25
25
|
* 仅在 DEBUG_MODE 启用时输出详细日志到文件
|
|
26
|
+
* 日志格式设计:方便贴给AI分析问题
|
|
26
27
|
*/
|
|
27
28
|
function debugLog(message, data = null) {
|
|
28
29
|
if (!DEBUG_MODE) return;
|
|
@@ -31,7 +32,7 @@ function debugLog(message, data = null) {
|
|
|
31
32
|
const timestamp = now.toISOString().replace('T', ' ').replace('Z', '');
|
|
32
33
|
const logFile = path.join(DEBUG_LOG_DIR, `acw-mcp-debug-${now.toISOString().split('T')[0]}.log`);
|
|
33
34
|
|
|
34
|
-
let logLine =
|
|
35
|
+
let logLine = `[${timestamp}] ${message}`;
|
|
35
36
|
if (data !== null) {
|
|
36
37
|
if (typeof data === 'object') {
|
|
37
38
|
logLine += '\n' + JSON.stringify(data, null, 2);
|
|
@@ -237,6 +238,18 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
237
238
|
duration = Math.max(0, duration);
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
// DEBUG 模式:记录解析开始
|
|
242
|
+
if (DEBUG_MODE) {
|
|
243
|
+
debugLog(`\n========== [开始解析会话] ==========`);
|
|
244
|
+
debugLog(`composerId: ${composerData?.composerId}`);
|
|
245
|
+
debugLog(`会话名称: ${composerData?.name || '无'}`);
|
|
246
|
+
debugLog(`bubble总数: ${bubbles.length}`);
|
|
247
|
+
// 统计有 timingInfo 的 bubble 数量
|
|
248
|
+
const bubblesWithTimingInfo = bubbles.filter(b => b.type === 2 && b.timingInfo);
|
|
249
|
+
debugLog(`其中AI bubble有timingInfo的数量: ${bubblesWithTimingInfo.length}`);
|
|
250
|
+
debugLog(`====================================`);
|
|
251
|
+
}
|
|
252
|
+
|
|
240
253
|
const additionalInfo = {
|
|
241
254
|
// 会话元数据
|
|
242
255
|
metadata: {
|
|
@@ -342,46 +355,50 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
342
355
|
const tokenCount = bubble.tokenCount || {};
|
|
343
356
|
const modelInfo = bubble.modelInfo || {};
|
|
344
357
|
|
|
345
|
-
// DEBUG:
|
|
346
|
-
const
|
|
358
|
+
// DEBUG: 只记录有 timingInfo 的 bubble(这才是关键数据)
|
|
359
|
+
const hasTimingInfo = !!bubble.timingInfo;
|
|
347
360
|
const hasClientRpcSendTime = !!(bubble.timingInfo && bubble.timingInfo.clientRpcSendTime);
|
|
348
361
|
const hasClientSettleTime = !!(bubble.timingInfo && bubble.timingInfo.clientSettleTime);
|
|
349
362
|
|
|
350
|
-
// DEBUG
|
|
351
|
-
if (DEBUG_MODE) {
|
|
352
|
-
debugLog(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
363
|
+
// DEBUG 模式:只打印有 timingInfo 的 bubble,并输出完整 value
|
|
364
|
+
if (DEBUG_MODE && hasTimingInfo) {
|
|
365
|
+
debugLog(`━━━ [有timingInfo的Bubble] ━━━`);
|
|
366
|
+
debugLog(`bubbleId: ${bubble.bubbleId}`);
|
|
367
|
+
debugLog(`timingInfo原始值:`, bubble.timingInfo);
|
|
368
|
+
debugLog(`解析过程:`, {
|
|
369
|
+
step1_hasTimingInfo: hasTimingInfo,
|
|
370
|
+
step2_hasClientRpcSendTime: hasClientRpcSendTime,
|
|
371
|
+
step3_hasClientSettleTime: hasClientSettleTime,
|
|
372
|
+
step4_clientRpcSendTime: aiTimingInfo.clientRpcSendTime,
|
|
373
|
+
step5_clientSettleTime: aiTimingInfo.clientSettleTime,
|
|
374
|
+
step6_计算executionTime: hasClientRpcSendTime && hasClientSettleTime
|
|
375
|
+
? `${aiTimingInfo.clientSettleTime} - ${aiTimingInfo.clientRpcSendTime} = ${aiTimingInfo.clientSettleTime - aiTimingInfo.clientRpcSendTime}ms`
|
|
376
|
+
: '缺少必要字段,无法计算'
|
|
359
377
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
text: bubble.text ? `[长度: ${bubble.text.length}] ${bubble.text.substring(0, 200)}...` : null,
|
|
374
|
-
richText: bubble.richText ? `[长度: ${bubble.richText.length}]` : null
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
+
// 输出完整 bubble(排除超长文本)
|
|
379
|
+
debugLog(`bubble完整数据:`, {
|
|
380
|
+
bubbleId: bubble.bubbleId,
|
|
381
|
+
type: bubble.type,
|
|
382
|
+
createdAt: bubble.createdAt,
|
|
383
|
+
timingInfo: bubble.timingInfo,
|
|
384
|
+
tokenCount: bubble.tokenCount,
|
|
385
|
+
modelInfo: bubble.modelInfo,
|
|
386
|
+
toolFormerData: bubble.toolFormerData ? { name: bubble.toolFormerData.name } : null,
|
|
387
|
+
// 其他字段(排除大文本)
|
|
388
|
+
otherKeys: Object.keys(bubble).filter(k => !['text', 'richText', 'timingInfo', 'tokenCount', 'modelInfo', 'toolFormerData', 'bubbleId', 'type', 'createdAt'].includes(k))
|
|
389
|
+
});
|
|
390
|
+
debugLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
378
391
|
}
|
|
379
392
|
|
|
380
393
|
let executionTime = 0;
|
|
394
|
+
let isEstimated = false; // 标记是否为估算值
|
|
395
|
+
|
|
381
396
|
if (aiTimingInfo.clientRpcSendTime && aiTimingInfo.clientSettleTime) {
|
|
397
|
+
// 有 timingInfo,使用精确计算
|
|
382
398
|
executionTime = aiTimingInfo.clientSettleTime - aiTimingInfo.clientRpcSendTime;
|
|
383
399
|
additionalInfo.performance.totalExecutionTime += executionTime;
|
|
384
400
|
}
|
|
401
|
+
// 注意:兜底计算将在第二遍遍历中进行
|
|
385
402
|
|
|
386
403
|
// 构建execution对象(timestamp已在上面统一处理)
|
|
387
404
|
const execution = {
|
|
@@ -398,9 +415,10 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
398
415
|
}
|
|
399
416
|
};
|
|
400
417
|
|
|
401
|
-
// 如果有timingInfo
|
|
418
|
+
// 如果有timingInfo,添加原始时间戳,并标记为精确值
|
|
402
419
|
if (Object.keys(aiTimingInfo).length > 0) {
|
|
403
420
|
execution.timingInfo = {
|
|
421
|
+
isEstimated: false, // 标记为精确值(来自 Cursor 原生 timingInfo)
|
|
404
422
|
clientStartTime: aiTimingInfo.clientStartTime || null,
|
|
405
423
|
clientRpcSendTime: aiTimingInfo.clientRpcSendTime || null,
|
|
406
424
|
clientSettleTime: aiTimingInfo.clientSettleTime || null,
|
|
@@ -424,6 +442,134 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
424
442
|
}
|
|
425
443
|
}
|
|
426
444
|
|
|
445
|
+
// ==================== 兜底方案:使用 createdAt 时间差估算执行时间 ====================
|
|
446
|
+
// 核心逻辑:
|
|
447
|
+
// 1. 一个会话包含多轮对话,每轮对话 = 1个User bubble + N个连续AI bubble
|
|
448
|
+
// 2. 正常情况下,第一个AI bubble有timingInfo,其executionTime包含整轮对话的执行时间
|
|
449
|
+
// 3. 兜底:如果第一个AI bubble没有timingInfo,用「下一个User bubble的createdAt - 第一个AI bubble的createdAt」估算
|
|
450
|
+
// 4. 对于最后一轮对话,用「composerData.lastUpdatedAt - 第一个AI bubble的createdAt」估算
|
|
451
|
+
|
|
452
|
+
const executions = additionalInfo.performance.executions;
|
|
453
|
+
let estimatedCount = 0;
|
|
454
|
+
|
|
455
|
+
// 辅助函数:解析时间戳为毫秒
|
|
456
|
+
const parseTimestamp = (ts) => {
|
|
457
|
+
if (!ts) return null;
|
|
458
|
+
if (typeof ts === 'number') return ts;
|
|
459
|
+
const parsed = new Date(ts).getTime();
|
|
460
|
+
return isNaN(parsed) ? null : parsed;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// 获取 composerData.lastUpdatedAt 作为最后一轮的结束时间
|
|
464
|
+
const lastUpdatedAt = parseTimestamp(composerData?.lastUpdatedAt);
|
|
465
|
+
|
|
466
|
+
// 遍历 executions,识别每一轮对话并处理兜底
|
|
467
|
+
let i = 0;
|
|
468
|
+
while (i < executions.length) {
|
|
469
|
+
const current = executions[i];
|
|
470
|
+
|
|
471
|
+
// 跳过非 User bubble
|
|
472
|
+
if (current.type !== 'user') {
|
|
473
|
+
i++;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 找到 User bubble,开始处理这一轮对话
|
|
478
|
+
const userBubbleIndex = i;
|
|
479
|
+
i++; // 移动到下一个 bubble
|
|
480
|
+
|
|
481
|
+
// 找到这一轮的第一个 AI bubble
|
|
482
|
+
if (i >= executions.length || executions[i].type !== 'ai') {
|
|
483
|
+
continue; // 没有 AI bubble,跳过
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const firstAiBubbleIndex = i;
|
|
487
|
+
const firstAiBubble = executions[firstAiBubbleIndex];
|
|
488
|
+
|
|
489
|
+
// 找到这一轮的最后一个 AI bubble(连续的 AI bubble)
|
|
490
|
+
let lastAiBubbleIndex = firstAiBubbleIndex;
|
|
491
|
+
while (lastAiBubbleIndex + 1 < executions.length && executions[lastAiBubbleIndex + 1].type === 'ai') {
|
|
492
|
+
lastAiBubbleIndex++;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// 移动索引到下一轮
|
|
496
|
+
i = lastAiBubbleIndex + 1;
|
|
497
|
+
|
|
498
|
+
// 检查第一个 AI bubble 是否需要兜底
|
|
499
|
+
// 条件:没有 timingInfo 且 executionTime 为 0
|
|
500
|
+
if (firstAiBubble.timingInfo || firstAiBubble.executionTime > 0) {
|
|
501
|
+
continue; // 已有执行时间,不需要兜底
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// 计算估算执行时间
|
|
505
|
+
const firstAiCreatedAt = parseTimestamp(firstAiBubble.timestamp);
|
|
506
|
+
if (!firstAiCreatedAt) {
|
|
507
|
+
continue; // 没有时间戳,无法估算
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let endTime = null;
|
|
511
|
+
let endTimeSource = '';
|
|
512
|
+
|
|
513
|
+
// 优先使用下一个 User bubble 的 createdAt
|
|
514
|
+
if (i < executions.length && executions[i].type === 'user') {
|
|
515
|
+
endTime = parseTimestamp(executions[i].timestamp);
|
|
516
|
+
endTimeSource = 'nextUserBubble';
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// 如果是最后一轮对话,使用 lastUpdatedAt
|
|
520
|
+
if (!endTime && lastUpdatedAt) {
|
|
521
|
+
endTime = lastUpdatedAt;
|
|
522
|
+
endTimeSource = 'lastUpdatedAt';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!endTime) {
|
|
526
|
+
continue; // 无法确定结束时间
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// 计算时间差
|
|
530
|
+
const estimatedTime = endTime - firstAiCreatedAt;
|
|
531
|
+
|
|
532
|
+
// 验证估算值的合理性(正数且小于30分钟)
|
|
533
|
+
if (estimatedTime <= 0 || estimatedTime > 30 * 60 * 1000) {
|
|
534
|
+
if (DEBUG_MODE) {
|
|
535
|
+
debugLog(`[兜底跳过] 估算值不合理`, {
|
|
536
|
+
bubbleId: firstAiBubble.bubbleId,
|
|
537
|
+
firstAiCreatedAt,
|
|
538
|
+
endTime,
|
|
539
|
+
endTimeSource,
|
|
540
|
+
estimatedTime: `${estimatedTime}ms`
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 应用估算值
|
|
547
|
+
firstAiBubble.executionTime = estimatedTime;
|
|
548
|
+
firstAiBubble.timingInfo = {
|
|
549
|
+
isEstimated: true, // 标记为估算值
|
|
550
|
+
estimatedFrom: endTimeSource,
|
|
551
|
+
firstAiCreatedAt: firstAiCreatedAt,
|
|
552
|
+
endTime: endTime
|
|
553
|
+
};
|
|
554
|
+
additionalInfo.performance.totalExecutionTime += estimatedTime;
|
|
555
|
+
estimatedCount++;
|
|
556
|
+
|
|
557
|
+
if (DEBUG_MODE) {
|
|
558
|
+
debugLog(`[兜底估算成功]`, {
|
|
559
|
+
bubbleId: firstAiBubble.bubbleId,
|
|
560
|
+
轮次AI_bubble数: lastAiBubbleIndex - firstAiBubbleIndex + 1,
|
|
561
|
+
firstAiCreatedAt: new Date(firstAiCreatedAt).toISOString(),
|
|
562
|
+
endTime: new Date(endTime).toISOString(),
|
|
563
|
+
endTimeSource,
|
|
564
|
+
estimatedExecutionTime: `${estimatedTime}ms (${(estimatedTime / 1000).toFixed(1)}s)`
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (DEBUG_MODE && estimatedCount > 0) {
|
|
570
|
+
debugLog(`[兜底估算汇总] 共估算了 ${estimatedCount} 轮对话的执行时间`);
|
|
571
|
+
}
|
|
572
|
+
|
|
427
573
|
// 计算平均执行时间、最大执行时间和最小执行时间(只统计type='ai'且executionTime>0的)
|
|
428
574
|
let validAiExecutionCount = 0;
|
|
429
575
|
let validTotalExecutionTime = 0;
|
|
@@ -469,20 +615,34 @@ function extractAdditionalInfo(composerData, bubbles, markdownContent = '') {
|
|
|
469
615
|
additionalInfo.performance.minExecutionTime = 0;
|
|
470
616
|
}
|
|
471
617
|
|
|
472
|
-
// DEBUG 模式:输出 timingInfo
|
|
618
|
+
// DEBUG 模式:输出 timingInfo 解析结果汇总
|
|
473
619
|
if (DEBUG_MODE) {
|
|
474
|
-
debugLog(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
.map(e => ({ bubbleId: e.bubbleId, modelName: e.modelName, timestamp: e.timestamp }))
|
|
620
|
+
debugLog(`\n========== [会话解析完成] ==========`);
|
|
621
|
+
debugLog(`composerId: ${composerData?.composerId}`);
|
|
622
|
+
debugLog(`会话名称: ${composerData?.name || '无'}`);
|
|
623
|
+
debugLog(`解析结果汇总:`, {
|
|
624
|
+
总AI_bubble数: additionalInfo.performance.aiExecutionCount,
|
|
625
|
+
有timingInfo的数量: aiExecutionsWithTimingInfo,
|
|
626
|
+
无timingInfo的数量: aiExecutionsWithoutTimingInfo,
|
|
627
|
+
有效执行时间的数量: validAiExecutionCount,
|
|
628
|
+
总执行时间ms: additionalInfo.performance.totalExecutionTime,
|
|
629
|
+
平均执行时间ms: Math.round(additionalInfo.performance.averageExecutionTime)
|
|
485
630
|
});
|
|
631
|
+
// 列出所有解析出的 execution(带 timingInfo 的)
|
|
632
|
+
const executionsWithTiming = additionalInfo.performance.executions
|
|
633
|
+
.filter(e => e.type === 'ai' && e.timingInfo);
|
|
634
|
+
if (executionsWithTiming.length > 0) {
|
|
635
|
+
debugLog(`有timingInfo的execution列表 (共${executionsWithTiming.length}个):`,
|
|
636
|
+
executionsWithTiming.map(e => ({
|
|
637
|
+
bubbleId: e.bubbleId,
|
|
638
|
+
executionTime: e.executionTime,
|
|
639
|
+
timingInfo: e.timingInfo
|
|
640
|
+
}))
|
|
641
|
+
);
|
|
642
|
+
} else {
|
|
643
|
+
debugLog(`⚠️ 警告: 该会话没有任何带timingInfo的AI bubble!`);
|
|
644
|
+
}
|
|
645
|
+
debugLog(`====================================\n`);
|
|
486
646
|
}
|
|
487
647
|
|
|
488
648
|
// 分离executions数组
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ACW工具集",
|
|
3
3
|
"description": "ACW平台工具集:智能下载规则到项目、初始化Common Admin模板项目",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.9",
|
|
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