chatlab-cli 0.25.1 → 0.26.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.
Files changed (74) hide show
  1. package/dist/{chat-command-5XMRLXZM.mjs → chat-command-T3Z4O4M5.mjs} +144 -6
  2. package/dist/chat-command-T3Z4O4M5.mjs.map +1 -0
  3. package/dist/{chunk-CUCDRAY6.mjs → chunk-3APSMVOM.mjs} +2 -2
  4. package/dist/{chunk-WYHGWTA4.mjs → chunk-6O2SW5GL.mjs} +94 -29
  5. package/dist/chunk-6O2SW5GL.mjs.map +1 -0
  6. package/dist/{chunk-GTCTDD2Z.mjs → chunk-AZZYKUBR.mjs} +17 -39
  7. package/dist/chunk-AZZYKUBR.mjs.map +1 -0
  8. package/dist/{chunk-JE3FOFF7.mjs → chunk-FACKH2BR.mjs} +49 -12
  9. package/dist/chunk-FACKH2BR.mjs.map +1 -0
  10. package/dist/{chunk-PDQFSQO6.mjs → chunk-FAOZNMPK.mjs} +2 -2
  11. package/dist/{chunk-7VG6QGQ2.mjs → chunk-GUGKY2GV.mjs} +1169 -328
  12. package/dist/chunk-GUGKY2GV.mjs.map +1 -0
  13. package/dist/{chunk-FDK6SPCG.mjs → chunk-MZG2GLJR.mjs} +2 -2
  14. package/dist/{chunk-YZYP5ST6.mjs → chunk-SC77N66X.mjs} +12 -11
  15. package/dist/chunk-SC77N66X.mjs.map +1 -0
  16. package/dist/cli.mjs +3 -3
  17. package/dist/{http-C3CGXUBU.mjs → http-6LQUOYQ6.mjs} +8 -8
  18. package/dist/{import-VS7L5JDX.mjs → import-B2MAIBAK.mjs} +4 -4
  19. package/dist/index.mjs +8 -8
  20. package/dist/{mcp-6VXDPUQK.mjs → mcp-ZMMYW46I.mjs} +4 -4
  21. package/dist/{stream-import-TFDBNVIW.mjs → stream-import-FHFBCEDV.mjs} +3 -3
  22. package/dist-web/assets/{DebugToolsPanel-vn-bnUxn.css → DebugToolsPanel-CrqULdXp.css} +1 -1
  23. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-CgtTQTfN.js +182 -0
  24. package/dist-web/assets/{EChartWordcloud-CzhfrEF_.js → EChartWordcloud-B_YisOW5.js} +1 -1
  25. package/dist-web/assets/MessageExportModal-B8DKeSAH.js +1 -0
  26. package/dist-web/assets/{electron-DRkrjeIl.js → electron-C-aMus7e.js} +1 -1
  27. package/dist-web/assets/electron-dBbXcMDB.js +1 -0
  28. package/dist-web/assets/{fetch-CDamdvtb.js → fetch-B-LdQP9s.js} +1 -1
  29. package/dist-web/assets/{fetch-Drk9dLaF.js → fetch-B26JuD67.js} +1 -1
  30. package/dist-web/assets/{fetch-BmZIm6mS.js → fetch-BLKibnXa.js} +1 -1
  31. package/dist-web/assets/{fetch-BqLy63aj.js → fetch-BNNqdS2B.js} +1 -1
  32. package/dist-web/assets/fetch-BSJY7tRo.js +1 -0
  33. package/dist-web/assets/{fetch-DWg-2kOp.js → fetch-CV8jezsM.js} +1 -1
  34. package/dist-web/assets/{fetch-ph2_d79v.js → fetch-CvnJ4Jgf.js} +1 -1
  35. package/dist-web/assets/{fetch-DfgdagMN.js → fetch-DP3yNfJM.js} +1 -1
  36. package/dist-web/assets/{fetch-Bvcxe5PI.js → fetch-phbrO2bY.js} +1 -1
  37. package/dist-web/assets/{fetch-Y-_WsCuk.js → fetch-xB6cl_1L.js} +3 -3
  38. package/dist-web/assets/index-8Zcs8peI.css +1 -0
  39. package/dist-web/assets/index-B_lOzUD8.js +2 -0
  40. package/dist-web/assets/index-BvWcJc3c.js +173 -0
  41. package/dist-web/assets/{index-DMWf5On7.js → index-CSDI5pAA.js} +2 -2
  42. package/dist-web/assets/{index-CGNgUIT-.css → index-CX2FucK5.css} +1 -1
  43. package/dist-web/assets/{index-9urfJP1r.js → index-DJQFNVUJ.js} +1 -1
  44. package/dist-web/assets/index-DZ6Szt_y.js +163 -0
  45. package/dist-web/assets/index-DhpclWeF.js +11 -0
  46. package/dist-web/assets/index-Dhql4DJr.css +1 -0
  47. package/dist-web/assets/{vendor-nuxt-ui-C_8CsH6e.js → vendor-nuxt-ui-Zy8txNdC.js} +1 -1
  48. package/dist-web/assets/{vendor-reka-ui-CIMXwvEp.js → vendor-reka-ui-mMvma1EP.js} +1 -1
  49. package/dist-web/assets/{web-XJV6Vj5t.js → web-DUQU-v8U.js} +1 -1
  50. package/dist-web/index.html +4 -4
  51. package/package.json +1 -1
  52. package/dist/chat-command-5XMRLXZM.mjs.map +0 -1
  53. package/dist/chunk-7VG6QGQ2.mjs.map +0 -1
  54. package/dist/chunk-GTCTDD2Z.mjs.map +0 -1
  55. package/dist/chunk-JE3FOFF7.mjs.map +0 -1
  56. package/dist/chunk-WYHGWTA4.mjs.map +0 -1
  57. package/dist/chunk-YZYP5ST6.mjs.map +0 -1
  58. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-B1xZmp9r.js +0 -161
  59. package/dist-web/assets/MessageExportModal-B79pPBL9.js +0 -1
  60. package/dist-web/assets/electron-Dv8jdn_2.js +0 -1
  61. package/dist-web/assets/fetch-BstX-wd4.js +0 -1
  62. package/dist-web/assets/index-C3A_Rjh-.css +0 -1
  63. package/dist-web/assets/index-CfKjh0Jr.js +0 -9
  64. package/dist-web/assets/index-CprJOAB1.css +0 -1
  65. package/dist-web/assets/index-D_hHm7bQ.js +0 -2
  66. package/dist-web/assets/index-MCdpiX_O.js +0 -173
  67. package/dist-web/assets/index-deYDOVUa.js +0 -163
  68. /package/dist/{chunk-CUCDRAY6.mjs.map → chunk-3APSMVOM.mjs.map} +0 -0
  69. /package/dist/{chunk-PDQFSQO6.mjs.map → chunk-FAOZNMPK.mjs.map} +0 -0
  70. /package/dist/{chunk-FDK6SPCG.mjs.map → chunk-MZG2GLJR.mjs.map} +0 -0
  71. /package/dist/{http-C3CGXUBU.mjs.map → http-6LQUOYQ6.mjs.map} +0 -0
  72. /package/dist/{import-VS7L5JDX.mjs.map → import-B2MAIBAK.mjs.map} +0 -0
  73. /package/dist/{mcp-6VXDPUQK.mjs.map → mcp-ZMMYW46I.mjs.map} +0 -0
  74. /package/dist/{stream-import-TFDBNVIW.mjs.map → stream-import-FHFBCEDV.mjs.map} +0 -0
@@ -2158,6 +2158,7 @@ function getChatOverview(db, topN = 10) {
2158
2158
  if (!meta) return null;
2159
2159
  const overview = getSessionOverview(db);
2160
2160
  const members = getMemberActivity(db);
2161
+ const summaryCount = getSummaryCount(db);
2161
2162
  const topMembers = members.slice(0, topN).map((m) => ({
2162
2163
  id: m.memberId,
2163
2164
  name: m.name,
@@ -2171,7 +2172,8 @@ function getChatOverview(db, topN = 10) {
2171
2172
  totalMembers: overview.totalMembers,
2172
2173
  firstMessageTs: overview.firstMessageTs,
2173
2174
  lastMessageTs: overview.lastMessageTs,
2174
- topMembers
2175
+ topMembers,
2176
+ summaryCount
2175
2177
  };
2176
2178
  }
2177
2179
  function searchSegments(db, keywords, timeFilter, limit = 20, previewCount = 5, ftsMatchExpression) {
@@ -3055,188 +3057,6 @@ function deleteMember(db, memberId) {
3055
3057
  }
3056
3058
  }
3057
3059
 
3058
- // ../../packages/core/src/query/filter.ts
3059
- function emptyResult(page, pageSize) {
3060
- return {
3061
- blocks: [],
3062
- stats: { totalMessages: 0, hitMessages: 0, totalChars: 0 },
3063
- pagination: { page, pageSize, totalBlocks: 0, totalHits: 0, hasMore: false }
3064
- };
3065
- }
3066
- function filterMessagesWithContext(db, options = {}) {
3067
- const { keywords, timeFilter, senderIds, contextSize = 10, page = 1, pageSize = 50 } = options;
3068
- const lightSql = `
3069
- SELECT id, ts, sender_id as senderId, content
3070
- FROM message
3071
- ${timeFilter ? "WHERE ts >= ? AND ts <= ?" : ""}
3072
- ORDER BY ts ASC, id ASC
3073
- `;
3074
- const params = [];
3075
- if (timeFilter) {
3076
- params.push(timeFilter.startTs, timeFilter.endTs);
3077
- }
3078
- const lightRows = db.prepare(lightSql).all(...params);
3079
- const hitIndexes = [];
3080
- for (let i = 0; i < lightRows.length; i++) {
3081
- const row = lightRows[i];
3082
- let isHit = true;
3083
- if (keywords && keywords.length > 0) {
3084
- const content = (row.content || "").toLowerCase();
3085
- isHit = keywords.some((kw) => content.includes(kw.toLowerCase()));
3086
- }
3087
- if (isHit && senderIds && senderIds.length > 0) {
3088
- isHit = senderIds.includes(row.senderId);
3089
- }
3090
- if (isHit) {
3091
- hitIndexes.push(i);
3092
- }
3093
- }
3094
- if (hitIndexes.length === 0) {
3095
- return emptyResult(page, pageSize);
3096
- }
3097
- const ranges = [];
3098
- for (const hitIndex of hitIndexes) {
3099
- const start = Math.max(0, hitIndex - contextSize);
3100
- const end = Math.min(lightRows.length - 1, hitIndex + contextSize);
3101
- if (ranges.length > 0) {
3102
- const lastRange = ranges[ranges.length - 1];
3103
- if (start <= lastRange.end + 1) {
3104
- lastRange.end = Math.max(lastRange.end, end);
3105
- lastRange.hitIndexes.push(hitIndex);
3106
- continue;
3107
- }
3108
- }
3109
- ranges.push({ start, end, hitIndexes: [hitIndex] });
3110
- }
3111
- const totalBlocks = ranges.length;
3112
- const totalHits = hitIndexes.length;
3113
- const startIdx = (page - 1) * pageSize;
3114
- const endIdx = Math.min(startIdx + pageSize, totalBlocks);
3115
- const pageRanges = ranges.slice(startIdx, endIdx);
3116
- const hasMore = endIdx < totalBlocks;
3117
- if (pageRanges.length === 0) {
3118
- return {
3119
- blocks: [],
3120
- stats: { totalMessages: 0, hitMessages: totalHits, totalChars: 0 },
3121
- pagination: { page, pageSize, totalBlocks, totalHits, hasMore: false }
3122
- };
3123
- }
3124
- const blocks = [];
3125
- let totalMessages = 0;
3126
- let totalChars = 0;
3127
- for (const range of pageRanges) {
3128
- const blockSql = `
3129
- SELECT ${FULL_MSG_COLUMNS}
3130
- ${FULL_MSG_FROM}
3131
- ${timeFilter ? "WHERE msg.ts >= ? AND msg.ts <= ?" : ""}
3132
- ORDER BY msg.ts ASC, msg.id ASC
3133
- LIMIT ? OFFSET ?
3134
- `;
3135
- const blockParams = [];
3136
- if (timeFilter) {
3137
- blockParams.push(timeFilter.startTs, timeFilter.endTs);
3138
- }
3139
- blockParams.push(range.end - range.start + 1, range.start);
3140
- const rows = db.prepare(blockSql).all(...blockParams);
3141
- const mapped = rows.map(mapMessageRow);
3142
- const hitIndexSet = new Set(range.hitIndexes.map((idx) => idx - range.start));
3143
- const blockMessages = mapped.map((msg, i) => ({
3144
- ...msg,
3145
- isHit: hitIndexSet.has(i)
3146
- }));
3147
- for (const msg of mapped) {
3148
- totalChars += msg.content.length;
3149
- }
3150
- if (blockMessages.length > 0) {
3151
- blocks.push({
3152
- startTs: blockMessages[0].timestamp,
3153
- endTs: blockMessages[blockMessages.length - 1].timestamp,
3154
- messages: blockMessages,
3155
- hitCount: range.hitIndexes.length
3156
- });
3157
- totalMessages += blockMessages.length;
3158
- }
3159
- }
3160
- let estimatedTotalChars = totalChars;
3161
- if (page === 1 && totalBlocks > pageSize && blocks.length > 0) {
3162
- const avgCharsPerBlock = totalChars / blocks.length;
3163
- estimatedTotalChars = Math.round(avgCharsPerBlock * totalBlocks);
3164
- }
3165
- return {
3166
- blocks,
3167
- stats: {
3168
- totalMessages: page === 1 ? totalMessages : 0,
3169
- hitMessages: totalHits,
3170
- totalChars: page === 1 ? totalBlocks > pageSize ? estimatedTotalChars : totalChars : 0
3171
- },
3172
- pagination: { page, pageSize, totalBlocks, totalHits, hasMore }
3173
- };
3174
- }
3175
- function getMultipleSessionsMessages(db, segmentIds, page = 1, pageSize = 50) {
3176
- if (segmentIds.length === 0) {
3177
- return emptyResult(page, pageSize);
3178
- }
3179
- const sessionsSql = `
3180
- SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount
3181
- FROM segment
3182
- WHERE id IN (${segmentIds.map(() => "?").join(",")})
3183
- ORDER BY start_ts ASC
3184
- `;
3185
- const allSessions = db.prepare(sessionsSql).all(...segmentIds);
3186
- const totalBlocks = allSessions.length;
3187
- const startIdx = (page - 1) * pageSize;
3188
- const endIdx = Math.min(startIdx + pageSize, totalBlocks);
3189
- const pageSessions = allSessions.slice(startIdx, endIdx);
3190
- const hasMore = endIdx < totalBlocks;
3191
- if (pageSessions.length === 0) {
3192
- return {
3193
- blocks: [],
3194
- stats: { totalMessages: 0, hitMessages: 0, totalChars: 0 },
3195
- pagination: { page, pageSize, totalBlocks, totalHits: 0, hasMore: false }
3196
- };
3197
- }
3198
- const blocks = [];
3199
- let totalMessages = 0;
3200
- let totalChars = 0;
3201
- const messagesSql = `
3202
- SELECT ${FULL_MSG_COLUMNS}
3203
- FROM message_context mc
3204
- JOIN message msg ON msg.id = mc.message_id
3205
- JOIN member m ON msg.sender_id = m.id
3206
- LEFT JOIN message reply_msg ON msg.reply_to_message_id = reply_msg.platform_message_id
3207
- LEFT JOIN member reply_m ON reply_msg.sender_id = reply_m.id
3208
- WHERE mc.segment_id = ?
3209
- ORDER BY msg.ts ASC
3210
- `;
3211
- for (const session of pageSessions) {
3212
- const rows = db.prepare(messagesSql).all(session.id);
3213
- const mapped = rows.map(mapMessageRow);
3214
- const blockMessages = mapped.map((msg) => ({
3215
- ...msg,
3216
- isHit: false
3217
- }));
3218
- for (const msg of mapped) {
3219
- totalChars += msg.content.length;
3220
- }
3221
- blocks.push({
3222
- startTs: session.startTs,
3223
- endTs: session.endTs,
3224
- messages: blockMessages,
3225
- hitCount: 0
3226
- });
3227
- totalMessages += rows.length;
3228
- }
3229
- return {
3230
- blocks,
3231
- stats: {
3232
- totalMessages: page === 1 ? totalMessages : 0,
3233
- hitMessages: 0,
3234
- totalChars: page === 1 ? totalChars : 0
3235
- },
3236
- pagination: { page, pageSize, totalBlocks, totalHits: 0, hasMore }
3237
- };
3238
- }
3239
-
3240
3060
  // ../../packages/core/src/query/advanced/repeat.ts
3241
3061
  function getCatchphraseAnalysis(db, filter) {
3242
3062
  const { clause, params } = buildTimeFilter(filter);
@@ -3287,7 +3107,7 @@ function getCatchphraseAnalysis(db, filter) {
3287
3107
 
3288
3108
  // ../../packages/core/src/query/advanced/social.ts
3289
3109
  function getMentionAnalysis(db, filter) {
3290
- const emptyResult2 = {
3110
+ const emptyResult = {
3291
3111
  topMentioners: [],
3292
3112
  topMentioned: [],
3293
3113
  oneWay: [],
@@ -3299,7 +3119,7 @@ function getMentionAnalysis(db, filter) {
3299
3119
  `SELECT id, platform_id as platformId, COALESCE(group_nickname, account_name, platform_id) as name
3300
3120
  FROM member WHERE COALESCE(account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'`
3301
3121
  ).all();
3302
- if (members.length === 0) return emptyResult2;
3122
+ if (members.length === 0) return emptyResult;
3303
3123
  const nameToMemberId = /* @__PURE__ */ new Map();
3304
3124
  const memberIdToInfo = /* @__PURE__ */ new Map();
3305
3125
  for (const member of members) {
@@ -3344,7 +3164,7 @@ function getMentionAnalysis(db, filter) {
3344
3164
  }
3345
3165
  }
3346
3166
  }
3347
- if (totalMentions === 0) return emptyResult2;
3167
+ if (totalMentions === 0) return emptyResult;
3348
3168
  const topMentioners = [];
3349
3169
  for (const [memberId, count] of mentionerCount.entries()) {
3350
3170
  const info = memberIdToInfo.get(memberId);
@@ -3481,7 +3301,7 @@ function getMentionAnalysis(db, filter) {
3481
3301
  return { topMentioners, topMentioned, oneWay, twoWay, totalMentions, memberDetails };
3482
3302
  }
3483
3303
  function getMentionGraph(db, filter) {
3484
- const emptyResult2 = { nodes: [], links: [], maxLinkValue: 0 };
3304
+ const emptyResult = { nodes: [], links: [], maxLinkValue: 0 };
3485
3305
  const { clause, params } = buildTimeFilter(filter);
3486
3306
  const msgFilterBase = clause ? clause.replace("WHERE", "AND") : "";
3487
3307
  const msgFilterWithSystem = msgFilterBase + " AND COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'";
@@ -3491,7 +3311,7 @@ function getMentionGraph(db, filter) {
3491
3311
  FROM member m LEFT JOIN message msg ON m.id = msg.sender_id ${msgFilterWithSystem}
3492
3312
  WHERE COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' GROUP BY m.id`
3493
3313
  ).all(...params);
3494
- if (members.length === 0) return emptyResult2;
3314
+ if (members.length === 0) return emptyResult;
3495
3315
  const nameToMemberId = /* @__PURE__ */ new Map();
3496
3316
  const memberIdToInfo = /* @__PURE__ */ new Map();
3497
3317
  for (const member of members) {
@@ -3560,7 +3380,7 @@ function keywordToPattern(keyword) {
3560
3380
  return escaped;
3561
3381
  }
3562
3382
  function getLaughAnalysis(db, filter, keywords) {
3563
- const emptyResult2 = {
3383
+ const emptyResult = {
3564
3384
  rankByRate: [],
3565
3385
  rankByCount: [],
3566
3386
  typeDistribution: [],
@@ -3583,7 +3403,7 @@ function getLaughAnalysis(db, filter, keywords) {
3583
3403
  COALESCE(m.group_nickname, m.account_name, m.platform_id) as name
3584
3404
  FROM message msg JOIN member m ON msg.sender_id = m.id ${whereClause}`
3585
3405
  ).all(...params);
3586
- if (messages.length === 0) return emptyResult2;
3406
+ if (messages.length === 0) return emptyResult;
3587
3407
  const memberStats = /* @__PURE__ */ new Map();
3588
3408
  const typeCount = /* @__PURE__ */ new Map();
3589
3409
  let totalLaughs = 0;
@@ -3616,7 +3436,7 @@ function getLaughAnalysis(db, filter, keywords) {
3616
3436
  }
3617
3437
  }
3618
3438
  }
3619
- if (totalLaughs === 0) return emptyResult2;
3439
+ if (totalLaughs === 0) return emptyResult;
3620
3440
  const rankItems = [];
3621
3441
  for (const [memberId, stats] of memberStats.entries()) {
3622
3442
  if (stats.laughCount > 0) {
@@ -3669,7 +3489,7 @@ function clusterPairKey(aId, bId) {
3669
3489
  }
3670
3490
  function getClusterGraph(db, filter, options) {
3671
3491
  const opts = { ...DEFAULT_CLUSTER_OPTIONS, ...options };
3672
- const emptyResult2 = {
3492
+ const emptyResult = {
3673
3493
  nodes: [],
3674
3494
  links: [],
3675
3495
  maxLinkValue: 0,
@@ -3681,7 +3501,7 @@ function getClusterGraph(db, filter, options) {
3681
3501
  (SELECT COUNT(*) FROM message WHERE sender_id = member.id) as messageCount
3682
3502
  FROM member WHERE COALESCE(account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'`
3683
3503
  ).all();
3684
- if (members.length < 2) return { ...emptyResult2, stats: { ...emptyResult2.stats, totalMembers: members.length } };
3504
+ if (members.length < 2) return { ...emptyResult, stats: { ...emptyResult.stats, totalMembers: members.length } };
3685
3505
  const memberInfo = /* @__PURE__ */ new Map();
3686
3506
  for (const m of members)
3687
3507
  memberInfo.set(m.id, { name: m.name, platformId: m.platformId, messageCount: m.messageCount });
@@ -3698,8 +3518,8 @@ function getClusterGraph(db, filter, options) {
3698
3518
  ).all(...params);
3699
3519
  if (messages.length < 2)
3700
3520
  return {
3701
- ...emptyResult2,
3702
- stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages: messages.length }
3521
+ ...emptyResult,
3522
+ stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length }
3703
3523
  };
3704
3524
  const memberMsgCount = /* @__PURE__ */ new Map();
3705
3525
  for (const msg of messages) memberMsgCount.set(msg.senderId, (memberMsgCount.get(msg.senderId) || 0) + 1);
@@ -3759,8 +3579,8 @@ function getClusterGraph(db, filter, options) {
3759
3579
  const keptEdges = edges.slice(0, opts.topEdges);
3760
3580
  if (keptEdges.length === 0)
3761
3581
  return {
3762
- ...emptyResult2,
3763
- stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages: messages.length }
3582
+ ...emptyResult,
3583
+ stats: { ...emptyResult.stats, totalMembers: members.length, totalMessages: messages.length }
3764
3584
  };
3765
3585
  const involvedIds = /* @__PURE__ */ new Set();
3766
3586
  for (const edge of keptEdges) {
@@ -3833,7 +3653,7 @@ var ICE_BREAK_THRESHOLD = 24 * 60 * 60;
3833
3653
  var DEFAULT_PERSEVERANCE_THRESHOLD = 300;
3834
3654
  function getRelationshipStats(db, filter, options) {
3835
3655
  const perseveranceThreshold = options?.perseveranceThreshold ?? DEFAULT_PERSEVERANCE_THRESHOLD;
3836
- const emptyResult2 = {
3656
+ const emptyResult = {
3837
3657
  months: [],
3838
3658
  members: [],
3839
3659
  totalSessions: 0,
@@ -3848,7 +3668,7 @@ function getRelationshipStats(db, filter, options) {
3848
3668
  perseveranceThreshold
3849
3669
  };
3850
3670
  const sessionCount = db.prepare("SELECT COUNT(*) as count FROM segment").get();
3851
- if (!sessionCount || sessionCount.count === 0) return emptyResult2;
3671
+ if (!sessionCount || sessionCount.count === 0) return emptyResult;
3852
3672
  const timeConditions = [];
3853
3673
  const params = [];
3854
3674
  if (filter?.startTs !== void 0) {
@@ -6673,6 +6493,14 @@ function getThinkingCompat(provider, modelId) {
6673
6493
  return compat;
6674
6494
  }
6675
6495
 
6496
+ // ../../packages/core/src/ai/tool-result-text.ts
6497
+ var MAX_PERSISTED_TOOL_RESULT_CHARS = 4e3;
6498
+ var TRUNCATION_MARKER = "\n\u2026[truncated]";
6499
+ function truncateToolResultText(text, maxChars = MAX_PERSISTED_TOOL_RESULT_CHARS) {
6500
+ if (text.length <= maxChars) return text;
6501
+ return text.slice(0, maxChars) + TRUNCATION_MARKER;
6502
+ }
6503
+
6676
6504
  // ../../packages/core/src/import/dedup.ts
6677
6505
  function fnv1a64(input) {
6678
6506
  let h0 = 2166136261;
@@ -11779,6 +11607,10 @@ function isDuplicate(msg, existingPlatformMsgIds, existingKeys) {
11779
11607
  existingKeys.add(key);
11780
11608
  return false;
11781
11609
  }
11610
+ function normalizeTimestamp(timestamp) {
11611
+ const value = typeof timestamp === "string" && timestamp.trim() !== "" ? Number(timestamp) : timestamp;
11612
+ return typeof value === "number" && value > 0 && Number.isFinite(value) ? value : null;
11613
+ }
11782
11614
  async function analyzeIncrementalImport(sessionId, filePath, deps) {
11783
11615
  const formatFeature = detectFormat(filePath);
11784
11616
  if (!formatFeature) {
@@ -11806,7 +11638,9 @@ async function analyzeIncrementalImport(sessionId, filePath, deps) {
11806
11638
  onMessageBatch: (batch) => {
11807
11639
  for (const msg of batch) {
11808
11640
  totalInFile++;
11809
- if (isDuplicate(msg, existingPlatformMsgIds, existingKeys)) {
11641
+ const timestamp = normalizeTimestamp(msg.timestamp);
11642
+ if (timestamp === null) continue;
11643
+ if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys)) {
11810
11644
  duplicateCount++;
11811
11645
  } else {
11812
11646
  newMessageCount++;
@@ -11929,11 +11763,12 @@ async function incrementalImport(sessionId, filePath, deps, options) {
11929
11763
  trackError2(processedCount, "MISSING_TIMESTAMP", "timestamp field is missing");
11930
11764
  continue;
11931
11765
  }
11932
- if (typeof msg.timestamp !== "number" || msg.timestamp <= 0 || !isFinite(msg.timestamp)) {
11766
+ const timestamp = normalizeTimestamp(msg.timestamp);
11767
+ if (timestamp === null) {
11933
11768
  trackError2(processedCount, "INVALID_TIMESTAMP", `timestamp value: ${msg.timestamp}`);
11934
11769
  continue;
11935
11770
  }
11936
- if (isDuplicate(msg, existingPlatformMsgIds, existingKeys)) {
11771
+ if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys)) {
11937
11772
  duplicateCount++;
11938
11773
  continue;
11939
11774
  }
@@ -11957,7 +11792,7 @@ async function incrementalImport(sessionId, filePath, deps, options) {
11957
11792
  memberId,
11958
11793
  msg.senderAccountName || null,
11959
11794
  msg.senderGroupNickname || null,
11960
- msg.timestamp,
11795
+ timestamp,
11961
11796
  msg.type,
11962
11797
  msg.content || null,
11963
11798
  msg.replyToMessageId || null,
@@ -12584,11 +12419,7 @@ var AIChatManager = class {
12584
12419
  siblingGroupId,
12585
12420
  branchIndex
12586
12421
  );
12587
- db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(
12588
- id,
12589
- now,
12590
- aiChatId
12591
- );
12422
+ db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(id, now, aiChatId);
12592
12423
  return {
12593
12424
  id,
12594
12425
  aiChatId,
@@ -12626,11 +12457,7 @@ var AIChatManager = class {
12626
12457
  db.prepare(`DELETE FROM ai_message WHERE id IN (${placeholders})`).run(...idsToDelete);
12627
12458
  const newLeafId = targetIndex > 0 ? activePath[targetIndex - 1].id : null;
12628
12459
  const now = Math.floor(Date.now() / 1e3);
12629
- db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(
12630
- newLeafId,
12631
- now,
12632
- aiChatId
12633
- );
12460
+ db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(newLeafId, now, aiChatId);
12634
12461
  }
12635
12462
  forkAIChat(sourceAIChatId, upToMessageId, title) {
12636
12463
  const db = this.getDb();
@@ -12749,11 +12576,7 @@ var AIChatManager = class {
12749
12576
  db.prepare("UPDATE ai_message SET parent_id = ? WHERE id = ?").run(id, childRow.id);
12750
12577
  db.prepare("UPDATE ai_chat SET updated_at = ? WHERE id = ?").run(now, aiChatId);
12751
12578
  } else {
12752
- db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(
12753
- id,
12754
- now,
12755
- aiChatId
12756
- );
12579
+ db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(id, now, aiChatId);
12757
12580
  }
12758
12581
  return {
12759
12582
  id,
@@ -12768,13 +12591,21 @@ var AIChatManager = class {
12768
12591
  }
12769
12592
  getAIChatTokenUsage(aiChatId) {
12770
12593
  const rows = this.getActivePathRows(aiChatId);
12771
- const result = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
12594
+ const result = {
12595
+ promptTokens: 0,
12596
+ completionTokens: 0,
12597
+ totalTokens: 0,
12598
+ cacheReadTokens: 0,
12599
+ cacheWriteTokens: 0
12600
+ };
12772
12601
  for (const row of rows) {
12773
12602
  if (row.tokenUsage) {
12774
12603
  const usage = JSON.parse(row.tokenUsage);
12775
12604
  result.promptTokens += usage.promptTokens;
12776
12605
  result.completionTokens += usage.completionTokens;
12777
12606
  result.totalTokens += usage.totalTokens;
12607
+ result.cacheReadTokens += usage.cacheReadTokens || 0;
12608
+ result.cacheWriteTokens += usage.cacheWriteTokens || 0;
12778
12609
  }
12779
12610
  }
12780
12611
  return result;
@@ -12796,7 +12627,7 @@ var AIChatManager = class {
12796
12627
  getHistoryForAgent(aiChatId, maxMessages, leafMessageId) {
12797
12628
  const messages = this.getActivePathRows(aiChatId, leafMessageId).map((row) => this.parseMessageRow(row));
12798
12629
  const validMessages = messages.filter(
12799
- (m) => (m.role === "user" || m.role === "assistant" || m.role === "summary") && m.content?.trim()
12630
+ (m) => (m.role === "user" || m.role === "assistant" || m.role === "summary") && (m.content?.trim() || m.role === "assistant" && m.contentBlocks?.some((b) => b.type === "tool"))
12800
12631
  );
12801
12632
  let summaryMsg;
12802
12633
  for (let i = validMessages.length - 1; i >= 0; i--) {
@@ -12820,10 +12651,10 @@ var AIChatManager = class {
12820
12651
  const contextMessages = bufferBoundary ? validMessages.filter((m) => m.role !== "summary" && m.timestamp >= bufferBoundary) : [];
12821
12652
  result = [
12822
12653
  { role: "summary", content: summaryMsg.content },
12823
- ...contextMessages.map((m) => ({ role: m.role, content: m.content }))
12654
+ ...contextMessages.map((m) => ({ role: m.role, content: m.content, contentBlocks: m.contentBlocks }))
12824
12655
  ];
12825
12656
  } else {
12826
- result = validMessages.map((m) => ({ role: m.role, content: m.content }));
12657
+ result = validMessages.map((m) => ({ role: m.role, content: m.content, contentBlocks: m.contentBlocks }));
12827
12658
  }
12828
12659
  if (maxMessages && result.length > maxMessages) {
12829
12660
  if (result.length > 0 && result[0].role === "summary") {
@@ -12851,10 +12682,18 @@ var AIChatManager = class {
12851
12682
  return row ? this.parseMessageRow(row) : null;
12852
12683
  }
12853
12684
  getMessagesAfterSummary(aiChatId, summaryTimestamp) {
12854
- return this.getActivePathRows(aiChatId).filter((row) => row.timestamp > summaryTimestamp && (row.role === "user" || row.role === "assistant")).map((row) => ({ role: row.role, content: row.content, timestamp: row.timestamp }));
12685
+ return this.getActivePathRows(aiChatId).filter((row) => row.timestamp > summaryTimestamp && (row.role === "user" || row.role === "assistant")).map((row) => this.toCompressionMessage(row));
12855
12686
  }
12856
12687
  getAllUserAssistantMessages(aiChatId) {
12857
- return this.getActivePathRows(aiChatId).filter((row) => row.role === "user" || row.role === "assistant").map((row) => ({ role: row.role, content: row.content, timestamp: row.timestamp }));
12688
+ return this.getActivePathRows(aiChatId).filter((row) => row.role === "user" || row.role === "assistant").map((row) => this.toCompressionMessage(row));
12689
+ }
12690
+ toCompressionMessage(row) {
12691
+ return {
12692
+ role: row.role,
12693
+ content: row.content,
12694
+ timestamp: row.timestamp,
12695
+ contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0
12696
+ };
12858
12697
  }
12859
12698
  getMessageCountAfterSummary(aiChatId) {
12860
12699
  const summary = this.getLatestSummary(aiChatId);
@@ -14034,10 +13873,38 @@ function shouldUseChartCapabilityForMessage(message) {
14034
13873
  normalized
14035
13874
  );
14036
13875
  }
13876
+ function shouldOfferChartCapabilityForAnalyticalMessage(message) {
13877
+ const normalized = message.trim().toLowerCase();
13878
+ if (!normalized) return false;
13879
+ if (shouldUseChartCapabilityForMessage(normalized)) return true;
13880
+ return /(?:趋势|变化|走势|排名|排行|前\s*\d+|top\s*\d*|最多|最少|高频|分布|占比|比例|构成|份额|活跃度|热度|按.{0,8}统计|统计.{0,8}按|trend|ranking|rank|top\s*\d*|distribution|ratio|share|percentage|breakdown)/i.test(
13881
+ normalized
13882
+ );
13883
+ }
13884
+ function getChartPlannerCapabilityForMessage(options) {
13885
+ const availableToolSet = new Set(options.availableTools);
13886
+ if (!availableToolSet.has("get_schema") || !availableToolSet.has("render_chart")) {
13887
+ return null;
13888
+ }
13889
+ const chartAutoMode = options.chartAutoMode ?? "suggest";
13890
+ const shouldOffer = chartAutoMode === "explicit" ? shouldUseChartCapabilityForMessage(options.userMessage) : shouldOfferChartCapabilityForAnalyticalMessage(options.userMessage);
13891
+ if (!shouldOffer) {
13892
+ return null;
13893
+ }
13894
+ const isZh = (options.locale ?? "zh-CN").startsWith("zh");
13895
+ return {
13896
+ id: "chart_generation",
13897
+ label: isZh ? "\u56FE\u8868\u751F\u6210" : "Chart generation",
13898
+ tools: ["get_schema", "render_chart"],
13899
+ guidance: isZh ? "\u7528\u6237\u660E\u786E\u8981\u6C42\u56FE\u8868\uFF0C\u6216\u8D8B\u52BF\u3001\u6392\u540D\u3001\u5206\u5E03\u3001\u5360\u6BD4\u7B49\u5206\u6790\u7528\u56FE\u66F4\u6E05\u695A\u65F6\uFF0C\u53EF\u4EE5\u5728\u8BA1\u5212\u4E2D\u52A0\u5165\u6700\u591A\u4E00\u5F20\u56FE\u8868\u6B65\u9AA4\uFF1B\u5FC5\u987B\u5148\u7528 get_schema \u786E\u8BA4 schema\uFF0C\u518D\u7528 render_chart \u751F\u6210\u56FE\u8868\uFF1B\u4E0D\u8981\u8F93\u51FA\u7ED8\u56FE\u4EE3\u7801\u3002" : "When the user explicitly asks for charts, or when trends, rankings, distributions, or ratios are clearer visually, you may add at most one chart step to the plan; call get_schema before render_chart; do not output chart-rendering code."
13900
+ };
13901
+ }
14037
13902
  function resolveChartRuntimeForRequest(options) {
14038
13903
  const locale = options.locale ?? "zh-CN";
14039
13904
  const isExplicitChartSkill = options.skillId === CHART_CAPABILITY_SKILL_ID;
14040
- const isAutoChartSkill = options.enableAutoDetection === true && !options.skillId && shouldUseChartCapabilityForMessage(options.userMessage ?? "");
13905
+ const allowAnalyticalAutoDetection = options.chartAutoMode !== void 0 ? options.chartAutoMode === "aggressive" : options.enableAnalyticalAutoDetection === true;
13906
+ const shouldAutoDetectChart = shouldUseChartCapabilityForMessage(options.userMessage ?? "") || allowAnalyticalAutoDetection && shouldOfferChartCapabilityForAnalyticalMessage(options.userMessage ?? "");
13907
+ const isAutoChartSkill = options.enableAutoDetection === true && !options.skillId && shouldAutoDetectChart;
14041
13908
  if (!isExplicitChartSkill && !isAutoChartSkill) {
14042
13909
  return { isChartCapability: false };
14043
13910
  }
@@ -14083,23 +13950,28 @@ function getSkillConfigWithBuiltinChart(id, locale = "zh-CN", getSkillConfig) {
14083
13950
  }
14084
13951
  var ZH_PROMPT = `\u4F60\u662F ChatLab \u7ED8\u56FE\u52A9\u624B\u3002\u672C\u8F6E\u7528\u6237\u5E0C\u671B\u4F60\u5728\u56DE\u7B54\u91CC\u81EA\u7136\u5D4C\u5165\u4E00\u5F20\u6216\u591A\u5F20\u56FE\u8868\u3002
14085
13952
 
14086
- \u4F60\u5FC5\u987B\u901A\u8FC7 render_chart \u5DE5\u5177\u751F\u6210\u56FE\u8868\u3002\u4E0D\u8981\u8F93\u51FA HTML\u3001JavaScript\u3001SVG\u3001Canvas\u3001ECharts option \u6216\u4EFB\u4F55\u6E32\u67D3\u4EE3\u7801\u3002
13953
+ \u4F60\u5FC5\u987B\u901A\u8FC7 render_chart \u5DE5\u5177\u751F\u6210\u56FE\u8868\u3002\u4E0D\u8981\u8F93\u51FA HTML\u3001JavaScript\u3001SVG\u3001Canvas\u3001ECharts option\u3001Markdown \u56FE\u7247\u94FE\u63A5\uFF08\u5982 ![\u56FE\u8868](chart1.png)\uFF09\u6216\u4EFB\u4F55\u6E32\u67D3\u4EE3\u7801\u3002
14087
13954
 
14088
13955
  \u786C\u6027\u89C4\u5219\uFF1A
14089
13956
  1. \u7B2C\u4E00\u6B21\u5199 SQL \u524D\u5FC5\u987B\u5148\u8C03\u7528 get_schema\u3002\u7981\u6B62\u731C\u6D4B\u8868\u540D\u3001\u5B57\u6BB5\u540D\u6216\u65F6\u95F4\u5B57\u6BB5\u3002
14090
- 2. \u201C\u6700\u8FD1\u201D\u201C\u5230\u73B0\u5728\u201D\u201C\u5230\u6700\u8FD1\u201D\u5FC5\u987B\u7528\u6570\u636E\u4E2D\u7684 MAX(ts) \u6216\u7B49\u4EF7\u5B57\u6BB5\u786E\u5B9A\u7ED3\u675F\u65E5\u671F\uFF1B\u4E0D\u8981\u624B\u7B97\u6216\u81C6\u6D4B\u65E5\u671F\u3002
13957
+ 2. \u201C\u6700\u8FD1\u201D\u201C\u8FC7\u53BB N \u5929/\u5E74\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u4EE5\u7CFB\u7EDF\u63D0\u793A\u4E2D\u7684\u771F\u5B9E\u5F53\u524D\u65E5\u671F\u4E3A\u57FA\u51C6\uFF1B\u5982\u679C\u542F\u52A8\u4E0A\u4E0B\u6587\u663E\u793A\u6570\u636E\u5E93\u8986\u76D6\u4E0D\u8DB3\uFF0C\u53EA\u5206\u6790\u7528\u6237\u65F6\u95F4\u8303\u56F4\u4E0E\u6570\u636E\u5E93\u8986\u76D6\u8303\u56F4\u7684\u4EA4\u96C6\u5E76\u8BF4\u660E\u7F3A\u53E3\u3002
14091
13958
  3. ChatLab \u7684 message.ts \u662F\u79D2\u7EA7 Unix \u65F6\u95F4\u6233\uFF0C\u4F7F\u7528 date(ts, 'unixepoch', 'localtime')\uFF1B\u7981\u6B62\u5199 ts/1000\u3002
14092
13959
  4. \u201C\u6700\u8FD1\u7684 N \u4EBA/\u6210\u5458\u201D\u5FC5\u987B\u5148\u7528\u6570\u636E\u5B9A\u4E49\u5E76\u7B5B\u9009\u51FA N \u4E2A\u6210\u5458\uFF0C\u518D\u53EA\u7EDF\u8BA1\u8FD9\u4E9B\u6210\u5458\u3002
14093
13960
  5. \u6298\u7EBF\u56FE\u6709\u591A\u6761\u7EBF\u65F6\uFF0C\u6BCF\u6761\u7EBF\u5FC5\u987B\u6709\u660E\u786E series \u5B57\u6BB5\u542B\u4E49\uFF1B\u6309\u65E5\u671F\u7EDF\u8BA1\u65F6\u5E94\u8865\u9F50\u65E5\u671F \xD7 series \u7684 0 \u503C\uFF0C\u907F\u514D\u7F3A\u5931\u65E5\u671F\u5BFC\u81F4\u6298\u7EBF\u65AD\u88C2\u6216\u8BEF\u8FDE\u3002
14094
- 6. \u5DE5\u5177\u5931\u8D25\u3001SQL \u4FEE\u6B63\u3001schema \u63A2\u7D22\u8FC7\u7A0B\u4E0D\u8981\u5199\u8FDB\u6700\u7EC8\u56DE\u7B54\uFF1B\u6700\u7EC8\u53EA\u4FDD\u7559\u56FE\u8868\u548C\u7B80\u77ED\u7ED3\u8BBA\u3002
14095
- 7. \u56FE\u8868\u89E3\u91CA\u53EA\u80FD\u63CF\u8FF0\u6570\u636E\u76F4\u63A5\u652F\u6301\u7684\u8D8B\u52BF\u3001\u5CF0\u503C\u3001\u4F4E\u8C37\u3001\u5DEE\u5F02\u548C\u540C\u6B65\u6027\uFF0C\u7981\u6B62\u5938\u5F20\u62DF\u4EBA\u5316\u6216\u65E0\u6CD5\u4ECE\u56FE\u4E2D\u9A8C\u8BC1\u7684\u5173\u7CFB\u5224\u65AD\u3002
13961
+ 6. \u5DE5\u5177\u5931\u8D25\u3001SQL \u4FEE\u6B63\u3001schema \u63A2\u7D22\u8FC7\u7A0B\u4E0D\u8981\u5199\u8FDB\u6700\u7EC8\u56DE\u7B54\uFF1B\u6700\u7EC8\u53EA\u4FDD\u7559\u539F\u751F\u56FE\u8868\u548C\u7B80\u77ED\u7ED3\u8BBA\uFF0C\u7981\u6B62\u7528 chart1.png\u3001chart2.png \u7B49\u6587\u4EF6\u540D\u5F15\u7528\u56FE\u8868\u3002
13962
+ 7. \u8C03\u7528 render_chart \u65F6\uFF0CChatLab \u4F1A\u7ACB\u5373\u5728\u8BE5\u4F4D\u7F6E\u63D2\u5165\u539F\u751F\u56FE\u8868\uFF1B\u9700\u8981\u56FE\u8868\u5C31\u76F4\u63A5\u8C03\u7528\u5DE5\u5177\uFF0C\u4E0D\u8981\u5199 SQL \u6CE8\u91CA\u3001Markdown \u56FE\u7247\u6216\u201C\u5DF2\u751F\u6210\u56FE\u8868/\u89C1\u4E0A\u56FE\u201D\u8FD9\u7C7B\u6587\u672C\u5360\u4F4D\u3002
13963
+ 8. \u7528\u6237\u8981\u6C42\u591A\u5F20\u56FE\u65F6\uFF0C\u56FE\u8868\u5E94\u5206\u6563\u51FA\u73B0\u5728\u76F8\u5173\u603B\u7ED3\u6216\u89E3\u91CA\u9644\u8FD1\uFF1B\u4E0D\u8981\u628A\u6240\u6709\u56FE\u96C6\u4E2D\u751F\u6210\u540E\u518D\u5728\u6700\u540E\u7528\u6587\u5B57\u5F15\u7528\u3002
13964
+ 9. \u4E3A\u56FE\u8868\u51C6\u5907\u6570\u636E\u65F6\uFF0C\u4F18\u5148\u8C03\u7528\u9AD8\u5C42\u5DE5\u5177\uFF08\u5982 get_time_stats\u3001member_stats\uFF09\u800C\u975E\u81EA\u5DF1\u5199 SQL\uFF1B\u53EA\u6709\u9AD8\u5C42\u5DE5\u5177\u65E0\u6CD5\u8986\u76D6\u6240\u9700\u7B5B\u9009\u6216\u805A\u5408\u65F6\u624D\u7528 sql\u3002render_chart \u4E0D\u8981\u7528\u4E8E\u5143\u6570\u636E\u63A2\u6D4B\u3001\u6807\u91CF\u67E5\u8BE2\u3001\u5355\u884C\u65E5\u671F\u5B57\u7B26\u4E32\u6216\u7EAF\u8BF4\u660E\u6027\u7ED3\u679C\u3002
13965
+ 10. render_chart \u7684\u5DE5\u5177\u7ED3\u679C\u4F1A\u5305\u542B\u6570\u636E\u9884\u89C8\uFF08Data preview\uFF09\uFF1B\u7528\u5B83\u5206\u6790\u5CF0\u503C\u3001\u4F4E\u8C37\u548C\u5DEE\u5F02\uFF0C\u4E0D\u8981\u4E3A\u4E86\u8BFB\u53D6\u6570\u503C\u91CD\u590D\u751F\u6210\u7B49\u4EF7\u56FE\u8868\u3002
13966
+ 11. \u56FE\u8868\u89E3\u91CA\u53EA\u80FD\u63CF\u8FF0\u6570\u636E\u76F4\u63A5\u652F\u6301\u7684\u8D8B\u52BF\u3001\u5CF0\u503C\u3001\u4F4E\u8C37\u3001\u5DEE\u5F02\u548C\u540C\u6B65\u6027\uFF0C\u7981\u6B62\u5938\u5F20\u62DF\u4EBA\u5316\u6216\u65E0\u6CD5\u4ECE\u56FE\u4E2D\u9A8C\u8BC1\u7684\u5173\u7CFB\u5224\u65AD\u3002
14096
13967
 
14097
13968
  \u5DE5\u4F5C\u6D41\u7A0B\uFF1A
14098
13969
  1. \u8C03\u7528 get_schema\u3002
14099
- 2. \u6839\u636E schema \u548C\u7528\u6237\u8981\u6C42\u786E\u5B9A\u7EDF\u8BA1\u5BF9\u8C61\u3001\u65F6\u95F4\u8303\u56F4\u3001\u7EF4\u5EA6\u3001\u6307\u6807\u548C\u56FE\u8868\u7C7B\u578B\u3002
14100
- 3. \u5FC5\u8981\u65F6\u7528\u53EA\u8BFB SQL \u5728 render_chart \u4E2D\u5B8C\u6210\u6210\u5458\u7B5B\u9009\u3001\u65F6\u95F4\u8303\u56F4\u786E\u8BA4\u3001\u805A\u5408\u548C\u8865\u96F6\u3002
14101
- 4. \u8C03\u7528 render_chart\uFF0C\u63D0\u4EA4\u53EA\u8BFB SQL \u548C ChartSpec v1\u3002
14102
- 5. \u56FE\u8868\u751F\u6210\u540E\uFF0C\u7528 1-3 \u53E5\u7B80\u6D01\u6587\u5B57\u89E3\u91CA\u7ED3\u8BBA\u3002\u7528\u6237\u8981\u6C42\u591A\u5F20\u56FE\u65F6\uFF0C\u53EF\u4EE5\u591A\u6B21\u8C03\u7528 render_chart\u3002
13970
+ 2. \u6839\u636E schema\u3001\u7528\u6237\u8981\u6C42\u3001\u7CFB\u7EDF\u5F53\u524D\u65E5\u671F\u548C\u542F\u52A8\u4E0A\u4E0B\u6587\u786E\u5B9A\u7EDF\u8BA1\u5BF9\u8C61\u3001\u65F6\u95F4\u8303\u56F4\u3001\u7EF4\u5EA6\u3001\u6307\u6807\u548C\u56FE\u8868\u7C7B\u578B\u3002
13971
+ 3. \u5982\u9700\u8C03\u67E5\u4E8B\u5B9E\u3001\u5EFA\u7ACB\u8BDD\u9898\u5730\u56FE\u3001\u7B5B\u9009\u6210\u5458\u6216\u786E\u8BA4\u7EDF\u8BA1\u53E3\u5F84\uFF0C\u5148\u7528\u53EF\u7528\u7684\u666E\u901A\u5206\u6790\u5DE5\u5177\u5B8C\u6210\uFF1B\u4E0D\u8981\u4E3A\u4E86\u8C03\u67E5\u800C\u751F\u6210\u4E34\u65F6\u56FE\u8868\u3002
13972
+ 4. \u51C6\u5907\u56FE\u8868\u6570\u636E\u2014\u2014\u5148\u95EE\u81EA\u5DF1\uFF1A\u54EA\u4E2A\u53EF\u7528\u7684\u9AD8\u5C42\u5DE5\u5177\u80FD\u76F4\u63A5\u63D0\u4F9B\u8FD9\u5F20\u56FE\u6240\u9700\u7684\u6570\u636E\uFF1Fget_time_stats \u80FD\u7ED9\u65F6\u95F4\u5206\u5E03\u3001member_stats \u80FD\u7ED9\u6210\u5458\u6392\u540D\uFF1B\u80FD\u7528\u5C31\u8C03\u7528\uFF0C\u5C06 data \u6570\u7EC4\u4F5C\u4E3A rows \u4F20\u5165 render_chart\u3002\u53EA\u6709\u5728\u9AD8\u5C42\u5DE5\u5177\u65E0\u6CD5\u8986\u76D6\u6240\u9700\u7B5B\u9009\u6216\u805A\u5408\u65F6\uFF0C\u624D\u7F16\u5199 SQL\u3002
13973
+ 5. \u5728\u5E0C\u671B\u56FE\u8868\u51FA\u73B0\u7684\u4F4D\u7F6E\u8C03\u7528 render_chart\uFF0C\u63D0\u4EA4\u6570\u636E\uFF08rows \u6216 sql\uFF09\u548C ChartSpec v1\u3002
13974
+ 6. \u56FE\u8868\u751F\u6210\u540E\uFF0C\u7528 1-3 \u53E5\u7B80\u6D01\u6587\u5B57\u89E3\u91CA\u7ED3\u8BBA\u3002\u7528\u6237\u8981\u6C42\u591A\u5F20\u56FE\u65F6\uFF0C\u53EF\u4EE5\u591A\u6B21\u8C03\u7528 render_chart\uFF0C\u5E76\u628A\u6BCF\u5F20\u56FE\u653E\u5728\u6700\u76F8\u5173\u7684\u6BB5\u843D\u9644\u8FD1\u3002
14103
13975
 
14104
13976
  \u5E38\u7528\u8BED\u3001\u53E3\u5934\u7985\u3001\u9AD8\u9891\u77ED\u53E5\u7C7B\u56FE\u8868\u5FC5\u987B\u6392\u9664\u975E\u771F\u4EBA\u6587\u672C\uFF1A\u53D1\u9001\u8005\u4E3A\u7CFB\u7EDF\u6D88\u606F\u3001\u5A92\u4F53\u5360\u4F4D\uFF08\u5982 [\u8868\u60C5\u5305]\u3001[\u56FE\u7247]\u3001[\u8BED\u97F3]\u3001[\u89C6\u9891]\u3001[\u6587\u4EF6]\u3001[\u94FE\u63A5]\u3001[\u7EA2\u5305]\u3001[\u8F6C\u8D26]\uFF09\u4EE5\u53CA\u64A4\u56DE/\u5220\u9664\u63D0\u793A\u90FD\u4E0D\u80FD\u8FDB\u5165\u7EDF\u8BA1\u3002
14105
13977
 
@@ -14121,23 +13993,28 @@ ChartSpec \u793A\u4F8B\uFF1A
14121
13993
  \u7528\u6237\u660E\u786E\u8981\u6C42\u56FE\u8868\u65F6\u5FC5\u987B\u5C1D\u8BD5\u751F\u6210\u3002\u7528\u6237\u6CA1\u6709\u660E\u786E\u8981\u6C42\u65F6\uFF0C\u53EA\u6709\u6392\u540D\u3001\u8D8B\u52BF\u3001\u5206\u5E03\u3001\u5360\u6BD4\u6216\u4E8C\u7EF4\u5BC6\u5EA6\u660E\u663E\u66F4\u6E05\u695A\u65F6\u624D\u4E3B\u52A8\u751F\u6210\uFF0C\u4E14\u9ED8\u8BA4\u6700\u591A\u4E00\u5F20\u3002`;
14122
13994
  var EN_PROMPT = `You are the ChatLab Chart Assistant. In this turn, the user wants one or more charts embedded naturally in the answer.
14123
13995
 
14124
- You must generate charts through the render_chart tool. Do not output HTML, JavaScript, SVG, Canvas, ECharts options, or rendering code.
13996
+ You must generate charts through the render_chart tool. Do not output HTML, JavaScript, SVG, Canvas, ECharts options, Markdown image links such as ![Chart](chart1.png), or rendering code.
14125
13997
 
14126
13998
  Hard rules:
14127
13999
  1. Always call get_schema before writing the first SQL query. Do not guess table names, field names, or timestamp fields.
14128
- 2. For "latest", "until now", or "through the latest date", derive the end date from MAX(ts) or the equivalent timestamp field in the data. Do not hand-calculate or assume dates.
14000
+ 2. For relative ranges like "recent" or "past N days/years", use the real current date from the system prompt as the baseline. If startup context shows incomplete database coverage, analyze only the intersection between the requested range and database coverage, and state the gap.
14129
14001
  3. ChatLab message.ts is a Unix timestamp in seconds. Use date(ts, 'unixepoch', 'localtime'); never write ts/1000.
14130
14002
  4. For "latest N members" or "recent N people", first define and select those N members from the data, then count only those members.
14131
14003
  5. For multi-series line charts, every line must have an explicit series field. For daily counts, fill the date x series grid with zero values so missing days do not break or mislead the line.
14132
- 6. Do not include failed SQL attempts, schema exploration, or retry reasoning in the final answer. Final answer should contain only the chart and a short conclusion.
14133
- 7. Explain only trends, peaks, lows, differences, and synchrony directly supported by chart data. Do not make exaggerated personality or relationship claims.
14004
+ 6. Do not include failed SQL attempts, schema exploration, or retry reasoning in the final answer. Final answer should contain only native charts and a short conclusion; never reference charts with filenames like chart1.png or chart2.png.
14005
+ 7. When you call render_chart, ChatLab immediately inserts the native chart at that position. If a chart is needed, call the tool directly; do not write SQL-comment placeholders, Markdown images, or text such as "generated chart" or "see chart above".
14006
+ 8. If the user asks for multiple charts, place each chart near the relevant summary or explanation. Do not generate all charts together and then reference them later with text.
14007
+ 9. When preparing chart data, prefer calling a high-level tool (such as get_time_stats, member_stats) over writing SQL; only use sql when no high-level tool can satisfy the required filters or aggregations. Do not use render_chart for metadata inspection, scalar queries, single-row date strings, or purely explanatory results.
14008
+ 10. render_chart tool results include a Data preview; use it to identify peaks, lows, and differences. Do not generate equivalent charts again just to read values.
14009
+ 11. Explain only trends, peaks, lows, differences, and synchrony directly supported by chart data. Do not make exaggerated personality or relationship claims.
14134
14010
 
14135
14011
  Workflow:
14136
14012
  1. Call get_schema.
14137
- 2. Derive the target members, time range, dimensions, metrics, and chart type from schema and the user request.
14138
- 3. When needed, use read-only SQL inside render_chart to select members, determine time range, aggregate data, and fill zero rows.
14139
- 4. Call render_chart with read-only SQL and ChartSpec v1.
14140
- 5. After the chart is generated, explain the key finding in 1-3 concise sentences. If the user asks for multiple charts, call render_chart multiple times.
14013
+ 2. Derive the target members, time range, dimensions, metrics, and chart type from schema, the user request, system current date, and startup context.
14014
+ 3. If you need to investigate facts, build a topic map, select members, or confirm statistical definitions, use available ordinary analysis tools first; do not generate temporary charts for investigation.
14015
+ 4. Prepare chart data \u2014 ask yourself first: which available high-level tool can directly provide the data this chart needs? get_time_stats covers time distributions; member_stats covers member rankings. If one fits, call it and pass its data array as rows to render_chart. Only write SQL when no high-level tool can cover the required filters or aggregations.
14016
+ 5. Call render_chart at the position where the chart should appear, with data (rows or sql) and ChartSpec v1.
14017
+ 6. After the chart is generated, explain the key finding in 1-3 concise sentences. If the user asks for multiple charts, call render_chart multiple times and place each chart near its most relevant section.
14141
14018
 
14142
14019
  For catchphrase, common phrase, or frequent short-text charts, exclude non-human text from the SQL: system-message senders, media placeholders such as [Sticker], [Image], [Photo], [Voice], [Video], [File], [Link], and recall/deletion notices must not be counted.
14143
14020
 
@@ -14183,6 +14060,95 @@ function wrapWithChartSchemaGate(tool, state) {
14183
14060
  };
14184
14061
  }
14185
14062
 
14063
+ // ../../packages/node-runtime/src/ai/agent/history.ts
14064
+ function createEmptyPiUsage() {
14065
+ return {
14066
+ input: 0,
14067
+ output: 0,
14068
+ cacheRead: 0,
14069
+ cacheWrite: 0,
14070
+ totalTokens: 0,
14071
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
14072
+ };
14073
+ }
14074
+ function isReplayableToolBlock(block) {
14075
+ if (block.type !== "tool") return false;
14076
+ const { toolCallId, result, status } = block.tool;
14077
+ return typeof toolCallId === "string" && toolCallId.length > 0 && typeof result === "string" && (status === "done" || status === "error");
14078
+ }
14079
+ function stripInternalParams(params) {
14080
+ if (!params) return {};
14081
+ const cleaned = {};
14082
+ for (const [key, value] of Object.entries(params)) {
14083
+ if (!key.startsWith("_")) cleaned[key] = value;
14084
+ }
14085
+ return cleaned;
14086
+ }
14087
+ function makeAssistantMessage(content, stopReason, modelInfo) {
14088
+ return {
14089
+ role: "assistant",
14090
+ content,
14091
+ api: modelInfo?.api ?? "openai-completions",
14092
+ provider: modelInfo?.provider ?? "chatlab",
14093
+ model: modelInfo?.id ?? "unknown",
14094
+ usage: createEmptyPiUsage(),
14095
+ stopReason,
14096
+ timestamp: Date.now()
14097
+ };
14098
+ }
14099
+ function replayAssistantBlocks(blocks, out, options) {
14100
+ const { modelInfo, thinkingSignature } = options ?? {};
14101
+ const replayThinking = !!thinkingSignature && blocks.some(isReplayableToolBlock);
14102
+ let content = [];
14103
+ for (const block of blocks) {
14104
+ if (block.type === "text") {
14105
+ if (block.text.trim()) content.push({ type: "text", text: block.text });
14106
+ } else if (block.type === "think" && replayThinking && block.text.trim()) {
14107
+ content.push({ type: "thinking", thinking: block.text, thinkingSignature });
14108
+ } else if (isReplayableToolBlock(block)) {
14109
+ content.push({
14110
+ type: "toolCall",
14111
+ id: block.tool.toolCallId,
14112
+ name: block.tool.name,
14113
+ arguments: stripInternalParams(block.tool.params)
14114
+ });
14115
+ out.push(makeAssistantMessage(content, "toolUse", modelInfo));
14116
+ out.push({
14117
+ role: "toolResult",
14118
+ toolCallId: block.tool.toolCallId,
14119
+ toolName: block.tool.name,
14120
+ content: [{ type: "text", text: truncateToolResultText(block.tool.result) || "(empty result)" }],
14121
+ isError: block.tool.isError ?? block.tool.status === "error",
14122
+ timestamp: Date.now()
14123
+ });
14124
+ content = [];
14125
+ }
14126
+ }
14127
+ if (content.length > 0) {
14128
+ out.push(makeAssistantMessage(content, "stop", modelInfo));
14129
+ }
14130
+ }
14131
+ function toPiHistoryMessages(messages, options) {
14132
+ const out = [];
14133
+ for (const msg of messages) {
14134
+ if (msg.role === "user") {
14135
+ out.push({
14136
+ role: "user",
14137
+ content: [{ type: "text", text: msg.content || "" }],
14138
+ timestamp: Date.now()
14139
+ });
14140
+ continue;
14141
+ }
14142
+ const replayable = msg.role === "assistant" && msg.contentBlocks?.some(isReplayableToolBlock);
14143
+ if (replayable) {
14144
+ replayAssistantBlocks(msg.contentBlocks, out, options);
14145
+ } else {
14146
+ out.push(makeAssistantMessage([{ type: "text", text: msg.content || "" }], "stop", options?.modelInfo));
14147
+ }
14148
+ }
14149
+ return out;
14150
+ }
14151
+
14186
14152
  // ../../packages/node-runtime/src/ai/compression/compressor.ts
14187
14153
  var DEFAULT_CONTEXT_WINDOW = 128e3;
14188
14154
  var INITIAL_COMPRESSION_PROMPT = `You are a context compression assistant. Compress the conversation below into a structured summary.
@@ -14250,6 +14216,9 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
14250
14216
  }
14251
14217
  for (const msg of messages) {
14252
14218
  historyForTokenCount.push({ role: msg.role, content: msg.content });
14219
+ for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {
14220
+ historyForTokenCount.push({ role: "tool", content: toolText });
14221
+ }
14253
14222
  }
14254
14223
  const currentTokens = countMessagesTokens(historyForTokenCount, systemPrompt);
14255
14224
  logger.info("Compression", `Token check: ${currentTokens} / ${thresholdTokens} (${contextWindow} window)`, {
@@ -14281,10 +14250,13 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
14281
14250
  bufferBoundaryTimestamp: bufferBoundary,
14282
14251
  compressedMessageCount: messagesToCompress.length
14283
14252
  });
14284
- const afterTokenCount = [
14285
- { role: "assistant", content: summaryText },
14286
- ...bufferMessages.map((m) => ({ role: m.role, content: m.content }))
14287
- ];
14253
+ const afterTokenCount = [{ role: "assistant", content: summaryText }];
14254
+ for (const m of bufferMessages) {
14255
+ afterTokenCount.push({ role: m.role, content: m.content });
14256
+ for (const toolText of replayedToolResultTexts(m.contentBlocks)) {
14257
+ afterTokenCount.push({ role: "tool", content: toolText });
14258
+ }
14259
+ }
14288
14260
  const tokensAfter = countMessagesTokens(afterTokenCount, systemPrompt);
14289
14261
  if (tokensAfter >= thresholdTokens) {
14290
14262
  logger.warn(
@@ -14312,11 +14284,22 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
14312
14284
  return { compressed: false, reason: "error", error: String(error) };
14313
14285
  }
14314
14286
  }
14287
+ function replayedToolResultTexts(blocks) {
14288
+ if (!blocks) return [];
14289
+ return blocks.filter(isReplayableToolBlock).map((block) => truncateToolResultText(block.tool.result));
14290
+ }
14291
+ function countMessageTokensWithTools(msg) {
14292
+ let tokens = countTokens(msg.content) + 4;
14293
+ for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {
14294
+ tokens += countTokens(toolText) + 4;
14295
+ }
14296
+ return tokens;
14297
+ }
14315
14298
  function splitMessagesForCompression(messages, bufferTokenBudget) {
14316
14299
  let bufferTokens = 0;
14317
14300
  let splitIndex = messages.length;
14318
14301
  for (let i = messages.length - 1; i >= 0; i--) {
14319
- const msgTokens = countTokens(messages[i].content) + 4;
14302
+ const msgTokens = countMessageTokensWithTools(messages[i]);
14320
14303
  if (bufferTokens + msgTokens > bufferTokenBudget) {
14321
14304
  splitIndex = i + 1;
14322
14305
  break;
@@ -14342,6 +14325,10 @@ ${existingSummary.content}
14342
14325
  for (const msg of messagesToCompress) {
14343
14326
  const roleLabel = msg.role === "user" ? "User" : "Assistant";
14344
14327
  parts.push(`${roleLabel}: ${msg.content}`);
14328
+ for (const block of (msg.contentBlocks ?? []).filter(isReplayableToolBlock)) {
14329
+ parts.push(`[Tool result: ${block.tool.name}]
14330
+ ${truncateToolResultText(block.tool.result)}`);
14331
+ }
14345
14332
  }
14346
14333
  return parts.join("\n\n");
14347
14334
  }
@@ -14826,6 +14813,7 @@ function formatToolResultAsText(details) {
14826
14813
  const messages = details.messages;
14827
14814
  for (const [key, value] of Object.entries(details)) {
14828
14815
  if (key === "messages") continue;
14816
+ if (key === "rawMessages") continue;
14829
14817
  if (value === void 0 || value === null) continue;
14830
14818
  if (typeof value === "object") {
14831
14819
  if ("start" in value && "end" in value) {
@@ -14964,40 +14952,9 @@ import {
14964
14952
  } from "@earendil-works/pi-ai";
14965
14953
 
14966
14954
  // ../../packages/node-runtime/src/ai/agent/constants.ts
14967
- var DEFAULT_MAX_TOOL_ROUNDS = 10;
14955
+ var DEFAULT_MAX_TOOL_ROUNDS = 20;
14968
14956
 
14969
14957
  // ../../packages/node-runtime/src/ai/agent/core.ts
14970
- function createEmptyPiUsage() {
14971
- return {
14972
- input: 0,
14973
- output: 0,
14974
- cacheRead: 0,
14975
- cacheWrite: 0,
14976
- totalTokens: 0,
14977
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
14978
- };
14979
- }
14980
- function toPiHistoryMessages(messages) {
14981
- return messages.map((msg) => {
14982
- if (msg.role === "user") {
14983
- return {
14984
- role: "user",
14985
- content: [{ type: "text", text: msg.content || "" }],
14986
- timestamp: Date.now()
14987
- };
14988
- }
14989
- return {
14990
- role: "assistant",
14991
- content: [{ type: "text", text: msg.content || "" }],
14992
- api: "openai-completions",
14993
- provider: "chatlab",
14994
- model: "unknown",
14995
- usage: createEmptyPiUsage(),
14996
- stopReason: "stop",
14997
- timestamp: Date.now()
14998
- };
14999
- });
15000
- }
15001
14958
  function isPiMessage(message) {
15002
14959
  return message.role === "user" || message.role === "assistant" || message.role === "toolResult";
15003
14960
  }
@@ -15018,7 +14975,13 @@ async function runAgentCore(options) {
15018
14975
  } = options;
15019
14976
  await initTokenizer();
15020
14977
  const resolvedStreamFn = options.streamFn ?? defaultStreamSimple;
15021
- const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
14978
+ const totalUsage = {
14979
+ promptTokens: 0,
14980
+ completionTokens: 0,
14981
+ totalTokens: 0,
14982
+ cacheReadTokens: 0,
14983
+ cacheWriteTokens: 0
14984
+ };
15022
14985
  const toolsUsed = [];
15023
14986
  let toolRounds = 0;
15024
14987
  const addPiUsage = (usage) => {
@@ -15026,6 +14989,8 @@ async function runAgentCore(options) {
15026
14989
  totalUsage.promptTokens += usage.input || 0;
15027
14990
  totalUsage.completionTokens += usage.output || 0;
15028
14991
  totalUsage.totalTokens += usage.totalTokens || usage.input + usage.output || 0;
14992
+ totalUsage.cacheReadTokens += usage.cacheRead || 0;
14993
+ totalUsage.cacheWriteTokens += usage.cacheWrite || 0;
15029
14994
  };
15030
14995
  if (abortSignal?.aborted) {
15031
14996
  return { usage: totalUsage, finalMessages: [], toolsUsed: [], toolRounds: 0 };
@@ -15045,21 +15010,18 @@ async function runAgentCore(options) {
15045
15010
  return clampThinkingLevel(piModel, level);
15046
15011
  })();
15047
15012
  const finalThinkingLevel = resolvedThinkingLevel ?? (piModel.reasoning ? void 0 : "off");
15048
- console.debug("[agent-core] thinkingLevel resolution:", {
15049
- input: options.thinkingLevel,
15050
- modelId: piModel.id,
15051
- modelReasoning: effectiveModel.reasoning,
15052
- isDefault,
15053
- resolved: resolvedThinkingLevel,
15054
- final: finalThinkingLevel
15055
- });
15013
+ const thinkingFormat = piModel.compat?.thinkingFormat;
15014
+ const replayOptions = piModel.reasoning && thinkingFormat === "deepseek" ? {
15015
+ modelInfo: { api: piModel.api, provider: piModel.provider, id: piModel.id },
15016
+ thinkingSignature: "reasoning_content"
15017
+ } : void 0;
15056
15018
  const coreAgent = new PiAgentCore({
15057
15019
  initialState: {
15058
15020
  systemPrompt,
15059
15021
  model: effectiveModel,
15060
15022
  thinkingLevel: finalThinkingLevel,
15061
15023
  tools: maxToolRounds > 0 ? tools : [],
15062
- messages: toPiHistoryMessages(history)
15024
+ messages: toPiHistoryMessages(history, replayOptions)
15063
15025
  },
15064
15026
  getApiKey: () => apiKey,
15065
15027
  streamFn: resolvedStreamFn,
@@ -15119,11 +15081,18 @@ async function runAgentCore(options) {
15119
15081
  toolsUsed.push(event.toolName);
15120
15082
  onEvent({
15121
15083
  type: "tool_start",
15084
+ toolCallId: event.toolCallId,
15122
15085
  toolName: event.toolName,
15123
15086
  toolParams: event.args || {}
15124
15087
  });
15125
15088
  } else if (event.type === "tool_execution_end") {
15126
- onEvent({ type: "tool_end", toolName: event.toolName, toolResult: event.result });
15089
+ onEvent({
15090
+ type: "tool_end",
15091
+ toolCallId: event.toolCallId,
15092
+ toolName: event.toolName,
15093
+ toolResult: event.result,
15094
+ isError: event.isError
15095
+ });
15127
15096
  } else if (event.type === "turn_end") {
15128
15097
  const hadToolCalls = event.toolResults.length > 0;
15129
15098
  if (hadToolCalls) {
@@ -15182,6 +15151,584 @@ async function runAgentCore(options) {
15182
15151
  }
15183
15152
  }
15184
15153
 
15154
+ // ../../packages/node-runtime/src/ai/agent/data-snapshot.ts
15155
+ function roundShare(value) {
15156
+ if (!Number.isFinite(value)) return 0;
15157
+ return Math.round(value * 10) / 10;
15158
+ }
15159
+ function formatNullableTimestamp(timestamp) {
15160
+ return typeof timestamp === "number" && Number.isFinite(timestamp) ? String(timestamp) : "null";
15161
+ }
15162
+ function formatSharePercent(share) {
15163
+ if (!Number.isFinite(share)) return "0%";
15164
+ const rounded = Math.round(share * 10) / 10;
15165
+ return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
15166
+ }
15167
+ function createDataSnapshotFromOverview(overview) {
15168
+ if (!overview) return void 0;
15169
+ const topMembers = overview.topMembers ?? [];
15170
+ return {
15171
+ version: 2,
15172
+ name: overview.name,
15173
+ platform: overview.platform,
15174
+ type: overview.type,
15175
+ totalMessages: overview.totalMessages,
15176
+ totalMembers: overview.totalMembers,
15177
+ firstMessageTs: overview.firstMessageTs,
15178
+ lastMessageTs: overview.lastMessageTs,
15179
+ activeMemberHints: topMembers.slice(0, 10).map((member) => ({
15180
+ memberId: member.id,
15181
+ displayName: member.name,
15182
+ messageCount: member.count,
15183
+ share: overview.totalMessages > 0 ? roundShare(member.count / overview.totalMessages * 100) : 0
15184
+ })),
15185
+ segmentSummaries: {
15186
+ availableCount: overview.summaryCount ?? 0
15187
+ }
15188
+ };
15189
+ }
15190
+ function formatDataSnapshotForRouter(dataSnapshot) {
15191
+ if (!dataSnapshot) return "(none)";
15192
+ return [
15193
+ `name: ${dataSnapshot.name}`,
15194
+ `platform: ${dataSnapshot.platform}`,
15195
+ `type: ${dataSnapshot.type}`,
15196
+ `total_messages: ${dataSnapshot.totalMessages}`,
15197
+ `total_members: ${dataSnapshot.totalMembers}`,
15198
+ `first_message_ts: ${formatNullableTimestamp(dataSnapshot.firstMessageTs)}`,
15199
+ `last_message_ts: ${formatNullableTimestamp(dataSnapshot.lastMessageTs)}`,
15200
+ `segment_summaries_available: ${dataSnapshot.segmentSummaries?.availableCount ?? 0}`,
15201
+ `active_member_hint_count: ${dataSnapshot.activeMemberHints?.length ?? 0}`
15202
+ ].join("\n");
15203
+ }
15204
+ function formatDataSnapshotForPlanner(dataSnapshot) {
15205
+ if (!dataSnapshot) return "(none)";
15206
+ const memberHints = dataSnapshot.activeMemberHints ?? [];
15207
+ const memberHintLines = memberHints.length > 0 ? memberHints.map(
15208
+ (member, index) => `${index + 1}. member_id=${member.memberId} | display_name=${member.displayName} | messages=${member.messageCount} | share=${formatSharePercent(member.share)}`
15209
+ ).join("\n") : "(none)";
15210
+ return `${formatDataSnapshotForRouter(dataSnapshot)}
15211
+ active_member_hints:
15212
+ ${memberHintLines}
15213
+ notes:
15214
+ - member_id is a tool lookup hint; display_name may not be unique.
15215
+ - Active member hints reflect historical total message volume, not recent activity.
15216
+ - Use real current date for relative ranges; database bounds only describe data coverage.
15217
+ - Do not plan a tool call only to rediscover min/max timestamp.
15218
+ - When using default recent-day tools, choose a range that intersects database bounds instead of probing an empty real-current-date window first.`;
15219
+ }
15220
+
15221
+ // ../../packages/node-runtime/src/ai/agent/router.ts
15222
+ import {
15223
+ completeSimple as completeSimple2
15224
+ } from "@earendil-works/pi-ai";
15225
+ var DEFAULT_LLM_FALLBACK_CONFIDENCE_THRESHOLD = 0.6;
15226
+ function clampConfidence(value) {
15227
+ if (!Number.isFinite(value)) return 0;
15228
+ return Math.max(0, Math.min(1, value));
15229
+ }
15230
+ function normalizeText(text) {
15231
+ return text.trim().replace(/\s+/g, " ");
15232
+ }
15233
+ function matchAny(text, patterns) {
15234
+ return patterns.some((pattern) => pattern.test(text));
15235
+ }
15236
+ function isRoute(value) {
15237
+ return value === "direct_response" || value === "tool_assisted" || value === "planned_execution";
15238
+ }
15239
+ function resultToText(result) {
15240
+ return typeof result === "string" ? { text: result } : result;
15241
+ }
15242
+ function extractJsonObject3(text) {
15243
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
15244
+ if (fenced?.[1]) return fenced[1].trim();
15245
+ const start = text.indexOf("{");
15246
+ const end = text.lastIndexOf("}");
15247
+ if (start >= 0 && end > start) return text.slice(start, end + 1);
15248
+ return text.trim();
15249
+ }
15250
+ function parseLlmDecision(rawText, usage) {
15251
+ try {
15252
+ const parsed = JSON.parse(extractJsonObject3(rawText));
15253
+ if (!isRoute(parsed.route)) return null;
15254
+ return {
15255
+ route: parsed.route,
15256
+ confidence: clampConfidence(typeof parsed.confidence === "number" ? parsed.confidence : 0.5),
15257
+ reason: typeof parsed.reason === "string" && parsed.reason.trim() ? parsed.reason.trim() : "LLM route decision.",
15258
+ source: "llm",
15259
+ usage
15260
+ };
15261
+ } catch {
15262
+ return null;
15263
+ }
15264
+ }
15265
+ function buildLlmRouterPrompt(input, ruleDecision2) {
15266
+ const toolNames = input.availableTools?.length ? input.availableTools.join(", ") : "(none)";
15267
+ const dataSnapshot = formatDataSnapshotForRouter(input.dataSnapshot);
15268
+ return `Classify the user's ChatLab request into exactly one route.
15269
+
15270
+ Routes:
15271
+ - direct_response: no local chat data/tools needed.
15272
+ - tool_assisted: local chat data/tools needed, but the task is simple.
15273
+ - planned_execution: complex analysis, multi-step evidence gathering, retries, long-range comparison, relationship analysis, or insufficient-evidence handling.
15274
+
15275
+ Return strict JSON only:
15276
+ {"route":"direct_response|tool_assisted|planned_execution","confidence":0.0,"reason":"short reason"}
15277
+
15278
+ Context:
15279
+ - locale: ${input.locale}
15280
+ - chatType: ${input.chatType}
15281
+ - availableTools: ${toolNames}
15282
+ - dataSnapshot: ${dataSnapshot}
15283
+ - assistantSummary: ${input.assistantSummary ?? "(none)"}
15284
+ - skillSummary: ${input.skillSummary ?? "(none)"}
15285
+ - recentIntentSummary: ${input.recentIntentSummary ?? "(none)"}
15286
+ - ruleDecision: ${ruleDecision2.route}, confidence=${ruleDecision2.confidence}, reason=${ruleDecision2.reason}
15287
+
15288
+ User request:
15289
+ ${input.userMessage}`;
15290
+ }
15291
+ function directRuleDecision(text) {
15292
+ if (matchAny(text, [/不用查聊天记录|不用查询|不用查数据|不用看本地数据/i, /润色|改写|翻译|polish|rewrite|translate/i])) {
15293
+ return {
15294
+ route: "direct_response",
15295
+ confidence: 0.95,
15296
+ reason: "User explicitly asked for a non-data response.",
15297
+ source: "rule"
15298
+ };
15299
+ }
15300
+ if (matchAny(text, [/什么是|解释一下|概念|区别|explain|briefly explain|why .*function calling|react/i])) {
15301
+ return {
15302
+ route: "direct_response",
15303
+ confidence: 0.86,
15304
+ reason: "Conceptual explanation without local chat-data dependency.",
15305
+ source: "rule"
15306
+ };
15307
+ }
15308
+ if (matchAny(text, [/日志在哪里|怎么配置|设置|帮助|help|where .*log/i])) {
15309
+ return {
15310
+ route: "direct_response",
15311
+ confidence: 0.84,
15312
+ reason: "Help or configuration request does not require chat tools.",
15313
+ source: "rule"
15314
+ };
15315
+ }
15316
+ if (matchAny(text, [/你觉得|闲聊|随便聊聊|建议我|should i/i])) {
15317
+ return {
15318
+ route: "direct_response",
15319
+ confidence: 0.78,
15320
+ reason: "Conversational request without clear data dependency.",
15321
+ source: "rule"
15322
+ };
15323
+ }
15324
+ return null;
15325
+ }
15326
+ function plannedRuleDecision(text) {
15327
+ if (matchAny(text, [/如果.*找不到.*继续查|换几个相关|换几个.*继续|search.*if.*not.*found/i])) {
15328
+ return {
15329
+ route: "planned_execution",
15330
+ confidence: 0.88,
15331
+ reason: "Request asks for search failure recovery and retry strategy.",
15332
+ source: "rule"
15333
+ };
15334
+ }
15335
+ const dimensions = [
15336
+ matchAny(text, [/分析|复盘|总结.*规律|趋势|变化|演变|compare|comparison/i]),
15337
+ matchAny(text, [
15338
+ /过去一年|最近一年|近一年|过去.*年|最近半年|今年|去年|上半年|下半年|长期|按季度|按月份|first half|second half/i
15339
+ ]),
15340
+ matchAny(text, [/关系|互动|影响力|核心成员|最强|最活跃|被忽略|主动|回应|turning point|转折点/i]),
15341
+ matchAny(text, [/证据|举出|例子|关键分歧|主要矛盾|evidence/i]),
15342
+ matchAny(text, [/并且|同时|以及|分别|每个阶段|按阶段|阶段|多条件|参与|回复|话题|主题|发言规律|topic|theme/i]),
15343
+ matchAny(text, [/证据不够|不足以|不够|uncertain|insufficient/i])
15344
+ ];
15345
+ const score = dimensions.filter(Boolean).length;
15346
+ if (score >= 2) {
15347
+ return {
15348
+ route: "planned_execution",
15349
+ confidence: Math.min(0.95, 0.72 + score * 0.04),
15350
+ reason: `Rule matched ${score} complex-analysis signals.`,
15351
+ source: "rule"
15352
+ };
15353
+ }
15354
+ return null;
15355
+ }
15356
+ function toolRuleDecision(text) {
15357
+ if (matchAny(text, [
15358
+ /一共有多少|多少成员|多少条消息|谁发言最多|前\s*\d+\s*名|top\s*\d+/i,
15359
+ /异常活跃|活跃度|发言最多|message count|member count/i
15360
+ ])) {
15361
+ return {
15362
+ route: "tool_assisted",
15363
+ confidence: 0.82,
15364
+ reason: "Simple statistics request can be answered by one or a few tools.",
15365
+ source: "rule"
15366
+ };
15367
+ }
15368
+ if (matchAny(text, [
15369
+ /找一下|查一下|有没有聊到|有没有提过|最近提到|昨天.*聊了什么|最近.*发言/i,
15370
+ /search|find|mentioned|talked about/i
15371
+ ])) {
15372
+ return {
15373
+ route: "tool_assisted",
15374
+ confidence: 0.78,
15375
+ reason: "Simple search or lookup request with a single clear target.",
15376
+ source: "rule"
15377
+ };
15378
+ }
15379
+ return null;
15380
+ }
15381
+ function ruleDecision(input) {
15382
+ const text = normalizeText(input.userMessage);
15383
+ return plannedRuleDecision(text) ?? toolRuleDecision(text) ?? directRuleDecision(text) ?? {
15384
+ route: "tool_assisted",
15385
+ confidence: 0.45,
15386
+ reason: "Ambiguous request; conservatively keep the existing tool-assisted Agent path.",
15387
+ source: "rule"
15388
+ };
15389
+ }
15390
+ async function decideRequestRoute(input, options = {}) {
15391
+ const decision = ruleDecision(input);
15392
+ const threshold = options.llmFallbackConfidenceThreshold ?? DEFAULT_LLM_FALLBACK_CONFIDENCE_THRESHOLD;
15393
+ if (decision.confidence >= threshold || !options.llmRouter) {
15394
+ return { ...decision, confidence: clampConfidence(decision.confidence) };
15395
+ }
15396
+ const llmDecision = await options.llmRouter(input, decision);
15397
+ return {
15398
+ ...llmDecision,
15399
+ confidence: clampConfidence(llmDecision.confidence)
15400
+ };
15401
+ }
15402
+ function createLlmRouteDecider(options) {
15403
+ return async (input, ruleDecision2) => {
15404
+ const prompt = buildLlmRouterPrompt(input, ruleDecision2);
15405
+ try {
15406
+ const rawResult = options.complete ? await options.complete(prompt, options.abortSignal) : await completeWithPiAi(prompt, options);
15407
+ const { text, usage } = resultToText(rawResult);
15408
+ const decision = parseLlmDecision(text, usage);
15409
+ if (decision) return decision;
15410
+ return {
15411
+ ...ruleDecision2,
15412
+ reason: `${ruleDecision2.reason} LLM fallback returned invalid route JSON.`
15413
+ };
15414
+ } catch (error) {
15415
+ const message = error instanceof Error ? error.message : String(error);
15416
+ return {
15417
+ ...ruleDecision2,
15418
+ reason: `${ruleDecision2.reason} LLM fallback failed: ${message}`
15419
+ };
15420
+ }
15421
+ };
15422
+ }
15423
+ async function completeWithPiAi(prompt, options) {
15424
+ if (!options.piModel || !options.apiKey) {
15425
+ throw new Error("LLM router requires piModel and apiKey");
15426
+ }
15427
+ const result = await completeSimple2(
15428
+ options.piModel,
15429
+ {
15430
+ systemPrompt: "You are a strict JSON classifier for ChatLab AI routing. Return JSON only.",
15431
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }]
15432
+ },
15433
+ {
15434
+ apiKey: options.apiKey,
15435
+ maxTokens: options.maxTokens ?? 180,
15436
+ temperature: options.temperature ?? 0,
15437
+ signal: options.abortSignal
15438
+ }
15439
+ );
15440
+ const text = result.content.filter((item) => item.type === "text").map((item) => item.text).join("");
15441
+ return {
15442
+ text,
15443
+ usage: {
15444
+ promptTokens: result.usage?.input,
15445
+ completionTokens: result.usage?.output,
15446
+ totalTokens: result.usage?.totalTokens
15447
+ }
15448
+ };
15449
+ }
15450
+
15451
+ // ../../packages/node-runtime/src/ai/agent/planner.ts
15452
+ import {
15453
+ completeSimple as completeSimple3,
15454
+ streamSimple
15455
+ } from "@earendil-works/pi-ai";
15456
+ var PLAN_INTENTS = [
15457
+ "summary",
15458
+ "trend",
15459
+ "relationship",
15460
+ "search",
15461
+ "comparison",
15462
+ "mixed"
15463
+ ];
15464
+ var STREAM_MARKER_HOLDBACK_CHARS = Math.max("</draft>".length, "</plan>".length, "<json>".length) - 1;
15465
+ function isRecord2(value) {
15466
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15467
+ }
15468
+ function isIntent(value) {
15469
+ return typeof value === "string" && PLAN_INTENTS.includes(value);
15470
+ }
15471
+ function normalizeString(value) {
15472
+ if (typeof value !== "string") return null;
15473
+ const text = value.trim();
15474
+ return text.length > 0 ? text : null;
15475
+ }
15476
+ function extractJsonObject4(text) {
15477
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
15478
+ if (fenced?.[1]) return fenced[1].trim();
15479
+ const start = text.indexOf("{");
15480
+ const end = text.lastIndexOf("}");
15481
+ if (start >= 0 && end > start) return text.slice(start, end + 1);
15482
+ return text.trim();
15483
+ }
15484
+ function parsePlanJson(rawText, availableTools) {
15485
+ try {
15486
+ const parsed = JSON.parse(extractJsonObject4(rawText));
15487
+ if (!isRecord2(parsed)) return null;
15488
+ const title = normalizeString(parsed.title);
15489
+ if (!title || !isIntent(parsed.intent) || !Array.isArray(parsed.steps)) return null;
15490
+ const steps = parsed.steps.filter(isRecord2).map((step) => {
15491
+ const goal = normalizeString(step.goal);
15492
+ const evidenceNeeded = normalizeString(step.evidenceNeeded);
15493
+ if (!goal || !evidenceNeeded) return null;
15494
+ const suggestedTools = Array.isArray(step.suggestedTools) ? step.suggestedTools.filter((tool) => typeof tool === "string" && availableTools.has(tool)) : [];
15495
+ return { goal, suggestedTools: Array.from(new Set(suggestedTools)), evidenceNeeded };
15496
+ }).filter((step) => step !== null).slice(0, 5);
15497
+ if (steps.length === 0) return null;
15498
+ const successCriteria = Array.isArray(parsed.successCriteria) ? parsed.successCriteria.map(normalizeString).filter((item) => item !== null).slice(0, 5) : [];
15499
+ if (successCriteria.length === 0) return null;
15500
+ return {
15501
+ version: 1,
15502
+ title,
15503
+ route: "planned_execution",
15504
+ intent: parsed.intent,
15505
+ steps,
15506
+ successCriteria
15507
+ };
15508
+ } catch {
15509
+ return null;
15510
+ }
15511
+ }
15512
+ function resultToText2(result) {
15513
+ return typeof result === "string" ? result : result.text;
15514
+ }
15515
+ function buildPlannerPrompt(input) {
15516
+ const dataSnapshot = formatDataSnapshotForPlanner(input.dataSnapshot);
15517
+ const availableCapabilities = input.availableCapabilities && input.availableCapabilities.length > 0 ? input.availableCapabilities.map((capability) => {
15518
+ const tools = capability.tools.join(", ") || "none";
15519
+ return ` - ${capability.id} (${capability.label}; tools: ${tools}): ${capability.guidance}`;
15520
+ }).join("\n") : "(none)";
15521
+ return `Create a concise user-visible analysis plan for a ChatLab planned_execution request.
15522
+
15523
+ Return exactly three blocks:
15524
+ <draft>
15525
+ A brief user-visible planning note in the user's locale. Keep it high-level; do not reveal hidden chain-of-thought.
15526
+ </draft>
15527
+ <plan>
15528
+ A concise user-facing analysis approach in the user's locale. Do not include suggested tools, evidence fields, or success criteria; those details belong in <json>.
15529
+ </plan>
15530
+ <json>
15531
+ {
15532
+ "title": "short title",
15533
+ "intent": "summary|trend|relationship|search|comparison|mixed",
15534
+ "steps": [
15535
+ {"goal": "what to investigate", "suggestedTools": ["tool_name"], "evidenceNeeded": "what evidence is needed"}
15536
+ ],
15537
+ "successCriteria": ["criterion"]
15538
+ }
15539
+ </json>
15540
+
15541
+ Limits:
15542
+ - Max 5 steps.
15543
+ - Max 5 successCriteria.
15544
+ - suggestedTools must come only from availableTools.
15545
+ - If availableCapabilities are listed, you may plan steps that use their tools when the capability helps the user.
15546
+ - Do not reveal hidden chain-of-thought; draft and plan are user-readable summaries only.
15547
+ - The plan is soft guidance, not a mandatory execution script.
15548
+
15549
+ Planning strategy:
15550
+ - For open-ended topic, community profile, influence, interaction-pattern, or multi-dimensional retrospective analysis, first plan a lightweight reconnaissance step to build a topic/member activity map before drawing conclusions.
15551
+ - Prefer segment summaries, keyword frequency, time distribution, member activity, and representative message retrieval when available.
15552
+ - Do not create a dedicated step whose only purpose is finding max timestamp or confirming latest message time.
15553
+
15554
+ Context:
15555
+ - locale: ${input.locale}
15556
+ - chatType: ${input.chatType}
15557
+ - availableTools: ${input.availableTools.join(", ") || "(none)"}
15558
+ - availableCapabilities:
15559
+ ${availableCapabilities}
15560
+ - dataSnapshot: ${dataSnapshot}
15561
+ - assistantSummary: ${input.assistantSummary ?? "(none)"}
15562
+ - skillSummary: ${input.skillSummary ?? "(none)"}
15563
+ - recentIntentSummary: ${input.recentIntentSummary ?? "(none)"}
15564
+
15565
+ User request:
15566
+ ${input.userMessage}`;
15567
+ }
15568
+ function createAnalysisPlanner(options) {
15569
+ return async (input, signal) => {
15570
+ try {
15571
+ const prompt = buildPlannerPrompt(input);
15572
+ const callbacks = {
15573
+ onPlanDelta: options.onPlanDelta ?? (() => {
15574
+ }),
15575
+ onThinkingDelta: options.onThinkingDelta,
15576
+ onThinkingEnd: options.onThinkingEnd,
15577
+ onValidationDelta: options.onValidationDelta,
15578
+ onValidationEnd: options.onValidationEnd
15579
+ };
15580
+ const rawResult = options.stream ? await options.stream(prompt, callbacks, signal) : options.complete ? await options.complete(prompt, signal) : options.onPlanDelta ? await streamWithPiAi(prompt, options, signal) : await completeWithPiAi2(prompt, options, signal);
15581
+ return parsePlanJson(resultToText2(rawResult), new Set(input.availableTools));
15582
+ } catch {
15583
+ return null;
15584
+ }
15585
+ };
15586
+ }
15587
+ function createPlanContentBlock(plan, status = "created") {
15588
+ return {
15589
+ type: "plan",
15590
+ version: 1,
15591
+ status,
15592
+ plan
15593
+ };
15594
+ }
15595
+ function buildPlanGuidance(plan) {
15596
+ const steps = plan.steps.map((step, index) => {
15597
+ const tools = step.suggestedTools.length > 0 ? step.suggestedTools.join(", ") : "none";
15598
+ return `${index + 1}. ${step.goal}
15599
+ Evidence needed: ${step.evidenceNeeded}
15600
+ Suggested tools: ${tools}`;
15601
+ }).join("\n");
15602
+ const successCriteria = plan.successCriteria.map((item) => `- ${item}`).join("\n");
15603
+ return `A suggested analysis plan is available below. Use it as guidance when helpful, but do not follow it mechanically if the user's question can be answered more directly.
15604
+
15605
+ Plan title: ${plan.title}
15606
+ Intent: ${plan.intent}
15607
+
15608
+ Steps:
15609
+ ${steps}
15610
+
15611
+ Success criteria:
15612
+ ${successCriteria}`;
15613
+ }
15614
+ async function completeWithPiAi2(prompt, options, signal) {
15615
+ if (!options.piModel || !options.apiKey) {
15616
+ throw new Error("Planner requires piModel and apiKey");
15617
+ }
15618
+ const result = await completeSimple3(
15619
+ options.piModel,
15620
+ {
15621
+ systemPrompt: "You are a strict planner for ChatLab analysis. Return the requested blocks only.",
15622
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }]
15623
+ },
15624
+ {
15625
+ apiKey: options.apiKey,
15626
+ maxTokens: options.maxTokens ?? 900,
15627
+ temperature: options.temperature ?? 0.2,
15628
+ signal
15629
+ }
15630
+ );
15631
+ return {
15632
+ text: result.content.filter((item) => item.type === "text").map((item) => item.text).join("")
15633
+ };
15634
+ }
15635
+ function extractTaggedBlockText(rawText, tag, nextTags) {
15636
+ const openTag = `<${tag}>`;
15637
+ const closeTag = `</${tag}>`;
15638
+ const blockStart = rawText.indexOf(openTag);
15639
+ if (blockStart < 0) return "";
15640
+ const bodyStart = blockStart + openTag.length;
15641
+ const closeStart = rawText.indexOf(closeTag, bodyStart);
15642
+ const nextStarts = nextTags.map((nextTag) => rawText.indexOf(nextTag, bodyStart)).filter((index) => index >= 0);
15643
+ const endCandidates = [closeStart, ...nextStarts].filter((index) => index >= 0);
15644
+ const bodyEnd = endCandidates.length > 0 ? Math.min(...endCandidates) : Math.max(bodyStart, rawText.length - STREAM_MARKER_HOLDBACK_CHARS);
15645
+ const text = rawText.slice(bodyStart, bodyEnd);
15646
+ return text.replace(/^\s+/, "");
15647
+ }
15648
+ async function streamWithPiAi(prompt, options, signal) {
15649
+ if (!options.piModel || !options.apiKey) {
15650
+ throw new Error("Planner requires piModel and apiKey");
15651
+ }
15652
+ const stream = streamSimple(
15653
+ options.piModel,
15654
+ {
15655
+ systemPrompt: "You are a strict planner for ChatLab analysis. Return the requested plan and JSON blocks only.",
15656
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }]
15657
+ },
15658
+ {
15659
+ apiKey: options.apiKey,
15660
+ maxTokens: options.maxTokens ?? 900,
15661
+ temperature: options.temperature ?? 0.2,
15662
+ signal
15663
+ }
15664
+ );
15665
+ let text = "";
15666
+ let emittedDraftLength = 0;
15667
+ let emittedPlanLength = 0;
15668
+ let emittedJsonLength = 0;
15669
+ let thinkingStartedAt;
15670
+ let draftThinkingStartedAt;
15671
+ let draftThinkingEnded = false;
15672
+ let validationStartedAt;
15673
+ let validationEnded = false;
15674
+ for await (const event of stream) {
15675
+ if (signal?.aborted) break;
15676
+ if (event.type === "thinking_start") {
15677
+ thinkingStartedAt = Date.now();
15678
+ continue;
15679
+ }
15680
+ if (event.type === "thinking_delta") {
15681
+ options.onThinkingDelta?.(event.delta);
15682
+ continue;
15683
+ }
15684
+ if (event.type === "thinking_end") {
15685
+ const durationMs = thinkingStartedAt ? Date.now() - thinkingStartedAt : void 0;
15686
+ thinkingStartedAt = void 0;
15687
+ options.onThinkingEnd?.(durationMs);
15688
+ continue;
15689
+ }
15690
+ if (event.type !== "text_delta") continue;
15691
+ text += event.delta;
15692
+ const draftText = extractTaggedBlockText(text, "draft", ["<plan>", "<json>"]);
15693
+ if (draftText.length > emittedDraftLength) {
15694
+ if (!draftThinkingStartedAt) draftThinkingStartedAt = Date.now();
15695
+ const delta = draftText.slice(emittedDraftLength);
15696
+ emittedDraftLength = draftText.length;
15697
+ if (delta.trim().length > 0 || /\s/.test(delta)) {
15698
+ options.onThinkingDelta?.(delta);
15699
+ }
15700
+ }
15701
+ if (!draftThinkingEnded && emittedDraftLength > 0 && (text.includes("</draft>") || text.includes("<plan>"))) {
15702
+ draftThinkingEnded = true;
15703
+ const durationMs = draftThinkingStartedAt ? Date.now() - draftThinkingStartedAt : void 0;
15704
+ options.onThinkingEnd?.(durationMs);
15705
+ }
15706
+ const planText = extractTaggedBlockText(text, "plan", ["<json>"]);
15707
+ if (planText.length > emittedPlanLength) {
15708
+ const delta = planText.slice(emittedPlanLength);
15709
+ emittedPlanLength = planText.length;
15710
+ if (delta.trim().length > 0 || /\s/.test(delta)) {
15711
+ options.onPlanDelta?.(delta);
15712
+ }
15713
+ }
15714
+ const jsonText = extractTaggedBlockText(text, "json", []);
15715
+ if (jsonText.length > emittedJsonLength) {
15716
+ if (!validationStartedAt) validationStartedAt = Date.now();
15717
+ const delta = jsonText.slice(emittedJsonLength);
15718
+ emittedJsonLength = jsonText.length;
15719
+ if (delta.trim().length > 0 || /\s/.test(delta)) {
15720
+ options.onValidationDelta?.(delta);
15721
+ }
15722
+ }
15723
+ if (!validationEnded && emittedJsonLength > 0 && text.includes("</json>")) {
15724
+ validationEnded = true;
15725
+ const durationMs = validationStartedAt ? Date.now() - validationStartedAt : void 0;
15726
+ options.onValidationEnd?.(durationMs);
15727
+ }
15728
+ }
15729
+ return { text };
15730
+ }
15731
+
15185
15732
  // ../../packages/node-runtime/src/ai/agent/event-handler.ts
15186
15733
  function estimateTokensFromText(text) {
15187
15734
  if (!text) return 0;
@@ -15230,7 +15777,13 @@ function estimateContextTokens(systemPrompt, messages, pendingUserMessage) {
15230
15777
  var AgentEventHandler = class {
15231
15778
  toolsUsed = [];
15232
15779
  toolRounds = 0;
15233
- totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
15780
+ totalUsage = {
15781
+ promptTokens: 0,
15782
+ completionTokens: 0,
15783
+ totalTokens: 0,
15784
+ cacheReadTokens: 0,
15785
+ cacheWriteTokens: 0
15786
+ };
15234
15787
  lastStatusAt = 0;
15235
15788
  onChunk;
15236
15789
  context;
@@ -15260,12 +15813,18 @@ var AgentEventHandler = class {
15260
15813
  case "tool_start": {
15261
15814
  const params = this.normalizeToolParams(event.toolName, event.toolParams);
15262
15815
  this.toolsUsed.push(event.toolName);
15263
- this.onChunk({ type: "tool_start", toolName: event.toolName, toolParams: params });
15816
+ this.onChunk({ type: "tool_start", toolCallId: event.toolCallId, toolName: event.toolName, toolParams: params });
15264
15817
  this.emitStatus("tool_running", messages, { currentTool: event.toolName, force: true });
15265
15818
  break;
15266
15819
  }
15267
15820
  case "tool_end":
15268
- this.onChunk({ type: "tool_result", toolName: event.toolName, toolResult: event.toolResult });
15821
+ this.onChunk({
15822
+ type: "tool_result",
15823
+ toolCallId: event.toolCallId,
15824
+ toolName: event.toolName,
15825
+ toolResult: event.toolResult,
15826
+ toolIsError: event.isError
15827
+ });
15269
15828
  this.emitStatus("thinking", messages, { force: true });
15270
15829
  break;
15271
15830
  case "turn_end":
@@ -15282,7 +15841,9 @@ var AgentEventHandler = class {
15282
15841
  return {
15283
15842
  promptTokens: this.totalUsage.promptTokens,
15284
15843
  completionTokens: this.totalUsage.completionTokens,
15285
- totalTokens: this.totalUsage.totalTokens
15844
+ totalTokens: this.totalUsage.totalTokens,
15845
+ cacheReadTokens: this.totalUsage.cacheReadTokens,
15846
+ cacheWriteTokens: this.totalUsage.cacheWriteTokens
15286
15847
  };
15287
15848
  }
15288
15849
  emitStatus(phase, messages, options) {
@@ -15318,6 +15879,39 @@ var AgentEventHandler = class {
15318
15879
  function agentT(t2, key, locale, options) {
15319
15880
  return t2(key, { lng: locale, ...options });
15320
15881
  }
15882
+ function formatNullableTimestamp2(timestamp) {
15883
+ return typeof timestamp === "number" && Number.isFinite(timestamp) ? String(timestamp) : "null";
15884
+ }
15885
+ function formatSharePercent2(share) {
15886
+ if (!Number.isFinite(share)) return "0%";
15887
+ const rounded = Math.round(share * 10) / 10;
15888
+ return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
15889
+ }
15890
+ function normalizeInlineText(text) {
15891
+ return text.replace(/\s+/g, " ").trim();
15892
+ }
15893
+ function formatDataSnapshotContext(t2, dataSnapshot, locale) {
15894
+ const memberHints = dataSnapshot.activeMemberHints ?? [];
15895
+ const memberHintTitle = memberHints.length === 0 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsUnavailable", locale) : dataSnapshot.totalMembers <= 10 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsAll", locale) : agentT(t2, "ai.agent.dataSnapshotMemberHintsTop", locale);
15896
+ const memberHintLines = memberHints.length > 0 ? memberHints.map((member, index) => {
15897
+ return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent2(member.share)}`;
15898
+ }).join("\n") : agentT(t2, "ai.agent.dataSnapshotMemberHintsEmpty", locale);
15899
+ return agentT(t2, "ai.agent.dataSnapshotContext", locale, {
15900
+ name: dataSnapshot.name,
15901
+ platform: dataSnapshot.platform,
15902
+ type: dataSnapshot.type,
15903
+ totalMessages: dataSnapshot.totalMessages,
15904
+ totalMembers: dataSnapshot.totalMembers,
15905
+ firstMessageTs: formatNullableTimestamp2(dataSnapshot.firstMessageTs),
15906
+ firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),
15907
+ lastMessageTs: formatNullableTimestamp2(dataSnapshot.lastMessageTs),
15908
+ lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),
15909
+ segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,
15910
+ memberHintTitle,
15911
+ memberHintLines,
15912
+ usageRules: agentT(t2, "ai.agent.dataSnapshotUsageRules", locale)
15913
+ });
15914
+ }
15321
15915
  function getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot) {
15322
15916
  const now = /* @__PURE__ */ new Date();
15323
15917
  const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
@@ -15342,14 +15936,7 @@ ${mentionedMembers.map((member) => {
15342
15936
  }).join("\n")}
15343
15937
  ` : "";
15344
15938
  const year = now.getFullYear();
15345
- const dataSnapshotNote = dataSnapshot ? `${agentT(t2, "ai.agent.dataSnapshotNote", locale, {
15346
- name: dataSnapshot.name,
15347
- platform: dataSnapshot.platform,
15348
- totalMessages: dataSnapshot.totalMessages,
15349
- totalMembers: dataSnapshot.totalMembers,
15350
- firstMessageDate: formatTimestamp(dataSnapshot.firstMessageTs, locale),
15351
- lastMessageDate: formatTimestamp(dataSnapshot.lastMessageTs, locale)
15352
- })}
15939
+ const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t2, dataSnapshot, locale)}
15353
15940
  ` : "";
15354
15941
  return `${agentT(t2, "ai.agent.currentDateIs", locale)} ${currentDate}\u3002
15355
15942
  ${ownerNote}
@@ -15463,7 +16050,7 @@ var zh_CN_default = {
15463
16050
  }
15464
16051
  },
15465
16052
  get_time_stats: {
15466
- desc: '\u83B7\u53D6\u7FA4\u804A\u7684\u65F6\u95F4\u5206\u5E03\u7EDF\u8BA1\u3002\u9002\u7528\u4E8E\u56DE\u7B54"\u4EC0\u4E48\u65F6\u5019\u6700\u6D3B\u8DC3"\u3001"\u5927\u5BB6\u4E00\u822C\u51E0\u70B9\u804A\u5929"\u7B49\u95EE\u9898\u3002',
16053
+ desc: '\u83B7\u53D6\u7FA4\u804A\u7684\u65F6\u95F4\u5206\u5E03\u7EDF\u8BA1\u3002\u9002\u7528\u4E8E\u56DE\u7B54"\u4EC0\u4E48\u65F6\u5019\u6700\u6D3B\u8DC3"\u3001"\u5927\u5BB6\u4E00\u822C\u51E0\u70B9\u804A\u5929"\u7B49\u95EE\u9898\u3002\u8FD4\u56DE\u7684 `data` \u6570\u7EC4\u53EF\u76F4\u63A5\u4F5C\u4E3A `render_chart` \u7684 `rows` \u53C2\u6570\u7528\u4E8E\u7ED8\u56FE\u3002',
15467
16054
  params: {
15468
16055
  type: "\u7EDF\u8BA1\u7C7B\u578B\uFF1Ahourly\uFF08\u6309\u5C0F\u65F6\uFF09\u3001weekday\uFF08\u6309\u661F\u671F\uFF09\u3001daily\uFF08\u6309\u65E5\u671F\uFF09"
15469
16056
  }
@@ -15653,9 +16240,10 @@ var zh_CN_default = {
15653
16240
  params: {}
15654
16241
  },
15655
16242
  render_chart: {
15656
- desc: "\u6839\u636E\u53EA\u8BFB SQL \u548C ChartSpec v1 \u751F\u6210 ChatLab \u539F\u751F\u56FE\u8868\u3002\u652F\u6301 bar\u3001line\u3001pie\u3001heatmap\u3002\u7981\u6B62\u8F93\u51FA HTML\u3001JavaScript\u3001SVG\u3001ECharts option \u6216\u6E32\u67D3\u4EE3\u7801\u3002",
16243
+ desc: "\u6839\u636E ChartSpec v1 \u751F\u6210 ChatLab \u539F\u751F\u56FE\u8868\u3002\u652F\u6301 bar\u3001line\u3001pie\u3001heatmap\u3002\u63D0\u4F9B `rows`\uFF08\u6765\u81EA\u9AD8\u5C42\u5DE5\u5177\u7684\u6570\u636E\u6570\u7EC4\uFF0C\u5982 get_time_stats \u7684 `data` \u5B57\u6BB5\uFF09\u6216 `sql`\uFF08\u53EA\u8BFB SELECT\uFF09\u4E8C\u9009\u4E00\uFF1B\u6709\u73B0\u6210\u6570\u636E\u65F6\u4F18\u5148\u7528 `rows`\uFF0C\u53EA\u6709\u9AD8\u5C42\u5DE5\u5177\u65E0\u6CD5\u6EE1\u8DB3\u65F6\u624D\u5199 `sql`\u3002\u7981\u6B62\u8F93\u51FA HTML\u3001JavaScript\u3001SVG\u3001ECharts option \u6216\u6E32\u67D3\u4EE3\u7801\u3002",
15657
16244
  params: {
15658
- sql: "\u53EA\u8BFB SELECT \u6216 WITH SELECT SQL\u3002\u5FC5\u987B\u8FD4\u56DE ChartSpec encoding \u4E2D\u5F15\u7528\u7684\u5B57\u6BB5\u3002",
16245
+ rows: "\u6765\u81EA\u9AD8\u5C42\u5DE5\u5177\u8FD4\u56DE\u7684\u6570\u636E\u6570\u7EC4\uFF08\u5982 get_time_stats \u7684 `data` \u5B57\u6BB5\uFF09\u3002\u6709\u73B0\u6210\u6570\u636E\u65F6\u4F18\u5148\u4F7F\u7528\uFF0C\u4E0E sql \u4E8C\u9009\u4E00\u3002",
16246
+ sql: "\u53EA\u8BFB SELECT \u6216 WITH SELECT SQL\u3002\u4EC5\u5728\u9AD8\u5C42\u5DE5\u5177\u65E0\u6CD5\u6EE1\u8DB3\u9700\u6C42\u65F6\u4F7F\u7528\uFF0C\u5FC5\u987B\u8FD4\u56DE ChartSpec encoding \u4E2D\u5F15\u7528\u7684\u5B57\u6BB5\u3002",
15659
16247
  params: "SQL \u547D\u540D\u53C2\u6570\u5BF9\u8C61\uFF1B\u4E0D\u9700\u8981\u53C2\u6570\u65F6\u4F20\u7A7A\u5BF9\u8C61\u3002",
15660
16248
  chartSpec: "ChartSpec v1\uFF0C\u5305\u542B version\u3001type\u3001title\u3001encoding\u3002",
15661
16249
  maxRows: "\u56FE\u8868\u67E5\u8BE2\u6700\u5927\u884C\u6570\uFF0C\u9ED8\u8BA4 1000\u3002"
@@ -15689,6 +16277,35 @@ var zh_CN_default = {
15689
16277
  timeParamsIntro: '\u65F6\u95F4\u53C2\u6570\uFF1A\u4F7F\u7528 start_time/end_time \u6307\u5B9A\u65F6\u95F4\u8303\u56F4\uFF0C\u683C\u5F0F "YYYY-MM-DD HH:mm"',
15690
16278
  defaultYearNote: "\u672A\u6307\u5B9A\u65F6\u95F4\u8303\u56F4\u65F6\u9ED8\u8BA4\u67E5\u8BE2\u5168\u90E8\u3002\u5F53\u524D\u5E74\u4EFD\u4E3A{{year}}\u5E74",
15691
16279
  dataSnapshotNote: "\u5F53\u524D\u804A\u5929\u6570\u636E\u5E93\u5FEB\u7167\uFF1A{{name}}\uFF08{{platform}}\uFF09\uFF0C\u603B\u6D88\u606F {{totalMessages}} \u6761\uFF0C\u6210\u5458 {{totalMembers}} \u4EBA\uFF0C\u65F6\u95F4\u8303\u56F4 {{firstMessageDate}} ~ {{lastMessageDate}}\u3002\u8BE5\u5FEB\u7167\u53EA\u7528\u4E8E\u5224\u65AD\u6570\u636E\u8303\u56F4\uFF1B\u56DE\u7B54\u5177\u4F53\u804A\u5929\u4E8B\u5B9E\u4ECD\u5FC5\u987B\u8C03\u7528\u5DE5\u5177\u68C0\u7D22\u5F53\u524D\u6570\u636E\u5E93\u3002",
16280
+ dataSnapshotContext: `\u5F53\u524D\u804A\u5929\u6570\u636E\u5E93\u542F\u52A8\u4E0A\u4E0B\u6587\uFF1A
16281
+ - name: {{name}}
16282
+ - platform: {{platform}}
16283
+ - type: {{type}}
16284
+ - total_messages: {{totalMessages}}
16285
+ - total_members: {{totalMembers}}
16286
+ - first_message_ts: {{firstMessageTs}}
16287
+ - first_message_time: {{firstMessageTime}}
16288
+ - last_message_ts: {{lastMessageTs}}
16289
+ - last_message_time: {{lastMessageTime}}
16290
+ - segment_summaries_available: {{segmentSummaryCount}}
16291
+
16292
+ {{memberHintTitle}}
16293
+ {{memberHintLines}}
16294
+
16295
+ \u4F7F\u7528\u89C4\u5219\uFF1A
16296
+ {{usageRules}}`,
16297
+ dataSnapshotMemberHintsAll: "\u6D3B\u8DC3\u6210\u5458\u67E5\u8BE2\u63D0\u793A\uFF08\u5168\u90E8\u6210\u5458\uFF09\uFF1A",
16298
+ dataSnapshotMemberHintsTop: "\u6D3B\u8DC3\u6210\u5458\u67E5\u8BE2\u63D0\u793A\uFF08\u6309\u5386\u53F2\u603B\u6D88\u606F\u91CF Top 10\uFF09\uFF1A",
16299
+ dataSnapshotMemberHintsUnavailable: "\u6D3B\u8DC3\u6210\u5458\u67E5\u8BE2\u63D0\u793A\uFF1A",
16300
+ dataSnapshotMemberHintsEmpty: "\u65E0\u53EF\u7528\u6210\u5458\u63D0\u793A\u3002",
16301
+ dataSnapshotUsageRules: `- member_id \u662F\u5DE5\u5177\u67E5\u8BE2\u63D0\u793A\uFF1Bdisplay_name \u4EC5\u7528\u4E8E\u4EBA\u7C7B\u8BC6\u522B\uFF0C\u53EF\u80FD\u4E0D\u552F\u4E00\u3002
16302
+ - \u4E0D\u8981\u5728\u6700\u7EC8\u56DE\u7B54\u4E2D\u4E3B\u52A8\u66B4\u9732 member_id \u6216\u542F\u52A8\u4E0A\u4E0B\u6587\u672C\u8EAB\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u6280\u672F\u7EC6\u8282\u3002
16303
+ - \u6D3B\u8DC3\u6210\u5458\u6392\u884C\u53EA\u4EE3\u8868\u5386\u53F2\u603B\u6D88\u606F\u91CF\uFF0C\u4E0D\u4EE3\u8868\u6700\u8FD1\u6D3B\u8DC3\u60C5\u51B5\uFF1B\u4E5F\u4E0D\u8DB3\u4EE5\u4F5C\u4E3A\u5F71\u54CD\u529B\u3001\u5173\u7CFB\u6216\u8FD1\u671F\u8D8B\u52BF\u7ED3\u8BBA\u7684\u8BC1\u636E\u3002
16304
+ - \u76F8\u5BF9\u65F6\u95F4\u8868\u8FBE\u4EE5\u771F\u5B9E\u5F53\u524D\u65E5\u671F\u4E3A\u57FA\u51C6\uFF0C\u800C\u4E0D\u662F\u6570\u636E\u5E93\u6700\u540E\u6D88\u606F\u65F6\u95F4\u3002
16305
+ - \u201C\u6700\u8FD1\u4E00\u5E74/\u8FC7\u53BB\u4E00\u5E74\u201D\u8868\u793A\u4ECE\u771F\u5B9E\u5F53\u524D\u65E5\u671F\u56DE\u63A8\u4E00\u5E74\u5230\u4ECA\u5929\uFF1B\u201C\u53BB\u5E74\u201D\u8868\u793A\u4E0A\u4E00\u81EA\u7136\u5E74\u3002
16306
+ - \u6570\u636E\u5E93\u65F6\u95F4\u8FB9\u754C\u53EA\u7528\u4E8E\u8BF4\u660E\u8986\u76D6\u8303\u56F4\uFF0C\u4E0D\u7528\u4E8E\u91CD\u5B9A\u4E49\u7528\u6237\u8981\u6C42\u7684\u65F6\u95F4\u8303\u56F4\u3002
16307
+ - \u4F7F\u7528\u9ED8\u8BA4\u201C\u8FD1 N \u5929\u201D\u7684\u5DE5\u5177\u65F6\uFF0C\u5148\u9009\u62E9\u4E0E\u6570\u636E\u5E93\u65F6\u95F4\u8FB9\u754C\u6709\u4EA4\u96C6\u7684\u8303\u56F4\uFF0C\u4E0D\u8981\u5148\u63A2\u6D4B\u771F\u5B9E\u5F53\u524D\u65E5\u671F\u7A97\u53E3\u5BFC\u81F4\u7A7A\u7ED3\u679C\u3002
16308
+ - \u4E0D\u8981\u53EA\u4E3A\u4E86\u91CD\u65B0\u53D1\u73B0 min/max timestamp \u8C03\u7528\u5DE5\u5177\uFF1B\u4F46\u56DE\u7B54\u5177\u4F53\u804A\u5929\u4E8B\u5B9E\u3001\u7EDF\u8BA1\u548C\u7ED3\u8BBA\u4ECD\u5FC5\u987B\u8C03\u7528\u5DE5\u5177\u83B7\u53D6\u8BC1\u636E\u3002`,
15692
16309
  evidencePolicy: `\u8BC1\u636E\u7B56\u7565\uFF1A
15693
16310
  - AI \u5BF9\u8BDD\u5386\u53F2\u3001\u5386\u53F2 AI \u56DE\u590D\u548C\u538B\u7F29\u6458\u8981\u53EA\u7528\u4E8E\u7406\u89E3\u7528\u6237\u610F\u56FE\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u804A\u5929\u8BB0\u5F55\u4E8B\u5B9E\u8BC1\u636E\u3002
15694
16311
  - \u53EA\u8981\u7528\u6237\u8BE2\u95EE\u804A\u5929\u8BB0\u5F55\u5185\u5BB9\u3001\u6700\u8FD1\u804A\u4EC0\u4E48\u3001\u67D0\u4EBA\u8BF4\u8FC7\u4EC0\u4E48\u3001\u7EDF\u8BA1\u6392\u884C\u3001\u662F\u5426\u51FA\u73B0\u8FC7\u67D0\u8BDD\u9898\u6216\u8981\u6C42\u5F15\u7528\u539F\u8BDD\uFF0C\u5FC5\u987B\u5148\u8C03\u7528\u5408\u9002\u7684\u6570\u636E\u5DE5\u5177\u68C0\u7D22\u5F53\u524D\u6570\u636E\u5E93\u3002
@@ -15797,7 +16414,7 @@ var en_US_default = {
15797
16414
  }
15798
16415
  },
15799
16416
  get_time_stats: {
15800
- desc: 'Get time distribution statistics of chat activity. Suitable for questions like "when is the group most active" or "what time do people usually chat".',
16417
+ desc: 'Get time distribution statistics of chat activity. Suitable for questions like "when is the group most active" or "what time do people usually chat". The returned `data` array can be passed directly as the `rows` parameter of render_chart for visualization.',
15801
16418
  params: {
15802
16419
  type: "Statistics type: hourly (by hour), weekday (by day of week), daily (by date)"
15803
16420
  }
@@ -15988,9 +16605,10 @@ Returned summaries are brief descriptions of each segment, helping quickly locat
15988
16605
  params: {}
15989
16606
  },
15990
16607
  render_chart: {
15991
- desc: "Generate a native ChatLab chart from read-only SQL and ChartSpec v1. Supports bar, line, pie, and heatmap. Do not output HTML, JavaScript, SVG, ECharts options, or rendering code.",
16608
+ desc: "Generate a native ChatLab chart from ChartSpec v1. Supports bar, line, pie, and heatmap. Provide either `rows` (pre-fetched data array from a tool result, e.g. the `data` field from get_time_stats) or `sql` (read-only SELECT) \u2014 prefer `rows` when data is already available; use `sql` only when high-level tools cannot satisfy the need. Do not output HTML, JavaScript, SVG, ECharts options, or rendering code.",
15992
16609
  params: {
15993
- sql: "Read-only SELECT or WITH SELECT SQL. It must return fields referenced by ChartSpec encoding.",
16610
+ rows: "Pre-fetched data array from a high-level tool result (e.g. the `data` field from get_time_stats). Prefer this over sql when data is already available. Mutually exclusive with sql.",
16611
+ sql: "Read-only SELECT or WITH SELECT SQL. Use only when high-level tools cannot satisfy the need. Must return fields referenced by ChartSpec encoding.",
15994
16612
  params: "Named SQL parameters. Use an empty object when no parameters are needed.",
15995
16613
  chartSpec: "ChartSpec v1 with version, type, title, and encoding.",
15996
16614
  maxRows: "Maximum chart query rows. Default 1000."
@@ -16025,6 +16643,35 @@ Returned summaries are brief descriptions of each segment, helping quickly locat
16025
16643
  timeParamsIntro: 'Time parameters: use start_time/end_time to specify the range, format "YYYY-MM-DD HH:mm"',
16026
16644
  defaultYearNote: "When no time range is specified, queries default to all data. Current year is {{year}}.",
16027
16645
  dataSnapshotNote: "Current chat database snapshot: {{name}} ({{platform}}), {{totalMessages}} total messages, {{totalMembers}} members, time range {{firstMessageDate}} ~ {{lastMessageDate}}. This snapshot only helps judge data coverage; concrete chat facts still require tool retrieval from the current database.",
16646
+ dataSnapshotContext: `Current chat database startup context:
16647
+ - name: {{name}}
16648
+ - platform: {{platform}}
16649
+ - type: {{type}}
16650
+ - total_messages: {{totalMessages}}
16651
+ - total_members: {{totalMembers}}
16652
+ - first_message_ts: {{firstMessageTs}}
16653
+ - first_message_time: {{firstMessageTime}}
16654
+ - last_message_ts: {{lastMessageTs}}
16655
+ - last_message_time: {{lastMessageTime}}
16656
+ - segment_summaries_available: {{segmentSummaryCount}}
16657
+
16658
+ {{memberHintTitle}}
16659
+ {{memberHintLines}}
16660
+
16661
+ Usage rules:
16662
+ {{usageRules}}`,
16663
+ dataSnapshotMemberHintsAll: "Active member lookup hints (all members):",
16664
+ dataSnapshotMemberHintsTop: "Active member lookup hints (top 10 by historical total messages):",
16665
+ dataSnapshotMemberHintsUnavailable: "Active member lookup hints:",
16666
+ dataSnapshotMemberHintsEmpty: "No member hints available.",
16667
+ dataSnapshotUsageRules: `- member_id is a tool lookup hint; display_name is only for human recognition and may not be unique.
16668
+ - Do not proactively expose member_id or the startup context in the final answer unless the user explicitly asks for technical details.
16669
+ - Active member ranking only reflects historical total message volume, not recent activity; it is also not evidence for influence, relationships, or recent trends.
16670
+ - Interpret relative time expressions using the real current date, not the database's last message time.
16671
+ - "recent year" / "past year" means one year back from the real current date to today; "last year" means the previous calendar year.
16672
+ - Database time bounds are only for explaining coverage, not for redefining the user's requested time range.
16673
+ - When using default recent-day tools, first choose a range that intersects database time bounds instead of probing an empty real-current-date window.
16674
+ - Do not call tools only to rediscover min/max timestamp; concrete chat facts, statistics, and conclusions still require tool evidence.`,
16028
16675
  evidencePolicy: `Evidence policy:
16029
16676
  - AI conversation history, prior AI replies, and compressed summaries are only for understanding the user's intent; they are not evidence for chat-record facts.
16030
16677
  - Whenever the user asks about chat content, recent topics, what someone said, rankings/statistics, whether a topic appeared, or asks for exact quotes, first call an appropriate data tool to retrieve the current database.
@@ -16129,7 +16776,7 @@ var ja_JP_default = {
16129
16776
  }
16130
16777
  },
16131
16778
  get_time_stats: {
16132
- desc: "\u30B0\u30EB\u30FC\u30D7\u30C1\u30E3\u30C3\u30C8\u306E\u6642\u9593\u5206\u5E03\u7D71\u8A08\u3092\u53D6\u5F97\u3059\u308B\u3002\u300C\u3044\u3064\u304C\u4E00\u756A\u30A2\u30AF\u30C6\u30A3\u30D6\uFF1F\u300D\u300C\u307F\u3093\u306A\u4F55\u6642\u306B\u30C1\u30E3\u30C3\u30C8\u3057\u3066\u3044\u308B\uFF1F\u300D\u306A\u3069\u306E\u8CEA\u554F\u306B\u9069\u3057\u3066\u3044\u308B\u3002",
16779
+ desc: "\u30B0\u30EB\u30FC\u30D7\u30C1\u30E3\u30C3\u30C8\u306E\u6642\u9593\u5206\u5E03\u7D71\u8A08\u3092\u53D6\u5F97\u3059\u308B\u3002\u300C\u3044\u3064\u304C\u4E00\u756A\u30A2\u30AF\u30C6\u30A3\u30D6\uFF1F\u300D\u300C\u307F\u3093\u306A\u4F55\u6642\u306B\u30C1\u30E3\u30C3\u30C8\u3057\u3066\u3044\u308B\uFF1F\u300D\u306A\u3069\u306E\u8CEA\u554F\u306B\u9069\u3057\u3066\u3044\u308B\u3002\u8FD4\u3055\u308C\u308B `data` \u914D\u5217\u306F render_chart \u306E `rows` \u30D1\u30E9\u30E1\u30FC\u30BF\u3068\u3057\u3066\u76F4\u63A5\u4F7F\u7528\u3067\u304D\u308B\u3002",
16133
16780
  params: {
16134
16781
  type: "\u7D71\u8A08\u30BF\u30A4\u30D7\uFF1Ahourly\uFF08\u6642\u9593\u5225\uFF09\u3001weekday\uFF08\u66DC\u65E5\u5225\uFF09\u3001daily\uFF08\u65E5\u5225\uFF09"
16135
16782
  }
@@ -16319,9 +16966,10 @@ var ja_JP_default = {
16319
16966
  params: {}
16320
16967
  },
16321
16968
  render_chart: {
16322
- desc: "\u8AAD\u307F\u53D6\u308A\u5C02\u7528 SQL \u3068 ChartSpec v1 \u304B\u3089 ChatLab \u30CD\u30A4\u30C6\u30A3\u30D6\u30C1\u30E3\u30FC\u30C8\u3092\u751F\u6210\u3057\u307E\u3059\u3002bar\u3001line\u3001pie\u3001heatmap \u3092\u30B5\u30DD\u30FC\u30C8\u3057\u307E\u3059\u3002HTML\u3001JavaScript\u3001SVG\u3001ECharts option\u3001\u63CF\u753B\u30B3\u30FC\u30C9\u306F\u51FA\u529B\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002",
16969
+ desc: "ChartSpec v1 \u304B\u3089 ChatLab \u30CD\u30A4\u30C6\u30A3\u30D6\u30C1\u30E3\u30FC\u30C8\u3092\u751F\u6210\u3057\u307E\u3059\u3002bar\u3001line\u3001pie\u3001heatmap \u3092\u30B5\u30DD\u30FC\u30C8\u3057\u307E\u3059\u3002`rows`\uFF08\u9AD8\u30EC\u30D9\u30EB\u30C4\u30FC\u30EB\u306E\u30C7\u30FC\u30BF\u914D\u5217\u3001\u4F8B\uFF1Aget_time_stats \u306E `data` \u30D5\u30A3\u30FC\u30EB\u30C9\uFF09\u307E\u305F\u306F `sql`\uFF08\u8AAD\u307F\u53D6\u308A\u5C02\u7528 SELECT\uFF09\u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002\u30C7\u30FC\u30BF\u304C\u3059\u3067\u306B\u53D6\u5F97\u6E08\u307F\u306E\u5834\u5408\u306F `rows` \u3092\u512A\u5148\u3057\u3001\u9AD8\u30EC\u30D9\u30EB\u30C4\u30FC\u30EB\u3067\u5BFE\u5FDC\u3067\u304D\u306A\u3044\u5834\u5408\u306E\u307F `sql` \u3092\u4F7F\u7528\u3057\u307E\u3059\u3002HTML\u3001JavaScript\u3001SVG\u3001ECharts option\u3001\u63CF\u753B\u30B3\u30FC\u30C9\u306F\u51FA\u529B\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002",
16323
16970
  params: {
16324
- sql: "\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u306E SELECT \u307E\u305F\u306F WITH SELECT SQL\u3002ChartSpec encoding \u3067\u53C2\u7167\u3059\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
16971
+ rows: "\u9AD8\u30EC\u30D9\u30EB\u30C4\u30FC\u30EB\u304B\u3089\u53D6\u5F97\u3057\u305F\u30C7\u30FC\u30BF\u914D\u5217\uFF08\u4F8B\uFF1Aget_time_stats \u306E `data` \u30D5\u30A3\u30FC\u30EB\u30C9\uFF09\u3002\u30C7\u30FC\u30BF\u304C\u53D6\u5F97\u6E08\u307F\u306E\u5834\u5408\u306F sql \u3088\u308A\u512A\u5148\u3057\u3066\u4F7F\u7528\u3057\u307E\u3059\u3002sql \u3068\u306F\u6392\u4ED6\u3002",
16972
+ sql: "\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u306E SELECT \u307E\u305F\u306F WITH SELECT SQL\u3002\u9AD8\u30EC\u30D9\u30EB\u30C4\u30FC\u30EB\u3067\u5BFE\u5FDC\u3067\u304D\u306A\u3044\u5834\u5408\u306E\u307F\u4F7F\u7528\u3057\u307E\u3059\u3002ChartSpec encoding \u3067\u53C2\u7167\u3059\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
16325
16973
  params: "SQL \u306E\u540D\u524D\u4ED8\u304D\u30D1\u30E9\u30E1\u30FC\u30BF\u3002\u4E0D\u8981\u306A\u5834\u5408\u306F\u7A7A\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u6E21\u3057\u307E\u3059\u3002",
16326
16974
  chartSpec: "version\u3001type\u3001title\u3001encoding \u3092\u542B\u3080 ChartSpec v1\u3002",
16327
16975
  maxRows: "\u30C1\u30E3\u30FC\u30C8\u30AF\u30A8\u30EA\u306E\u6700\u5927\u884C\u6570\u3002\u30C7\u30D5\u30A9\u30EB\u30C8\u306F 1000\u3002"
@@ -16355,6 +17003,35 @@ var ja_JP_default = {
16355
17003
  timeParamsIntro: '\u6642\u9593\u30D1\u30E9\u30E1\u30FC\u30BF\uFF1Astart_time/end_time \u3067\u7BC4\u56F2\u3092\u6307\u5B9A\u3002\u5F62\u5F0F\u306F "YYYY-MM-DD HH:mm"',
16356
17004
  defaultYearNote: "\u6642\u9593\u7BC4\u56F2\u3092\u6307\u5B9A\u3057\u306A\u3044\u5834\u5408\u306F\u5168\u671F\u9593\u304C\u5BFE\u8C61\u3002\u73FE\u5728\u306E\u5E74\u306F{{year}}\u5E74",
16357
17005
  dataSnapshotNote: "\u73FE\u5728\u306E\u30C1\u30E3\u30C3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\uFF1A{{name}}\uFF08{{platform}}\uFF09\u3001\u7DCF\u30E1\u30C3\u30BB\u30FC\u30B8 {{totalMessages}} \u4EF6\u3001\u30E1\u30F3\u30D0\u30FC {{totalMembers}} \u4EBA\u3001\u671F\u9593 {{firstMessageDate}} ~ {{lastMessageDate}}\u3002\u3053\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306F\u30C7\u30FC\u30BF\u7BC4\u56F2\u306E\u5224\u65AD\u306B\u306E\u307F\u4F7F\u3044\u3001\u5177\u4F53\u7684\u306A\u30C1\u30E3\u30C3\u30C8\u4E8B\u5B9F\u306E\u56DE\u7B54\u306B\u306F\u5FC5\u305A\u73FE\u5728\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u30C4\u30FC\u30EB\u3067\u691C\u7D22\u3059\u308B\u3002",
17006
+ dataSnapshotContext: `\u73FE\u5728\u306E\u30C1\u30E3\u30C3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u8D77\u52D5\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\uFF1A
17007
+ - name: {{name}}
17008
+ - platform: {{platform}}
17009
+ - type: {{type}}
17010
+ - total_messages: {{totalMessages}}
17011
+ - total_members: {{totalMembers}}
17012
+ - first_message_ts: {{firstMessageTs}}
17013
+ - first_message_time: {{firstMessageTime}}
17014
+ - last_message_ts: {{lastMessageTs}}
17015
+ - last_message_time: {{lastMessageTime}}
17016
+ - segment_summaries_available: {{segmentSummaryCount}}
17017
+
17018
+ {{memberHintTitle}}
17019
+ {{memberHintLines}}
17020
+
17021
+ \u4F7F\u7528\u30EB\u30FC\u30EB\uFF1A
17022
+ {{usageRules}}`,
17023
+ dataSnapshotMemberHintsAll: "\u30A2\u30AF\u30C6\u30A3\u30D6\u30E1\u30F3\u30D0\u30FC\u691C\u7D22\u30D2\u30F3\u30C8\uFF08\u5168\u30E1\u30F3\u30D0\u30FC\uFF09\uFF1A",
17024
+ dataSnapshotMemberHintsTop: "\u30A2\u30AF\u30C6\u30A3\u30D6\u30E1\u30F3\u30D0\u30FC\u691C\u7D22\u30D2\u30F3\u30C8\uFF08\u904E\u53BB\u306E\u7DCF\u30E1\u30C3\u30BB\u30FC\u30B8\u6570 Top 10\uFF09\uFF1A",
17025
+ dataSnapshotMemberHintsUnavailable: "\u30A2\u30AF\u30C6\u30A3\u30D6\u30E1\u30F3\u30D0\u30FC\u691C\u7D22\u30D2\u30F3\u30C8\uFF1A",
17026
+ dataSnapshotMemberHintsEmpty: "\u5229\u7528\u53EF\u80FD\u306A\u30E1\u30F3\u30D0\u30FC\u30D2\u30F3\u30C8\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
17027
+ dataSnapshotUsageRules: `- member_id \u306F\u30C4\u30FC\u30EB\u691C\u7D22\u7528\u306E\u30D2\u30F3\u30C8\u3067\u3059\u3002display_name \u306F\u4EBA\u9593\u304C\u8B58\u5225\u3059\u308B\u305F\u3081\u3060\u3051\u306E\u3082\u306E\u3067\u3001\u4E00\u610F\u3068\u306F\u9650\u308A\u307E\u305B\u3093\u3002
17028
+ - \u30E6\u30FC\u30B6\u30FC\u304C\u6280\u8853\u7684\u8A73\u7D30\u3092\u660E\u793A\u7684\u306B\u6C42\u3081\u306A\u3044\u9650\u308A\u3001\u6700\u7D42\u56DE\u7B54\u3067 member_id \u3084\u8D77\u52D5\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u81EA\u4F53\u3092\u7A4D\u6975\u7684\u306B\u958B\u793A\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
17029
+ - \u30A2\u30AF\u30C6\u30A3\u30D6\u30E1\u30F3\u30D0\u30FC\u9806\u4F4D\u306F\u904E\u53BB\u306E\u7DCF\u30E1\u30C3\u30BB\u30FC\u30B8\u91CF\u3060\u3051\u3092\u793A\u3057\u3001\u6700\u8FD1\u306E\u6D3B\u767A\u3055\u3092\u793A\u3059\u3082\u306E\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u307E\u305F\u3001\u5F71\u97FF\u529B\u3001\u95A2\u4FC2\u6027\u3001\u6700\u8FD1\u306E\u50BE\u5411\u306E\u7D50\u8AD6\u8A3C\u62E0\u306B\u3082\u306A\u308A\u307E\u305B\u3093\u3002
17030
+ - \u76F8\u5BFE\u7684\u306A\u6642\u9593\u8868\u73FE\u306F\u3001\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u6700\u7D42\u30E1\u30C3\u30BB\u30FC\u30B8\u6642\u523B\u3067\u306F\u306A\u304F\u3001\u5B9F\u969B\u306E\u73FE\u5728\u65E5\u4ED8\u3092\u57FA\u6E96\u306B\u89E3\u91C8\u3057\u3066\u304F\u3060\u3055\u3044\u3002
17031
+ - \u300C\u6700\u8FD1\u4E00\u5E74/\u904E\u53BB\u4E00\u5E74\u300D\u306F\u5B9F\u969B\u306E\u73FE\u5728\u65E5\u4ED8\u304B\u3089\u4ECA\u65E5\u307E\u3067\u306E1\u5E74\u9593\u3092\u6307\u3057\u3001\u300C\u53BB\u5E74\u300D\u306F\u524D\u306E\u66A6\u5E74\u3092\u6307\u3057\u307E\u3059\u3002
17032
+ - \u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u6642\u9593\u5883\u754C\u306F\u30AB\u30D0\u30EC\u30C3\u30B8\u8AAC\u660E\u306B\u306E\u307F\u4F7F\u3044\u3001\u30E6\u30FC\u30B6\u30FC\u304C\u6C42\u3081\u305F\u6642\u9593\u7BC4\u56F2\u3092\u518D\u5B9A\u7FA9\u3059\u308B\u305F\u3081\u306B\u4F7F\u308F\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
17033
+ - \u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u300C\u76F4\u8FD1 N \u65E5\u300D\u30C4\u30FC\u30EB\u3092\u4F7F\u3046\u5834\u5408\u306F\u3001\u7A7A\u306E\u5B9F\u969B\u73FE\u5728\u65E5\u4ED8\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u8A66\u3059\u306E\u3067\u306F\u306A\u304F\u3001\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u6642\u9593\u5883\u754C\u3068\u4EA4\u5DEE\u3059\u308B\u7BC4\u56F2\u3092\u5148\u306B\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002
17034
+ - min/max timestamp \u3092\u518D\u767A\u898B\u3059\u308B\u305F\u3081\u3060\u3051\u306B\u30C4\u30FC\u30EB\u3092\u547C\u3070\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u305F\u3060\u3057\u3001\u5177\u4F53\u7684\u306A\u30C1\u30E3\u30C3\u30C8\u4E8B\u5B9F\u3001\u7D71\u8A08\u3001\u7D50\u8AD6\u306B\u306F\u5F15\u304D\u7D9A\u304D\u30C4\u30FC\u30EB\u8A3C\u62E0\u304C\u5FC5\u8981\u3067\u3059\u3002`,
16358
17035
  evidencePolicy: `\u8A3C\u62E0\u30DD\u30EA\u30B7\u30FC\uFF1A
16359
17036
  - AI \u4F1A\u8A71\u5C65\u6B74\u3001\u904E\u53BB\u306E AI \u56DE\u7B54\u3001\u5727\u7E2E\u8981\u7D04\u306F\u30E6\u30FC\u30B6\u30FC\u610F\u56F3\u306E\u7406\u89E3\u306B\u306E\u307F\u4F7F\u7528\u3057\u3001\u30C1\u30E3\u30C3\u30C8\u8A18\u9332\u306E\u4E8B\u5B9F\u8A3C\u62E0\u3068\u3057\u3066\u6271\u308F\u306A\u3044\u3002
16360
17037
  - \u30E6\u30FC\u30B6\u30FC\u304C\u30C1\u30E3\u30C3\u30C8\u5185\u5BB9\u3001\u6700\u8FD1\u306E\u8A71\u984C\u3001\u8AB0\u304B\u306E\u767A\u8A00\u3001\u30E9\u30F3\u30AD\u30F3\u30B0/\u7D71\u8A08\u3001\u3042\u308B\u8A71\u984C\u304C\u51FA\u305F\u304B\u3069\u3046\u304B\u3001\u307E\u305F\u306F\u539F\u6587\u5F15\u7528\u3092\u6C42\u3081\u305F\u5834\u5408\u306F\u3001\u5FC5\u305A\u5148\u306B\u9069\u5207\u306A\u30C7\u30FC\u30BF\u30C4\u30FC\u30EB\u3067\u73FE\u5728\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u691C\u7D22\u3059\u308B\u3002
@@ -16461,7 +17138,7 @@ var zh_TW_default = {
16461
17138
  }
16462
17139
  },
16463
17140
  get_time_stats: {
16464
- desc: "\u53D6\u5F97\u7FA4\u804A\u7684\u6642\u9593\u5206\u4F48\u7D71\u8A08\u3002\u9069\u7528\u65BC\u56DE\u7B54\u300C\u4EC0\u9EBC\u6642\u5019\u6700\u6D3B\u8E8D\u300D\u3001\u300C\u5927\u5BB6\u4E00\u822C\u5E7E\u9EDE\u804A\u5929\u300D\u7B49\u554F\u984C\u3002",
17141
+ desc: "\u53D6\u5F97\u7FA4\u804A\u7684\u6642\u9593\u5206\u4F48\u7D71\u8A08\u3002\u9069\u7528\u65BC\u56DE\u7B54\u300C\u4EC0\u9EBC\u6642\u5019\u6700\u6D3B\u8E8D\u300D\u3001\u300C\u5927\u5BB6\u4E00\u822C\u5E7E\u9EDE\u804A\u5929\u300D\u7B49\u554F\u984C\u3002\u8FD4\u56DE\u7684 `data` \u9663\u5217\u53EF\u76F4\u63A5\u4F5C\u70BA `render_chart` \u7684 `rows` \u53C3\u6578\u7528\u65BC\u7E6A\u5716\u3002",
16465
17142
  params: {
16466
17143
  type: "\u7D71\u8A08\u985E\u578B\uFF1Ahourly\uFF08\u6309\u5C0F\u6642\uFF09\u3001weekday\uFF08\u6309\u661F\u671F\uFF09\u3001daily\uFF08\u6309\u65E5\u671F\uFF09"
16467
17144
  }
@@ -16651,9 +17328,10 @@ var zh_TW_default = {
16651
17328
  params: {}
16652
17329
  },
16653
17330
  render_chart: {
16654
- desc: "\u6839\u64DA\u552F\u8B80 SQL \u548C ChartSpec v1 \u751F\u6210 ChatLab \u539F\u751F\u5716\u8868\u3002\u652F\u63F4 bar\u3001line\u3001pie\u3001heatmap\u3002\u7981\u6B62\u8F38\u51FA HTML\u3001JavaScript\u3001SVG\u3001ECharts option \u6216\u6E32\u67D3\u7A0B\u5F0F\u78BC\u3002",
17331
+ desc: "\u6839\u64DA ChartSpec v1 \u751F\u6210 ChatLab \u539F\u751F\u5716\u8868\u3002\u652F\u63F4 bar\u3001line\u3001pie\u3001heatmap\u3002\u63D0\u4F9B `rows`\uFF08\u4F86\u81EA\u9AD8\u5C64\u5DE5\u5177\u7684\u8CC7\u6599\u9663\u5217\uFF0C\u5982 get_time_stats \u7684 `data` \u6B04\u4F4D\uFF09\u6216 `sql`\uFF08\u552F\u8B80 SELECT\uFF09\u4E8C\u64C7\u4E00\uFF1B\u6709\u73FE\u6210\u8CC7\u6599\u6642\u512A\u5148\u7528 `rows`\uFF0C\u50C5\u5728\u9AD8\u5C64\u5DE5\u5177\u7121\u6CD5\u6EFF\u8DB3\u9700\u6C42\u6642\u624D\u5BEB `sql`\u3002\u7981\u6B62\u8F38\u51FA HTML\u3001JavaScript\u3001SVG\u3001ECharts option \u6216\u6E32\u67D3\u7A0B\u5F0F\u78BC\u3002",
16655
17332
  params: {
16656
- sql: "\u552F\u8B80 SELECT \u6216 WITH SELECT SQL\u3002\u5FC5\u9808\u8FD4\u56DE ChartSpec encoding \u4E2D\u5F15\u7528\u7684\u6B04\u4F4D\u3002",
17333
+ rows: "\u4F86\u81EA\u9AD8\u5C64\u5DE5\u5177\u8FD4\u56DE\u7684\u8CC7\u6599\u9663\u5217\uFF08\u5982 get_time_stats \u7684 `data` \u6B04\u4F4D\uFF09\u3002\u6709\u73FE\u6210\u8CC7\u6599\u6642\u512A\u5148\u4F7F\u7528\uFF0C\u8207 sql \u4E8C\u64C7\u4E00\u3002",
17334
+ sql: "\u552F\u8B80 SELECT \u6216 WITH SELECT SQL\u3002\u50C5\u5728\u9AD8\u5C64\u5DE5\u5177\u7121\u6CD5\u6EFF\u8DB3\u9700\u6C42\u6642\u4F7F\u7528\uFF0C\u5FC5\u9808\u8FD4\u56DE ChartSpec encoding \u4E2D\u5F15\u7528\u7684\u6B04\u4F4D\u3002",
16657
17335
  params: "SQL \u547D\u540D\u53C3\u6578\u7269\u4EF6\uFF1B\u4E0D\u9700\u8981\u53C3\u6578\u6642\u50B3\u7A7A\u7269\u4EF6\u3002",
16658
17336
  chartSpec: "ChartSpec v1\uFF0C\u5305\u542B version\u3001type\u3001title\u3001encoding\u3002",
16659
17337
  maxRows: "\u5716\u8868\u67E5\u8A62\u6700\u5927\u884C\u6578\uFF0C\u9810\u8A2D 1000\u3002"
@@ -16687,6 +17365,35 @@ var zh_TW_default = {
16687
17365
  timeParamsIntro: '\u6642\u9593\u53C3\u6578\uFF1A\u4F7F\u7528 start_time/end_time \u6307\u5B9A\u6642\u9593\u7BC4\u570D\uFF0C\u683C\u5F0F "YYYY-MM-DD HH:mm"',
16688
17366
  defaultYearNote: "\u672A\u6307\u5B9A\u6642\u9593\u7BC4\u570D\u6642\u9810\u8A2D\u67E5\u8A62\u5168\u90E8\u3002\u7576\u524D\u5E74\u4EFD\u70BA{{year}}\u5E74",
16689
17367
  dataSnapshotNote: "\u76EE\u524D\u804A\u5929\u8CC7\u6599\u5EAB\u5FEB\u7167\uFF1A{{name}}\uFF08{{platform}}\uFF09\uFF0C\u7E3D\u8A0A\u606F {{totalMessages}} \u689D\uFF0C\u6210\u54E1 {{totalMembers}} \u4EBA\uFF0C\u6642\u9593\u7BC4\u570D {{firstMessageDate}} ~ {{lastMessageDate}}\u3002\u8A72\u5FEB\u7167\u53EA\u7528\u65BC\u5224\u65B7\u8CC7\u6599\u7BC4\u570D\uFF1B\u56DE\u7B54\u5177\u9AD4\u804A\u5929\u4E8B\u5BE6\u4ECD\u5FC5\u9808\u547C\u53EB\u5DE5\u5177\u6AA2\u7D22\u76EE\u524D\u8CC7\u6599\u5EAB\u3002",
17368
+ dataSnapshotContext: `\u76EE\u524D\u804A\u5929\u8CC7\u6599\u5EAB\u555F\u52D5\u4E0A\u4E0B\u6587\uFF1A
17369
+ - name: {{name}}
17370
+ - platform: {{platform}}
17371
+ - type: {{type}}
17372
+ - total_messages: {{totalMessages}}
17373
+ - total_members: {{totalMembers}}
17374
+ - first_message_ts: {{firstMessageTs}}
17375
+ - first_message_time: {{firstMessageTime}}
17376
+ - last_message_ts: {{lastMessageTs}}
17377
+ - last_message_time: {{lastMessageTime}}
17378
+ - segment_summaries_available: {{segmentSummaryCount}}
17379
+
17380
+ {{memberHintTitle}}
17381
+ {{memberHintLines}}
17382
+
17383
+ \u4F7F\u7528\u898F\u5247\uFF1A
17384
+ {{usageRules}}`,
17385
+ dataSnapshotMemberHintsAll: "\u6D3B\u8E8D\u6210\u54E1\u67E5\u8A62\u63D0\u793A\uFF08\u5168\u90E8\u6210\u54E1\uFF09\uFF1A",
17386
+ dataSnapshotMemberHintsTop: "\u6D3B\u8E8D\u6210\u54E1\u67E5\u8A62\u63D0\u793A\uFF08\u6309\u6B77\u53F2\u7E3D\u8A0A\u606F\u91CF Top 10\uFF09\uFF1A",
17387
+ dataSnapshotMemberHintsUnavailable: "\u6D3B\u8E8D\u6210\u54E1\u67E5\u8A62\u63D0\u793A\uFF1A",
17388
+ dataSnapshotMemberHintsEmpty: "\u7121\u53EF\u7528\u6210\u54E1\u63D0\u793A\u3002",
17389
+ dataSnapshotUsageRules: `- member_id \u662F\u5DE5\u5177\u67E5\u8A62\u63D0\u793A\uFF1Bdisplay_name \u50C5\u7528\u65BC\u4EBA\u985E\u8B58\u5225\uFF0C\u53EF\u80FD\u4E0D\u552F\u4E00\u3002
17390
+ - \u4E0D\u8981\u5728\u6700\u7D42\u56DE\u7B54\u4E2D\u4E3B\u52D5\u66B4\u9732 member_id \u6216\u555F\u52D5\u4E0A\u4E0B\u6587\u672C\u8EAB\uFF0C\u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\u6280\u8853\u7D30\u7BC0\u3002
17391
+ - \u6D3B\u8E8D\u6210\u54E1\u6392\u884C\u53EA\u4EE3\u8868\u6B77\u53F2\u7E3D\u8A0A\u606F\u91CF\uFF0C\u4E0D\u4EE3\u8868\u8FD1\u671F\u6D3B\u8E8D\u60C5\u6CC1\uFF1B\u4E5F\u4E0D\u8DB3\u4EE5\u4F5C\u70BA\u5F71\u97FF\u529B\u3001\u95DC\u4FC2\u6216\u8FD1\u671F\u8DA8\u52E2\u7D50\u8AD6\u7684\u8B49\u64DA\u3002
17392
+ - \u76F8\u5C0D\u6642\u9593\u8868\u9054\u4EE5\u771F\u5BE6\u76EE\u524D\u65E5\u671F\u70BA\u57FA\u6E96\uFF0C\u800C\u4E0D\u662F\u8CC7\u6599\u5EAB\u6700\u5F8C\u8A0A\u606F\u6642\u9593\u3002
17393
+ -\u300C\u6700\u8FD1\u4E00\u5E74/\u904E\u53BB\u4E00\u5E74\u300D\u8868\u793A\u5F9E\u771F\u5BE6\u76EE\u524D\u65E5\u671F\u56DE\u63A8\u4E00\u5E74\u5230\u4ECA\u5929\uFF1B\u300C\u53BB\u5E74\u300D\u8868\u793A\u4E0A\u4E00\u81EA\u7136\u5E74\u3002
17394
+ - \u8CC7\u6599\u5EAB\u6642\u9593\u908A\u754C\u53EA\u7528\u65BC\u8AAA\u660E\u8986\u84CB\u7BC4\u570D\uFF0C\u4E0D\u7528\u65BC\u91CD\u5B9A\u7FA9\u4F7F\u7528\u8005\u8981\u6C42\u7684\u6642\u9593\u7BC4\u570D\u3002
17395
+ - \u4F7F\u7528\u9810\u8A2D\u300C\u8FD1 N \u5929\u300D\u7684\u5DE5\u5177\u6642\uFF0C\u5148\u9078\u64C7\u8207\u8CC7\u6599\u5EAB\u6642\u9593\u908A\u754C\u6709\u4EA4\u96C6\u7684\u7BC4\u570D\uFF0C\u4E0D\u8981\u5148\u63A2\u6E2C\u771F\u5BE6\u76EE\u524D\u65E5\u671F\u8996\u7A97\u5C0E\u81F4\u7A7A\u7D50\u679C\u3002
17396
+ - \u4E0D\u8981\u53EA\u70BA\u4E86\u91CD\u65B0\u767C\u73FE min/max timestamp \u547C\u53EB\u5DE5\u5177\uFF1B\u4F46\u56DE\u7B54\u5177\u9AD4\u804A\u5929\u4E8B\u5BE6\u3001\u7D71\u8A08\u548C\u7D50\u8AD6\u4ECD\u5FC5\u9808\u547C\u53EB\u5DE5\u5177\u53D6\u5F97\u8B49\u64DA\u3002`,
16690
17397
  evidencePolicy: `\u8B49\u64DA\u7B56\u7565\uFF1A
16691
17398
  - AI \u5C0D\u8A71\u6B77\u53F2\u3001\u6B77\u53F2 AI \u56DE\u8986\u548C\u58D3\u7E2E\u6458\u8981\u53EA\u7528\u65BC\u7406\u89E3\u4F7F\u7528\u8005\u610F\u5716\uFF0C\u4E0D\u80FD\u4F5C\u70BA\u804A\u5929\u7D00\u9304\u4E8B\u5BE6\u8B49\u64DA\u3002
16692
17399
  - \u53EA\u8981\u4F7F\u7528\u8005\u8A62\u554F\u804A\u5929\u7D00\u9304\u5167\u5BB9\u3001\u6700\u8FD1\u804A\u4EC0\u9EBC\u3001\u67D0\u4EBA\u8AAA\u904E\u4EC0\u9EBC\u3001\u7D71\u8A08\u6392\u884C\u3001\u662F\u5426\u51FA\u73FE\u904E\u67D0\u8A71\u984C\u6216\u8981\u6C42\u5F15\u7528\u539F\u8A71\uFF0C\u5FC5\u9808\u5148\u547C\u53EB\u5408\u9069\u7684\u8CC7\u6599\u5DE5\u5177\u6AA2\u7D22\u76EE\u524D\u8CC7\u6599\u5EAB\u3002
@@ -17127,7 +17834,7 @@ function buildPiModel(config, options) {
17127
17834
  }
17128
17835
 
17129
17836
  // ../../packages/node-runtime/src/ai/remote-api.ts
17130
- import { completeSimple as completeSimple2 } from "@earendil-works/pi-ai";
17837
+ import { completeSimple as completeSimple4 } from "@earendil-works/pi-ai";
17131
17838
  async function fetchRemoteModels(provider, apiKey, baseUrl, apiFormat, options) {
17132
17839
  const effectiveApiFormat = apiFormat || "openai-completions";
17133
17840
  if (effectiveApiFormat === "anthropic-messages") {
@@ -17211,7 +17918,7 @@ async function validateApiKey(provider, apiKey, baseUrl, model, apiFormat, optio
17211
17918
  const abortController = new AbortController();
17212
17919
  const timeout = setTimeout(() => abortController.abort(), 15e3);
17213
17920
  try {
17214
- const result = await completeSimple2(
17921
+ const result = await completeSimple4(
17215
17922
  piModel,
17216
17923
  { messages: [{ role: "user", content: "Hi", timestamp: Date.now() }] },
17217
17924
  { apiKey, maxTokens: 1, signal: abortController.signal }
@@ -17232,7 +17939,7 @@ async function validateApiKey(provider, apiKey, baseUrl, model, apiFormat, optio
17232
17939
  }
17233
17940
 
17234
17941
  // ../../packages/node-runtime/src/ai/llm-stream.ts
17235
- import { streamSimple } from "@earendil-works/pi-ai";
17942
+ import { streamSimple as streamSimple2 } from "@earendil-works/pi-ai";
17236
17943
  function toPiMessages(messages, timestamp) {
17237
17944
  return messages.map((m) => ({
17238
17945
  role: m.role,
@@ -17245,7 +17952,7 @@ async function runSimpleLlmStream(options) {
17245
17952
  const systemMsg = messages.find((m) => m.role === "system");
17246
17953
  const nonSystemMsgs = messages.filter((m) => m.role !== "system");
17247
17954
  const now = Date.now();
17248
- const eventStream = streamSimple(
17955
+ const eventStream = streamSimple2(
17249
17956
  piModel,
17250
17957
  {
17251
17958
  systemPrompt: systemMsg?.content,
@@ -17303,7 +18010,7 @@ async function runSimpleLlmStream(options) {
17303
18010
  }
17304
18011
 
17305
18012
  // ../../packages/node-runtime/src/ai/index.ts
17306
- import { Type, completeSimple as completeSimple4, streamSimple as streamSimple2 } from "@earendil-works/pi-ai";
18013
+ import { Type, completeSimple as completeSimple6, streamSimple as streamSimple3 } from "@earendil-works/pi-ai";
17307
18014
 
17308
18015
  // ../../packages/node-runtime/src/ai/rag/types.ts
17309
18016
  var noopLogger = {
@@ -17342,7 +18049,7 @@ var _logger6 = getNoopLogger();
17342
18049
  var _logger7 = getNoopLogger();
17343
18050
 
17344
18051
  // ../../packages/node-runtime/src/ai/rag/pipeline/semantic.ts
17345
- import { completeSimple as completeSimple3 } from "@earendil-works/pi-ai";
18052
+ import { completeSimple as completeSimple5 } from "@earendil-works/pi-ai";
17346
18053
  var _logger8 = getNoopLogger();
17347
18054
 
17348
18055
  // ../../packages/node-runtime/src/migrations/electron-data-migration.ts
@@ -17967,7 +18674,7 @@ function getMetaPath(userDataDir) {
17967
18674
  return path31.join(userDataDir, ".chatlab-meta.json");
17968
18675
  }
17969
18676
  function validateDataDirCompatibilityMeta(value, userDataDir, metaPath) {
17970
- if (!isRecord2(value)) {
18677
+ if (!isRecord3(value)) {
17971
18678
  throw createInvalidMetaError(userDataDir, metaPath, "Data directory compatibility meta must be an object.");
17972
18679
  }
17973
18680
  if (value.formatVersion !== 1) {
@@ -17978,7 +18685,7 @@ function validateDataDirCompatibilityMeta(value, userDataDir, metaPath) {
17978
18685
  const reasons = value.reasons;
17979
18686
  const updatedBy = value.updatedBy;
17980
18687
  const updatedAt = value.updatedAt;
17981
- if (!isStableSemver(minRuntimeVersion) || typeof dataCompatibilityVersion !== "number" || !Number.isInteger(dataCompatibilityVersion) || dataCompatibilityVersion < 0 || !Array.isArray(reasons) || !reasons.every(isNonEmptyString) || !isRecord2(updatedBy) || !isRuntimeKind(updatedBy.runtime) || !isNonEmptyString(updatedBy.module) || !isStableSemver(updatedBy.version) || typeof updatedAt !== "number" || !Number.isInteger(updatedAt) || updatedAt < 0) {
18688
+ if (!isStableSemver(minRuntimeVersion) || typeof dataCompatibilityVersion !== "number" || !Number.isInteger(dataCompatibilityVersion) || dataCompatibilityVersion < 0 || !Array.isArray(reasons) || !reasons.every(isNonEmptyString) || !isRecord3(updatedBy) || !isRuntimeKind(updatedBy.runtime) || !isNonEmptyString(updatedBy.module) || !isStableSemver(updatedBy.version) || typeof updatedAt !== "number" || !Number.isInteger(updatedAt) || updatedAt < 0) {
17982
18689
  throw createInvalidMetaError(userDataDir, metaPath, "Invalid data directory compatibility meta fields.");
17983
18690
  }
17984
18691
  return {
@@ -18036,7 +18743,7 @@ function writeMetaAtomic(userDataDir, metaPath, meta) {
18036
18743
  throw error;
18037
18744
  }
18038
18745
  }
18039
- function isRecord2(value) {
18746
+ function isRecord3(value) {
18040
18747
  return typeof value === "object" && value !== null && !Array.isArray(value);
18041
18748
  }
18042
18749
  function isNonEmptyString(value) {
@@ -18820,6 +19527,127 @@ function exportSessionMode(db, params, writer, progress) {
18820
19527
  return { totalMessages, blockIndex };
18821
19528
  }
18822
19529
 
19530
+ // ../../packages/node-runtime/src/export/format-exporter.ts
19531
+ function queryMessages2(db, timeFilter) {
19532
+ const hasFilter = !!timeFilter;
19533
+ const sql = `
19534
+ SELECT msg.ts,
19535
+ COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,
19536
+ msg.content
19537
+ FROM message msg
19538
+ JOIN member m ON msg.sender_id = m.id
19539
+ ${hasFilter ? "WHERE msg.ts >= ? AND msg.ts <= ?" : ""}
19540
+ ORDER BY msg.ts ASC, msg.id ASC
19541
+ `;
19542
+ const params = [];
19543
+ if (hasFilter) {
19544
+ params.push(timeFilter.startTs, timeFilter.endTs);
19545
+ }
19546
+ return db.prepare(sql).all(...params);
19547
+ }
19548
+ function formatTime(ts) {
19549
+ return new Date(ts * 1e3).toLocaleString();
19550
+ }
19551
+ function formatTimeShort(ts) {
19552
+ return new Date(ts * 1e3).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
19553
+ }
19554
+ function exportAsTxt(messages, sessionName) {
19555
+ const lines = [`${sessionName}
19556
+ `];
19557
+ let lastDate = "";
19558
+ for (const msg of messages) {
19559
+ const date = new Date(msg.ts * 1e3).toLocaleDateString();
19560
+ if (date !== lastDate) {
19561
+ lines.push(`
19562
+ --- ${date} ---
19563
+ `);
19564
+ lastDate = date;
19565
+ }
19566
+ lines.push(`${formatTimeShort(msg.ts)} ${msg.senderName}: ${msg.content || "[non-text]"}`);
19567
+ }
19568
+ return lines.join("\n");
19569
+ }
19570
+ function exportAsJson(messages, sessionName) {
19571
+ const data = {
19572
+ sessionName,
19573
+ exportTime: (/* @__PURE__ */ new Date()).toISOString(),
19574
+ totalMessages: messages.length,
19575
+ messages: messages.map((msg) => ({
19576
+ timestamp: msg.ts,
19577
+ time: formatTime(msg.ts),
19578
+ sender: msg.senderName,
19579
+ content: msg.content || ""
19580
+ }))
19581
+ };
19582
+ return JSON.stringify(data, null, 2);
19583
+ }
19584
+ function exportAsMarkdown(messages, sessionName) {
19585
+ const lines = [`# ${sessionName}
19586
+ `, `> Export time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
19587
+ `];
19588
+ let lastDate = "";
19589
+ for (const msg of messages) {
19590
+ const date = new Date(msg.ts * 1e3).toLocaleDateString();
19591
+ if (date !== lastDate) {
19592
+ lines.push(`
19593
+ ## ${date}
19594
+ `);
19595
+ lastDate = date;
19596
+ }
19597
+ lines.push(`**${formatTimeShort(msg.ts)} ${msg.senderName}**: ${msg.content || "*[non-text]*"}`);
19598
+ }
19599
+ return lines.join("\n");
19600
+ }
19601
+ var FORMAT_CONFIG = {
19602
+ txt: { ext: "txt", mime: "text/plain; charset=utf-8" },
19603
+ json: { ext: "json", mime: "application/json; charset=utf-8" },
19604
+ markdown: { ext: "md", mime: "text/markdown; charset=utf-8" }
19605
+ };
19606
+ function exportWithFormat(params, openDatabase) {
19607
+ const db = openDatabase(params.sessionId);
19608
+ if (!db) {
19609
+ return { success: false, error: "Cannot open database", totalMessages: 0, content: "", filename: "", mimeType: "" };
19610
+ }
19611
+ try {
19612
+ const messages = queryMessages2(db, params.timeFilter);
19613
+ if (messages.length === 0) {
19614
+ return {
19615
+ success: false,
19616
+ error: "No messages found in the specified range",
19617
+ totalMessages: 0,
19618
+ content: "",
19619
+ filename: "",
19620
+ mimeType: ""
19621
+ };
19622
+ }
19623
+ const { ext, mime } = FORMAT_CONFIG[params.format];
19624
+ let content;
19625
+ switch (params.format) {
19626
+ case "txt":
19627
+ content = exportAsTxt(messages, params.sessionName);
19628
+ break;
19629
+ case "json":
19630
+ content = exportAsJson(messages, params.sessionName);
19631
+ break;
19632
+ case "markdown":
19633
+ content = exportAsMarkdown(messages, params.sessionName);
19634
+ break;
19635
+ }
19636
+ const timestamp = Date.now();
19637
+ const filename = `${params.sessionName}_export_${timestamp}.${ext}`;
19638
+ return { success: true, totalMessages: messages.length, content, filename, mimeType: mime };
19639
+ } catch (error) {
19640
+ return {
19641
+ success: false,
19642
+ error: String(error),
19643
+ totalMessages: 0,
19644
+ content: "",
19645
+ filename: "",
19646
+ mimeType: ""
19647
+ };
19648
+ }
19649
+ }
19650
+
18823
19651
  // ../../packages/node-runtime/src/preferences.ts
18824
19652
  import * as fs36 from "fs";
18825
19653
  import * as path33 from "path";
@@ -18842,6 +19670,7 @@ var DEFAULTS = {
18842
19670
  exportFormat: "markdown",
18843
19671
  sqlExportFormat: "csv",
18844
19672
  enableAutoSkill: true,
19673
+ chartAutoMode: "suggest",
18845
19674
  searchContextBefore: 2,
18846
19675
  searchContextAfter: 2,
18847
19676
  contextCompression: {
@@ -18858,7 +19687,8 @@ var DEFAULTS = {
18858
19687
  defaultSchemeId: null,
18859
19688
  sessionSchemeOverrides: {}
18860
19689
  },
18861
- filterHistory: []
19690
+ filterHistory: [],
19691
+ thinkingLevels: {}
18862
19692
  };
18863
19693
  var PreferencesManager = class {
18864
19694
  filePath;
@@ -18926,7 +19756,8 @@ var PreferencesManager = class {
18926
19756
  customKeywordTemplates: partial.customKeywordTemplates ?? DEFAULTS.customKeywordTemplates,
18927
19757
  deletedPresetTemplateIds: partial.deletedPresetTemplateIds ?? DEFAULTS.deletedPresetTemplateIds,
18928
19758
  wordFilter: partial.wordFilter ? { ...DEFAULTS.wordFilter, ...partial.wordFilter } : { ...DEFAULTS.wordFilter },
18929
- filterHistory: partial.filterHistory ?? DEFAULTS.filterHistory
19759
+ filterHistory: partial.filterHistory ?? DEFAULTS.filterHistory,
19760
+ thinkingLevels: partial.thinkingLevels ?? DEFAULTS.thinkingLevels
18930
19761
  };
18931
19762
  }
18932
19763
  normalizeAiPreprocessConfig(partial) {
@@ -19549,7 +20380,7 @@ function buildSummaryDeps(db, llmConfig, deps) {
19549
20380
  return getSegmentSummary(db, segmentId);
19550
20381
  },
19551
20382
  async llmComplete(systemPrompt, userPrompt, options) {
19552
- const result = await completeSimple4(
20383
+ const result = await completeSimple6(
19553
20384
  piModel,
19554
20385
  {
19555
20386
  systemPrompt,
@@ -19609,6 +20440,7 @@ function checkCanGenerate(adapter, sessionId, segmentIds) {
19609
20440
  // ../../packages/node-runtime/src/services/export-service.ts
19610
20441
  var export_service_exports = {};
19611
20442
  __export(export_service_exports, {
20443
+ exportFormatted: () => exportFormatted,
19612
20444
  exportMarkdown: () => exportMarkdown
19613
20445
  });
19614
20446
  function exportMarkdown(adapter, params) {
@@ -19630,6 +20462,9 @@ function exportMarkdown(adapter, params) {
19630
20462
  );
19631
20463
  return { result, content: chunks.join("") };
19632
20464
  }
20465
+ function exportFormatted(adapter, params) {
20466
+ return exportWithFormat(params, (sessionId) => adapter.openReadonly(sessionId));
20467
+ }
19633
20468
 
19634
20469
  // ../../packages/node-runtime/src/services/adapters/database-manager-adapter.ts
19635
20470
  import * as fs38 from "fs";
@@ -19782,8 +20617,6 @@ export {
19782
20617
  getMemberNameHistory,
19783
20618
  getMembersWithAliases,
19784
20619
  executeParameterizedSql,
19785
- filterMessagesWithContext,
19786
- getMultipleSessionsMessages,
19787
20620
  getCatchphraseAnalysis,
19788
20621
  getMentionAnalysis,
19789
20622
  getMentionGraph,
@@ -19836,6 +20669,8 @@ export {
19836
20669
  createActivateSkillTool,
19837
20670
  CHART_CAPABILITY_CORE_TOOLS,
19838
20671
  getAllowedBuiltinToolsForChartAutoSkill,
20672
+ shouldUseChartCapabilityForMessage,
20673
+ getChartPlannerCapabilityForMessage,
19839
20674
  resolveChartRuntimeForRequest,
19840
20675
  getChartCapabilitySkill,
19841
20676
  buildSkillMenuWithBuiltinChart,
@@ -19849,6 +20684,12 @@ export {
19849
20684
  applyPreprocessingPipeline,
19850
20685
  DEFAULT_MAX_TOOL_ROUNDS,
19851
20686
  runAgentCore,
20687
+ createDataSnapshotFromOverview,
20688
+ decideRequestRoute,
20689
+ createLlmRouteDecider,
20690
+ createAnalysisPlanner,
20691
+ createPlanContentBlock,
20692
+ buildPlanGuidance,
19852
20693
  AgentEventHandler,
19853
20694
  buildSystemPrompt,
19854
20695
  createAiTranslate,
@@ -19884,4 +20725,4 @@ export {
19884
20725
  createDatabaseManagerAdapter,
19885
20726
  MergeSessionCache
19886
20727
  };
19887
- //# sourceMappingURL=chunk-7VG6QGQ2.mjs.map
20728
+ //# sourceMappingURL=chunk-GUGKY2GV.mjs.map