chatlab-cli 0.24.0 → 0.25.0

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 (68) hide show
  1. package/README.md +5 -2
  2. package/dist/{chunk-XJJXSOLO.mjs → chunk-4OMV4VNH.mjs} +738 -137
  3. package/dist/chunk-4OMV4VNH.mjs.map +1 -0
  4. package/dist/{chunk-A6JCL4PT.mjs → chunk-CIBTUWAE.mjs} +24 -10
  5. package/dist/chunk-CIBTUWAE.mjs.map +1 -0
  6. package/dist/{chunk-KCBIBRHL.mjs → chunk-E65ABM3C.mjs} +168 -72
  7. package/dist/chunk-E65ABM3C.mjs.map +1 -0
  8. package/dist/{chunk-NE4A6PND.mjs → chunk-FSHVDWI2.mjs} +126 -64
  9. package/dist/chunk-FSHVDWI2.mjs.map +1 -0
  10. package/dist/{chunk-36W2HWQN.mjs → chunk-GNT5G74Z.mjs} +2 -2
  11. package/dist/{chunk-RLCFXTTS.mjs → chunk-TCPXAX3F.mjs} +2 -2
  12. package/dist/cli.mjs +2 -2
  13. package/dist/{http-OPQ352FZ.mjs → http-PMNZ42EN.mjs} +6 -6
  14. package/dist/{import-GZMDLK2E.mjs → import-OP7R4CYY.mjs} +4 -4
  15. package/dist/index.mjs +6 -6
  16. package/dist/{mcp-SHZ2CVXY.mjs → mcp-ABXNFUJO.mjs} +3 -3
  17. package/dist/{stream-import-RDDB3N7B.mjs → stream-import-L6RJHH4L.mjs} +3 -3
  18. package/dist/{update-checker-JG5K67O2.mjs → update-checker-JIBA7T4U.mjs} +24 -21
  19. package/dist/update-checker-JIBA7T4U.mjs.map +1 -0
  20. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-BUwpIerl.js +161 -0
  21. package/dist-web/assets/{EChartWordcloud-VpNJDurs.js → EChartWordcloud-CzhfrEF_.js} +1 -1
  22. package/dist-web/assets/MessageExportModal-BAJKWX4y.js +1 -0
  23. package/dist-web/assets/{electron-BIWBW8k_.js → electron-CZt36wW5.js} +1 -1
  24. package/dist-web/assets/electron-CwERrTWp.js +1 -0
  25. package/dist-web/assets/{fetch-ChECfwh7.js → fetch-BxFAkm7O.js} +1 -1
  26. package/dist-web/assets/{fetch-DwPB8NOH.js → fetch-CgBIWyAo.js} +1 -1
  27. package/dist-web/assets/{fetch-BhsrPRzn.js → fetch-D4p9vj1A.js} +1 -1
  28. package/dist-web/assets/{fetch-CvaXKzA-.js → fetch-DD1EGRKV.js} +1 -1
  29. package/dist-web/assets/{fetch-BOevuRK_.js → fetch-DO8lty7_.js} +1 -1
  30. package/dist-web/assets/{fetch-CnyupWWc.js → fetch-DXfZysDf.js} +2 -2
  31. package/dist-web/assets/{fetch-DKD1bKt2.js → fetch-GMkMBzBS.js} +1 -1
  32. package/dist-web/assets/{fetch-D1_jL6PO.js → fetch-JzVEwamn.js} +1 -1
  33. package/dist-web/assets/{fetch-DFWZ48Xw.js → fetch-qoGEVjNg.js} +1 -1
  34. package/dist-web/assets/{fetch-ClDGP9DK.js → fetch-yWwp067l.js} +1 -1
  35. package/dist-web/assets/{index-BVQsKKwg.js → index-BrS_VrIY.js} +1 -1
  36. package/dist-web/assets/index-C1t-kzfo.css +1 -0
  37. package/dist-web/assets/index-Cvlw0RLu.js +2 -0
  38. package/dist-web/assets/index-DABpUm3J.js +9 -0
  39. package/dist-web/assets/{index-D6ga3hbF.js → index-DvWI6Yqa.js} +4 -4
  40. package/dist-web/assets/{index-Bzg8BftZ.js → index-O0PG26_v.js} +1 -1
  41. package/dist-web/assets/index-OsjX5hXw.js +173 -0
  42. package/dist-web/assets/vendor-echarts-CITQeskI.js +11 -0
  43. package/dist-web/assets/{vendor-echarts-wordcloud-CZ9gsX8l.js → vendor-echarts-wordcloud-B1kQMklB.js} +1 -1
  44. package/dist-web/assets/{vendor-nuxt-ui-BLMzqeB8.js → vendor-nuxt-ui-C_8CsH6e.js} +2 -2
  45. package/dist-web/assets/{vendor-reka-ui-CnB0mMNX.js → vendor-reka-ui-CIMXwvEp.js} +1 -1
  46. package/dist-web/assets/{web-DtlveK3q.js → web-4rtrAcLu.js} +1 -1
  47. package/dist-web/index.html +4 -4
  48. package/package.json +1 -1
  49. package/dist/chunk-A6JCL4PT.mjs.map +0 -1
  50. package/dist/chunk-KCBIBRHL.mjs.map +0 -1
  51. package/dist/chunk-NE4A6PND.mjs.map +0 -1
  52. package/dist/chunk-XJJXSOLO.mjs.map +0 -1
  53. package/dist/update-checker-JG5K67O2.mjs.map +0 -1
  54. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-_gbTn1ha.js +0 -161
  55. package/dist-web/assets/MessageExportModal-n4JgNg_F.js +0 -1
  56. package/dist-web/assets/electron-Bt9dK4Sr.js +0 -1
  57. package/dist-web/assets/index-BN79SJSr.js +0 -173
  58. package/dist-web/assets/index-C-5bP7_E.js +0 -2
  59. package/dist-web/assets/index-ESCx9pRR.js +0 -8
  60. package/dist-web/assets/index-JbR9Q-bb.css +0 -1
  61. package/dist-web/assets/model-catalog-DUuKjIVU.js +0 -1
  62. package/dist-web/assets/vendor-echarts-C5u62NnM.js +0 -11
  63. /package/dist/{chunk-36W2HWQN.mjs.map → chunk-GNT5G74Z.mjs.map} +0 -0
  64. /package/dist/{chunk-RLCFXTTS.mjs.map → chunk-TCPXAX3F.mjs.map} +0 -0
  65. /package/dist/{http-OPQ352FZ.mjs.map → http-PMNZ42EN.mjs.map} +0 -0
  66. /package/dist/{import-GZMDLK2E.mjs.map → import-OP7R4CYY.mjs.map} +0 -0
  67. /package/dist/{mcp-SHZ2CVXY.mjs.map → mcp-ABXNFUJO.mjs.map} +0 -0
  68. /package/dist/{stream-import-RDDB3N7B.mjs.map → stream-import-L6RJHH4L.mjs.map} +0 -0
@@ -4377,6 +4377,251 @@ function runMigrations(db, migrationsList, forceRepair = false) {
4377
4377
  return true;
4378
4378
  }
4379
4379
 
4380
+ // ../../packages/core/src/version.ts
4381
+ function parseStableVersion(version) {
4382
+ const normalized = version.trim().replace(/^v/i, "");
4383
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(normalized);
4384
+ if (!match) return null;
4385
+ return buildParsedVersion(match);
4386
+ }
4387
+ function parseComparableCurrentVersion(version) {
4388
+ const normalized = version.trim().replace(/^v/i, "");
4389
+ const match = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/.exec(normalized);
4390
+ if (!match) return null;
4391
+ return {
4392
+ ...buildParsedVersion(match),
4393
+ prerelease: Boolean(match[4])
4394
+ };
4395
+ }
4396
+ function buildParsedVersion(match) {
4397
+ return {
4398
+ major: Number(match[1]),
4399
+ minor: Number(match[2]),
4400
+ patch: Number(match[3])
4401
+ };
4402
+ }
4403
+ function isNewerStableVersion(latest, current) {
4404
+ const latestVersion = parseStableVersion(latest);
4405
+ const currentVersion = parseComparableCurrentVersion(current);
4406
+ if (!latestVersion || !currentVersion) return false;
4407
+ if (latestVersion.major !== currentVersion.major) return latestVersion.major > currentVersion.major;
4408
+ if (latestVersion.minor !== currentVersion.minor) return latestVersion.minor > currentVersion.minor;
4409
+ if (latestVersion.patch !== currentVersion.patch) return latestVersion.patch > currentVersion.patch;
4410
+ return currentVersion.prerelease;
4411
+ }
4412
+
4413
+ // ../../packages/core/src/chart/index.ts
4414
+ var ChartValidationError = class extends Error {
4415
+ constructor(message) {
4416
+ super(message);
4417
+ this.name = "ChartValidationError";
4418
+ }
4419
+ };
4420
+ var CHART_TYPES = /* @__PURE__ */ new Set(["bar", "line", "pie", "heatmap"]);
4421
+ function isRecord(value) {
4422
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4423
+ }
4424
+ function asFieldName(value, label) {
4425
+ if (typeof value !== "string" || value.trim().length === 0) {
4426
+ throw new ChartValidationError(`${label} must be a non-empty field name`);
4427
+ }
4428
+ return value;
4429
+ }
4430
+ function assertFieldExists(rows, field) {
4431
+ if (rows.length === 0) return;
4432
+ if (!Object.prototype.hasOwnProperty.call(rows[0], field)) {
4433
+ throw new ChartValidationError(`Field "${field}" does not exist in SQL result`);
4434
+ }
4435
+ }
4436
+ function toLabel(value) {
4437
+ if (value === null || value === void 0 || value === "") return "(empty)";
4438
+ return String(value);
4439
+ }
4440
+ function toNumber(value, field) {
4441
+ const num = typeof value === "number" ? value : Number(value);
4442
+ if (!Number.isFinite(num)) {
4443
+ throw new ChartValidationError(`Field "${field}" contains non-numeric value "${String(value)}"`);
4444
+ }
4445
+ return num;
4446
+ }
4447
+ function inferFieldType(values) {
4448
+ const present = values.filter((v) => v !== null && v !== void 0);
4449
+ if (present.length === 0) return "string";
4450
+ if (present.every((v) => typeof v === "number" && Number.isInteger(v))) return "integer";
4451
+ if (present.every(
4452
+ (v) => typeof v === "number" || typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v))
4453
+ )) {
4454
+ return "number";
4455
+ }
4456
+ if (present.every((v) => typeof v === "boolean")) return "boolean";
4457
+ return "category";
4458
+ }
4459
+ function normalizeChartSpec(raw) {
4460
+ if (!isRecord(raw)) throw new ChartValidationError("chartSpec must be an object");
4461
+ if (raw.version !== 1) throw new ChartValidationError("chartSpec.version must be 1");
4462
+ if (typeof raw.type !== "string" || !CHART_TYPES.has(raw.type)) {
4463
+ throw new ChartValidationError("chartSpec.type must be one of: bar, line, pie, heatmap");
4464
+ }
4465
+ if (typeof raw.title !== "string" || raw.title.trim().length === 0) {
4466
+ throw new ChartValidationError("chartSpec.title must be a non-empty string");
4467
+ }
4468
+ if (!isRecord(raw.encoding)) throw new ChartValidationError("chartSpec.encoding must be an object");
4469
+ const spec = raw;
4470
+ switch (spec.type) {
4471
+ case "bar":
4472
+ case "line":
4473
+ asFieldName(spec.encoding.x, "encoding.x");
4474
+ asFieldName(spec.encoding.y, "encoding.y");
4475
+ break;
4476
+ case "pie":
4477
+ asFieldName(spec.encoding.label, "encoding.label");
4478
+ asFieldName(spec.encoding.value, "encoding.value");
4479
+ break;
4480
+ case "heatmap":
4481
+ asFieldName(spec.encoding.x, "encoding.x");
4482
+ asFieldName(spec.encoding.y, "encoding.y");
4483
+ asFieldName(spec.encoding.value, "encoding.value");
4484
+ break;
4485
+ }
4486
+ return {
4487
+ ...spec,
4488
+ title: spec.title.trim()
4489
+ };
4490
+ }
4491
+ function inferChartDataset(rows, declaredFields) {
4492
+ const declared = new Map((declaredFields ?? []).map((field) => [field.name, field]));
4493
+ const columnNames = rows.length > 0 ? Object.keys(rows[0]) : [...declared.keys()];
4494
+ const columns = columnNames.map((name) => {
4495
+ const declaredField = declared.get(name);
4496
+ if (declaredField) return declaredField;
4497
+ return {
4498
+ name,
4499
+ type: inferFieldType(rows.map((row) => row[name]))
4500
+ };
4501
+ });
4502
+ return { columns, rows };
4503
+ }
4504
+ function aggregateByLabel(rows, labelField, valueField) {
4505
+ const labels = [];
4506
+ const indexByLabel = /* @__PURE__ */ new Map();
4507
+ const values = [];
4508
+ for (const row of rows) {
4509
+ const label = toLabel(row[labelField]);
4510
+ let index = indexByLabel.get(label);
4511
+ if (index === void 0) {
4512
+ index = labels.length;
4513
+ indexByLabel.set(label, index);
4514
+ labels.push(label);
4515
+ values.push(0);
4516
+ }
4517
+ values[index] += toNumber(row[valueField], valueField);
4518
+ }
4519
+ return { labels, values };
4520
+ }
4521
+ function buildLineData(rows, spec) {
4522
+ const xField = spec.encoding.x;
4523
+ const yField = spec.encoding.y;
4524
+ const seriesField = spec.encoding.series;
4525
+ if (!seriesField) return aggregateByLabel(rows, xField, yField);
4526
+ assertFieldExists(rows, seriesField);
4527
+ const labels = [];
4528
+ const labelIndex = /* @__PURE__ */ new Map();
4529
+ const seriesNames = [];
4530
+ const seriesIndex = /* @__PURE__ */ new Map();
4531
+ const matrix = [];
4532
+ for (const row of rows) {
4533
+ const label = toLabel(row[xField]);
4534
+ const seriesName = toLabel(row[seriesField]);
4535
+ let xIndex = labelIndex.get(label);
4536
+ if (xIndex === void 0) {
4537
+ xIndex = labels.length;
4538
+ labelIndex.set(label, xIndex);
4539
+ labels.push(label);
4540
+ for (const values of matrix) values.push(0);
4541
+ }
4542
+ let sIndex = seriesIndex.get(seriesName);
4543
+ if (sIndex === void 0) {
4544
+ sIndex = seriesNames.length;
4545
+ seriesIndex.set(seriesName, sIndex);
4546
+ seriesNames.push(seriesName);
4547
+ matrix.push(Array(labels.length).fill(0));
4548
+ }
4549
+ matrix[sIndex][xIndex] += toNumber(row[yField], yField);
4550
+ }
4551
+ const series = seriesNames.map((name, index) => ({ name, values: matrix[index] }));
4552
+ return {
4553
+ labels,
4554
+ values: series[0]?.values ?? [],
4555
+ series
4556
+ };
4557
+ }
4558
+ function buildHeatmapData(rows, spec) {
4559
+ const xField = spec.encoding.x;
4560
+ const yField = spec.encoding.y;
4561
+ const valueField = spec.encoding.value;
4562
+ const xLabels = [];
4563
+ const yLabels = [];
4564
+ const xIndex = /* @__PURE__ */ new Map();
4565
+ const yIndex = /* @__PURE__ */ new Map();
4566
+ const values = /* @__PURE__ */ new Map();
4567
+ for (const row of rows) {
4568
+ const xLabel = toLabel(row[xField]);
4569
+ const yLabel = toLabel(row[yField]);
4570
+ let xi = xIndex.get(xLabel);
4571
+ if (xi === void 0) {
4572
+ xi = xLabels.length;
4573
+ xIndex.set(xLabel, xi);
4574
+ xLabels.push(xLabel);
4575
+ }
4576
+ let yi = yIndex.get(yLabel);
4577
+ if (yi === void 0) {
4578
+ yi = yLabels.length;
4579
+ yIndex.set(yLabel, yi);
4580
+ yLabels.push(yLabel);
4581
+ }
4582
+ const key = `${xi}:${yi}`;
4583
+ values.set(key, (values.get(key) ?? 0) + toNumber(row[valueField], valueField));
4584
+ }
4585
+ const data = [];
4586
+ for (const [key, value] of values.entries()) {
4587
+ const [x, y] = key.split(":").map(Number);
4588
+ data.push([x, y, value]);
4589
+ }
4590
+ return { xLabels, yLabels, data };
4591
+ }
4592
+ function buildChartPayload(rows, rawSpec, options) {
4593
+ const spec = normalizeChartSpec(rawSpec);
4594
+ const dataset = inferChartDataset(rows, spec.fields);
4595
+ const requiredFields = /* @__PURE__ */ new Set();
4596
+ for (const field of Object.values(spec.encoding)) {
4597
+ if (typeof field === "string" && field) requiredFields.add(field);
4598
+ }
4599
+ for (const field of requiredFields) assertFieldExists(rows, field);
4600
+ let data;
4601
+ switch (spec.type) {
4602
+ case "bar":
4603
+ data = aggregateByLabel(rows, spec.encoding.x, spec.encoding.y);
4604
+ break;
4605
+ case "line":
4606
+ data = buildLineData(rows, spec);
4607
+ break;
4608
+ case "pie":
4609
+ data = aggregateByLabel(rows, spec.encoding.label, spec.encoding.value);
4610
+ break;
4611
+ case "heatmap":
4612
+ data = buildHeatmapData(rows, spec);
4613
+ break;
4614
+ }
4615
+ return {
4616
+ version: 1,
4617
+ spec,
4618
+ dataset,
4619
+ data,
4620
+ rowCount: rows.length,
4621
+ truncated: options?.truncated
4622
+ };
4623
+ }
4624
+
4380
4625
  // ../../packages/core/src/nlp/pos-tags.ts
4381
4626
  var POS_TAG_DEFINITIONS = [
4382
4627
  { tag: "n", name: "\u540D\u8BCD", description: "\u666E\u901A\u540D\u8BCD", meaningful: true },
@@ -5215,6 +5460,7 @@ var BUILTIN_TOOL_CATALOG = [
5215
5460
  { name: "search_sessions", category: "core" },
5216
5461
  { name: "get_session_messages", category: "core" },
5217
5462
  { name: "get_members", category: "core" },
5463
+ { name: "get_schema", category: "core" },
5218
5464
  // Analysis 工具
5219
5465
  { name: "get_member_stats", category: "analysis" },
5220
5466
  { name: "get_time_stats", category: "analysis" },
@@ -5223,6 +5469,8 @@ var BUILTIN_TOOL_CATALOG = [
5223
5469
  { name: "get_session_summaries", category: "analysis" },
5224
5470
  { name: "response_time_analysis", category: "analysis" },
5225
5471
  { name: "keyword_frequency", category: "analysis" },
5472
+ { name: "render_chart", category: "analysis" },
5473
+ { name: "execute_sql", category: "analysis" },
5226
5474
  // SQL Analysis 工具
5227
5475
  { name: "message_type_breakdown", category: "analysis" },
5228
5476
  { name: "peak_chat_hours_by_member", category: "analysis" },
@@ -5237,6 +5485,9 @@ var BUILTIN_TOOL_CATALOG = [
5237
5485
  { name: "unanswered_messages", category: "analysis" }
5238
5486
  ];
5239
5487
 
5488
+ // ../../packages/core/src/ai/chart-capability.ts
5489
+ var CHART_CAPABILITY_SKILL_ID = "chart_runtime";
5490
+
5240
5491
  // ../../packages/core/src/ai/provider-registry.ts
5241
5492
  var OPENAI = {
5242
5493
  id: "openai",
@@ -13361,93 +13612,43 @@ function countMessagesTokens(messages, systemPrompt) {
13361
13612
  // ../../packages/node-runtime/src/ai/skill-manager.ts
13362
13613
  import * as fs30 from "fs";
13363
13614
  import * as path27 from "path";
13364
- var SKILLS_DIR_NAME = "skills";
13615
+
13616
+ // ../../packages/node-runtime/src/ai/skill-menu.ts
13365
13617
  var MAX_SKILL_MENU_ITEMS = 15;
13366
- var defaultLogger2 = {
13367
- info: () => {
13368
- },
13369
- warn: () => {
13370
- }
13371
- };
13372
- var SkillManager = class {
13373
- skills = /* @__PURE__ */ new Map();
13374
- initialized = false;
13375
- skillsDir;
13376
- logger;
13377
- constructor(aiDataDir, logger) {
13378
- this.skillsDir = path27.join(aiDataDir, SKILLS_DIR_NAME);
13379
- this.logger = logger ?? defaultLogger2;
13380
- }
13381
- init() {
13382
- this.loadAll();
13383
- this.initialized = true;
13384
- this.logger.info(`SkillManager initialized: ${this.skills.size} skills`);
13385
- return { total: this.skills.size };
13386
- }
13387
- getSkillConfig(id) {
13388
- this.ensureInitialized();
13389
- return this.skills.get(id) ?? null;
13390
- }
13391
- getAllSkills() {
13392
- this.ensureInitialized();
13393
- return Array.from(this.skills.values()).map((def) => ({
13394
- id: def.id,
13395
- name: def.name,
13396
- description: def.description,
13397
- tags: def.tags,
13398
- chatScope: def.chatScope,
13399
- tools: def.tools,
13400
- builtinId: def.builtinId
13401
- }));
13402
- }
13403
- /**
13404
- * 构建 AI 自选技能菜单文本
13405
- * 只包含与当前 chatType + 助手工具权限兼容的技能
13406
- */
13407
- getSkillMenu(chatType, allowedTools) {
13408
- this.ensureInitialized();
13409
- const compatible = Array.from(this.skills.values()).filter((skill) => {
13410
- if (skill.chatScope !== "all" && skill.chatScope !== chatType) return false;
13411
- if (skill.tools.length > 0 && allowedTools && allowedTools.length > 0) {
13412
- const allCovered = skill.tools.every((t2) => allowedTools.includes(t2));
13413
- if (!allCovered) return false;
13414
- }
13415
- return true;
13416
- });
13417
- if (compatible.length === 0) return null;
13418
- const items = compatible.slice(0, MAX_SKILL_MENU_ITEMS);
13419
- const lines = items.map((s) => `- ${s.id}: ${s.name} \u2014 ${s.description}`);
13420
- return `## \u53EF\u7528\u6280\u80FD
13618
+ var SKILL_MENU_HEADER = `## \u53EF\u7528\u6280\u80FD
13421
13619
  \u4EE5\u4E0B\u662F\u4F60\u53EF\u4EE5\u4F7F\u7528\u7684\u5206\u6790\u6280\u80FD\u3002\u5F53\u4F60\u5224\u65AD\u7528\u6237\u7684\u95EE\u9898\u9002\u5408\u4F7F\u7528\u67D0\u4E2A\u6280\u80FD\u65F6\uFF0C
13422
- \u8BF7\u8C03\u7528 activate_skill \u5DE5\u5177\u6FC0\u6D3B\u5B83\uFF0C\u7136\u540E\u6309\u7167\u8FD4\u56DE\u7684\u6307\u5BFC\u5B8C\u6210\u4EFB\u52A1\u3002
13620
+ \u8BF7\u8C03\u7528 activate_skill \u5DE5\u5177\u6FC0\u6D3B\u5B83\uFF0C\u7136\u540E\u6309\u7167\u8FD4\u56DE\u7684\u6307\u5BFC\u5B8C\u6210\u4EFB\u52A1\u3002`;
13621
+ var SKILL_MENU_CLOSING = "\u5982\u679C\u7528\u6237\u7684\u95EE\u9898\u4E0D\u9700\u8981\u4F7F\u7528\u6280\u80FD\uFF0C\u76F4\u63A5\u56DE\u7B54\u5373\u53EF\u3002";
13622
+ var SKILL_MENU_CLOSING_BLOCK = `
13423
13623
 
13424
- ${lines.join("\n")}
13624
+ ${SKILL_MENU_CLOSING}`;
13625
+ function formatSkillMenuLine(item) {
13626
+ const guidanceSuffix = item.guidance ? `. ${item.guidance}` : "";
13627
+ return `- ${item.id}: ${item.name} \u2014 ${item.description}${guidanceSuffix}`;
13628
+ }
13629
+ function buildSkillMenuText(lines) {
13630
+ const visibleLines = normalizeSkillMenuLines(lines);
13631
+ if (visibleLines.length === 0) return null;
13632
+ return `${SKILL_MENU_HEADER}
13425
13633
 
13426
- \u5982\u679C\u7528\u6237\u7684\u95EE\u9898\u4E0D\u9700\u8981\u4F7F\u7528\u6280\u80FD\uFF0C\u76F4\u63A5\u56DE\u7B54\u5373\u53EF\u3002`;
13427
- }
13428
- ensureInitialized() {
13429
- if (!this.initialized) {
13430
- this.init();
13431
- }
13432
- }
13433
- loadAll() {
13434
- this.skills.clear();
13435
- if (!fs30.existsSync(this.skillsDir)) return;
13436
- const files = fs30.readdirSync(this.skillsDir).filter((f) => f.endsWith(".md"));
13437
- for (const file of files) {
13438
- try {
13439
- const filePath = path27.join(this.skillsDir, file);
13440
- const content = fs30.readFileSync(filePath, "utf-8");
13441
- const def = parseSkillFile(content, filePath);
13442
- if (def) {
13443
- this.skills.set(def.id, def);
13444
- }
13445
- } catch (error) {
13446
- this.logger.warn(`Failed to load skill: ${file}`, { error: String(error) });
13447
- }
13448
- }
13634
+ ${visibleLines.join("\n")}
13635
+
13636
+ ${SKILL_MENU_CLOSING}`;
13637
+ }
13638
+ function appendSkillMenuLines(baseMenu, lines) {
13639
+ const visibleLines = normalizeSkillMenuLines(lines);
13640
+ if (visibleLines.length === 0) return baseMenu ?? null;
13641
+ if (!baseMenu) return buildSkillMenuText(visibleLines);
13642
+ const insertion = `
13643
+ ${visibleLines.join("\n")}`;
13644
+ if (!baseMenu.includes(SKILL_MENU_CLOSING_BLOCK)) {
13645
+ return `${baseMenu}${insertion}`;
13449
13646
  }
13450
- };
13647
+ return baseMenu.replace(SKILL_MENU_CLOSING_BLOCK, `${insertion}${SKILL_MENU_CLOSING_BLOCK}`);
13648
+ }
13649
+ function normalizeSkillMenuLines(lines) {
13650
+ return lines.map((line) => line.trim()).filter(Boolean);
13651
+ }
13451
13652
 
13452
13653
  // ../../packages/node-runtime/src/ai/skill-manager-core.ts
13453
13654
  function toSummary2(def) {
@@ -13478,7 +13679,6 @@ function simpleHash(content) {
13478
13679
  }
13479
13680
  return hash.toString(36);
13480
13681
  }
13481
- var MAX_SKILL_MENU_ITEMS2 = 15;
13482
13682
  var SkillManagerCore = class {
13483
13683
  deps;
13484
13684
  builtinDefs = /* @__PURE__ */ new Map();
@@ -13632,21 +13832,14 @@ var SkillManagerCore = class {
13632
13832
  this.ensureInitialized();
13633
13833
  const compatible = Array.from(this.cache.values()).filter((skill) => {
13634
13834
  if (skill.chatScope !== "all" && skill.chatScope !== chatType) return false;
13635
- if (skill.tools.length > 0 && allowedTools && allowedTools.length > 0) {
13835
+ if (skill.tools.length > 0 && allowedTools !== void 0) {
13636
13836
  if (!skill.tools.every((t2) => allowedTools.includes(t2))) return false;
13637
13837
  }
13638
13838
  return true;
13639
13839
  });
13640
13840
  if (compatible.length === 0) return null;
13641
- const items = compatible.slice(0, MAX_SKILL_MENU_ITEMS2);
13642
- const lines = items.map((s) => `- ${s.id}: ${s.name} \u2014 ${s.description}`);
13643
- return `## \u53EF\u7528\u6280\u80FD
13644
- \u4EE5\u4E0B\u662F\u4F60\u53EF\u4EE5\u4F7F\u7528\u7684\u5206\u6790\u6280\u80FD\u3002\u5F53\u4F60\u5224\u65AD\u7528\u6237\u7684\u95EE\u9898\u9002\u5408\u4F7F\u7528\u67D0\u4E2A\u6280\u80FD\u65F6\uFF0C
13645
- \u8BF7\u8C03\u7528 activate_skill \u5DE5\u5177\u6FC0\u6D3B\u5B83\uFF0C\u7136\u540E\u6309\u7167\u8FD4\u56DE\u7684\u6307\u5BFC\u5B8C\u6210\u4EFB\u52A1\u3002
13646
-
13647
- ${lines.join("\n")}
13648
-
13649
- \u5982\u679C\u7528\u6237\u7684\u95EE\u9898\u4E0D\u9700\u8981\u4F7F\u7528\u6280\u80FD\uFF0C\u76F4\u63A5\u56DE\u7B54\u5373\u53EF\u3002`;
13841
+ const items = compatible.slice(0, MAX_SKILL_MENU_ITEMS);
13842
+ return buildSkillMenuText(items.map(formatSkillMenuLine));
13650
13843
  }
13651
13844
  // ==================== Internal ====================
13652
13845
  findByBuiltinId(builtinId) {
@@ -13676,6 +13869,80 @@ ${lines.join("\n")}
13676
13869
  }
13677
13870
  };
13678
13871
 
13872
+ // ../../packages/node-runtime/src/ai/skill-manager.ts
13873
+ var SKILLS_DIR_NAME = "skills";
13874
+ var defaultLogger2 = {
13875
+ info: () => {
13876
+ },
13877
+ warn: () => {
13878
+ }
13879
+ };
13880
+ var nodeFs = {
13881
+ ensureDir(dir) {
13882
+ fs30.mkdirSync(dir, { recursive: true });
13883
+ },
13884
+ listFiles(dir, ext) {
13885
+ if (!fs30.existsSync(dir)) return [];
13886
+ return fs30.readdirSync(dir).filter((file) => file.endsWith(ext));
13887
+ },
13888
+ readFile(filePath) {
13889
+ return fs30.readFileSync(filePath, "utf-8");
13890
+ },
13891
+ writeFile(filePath, content) {
13892
+ fs30.writeFileSync(filePath, content, "utf-8");
13893
+ },
13894
+ deleteFile(filePath) {
13895
+ fs30.unlinkSync(filePath);
13896
+ },
13897
+ fileExists(filePath) {
13898
+ return fs30.existsSync(filePath);
13899
+ },
13900
+ joinPath(...parts) {
13901
+ return path27.join(...parts);
13902
+ }
13903
+ };
13904
+ var SkillManager = class {
13905
+ core;
13906
+ logger;
13907
+ constructor(aiDataDir, logger) {
13908
+ this.logger = logger ?? defaultLogger2;
13909
+ this.core = new SkillManagerCore({
13910
+ fs: nodeFs,
13911
+ skillsDir: path27.join(aiDataDir, SKILLS_DIR_NAME),
13912
+ builtinRawSkills: [],
13913
+ logger: {
13914
+ info: () => {
13915
+ },
13916
+ warn: (_category, message, data) => this.logger.warn(message, toLoggerExtra(data)),
13917
+ error: (_category, message, data) => this.logger.warn(message, toLoggerExtra(data))
13918
+ }
13919
+ });
13920
+ }
13921
+ init() {
13922
+ const result = this.core.init();
13923
+ this.logger.info(`SkillManager initialized: ${result.total} skills`);
13924
+ return result;
13925
+ }
13926
+ getSkillConfig(id) {
13927
+ return this.core.getSkillConfig(id);
13928
+ }
13929
+ getAllSkills() {
13930
+ return this.core.getAllSkills();
13931
+ }
13932
+ /**
13933
+ * 构建 AI 自选技能菜单文本
13934
+ * 只包含与当前 chatType + 助手工具权限兼容的技能
13935
+ */
13936
+ getSkillMenu(chatType, allowedTools) {
13937
+ return this.core.getSkillMenu(chatType, allowedTools);
13938
+ }
13939
+ };
13940
+ function toLoggerExtra(data) {
13941
+ if (data === void 0) return void 0;
13942
+ if (data && typeof data === "object" && !Array.isArray(data)) return data;
13943
+ return { data };
13944
+ }
13945
+
13679
13946
  // ../../packages/node-runtime/src/ai/activate-skill-tool.ts
13680
13947
  function createActivateSkillTool(options) {
13681
13948
  const { chatType, allowedTools, coreToolNames, getSkillConfig, locale = "zh-CN" } = options;
@@ -13711,7 +13978,7 @@ function createActivateSkillTool(options) {
13711
13978
  details: { skillId, found: true, applicable: false }
13712
13979
  };
13713
13980
  }
13714
- if (skill.tools.length > 0 && allowedTools && allowedTools.length > 0) {
13981
+ if (skill.tools.length > 0 && allowedTools !== void 0) {
13715
13982
  const missing = skill.tools.filter((t2) => !(coreToolNames?.has(t2) ?? false) && !allowedTools.includes(t2));
13716
13983
  if (missing.length > 0) {
13717
13984
  const msg = isZh ? `\u5F53\u524D\u52A9\u624B\u7F3A\u5C11\u8BE5\u6280\u80FD\u6240\u9700\u7684\u5DE5\u5177\uFF1A${missing.join(", ")}` : `Current assistant lacks tools required by this skill: ${missing.join(", ")}`;
@@ -13730,6 +13997,175 @@ function createActivateSkillTool(options) {
13730
13997
  };
13731
13998
  }
13732
13999
 
14000
+ // ../../packages/node-runtime/src/ai/chart-runtime.ts
14001
+ var CHART_CAPABILITY_CORE_TOOLS = ["get_schema"];
14002
+ var CHART_CAPABILITY_ANALYSIS_TOOLS = ["render_chart"];
14003
+ var RAW_SQL_TOOL_NAMES = /* @__PURE__ */ new Set(["execute_sql"]);
14004
+ function getChartCapabilityAllowedBuiltinTools(allowedTools) {
14005
+ const baseTools = (allowedTools ?? []).filter((toolName) => !RAW_SQL_TOOL_NAMES.has(toolName));
14006
+ return Array.from(/* @__PURE__ */ new Set([...baseTools, ...CHART_CAPABILITY_ANALYSIS_TOOLS]));
14007
+ }
14008
+ function getAllowedBuiltinToolsForChartAutoSkill(allowedTools) {
14009
+ if (!allowedTools) return void 0;
14010
+ if (allowedTools.length === 0) return [];
14011
+ return Array.from(/* @__PURE__ */ new Set([...allowedTools, ...CHART_CAPABILITY_ANALYSIS_TOOLS]));
14012
+ }
14013
+ function shouldUseChartCapabilityForMessage(message) {
14014
+ const normalized = message.trim().toLowerCase();
14015
+ if (!normalized) return false;
14016
+ return /(?:画|绘制|生成|做|出|展示).{0,8}(?:图|图表)|(?:图表|可视化|饼图|柱状图|条形图|折线图|热力图|趋势图|占比图|分布图|chart|charts|plot|visuali[sz]e|visuali[sz]ation|pie chart|bar chart|line chart|heatmap)/i.test(
14017
+ normalized
14018
+ );
14019
+ }
14020
+ function resolveChartRuntimeForRequest(options) {
14021
+ const locale = options.locale ?? "zh-CN";
14022
+ const isExplicitChartSkill = options.skillId === CHART_CAPABILITY_SKILL_ID;
14023
+ const isAutoChartSkill = options.enableAutoDetection === true && !options.skillId && shouldUseChartCapabilityForMessage(options.userMessage ?? "");
14024
+ if (!isExplicitChartSkill && !isAutoChartSkill) {
14025
+ return { isChartCapability: false };
14026
+ }
14027
+ return {
14028
+ isChartCapability: true,
14029
+ skillDef: { ...getChartCapabilitySkill(locale), chatScope: "all" },
14030
+ allowedBuiltinTools: getChartCapabilityAllowedBuiltinTools(options.assistantAllowedTools)
14031
+ };
14032
+ }
14033
+ function getChartCapabilitySkill(locale = "zh-CN") {
14034
+ const isZh = locale.startsWith("zh");
14035
+ return {
14036
+ id: CHART_CAPABILITY_SKILL_ID,
14037
+ name: isZh ? "\u7ED8\u56FE\u52A9\u624B" : "Chart Assistant",
14038
+ description: isZh ? "\u6309\u672C\u8F6E\u95EE\u9898\u751F\u6210\u7075\u6D3B\u7684\u804A\u5929\u6570\u636E\u56FE\u8868" : "Generate flexible charts for this chat question",
14039
+ tags: [isZh ? "\u56FE\u8868" : "chart"],
14040
+ chatScope: "all",
14041
+ tools: ["render_chart", "get_schema"],
14042
+ prompt: isZh ? ZH_PROMPT : EN_PROMPT,
14043
+ builtinId: CHART_CAPABILITY_SKILL_ID
14044
+ };
14045
+ }
14046
+ function getChartMenuLine(locale) {
14047
+ const skill = getChartCapabilitySkill(locale);
14048
+ const isZh = locale.startsWith("zh");
14049
+ const guidance = isZh ? "\u7528\u6237\u660E\u786E\u8981\u6C42\u56FE\u8868\u3001\u753B\u56FE\u3001\u5360\u6BD4\u3001\u8D8B\u52BF\u3001\u5206\u5E03\u3001\u997C\u56FE\u3001\u67F1\u72B6\u56FE\u3001\u6298\u7EBF\u56FE\u6216\u70ED\u529B\u56FE\u65F6\u4F18\u5148\u6FC0\u6D3B\uFF1B\u4E0D\u8981\u8F93\u51FA Python/JS \u7ED8\u56FE\u4EE3\u7801" : "Activate first when the user explicitly asks for charts, visualization, ratios, trends, distributions, pie, bar, line, or heatmap charts; do not output Python/JS chart code";
14050
+ return formatSkillMenuLine({
14051
+ id: skill.id,
14052
+ name: skill.name,
14053
+ description: skill.description,
14054
+ guidance
14055
+ });
14056
+ }
14057
+ function buildSkillMenuWithBuiltinChart(baseMenu, locale = "zh-CN", allowedTools) {
14058
+ if (allowedTools && !allowedTools.includes(CHART_CAPABILITY_ANALYSIS_TOOLS[0])) return baseMenu ?? null;
14059
+ if (baseMenu?.includes(CHART_CAPABILITY_SKILL_ID)) return baseMenu;
14060
+ const chartLine = getChartMenuLine(locale);
14061
+ return appendSkillMenuLines(baseMenu, [chartLine]) ?? "";
14062
+ }
14063
+ function getSkillConfigWithBuiltinChart(id, locale = "zh-CN", getSkillConfig) {
14064
+ if (id === CHART_CAPABILITY_SKILL_ID) return getChartCapabilitySkill(locale);
14065
+ return getSkillConfig(id);
14066
+ }
14067
+ 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
14068
+
14069
+ \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
14070
+
14071
+ \u786C\u6027\u89C4\u5219\uFF1A
14072
+ 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
14073
+ 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
14074
+ 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
14075
+ 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
14076
+ 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
14077
+ 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
14078
+ 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
14079
+
14080
+ \u5DE5\u4F5C\u6D41\u7A0B\uFF1A
14081
+ 1. \u8C03\u7528 get_schema\u3002
14082
+ 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
14083
+ 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
14084
+ 4. \u8C03\u7528 render_chart\uFF0C\u63D0\u4EA4\u53EA\u8BFB SQL \u548C ChartSpec v1\u3002
14085
+ 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
14086
+
14087
+ \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
14088
+
14089
+ ChartSpec v1 \u652F\u6301\uFF1A
14090
+ - bar: encoding.x + encoding.y
14091
+ - line: encoding.x + encoding.y\uFF0C\u53EF\u9009 encoding.series \u8868\u793A\u6BCF\u6761\u7EBF\u7684\u542B\u4E49
14092
+ - pie: encoding.label + encoding.value
14093
+ - heatmap: encoding.x + encoding.y + encoding.value
14094
+
14095
+ ChartSpec \u793A\u4F8B\uFF1A
14096
+ {
14097
+ "version": 1,
14098
+ "type": "line",
14099
+ "title": "\u6700\u8FD1 30 \u5929\u6210\u5458\u53D1\u8A00\u8D8B\u52BF",
14100
+ "encoding": { "x": "day", "y": "msg_count", "series": "member_name" },
14101
+ "unit": "\u6761"
14102
+ }
14103
+
14104
+ \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`;
14105
+ var EN_PROMPT = `You are the ChatLab Chart Assistant. In this turn, the user wants one or more charts embedded naturally in the answer.
14106
+
14107
+ You must generate charts through the render_chart tool. Do not output HTML, JavaScript, SVG, Canvas, ECharts options, or rendering code.
14108
+
14109
+ Hard rules:
14110
+ 1. Always call get_schema before writing the first SQL query. Do not guess table names, field names, or timestamp fields.
14111
+ 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.
14112
+ 3. ChatLab message.ts is a Unix timestamp in seconds. Use date(ts, 'unixepoch', 'localtime'); never write ts/1000.
14113
+ 4. For "latest N members" or "recent N people", first define and select those N members from the data, then count only those members.
14114
+ 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.
14115
+ 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.
14116
+ 7. Explain only trends, peaks, lows, differences, and synchrony directly supported by chart data. Do not make exaggerated personality or relationship claims.
14117
+
14118
+ Workflow:
14119
+ 1. Call get_schema.
14120
+ 2. Derive the target members, time range, dimensions, metrics, and chart type from schema and the user request.
14121
+ 3. When needed, use read-only SQL inside render_chart to select members, determine time range, aggregate data, and fill zero rows.
14122
+ 4. Call render_chart with read-only SQL and ChartSpec v1.
14123
+ 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.
14124
+
14125
+ 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.
14126
+
14127
+ ChartSpec v1 supports:
14128
+ - bar: encoding.x + encoding.y
14129
+ - line: encoding.x + encoding.y, optional encoding.series for the meaning of each line
14130
+ - pie: encoding.label + encoding.value
14131
+ - heatmap: encoding.x + encoding.y + encoding.value
14132
+
14133
+ ChartSpec example:
14134
+ {
14135
+ "version": 1,
14136
+ "type": "line",
14137
+ "title": "Member message trend in the last 30 days",
14138
+ "encoding": { "x": "day", "y": "msg_count", "series": "member_name" },
14139
+ "unit": "messages"
14140
+ }
14141
+
14142
+ When the user explicitly asks for charts, you must try to generate them. If the user does not explicitly ask, add at most one chart only when ranking, trend, distribution, ratio, or two-dimensional density is clearly easier to understand visually.`;
14143
+
14144
+ // ../../packages/node-runtime/src/ai/chart-schema-gate.ts
14145
+ var CHART_SCHEMA_REQUIRED_MESSAGE = "Error: Call get_schema before render_chart. Do not guess table names, fields, or timestamp units.";
14146
+ function createChartSchemaGateState() {
14147
+ return { schemaSeen: false };
14148
+ }
14149
+ function wrapWithChartSchemaGate(tool, state) {
14150
+ const originalExecute = tool.execute;
14151
+ return {
14152
+ ...tool,
14153
+ execute: async (toolCallId, params, signal, onUpdate) => {
14154
+ if (tool.name === "render_chart" && !state.schemaSeen) {
14155
+ return {
14156
+ content: [{ type: "text", text: CHART_SCHEMA_REQUIRED_MESSAGE }],
14157
+ details: null
14158
+ };
14159
+ }
14160
+ const result = await originalExecute(toolCallId, params, signal, onUpdate);
14161
+ if (tool.name === "get_schema") {
14162
+ state.schemaSeen = true;
14163
+ }
14164
+ return result;
14165
+ }
14166
+ };
14167
+ }
14168
+
13733
14169
  // ../../packages/node-runtime/src/ai/compression/compressor.ts
13734
14170
  var DEFAULT_CONTEXT_WINDOW = 128e3;
13735
14171
  var INITIAL_COMPRESSION_PROMPT = `You are a context compression assistant. Compress the conversation below into a structured summary.
@@ -14132,6 +14568,7 @@ function compileRules(rules, logger) {
14132
14568
  }
14133
14569
 
14134
14570
  // ../../packages/node-runtime/src/ai/preprocessor/builtin-rules.ts
14571
+ var DESENSITIZE_RULES_SCHEMA_VERSION = 2;
14135
14572
  var BUILTIN_DESENSITIZE_RULES = [
14136
14573
  // ==================== 中国 (zh-CN) ====================
14137
14574
  {
@@ -14141,7 +14578,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14141
14578
  replacement: "[\u624B\u673A\u53F7]",
14142
14579
  enabled: true,
14143
14580
  builtin: true,
14144
- locales: ["zh-CN"]
14581
+ locales: ["zh-CN"],
14582
+ group: "region_cn"
14145
14583
  },
14146
14584
  {
14147
14585
  id: "cn_id_card",
@@ -14150,25 +14588,28 @@ var BUILTIN_DESENSITIZE_RULES = [
14150
14588
  replacement: "[\u8EAB\u4EFD\u8BC1]",
14151
14589
  enabled: true,
14152
14590
  builtin: true,
14153
- locales: ["zh-CN"]
14591
+ locales: ["zh-CN"],
14592
+ group: "region_cn"
14154
14593
  },
14155
14594
  {
14156
14595
  id: "cn_bank_card",
14157
14596
  label: "desensitize.rules.cn_bank_card",
14158
14597
  pattern: "(?<!\\d)\\d{16,19}(?!\\d)",
14159
14598
  replacement: "[\u94F6\u884C\u5361]",
14160
- enabled: false,
14599
+ enabled: true,
14161
14600
  builtin: true,
14162
- locales: ["zh-CN"]
14601
+ locales: ["zh-CN"],
14602
+ group: "region_cn"
14163
14603
  },
14164
14604
  {
14165
14605
  id: "cn_landline",
14166
14606
  label: "desensitize.rules.cn_landline",
14167
14607
  pattern: "(?<!\\d)0\\d{2,3}-?\\d{7,8}(?!\\d)",
14168
14608
  replacement: "[\u5EA7\u673A\u53F7]",
14169
- enabled: false,
14609
+ enabled: true,
14170
14610
  builtin: true,
14171
- locales: ["zh-CN"]
14611
+ locales: ["zh-CN"],
14612
+ group: "region_cn"
14172
14613
  },
14173
14614
  // ==================== 美国 (en-US) ====================
14174
14615
  {
@@ -14178,7 +14619,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14178
14619
  replacement: "[SSN]",
14179
14620
  enabled: true,
14180
14621
  builtin: true,
14181
- locales: ["en-US"]
14622
+ locales: ["en-US"],
14623
+ group: "region_us"
14182
14624
  },
14183
14625
  {
14184
14626
  id: "us_phone",
@@ -14187,16 +14629,18 @@ var BUILTIN_DESENSITIZE_RULES = [
14187
14629
  replacement: "[Phone]",
14188
14630
  enabled: true,
14189
14631
  builtin: true,
14190
- locales: ["en-US"]
14632
+ locales: ["en-US"],
14633
+ group: "region_us"
14191
14634
  },
14192
14635
  {
14193
14636
  id: "us_drivers_license",
14194
14637
  label: "desensitize.rules.us_drivers_license",
14195
14638
  pattern: "(?<![A-Z])\\b[A-Z]\\d{7,8}\\b",
14196
14639
  replacement: "[Driver's License]",
14197
- enabled: false,
14640
+ enabled: true,
14198
14641
  builtin: true,
14199
- locales: ["en-US"]
14642
+ locales: ["en-US"],
14643
+ group: "region_us"
14200
14644
  },
14201
14645
  // ==================== 日本 (ja-JP) ====================
14202
14646
  {
@@ -14206,16 +14650,18 @@ var BUILTIN_DESENSITIZE_RULES = [
14206
14650
  replacement: "[\u96FB\u8A71\u756A\u53F7]",
14207
14651
  enabled: true,
14208
14652
  builtin: true,
14209
- locales: ["ja-JP"]
14653
+ locales: ["ja-JP"],
14654
+ group: "region_jp"
14210
14655
  },
14211
14656
  {
14212
14657
  id: "jp_my_number",
14213
14658
  label: "desensitize.rules.jp_my_number",
14214
14659
  pattern: "(?<!\\d)\\d{4}\\s?\\d{4}\\s?\\d{4}(?!\\d)",
14215
14660
  replacement: "[\u30DE\u30A4\u30CA\u30F3\u30D0\u30FC]",
14216
- enabled: false,
14661
+ enabled: true,
14217
14662
  builtin: true,
14218
- locales: ["ja-JP"]
14663
+ locales: ["ja-JP"],
14664
+ group: "region_jp"
14219
14665
  },
14220
14666
  // ==================== 韩国 (ko-KR) ====================
14221
14667
  {
@@ -14225,7 +14671,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14225
14671
  replacement: "[\uC804\uD654\uBC88\uD638]",
14226
14672
  enabled: true,
14227
14673
  builtin: true,
14228
- locales: ["ko-KR"]
14674
+ locales: ["ko-KR"],
14675
+ group: "region_kr"
14229
14676
  },
14230
14677
  {
14231
14678
  id: "kr_rrn",
@@ -14234,7 +14681,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14234
14681
  replacement: "[\uC8FC\uBBFC\uBC88\uD638]",
14235
14682
  enabled: true,
14236
14683
  builtin: true,
14237
- locales: ["ko-KR"]
14684
+ locales: ["ko-KR"],
14685
+ group: "region_kr"
14238
14686
  },
14239
14687
  // ==================== 凭据 / Token (所有语言) ====================
14240
14688
  {
@@ -14244,7 +14692,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14244
14692
  replacement: "[API Key]",
14245
14693
  enabled: true,
14246
14694
  builtin: true,
14247
- locales: []
14695
+ locales: [],
14696
+ group: "credentials"
14248
14697
  },
14249
14698
  {
14250
14699
  id: "bearer_token",
@@ -14253,7 +14702,8 @@ var BUILTIN_DESENSITIZE_RULES = [
14253
14702
  replacement: "Bearer [Token]",
14254
14703
  enabled: true,
14255
14704
  builtin: true,
14256
- locales: []
14705
+ locales: [],
14706
+ group: "credentials"
14257
14707
  },
14258
14708
  // ==================== 通用 (所有语言) ====================
14259
14709
  {
@@ -14263,34 +14713,38 @@ var BUILTIN_DESENSITIZE_RULES = [
14263
14713
  replacement: "[Email]",
14264
14714
  enabled: true,
14265
14715
  builtin: true,
14266
- locales: []
14716
+ locales: [],
14717
+ group: "global_contact"
14267
14718
  },
14268
14719
  {
14269
14720
  id: "credit_card",
14270
14721
  label: "desensitize.rules.credit_card",
14271
14722
  pattern: "(?<!\\d)(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[-\\s.]?\\d{4}[-\\s.]?\\d{4}[-\\s.]?\\d{4}(?!\\d)",
14272
14723
  replacement: "[Credit Card]",
14273
- enabled: false,
14724
+ enabled: true,
14274
14725
  builtin: true,
14275
- locales: []
14726
+ locales: [],
14727
+ group: "global_financial"
14276
14728
  },
14277
14729
  {
14278
14730
  id: "ipv4",
14279
14731
  label: "desensitize.rules.ipv4",
14280
14732
  pattern: "(?<!\\d)(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)(?!\\d)",
14281
14733
  replacement: "[IP]",
14282
- enabled: false,
14734
+ enabled: true,
14283
14735
  builtin: true,
14284
- locales: []
14736
+ locales: [],
14737
+ group: "global_network"
14285
14738
  },
14286
14739
  {
14287
14740
  id: "url",
14288
14741
  label: "desensitize.rules.url",
14289
14742
  pattern: 'https?://[^\\s<>"]+',
14290
14743
  replacement: "[URL]",
14291
- enabled: false,
14744
+ enabled: true,
14292
14745
  builtin: true,
14293
- locales: []
14746
+ locales: [],
14747
+ group: "global_network"
14294
14748
  }
14295
14749
  ];
14296
14750
  function getDefaultRulesForLocale(locale) {
@@ -14298,12 +14752,16 @@ function getDefaultRulesForLocale(locale) {
14298
14752
  (rule) => ({ ...rule })
14299
14753
  );
14300
14754
  }
14301
- function mergeRulesForLocale(existing, locale) {
14302
- const existingIds = new Set(existing.map((r) => r.id));
14303
- const newRules = BUILTIN_DESENSITIZE_RULES.filter(
14304
- (rule) => !existingIds.has(rule.id) && (rule.locales.length === 0 || rule.locales.includes(locale))
14305
- ).map((rule) => ({ ...rule }));
14306
- return [...existing, ...newRules];
14755
+ function applyDesensitizeRuleOverrides(rules, overrides = {}) {
14756
+ return rules.map((rule) => ({
14757
+ ...rule,
14758
+ enabled: overrides[rule.id] ?? rule.enabled
14759
+ }));
14760
+ }
14761
+ function mergeRulesForLocale(existing, locale, overrides = {}) {
14762
+ const customRules = existing.filter((rule) => !rule.builtin);
14763
+ const builtinRules = applyDesensitizeRuleOverrides(getDefaultRulesForLocale(locale), overrides);
14764
+ return [...builtinRules, ...customRules];
14307
14765
  }
14308
14766
 
14309
14767
  // ../../packages/node-runtime/src/ai/preprocessor/format.ts
@@ -14487,6 +14945,11 @@ import {
14487
14945
  streamSimple as defaultStreamSimple,
14488
14946
  clampThinkingLevel
14489
14947
  } from "@earendil-works/pi-ai";
14948
+
14949
+ // ../../packages/node-runtime/src/ai/agent/constants.ts
14950
+ var DEFAULT_MAX_TOOL_ROUNDS = 10;
14951
+
14952
+ // ../../packages/node-runtime/src/ai/agent/core.ts
14490
14953
  function createEmptyPiUsage() {
14491
14954
  return {
14492
14955
  input: 0,
@@ -14529,7 +14992,7 @@ async function runAgentCore(options) {
14529
14992
  tools,
14530
14993
  history,
14531
14994
  userMessage,
14532
- maxToolRounds = 5,
14995
+ maxToolRounds = DEFAULT_MAX_TOOL_ROUNDS,
14533
14996
  abortSignal,
14534
14997
  steerMessage = "Please provide your final answer based on the information gathered.",
14535
14998
  onEvent,
@@ -15167,6 +15630,19 @@ var zh_CN_default = {
15167
15630
  days: "\u7EDF\u8BA1\u6700\u8FD1\u591A\u5C11\u5929\u7684\u6570\u636E",
15168
15631
  top_n: "\u8FD4\u56DE\u524D\u591A\u5C11\u4E2A\u5173\u952E\u8BCD"
15169
15632
  }
15633
+ },
15634
+ get_schema: {
15635
+ desc: "\u67E5\u770B\u804A\u5929\u6570\u636E\u5E93\u8868\u7ED3\u6784\u3002\u7ED8\u56FE\u6216\u81EA\u5B9A\u4E49 SQL \u524D\u5E94\u5148\u7528\u5B83\u786E\u8BA4\u8868\u540D\u548C\u5B57\u6BB5\u540D\u3002",
15636
+ params: {}
15637
+ },
15638
+ render_chart: {
15639
+ 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",
15640
+ params: {
15641
+ sql: "\u53EA\u8BFB SELECT \u6216 WITH SELECT SQL\u3002\u5FC5\u987B\u8FD4\u56DE ChartSpec encoding \u4E2D\u5F15\u7528\u7684\u5B57\u6BB5\u3002",
15642
+ params: "SQL \u547D\u540D\u53C2\u6570\u5BF9\u8C61\uFF1B\u4E0D\u9700\u8981\u53C2\u6570\u65F6\u4F20\u7A7A\u5BF9\u8C61\u3002",
15643
+ chartSpec: "ChartSpec v1\uFF0C\u5305\u542B version\u3001type\u3001title\u3001encoding\u3002",
15644
+ maxRows: "\u56FE\u8868\u67E5\u8BE2\u6700\u5927\u884C\u6570\uFF0C\u9ED8\u8BA4 1000\u3002"
15645
+ }
15170
15646
  }
15171
15647
  },
15172
15648
  agent: {
@@ -15489,6 +15965,19 @@ Returned summaries are brief descriptions of each session, helping quickly locat
15489
15965
  days: "Number of recent days to analyze",
15490
15966
  top_n: "Number of top keywords to return"
15491
15967
  }
15968
+ },
15969
+ get_schema: {
15970
+ desc: "Inspect the chat database schema. Use it before charting or custom SQL when table or field names are uncertain.",
15971
+ params: {}
15972
+ },
15973
+ render_chart: {
15974
+ 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.",
15975
+ params: {
15976
+ sql: "Read-only SELECT or WITH SELECT SQL. It must return fields referenced by ChartSpec encoding.",
15977
+ params: "Named SQL parameters. Use an empty object when no parameters are needed.",
15978
+ chartSpec: "ChartSpec v1 with version, type, title, and encoding.",
15979
+ maxRows: "Maximum chart query rows. Default 1000."
15980
+ }
15492
15981
  }
15493
15982
  },
15494
15983
  // ===== AI Agent system prompts =====
@@ -15807,6 +16296,19 @@ var ja_JP_default = {
15807
16296
  days: "\u76F4\u8FD1\u4F55\u65E5\u9593\u306E\u30C7\u30FC\u30BF\u3092\u96C6\u8A08\u3059\u308B\u304B",
15808
16297
  top_n: "\u4E0A\u4F4D\u4F55\u500B\u306E\u30AD\u30FC\u30EF\u30FC\u30C9\u3092\u8FD4\u5374\u3059\u308B\u304B"
15809
16298
  }
16299
+ },
16300
+ get_schema: {
16301
+ desc: "\u30C1\u30E3\u30C3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u30B9\u30AD\u30FC\u30DE\u3092\u78BA\u8A8D\u3057\u307E\u3059\u3002\u30C1\u30E3\u30FC\u30C8\u4F5C\u6210\u3084\u30AB\u30B9\u30BF\u30E0 SQL \u306E\u524D\u306B\u30C6\u30FC\u30D6\u30EB\u540D\u3068\u30D5\u30A3\u30FC\u30EB\u30C9\u540D\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
16302
+ params: {}
16303
+ },
16304
+ render_chart: {
16305
+ 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",
16306
+ params: {
16307
+ 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",
16308
+ 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",
16309
+ chartSpec: "version\u3001type\u3001title\u3001encoding \u3092\u542B\u3080 ChartSpec v1\u3002",
16310
+ maxRows: "\u30C1\u30E3\u30FC\u30C8\u30AF\u30A8\u30EA\u306E\u6700\u5927\u884C\u6570\u3002\u30C7\u30D5\u30A9\u30EB\u30C8\u306F 1000\u3002"
16311
+ }
15810
16312
  }
15811
16313
  },
15812
16314
  agent: {
@@ -16126,6 +16628,19 @@ var zh_TW_default = {
16126
16628
  days: "\u7D71\u8A08\u6700\u8FD1\u591A\u5C11\u5929\u7684\u8CC7\u6599",
16127
16629
  top_n: "\u56DE\u50B3\u524D\u591A\u5C11\u500B\u95DC\u9375\u8A5E"
16128
16630
  }
16631
+ },
16632
+ get_schema: {
16633
+ desc: "\u67E5\u770B\u804A\u5929\u8CC7\u6599\u5EAB\u8868\u7D50\u69CB\u3002\u7E6A\u5716\u6216\u81EA\u8A02 SQL \u524D\u61C9\u5148\u7528\u5B83\u78BA\u8A8D\u8868\u540D\u548C\u6B04\u4F4D\u540D\u3002",
16634
+ params: {}
16635
+ },
16636
+ render_chart: {
16637
+ 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",
16638
+ params: {
16639
+ sql: "\u552F\u8B80 SELECT \u6216 WITH SELECT SQL\u3002\u5FC5\u9808\u8FD4\u56DE ChartSpec encoding \u4E2D\u5F15\u7528\u7684\u6B04\u4F4D\u3002",
16640
+ params: "SQL \u547D\u540D\u53C3\u6578\u7269\u4EF6\uFF1B\u4E0D\u9700\u8981\u53C3\u6578\u6642\u50B3\u7A7A\u7269\u4EF6\u3002",
16641
+ chartSpec: "ChartSpec v1\uFF0C\u5305\u542B version\u3001type\u3001title\u3001encoding\u3002",
16642
+ maxRows: "\u5716\u8868\u67E5\u8A62\u6700\u5927\u884C\u6578\uFF0C\u9810\u8A2D 1000\u3002"
16643
+ }
16129
16644
  }
16130
16645
  },
16131
16646
  agent: {
@@ -17904,11 +18419,13 @@ var DEFAULTS = {
17904
18419
  pinnedSessionIds: [],
17905
18420
  aiPreprocessConfig: {
17906
18421
  dataCleaning: true,
17907
- mergeConsecutive: false,
18422
+ mergeConsecutive: true,
17908
18423
  mergeWindowSeconds: 180,
17909
18424
  blacklistKeywords: [],
17910
- denoise: false,
17911
- desensitize: false,
18425
+ denoise: true,
18426
+ desensitize: true,
18427
+ desensitizeRulesSchemaVersion: DESENSITIZE_RULES_SCHEMA_VERSION,
18428
+ desensitizeBuiltinRuleOverrides: {},
17912
18429
  desensitizeRules: [],
17913
18430
  anonymizeNames: false
17914
18431
  },
@@ -17947,7 +18464,11 @@ var PreferencesManager = class {
17947
18464
  if (fs35.existsSync(this.filePath)) {
17948
18465
  const raw = fs35.readFileSync(this.filePath, "utf-8");
17949
18466
  const parsed = JSON.parse(raw);
17950
- this.cache = this.mergeDefaults(parsed);
18467
+ const migrated = this.migrateLegacyDesensitizeRules(parsed, raw);
18468
+ this.cache = this.mergeDefaults(migrated.preferences);
18469
+ if (migrated.changed) {
18470
+ this.writePreferences(this.cache);
18471
+ }
17951
18472
  return this.cache;
17952
18473
  }
17953
18474
  } catch (err) {
@@ -17963,12 +18484,12 @@ var PreferencesManager = class {
17963
18484
  current,
17964
18485
  partial
17965
18486
  );
17966
- this.cache = merged;
17967
- const dir = path32.dirname(this.filePath);
17968
- if (!fs35.existsSync(dir)) {
17969
- fs35.mkdirSync(dir, { recursive: true });
18487
+ if (Object.prototype.hasOwnProperty.call(partial.aiPreprocessConfig ?? {}, "desensitizeBuiltinRuleOverrides")) {
18488
+ const mergedAiPreprocessConfig = merged.aiPreprocessConfig;
18489
+ mergedAiPreprocessConfig.desensitizeBuiltinRuleOverrides = partial.aiPreprocessConfig?.desensitizeBuiltinRuleOverrides;
17970
18490
  }
17971
- fs35.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2), "utf-8");
18491
+ this.cache = this.mergeDefaults(merged);
18492
+ this.writePreferences(this.cache);
17972
18493
  return { success: true };
17973
18494
  } catch (err) {
17974
18495
  const msg = err instanceof Error ? err.message : String(err);
@@ -17985,7 +18506,7 @@ var PreferencesManager = class {
17985
18506
  mergeDefaults(partial) {
17986
18507
  return {
17987
18508
  pinnedSessionIds: partial.pinnedSessionIds ?? DEFAULTS.pinnedSessionIds,
17988
- aiPreprocessConfig: partial.aiPreprocessConfig ? { ...DEFAULTS.aiPreprocessConfig, ...partial.aiPreprocessConfig } : { ...DEFAULTS.aiPreprocessConfig },
18509
+ aiPreprocessConfig: partial.aiPreprocessConfig ? this.normalizeAiPreprocessConfig(partial.aiPreprocessConfig) : { ...DEFAULTS.aiPreprocessConfig },
17989
18510
  aiGlobalSettings: partial.aiGlobalSettings ? {
17990
18511
  ...DEFAULTS.aiGlobalSettings,
17991
18512
  ...partial.aiGlobalSettings,
@@ -18000,6 +18521,75 @@ var PreferencesManager = class {
18000
18521
  filterHistory: partial.filterHistory ?? DEFAULTS.filterHistory
18001
18522
  };
18002
18523
  }
18524
+ normalizeAiPreprocessConfig(partial) {
18525
+ const defaults = DEFAULTS.aiPreprocessConfig;
18526
+ const rules = Array.isArray(partial?.desensitizeRules) ? partial.desensitizeRules : [];
18527
+ const customRules = rules.filter((rule) => !rule.builtin);
18528
+ return {
18529
+ dataCleaning: partial?.dataCleaning ?? defaults.dataCleaning,
18530
+ mergeConsecutive: partial?.mergeConsecutive ?? defaults.mergeConsecutive,
18531
+ mergeWindowSeconds: partial?.mergeWindowSeconds ?? defaults.mergeWindowSeconds,
18532
+ blacklistKeywords: partial?.blacklistKeywords ?? defaults.blacklistKeywords,
18533
+ denoise: partial?.denoise ?? defaults.denoise,
18534
+ desensitize: partial?.desensitize ?? defaults.desensitize,
18535
+ desensitizeRulesSchemaVersion: DESENSITIZE_RULES_SCHEMA_VERSION,
18536
+ desensitizeBuiltinRuleOverrides: this.normalizeBooleanMap(partial?.desensitizeBuiltinRuleOverrides),
18537
+ desensitizeRules: customRules,
18538
+ anonymizeNames: partial?.anonymizeNames ?? defaults.anonymizeNames
18539
+ };
18540
+ }
18541
+ normalizeBooleanMap(value) {
18542
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
18543
+ const result = {};
18544
+ for (const [key, flag] of Object.entries(value)) {
18545
+ if (typeof flag === "boolean") result[key] = flag;
18546
+ }
18547
+ return result;
18548
+ }
18549
+ migrateLegacyDesensitizeRules(partial, rawContent) {
18550
+ const rules = partial.aiPreprocessConfig?.desensitizeRules;
18551
+ if (!Array.isArray(rules) || !rules.some((rule) => rule.builtin)) {
18552
+ return { preferences: partial, changed: false };
18553
+ }
18554
+ this.backupLegacyPreferences(rawContent);
18555
+ const legacyBuiltinRuleOverrides = rules.filter((rule) => rule.builtin && typeof rule.enabled === "boolean").reduce((overrides, rule) => {
18556
+ overrides[rule.id] = rule.enabled;
18557
+ return overrides;
18558
+ }, {});
18559
+ const existingOverrides = this.normalizeBooleanMap(partial.aiPreprocessConfig?.desensitizeBuiltinRuleOverrides);
18560
+ return {
18561
+ preferences: {
18562
+ ...partial,
18563
+ aiPreprocessConfig: {
18564
+ ...partial.aiPreprocessConfig,
18565
+ desensitizeRulesSchemaVersion: DESENSITIZE_RULES_SCHEMA_VERSION,
18566
+ desensitizeBuiltinRuleOverrides: {
18567
+ ...legacyBuiltinRuleOverrides,
18568
+ ...existingOverrides
18569
+ },
18570
+ desensitizeRules: rules.filter((rule) => !rule.builtin)
18571
+ }
18572
+ },
18573
+ changed: true
18574
+ };
18575
+ }
18576
+ backupLegacyPreferences(rawContent) {
18577
+ const backupDir = path32.join(path32.dirname(this.filePath), "backups");
18578
+ if (!fs35.existsSync(backupDir)) {
18579
+ fs35.mkdirSync(backupDir, { recursive: true });
18580
+ }
18581
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18582
+ const backupPath = path32.join(backupDir, `preferences-pre-desensitize-groups-${timestamp}.json`);
18583
+ fs35.writeFileSync(backupPath, rawContent, "utf-8");
18584
+ console.info(`[Preferences] Backed up legacy desensitize rules to ${backupPath}`);
18585
+ }
18586
+ writePreferences(preferences) {
18587
+ const dir = path32.dirname(this.filePath);
18588
+ if (!fs35.existsSync(dir)) {
18589
+ fs35.mkdirSync(dir, { recursive: true });
18590
+ }
18591
+ fs35.writeFileSync(this.filePath, JSON.stringify(preferences, null, 2), "utf-8");
18592
+ }
18003
18593
  /**
18004
18594
  * Deep merge: arrays are replaced (not concatenated), objects are merged.
18005
18595
  */
@@ -18794,6 +19384,8 @@ export {
18794
19384
  getClusterGraph,
18795
19385
  getRelationshipStats,
18796
19386
  getLanguagePreferenceAnalysis,
19387
+ isNewerStableVersion,
19388
+ buildChartPayload,
18797
19389
  POS_TAG_DEFINITIONS,
18798
19390
  BUILTIN_TOOL_CATALOG,
18799
19391
  BUILTIN_PROVIDERS,
@@ -18832,14 +19424,23 @@ export {
18832
19424
  AIConversationManager,
18833
19425
  initTokenizer,
18834
19426
  countMessagesTokens,
18835
- SkillManager,
18836
19427
  SkillManagerCore,
19428
+ SkillManager,
18837
19429
  createActivateSkillTool,
19430
+ CHART_CAPABILITY_CORE_TOOLS,
19431
+ getAllowedBuiltinToolsForChartAutoSkill,
19432
+ resolveChartRuntimeForRequest,
19433
+ getChartCapabilitySkill,
19434
+ buildSkillMenuWithBuiltinChart,
19435
+ getSkillConfigWithBuiltinChart,
19436
+ createChartSchemaGateState,
19437
+ wrapWithChartSchemaGate,
18838
19438
  checkAndCompress,
18839
19439
  createCompressionLlmAdapter,
18840
19440
  getDefaultRulesForLocale,
18841
19441
  mergeRulesForLocale,
18842
19442
  applyPreprocessingPipeline,
19443
+ DEFAULT_MAX_TOOL_ROUNDS,
18843
19444
  runAgentCore,
18844
19445
  AgentEventHandler,
18845
19446
  buildSystemPrompt,
@@ -18874,4 +19475,4 @@ export {
18874
19475
  createDatabaseManagerAdapter,
18875
19476
  MergeSessionCache
18876
19477
  };
18877
- //# sourceMappingURL=chunk-XJJXSOLO.mjs.map
19478
+ //# sourceMappingURL=chunk-4OMV4VNH.mjs.map