chatlab-cli 0.30.2 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chat-command-VBDMWOKL.mjs → chat-command-MOETAGU5.mjs} +9 -8
- package/dist/{chat-command-VBDMWOKL.mjs.map → chat-command-MOETAGU5.mjs.map} +1 -1
- package/dist/{chunk-M2PXDX4G.mjs → chunk-2IXPWPLR.mjs} +168 -77
- package/dist/chunk-2IXPWPLR.mjs.map +1 -0
- package/dist/{chunk-TCZHG3G7.mjs → chunk-3VMKS7PU.mjs} +44 -5
- package/dist/chunk-3VMKS7PU.mjs.map +1 -0
- package/dist/{chunk-TEX44HNL.mjs → chunk-7HBVGVOB.mjs} +5 -15
- package/dist/chunk-7HBVGVOB.mjs.map +1 -0
- package/dist/{chunk-LVEJHB3X.mjs → chunk-DQ45LDKZ.mjs} +512 -98
- package/dist/chunk-DQ45LDKZ.mjs.map +1 -0
- package/dist/{chunk-ULNRUS55.mjs → chunk-E6W2RURS.mjs} +3 -3
- package/dist/{chunk-36JGKW6K.mjs → chunk-KCPGEILH.mjs} +1503 -530
- package/dist/chunk-KCPGEILH.mjs.map +1 -0
- package/dist/{chunk-WV5GSRQM.mjs → chunk-LJXSUN25.mjs} +5 -15
- package/dist/chunk-LJXSUN25.mjs.map +1 -0
- package/dist/chunk-N5AGDT7I.mjs +268 -0
- package/dist/chunk-N5AGDT7I.mjs.map +1 -0
- package/dist/{chunk-D3DPCDII.mjs → chunk-SZEBCNBB.mjs} +193 -79
- package/dist/chunk-SZEBCNBB.mjs.map +1 -0
- package/dist/{chunk-5EE2EFPE.mjs → chunk-VOVLPSG6.mjs} +29 -52
- package/dist/chunk-VOVLPSG6.mjs.map +1 -0
- package/dist/chunk-YAQLYL52.mjs +14 -0
- package/dist/chunk-YAQLYL52.mjs.map +1 -0
- package/dist/{chunk-326OCJOT.mjs → chunk-ZVWUR7ET.mjs} +2 -2
- package/dist/cli.mjs +10 -7
- package/dist/contacts-worker.mjs +2 -2
- package/dist/global-insight-worker.mjs +179 -0
- package/dist/global-insight-worker.mjs.map +1 -0
- package/dist/{http-L4AMELPZ.mjs → http-M6NHWJZD.mjs} +12 -11
- package/dist/{import-WWCSXGVS.mjs → import-SEYHFXS5.mjs} +12 -7
- package/dist/index.mjs +13 -12
- package/dist/{mcp-A4M3LT5O.mjs → mcp-MIPFYRLV.mjs} +12 -11
- package/dist/{mcp-A4M3LT5O.mjs.map → mcp-MIPFYRLV.mjs.map} +1 -1
- package/dist/people-relationships-worker.mjs +2 -2
- package/dist/semantic-index-worker.mjs +1 -1
- package/dist/{src-MPNBTFOE.mjs → src-7OD6RD5V.mjs} +38 -6
- package/dist/{stream-import-HIE7PNL4.mjs → stream-import-AWEHGQJY.mjs} +12 -7
- package/dist/{worker-runtime-BJXO24XX.mjs → worker-runtime-TGYMASIE.mjs} +6 -5
- package/dist/{worker-runtime-BJXO24XX.mjs.map → worker-runtime-TGYMASIE.mjs.map} +1 -1
- package/dist-web/assets/ActionToolsPanel-fAio70bI.css +1 -0
- package/dist-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-CkDGVRXe.js +110 -0
- package/dist-web/assets/{BufferResource-q3rzaWWd.js → BufferResource-CHI62pIA.js} +1 -1
- package/dist-web/assets/{CanvasRenderer-BRk1y8tp.js → CanvasRenderer-OZRoiF0j.js} +1 -1
- package/dist-web/assets/{EChartWordcloud-Clg60FMo.js → EChartWordcloud-DU3ZYdgz.js} +1 -1
- package/dist-web/assets/{Filter-BJwode73.js → Filter-DNb5VErW.js} +1 -1
- package/dist-web/assets/{LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js → LoadingState.vue_vue_type_script_setup_true_lang-DACMroOe.js} +1 -1
- package/dist-web/assets/MessageExportModal-CqP-w7HP.js +1 -0
- package/dist-web/assets/PageHeader-Ck8ULyZH.js +1 -0
- package/dist-web/assets/PageHeader-XIXMTomn.css +1 -0
- package/dist-web/assets/{RelationshipGalaxyCanvas-CORMD7vL.js → RelationshipGalaxyCanvas-ioRNObb5.js} +3 -3
- package/dist-web/assets/{RelationshipGalaxyThreeCanvas-B_w_P4ob.js → RelationshipGalaxyThreeCanvas-L1Y43p1A.js} +1 -1
- package/dist-web/assets/{RenderTargetSystem-CGhV9R9E.js → RenderTargetSystem-fJ7D7Chv.js} +1 -1
- package/dist-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-BoZyssJF.js +1 -0
- package/dist-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-7SEaCxDL.js +1 -0
- package/dist-web/assets/{WebGLRenderer-CAokTE5X.js → WebGLRenderer-BD3ZUtxH.js} +1 -1
- package/dist-web/assets/{WebGPURenderer-DzP4ggSy.js → WebGPURenderer-DZXYsT6D.js} +1 -1
- package/dist-web/assets/annual-summary-time-range-DVLXK1r4.js +1 -0
- package/dist-web/assets/base-DTElkJcC.js +1 -0
- package/dist-web/assets/{browserAll-CAZCK6dd.js → browserAll-ChT6N6a9.js} +1 -1
- package/dist-web/assets/dateFormat-D9WibwOu.js +1 -0
- package/dist-web/assets/{electron-CkPDbAaS.js → electron-CIzyGxH_.js} +1 -1
- package/dist-web/assets/electron-hcWiB7Gg.js +1 -0
- package/dist-web/assets/fetch-Avtnh8cJ.js +3 -0
- package/dist-web/assets/{fetch-DQ9UNNAy.js → fetch-BFnrBahy.js} +1 -1
- package/dist-web/assets/fetch-C9rx2isC.js +31 -0
- package/dist-web/assets/fetch-Ccq_0ecb.js +1 -0
- package/dist-web/assets/{fetch-BIEXkvDq.js → fetch-CuiQUcO_.js} +1 -1
- package/dist-web/assets/{fetch-LX9jrJCC.js → fetch-CyKILLnB.js} +1 -1
- package/dist-web/assets/fetch-Dd6t3p1r.js +6 -0
- package/dist-web/assets/{fetch--UbRoBT7.js → fetch-DnGx-1_t.js} +1 -1
- package/dist-web/assets/{fetch-wqMSiaK3.js → fetch-qC7om4QG.js} +1 -1
- package/dist-web/assets/fetch-wSSfDBdV.js +1 -0
- package/dist-web/assets/index-BH9jI5kV.js +173 -0
- package/dist-web/assets/index-BWtNqcYK.js +22 -0
- package/dist-web/assets/index-BfO_LpDv.css +1 -0
- package/dist-web/assets/index-Bkq7-GBp.js +1 -0
- package/dist-web/assets/index-Bqr-LkS1.css +1 -0
- package/dist-web/assets/index-ByUaiVQK.css +1 -0
- package/dist-web/assets/index-C6qtsaP7.js +1 -0
- package/dist-web/assets/index-C_0GHEcb.css +1 -0
- package/dist-web/assets/index-ChFGuu70.js +1 -0
- package/dist-web/assets/{index-S-YQRH1E.js → index-CiS20iAR.js} +1 -1
- package/dist-web/assets/index-CqD6u57u.js +1 -0
- package/dist-web/assets/index-CzDG8LVr.js +2 -0
- package/dist-web/assets/index-D0Euk-vQ.js +1 -0
- package/dist-web/assets/index-D26BWhHz.js +1 -0
- package/dist-web/assets/index-DXHYzIIj.js +1 -0
- package/dist-web/assets/index-Dq8u7gMb.js +2 -0
- package/dist-web/assets/index-ZsZwPLWy.css +1 -0
- package/dist-web/assets/index-u_UH6rDY.js +64 -0
- package/dist-web/assets/index-y7cxH1Et.css +1 -0
- package/dist-web/assets/{people-page-header-Mupn-U_b.js → people-page-header-BdKM1qhX.js} +1 -1
- package/dist-web/assets/types-CZnACqaB.js +1 -0
- package/dist-web/assets/{vendor-echarts-hJVnODyU.js → vendor-echarts-B-i_56Av.js} +1 -1
- package/dist-web/assets/{vendor-echarts-wordcloud-k5ih2Jl4.js → vendor-echarts-wordcloud-Ca8n6qS6.js} +1 -1
- package/dist-web/assets/{vendor-nuxt-ui-DaLxYaC4.js → vendor-nuxt-ui-CS1uxE9C.js} +2 -2
- package/dist-web/assets/{vendor-reka-ui-CzM4kDQ7.js → vendor-reka-ui-Cta6JPvN.js} +1 -1
- package/dist-web/assets/{web-DbKs6-1b.js → web-BDBdpdK7.js} +1 -1
- package/dist-web/assets/{webworkerAll-Bhv-hBqR.js → webworkerAll-C3dAOQj4.js} +1 -1
- package/dist-web/index.html +4 -4
- package/package.json +1 -1
- package/dist/chunk-36JGKW6K.mjs.map +0 -1
- package/dist/chunk-5EE2EFPE.mjs.map +0 -1
- package/dist/chunk-D3DPCDII.mjs.map +0 -1
- package/dist/chunk-KBSAVUFV.mjs +0 -161
- package/dist/chunk-KBSAVUFV.mjs.map +0 -1
- package/dist/chunk-LVEJHB3X.mjs.map +0 -1
- package/dist/chunk-M2PXDX4G.mjs.map +0 -1
- package/dist/chunk-TCZHG3G7.mjs.map +0 -1
- package/dist/chunk-TEX44HNL.mjs.map +0 -1
- package/dist/chunk-WV5GSRQM.mjs.map +0 -1
- package/dist-web/assets/DebugToolsPanel-D_lFIW6j.css +0 -1
- package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-F4n0zNX2.js +0 -110
- package/dist-web/assets/MessageExportModal-CGcdMOyw.js +0 -1
- package/dist-web/assets/PageHeader-Bbn87QZS.css +0 -1
- package/dist-web/assets/PageHeader-CZaqOdBQ.js +0 -1
- package/dist-web/assets/electron-D9vE5t3H.js +0 -1
- package/dist-web/assets/fetch-1pB-HbYw.js +0 -3
- package/dist-web/assets/fetch-Apd5OLJw.js +0 -5
- package/dist-web/assets/fetch-B8rik0TM.js +0 -17
- package/dist-web/assets/fetch-CF_3ZqRa.js +0 -1
- package/dist-web/assets/fetch-FSNxSnAp.js +0 -1
- package/dist-web/assets/index---rXG1hZ.css +0 -1
- package/dist-web/assets/index-B01uUoHE.js +0 -1
- package/dist-web/assets/index-B4l3rHie.css +0 -1
- package/dist-web/assets/index-BSIyyt0z.js +0 -1
- package/dist-web/assets/index-Bh8z441f.js +0 -2
- package/dist-web/assets/index-C0rya8v4.js +0 -1
- package/dist-web/assets/index-C1p9CbmN.css +0 -1
- package/dist-web/assets/index-COpza1A4.css +0 -1
- package/dist-web/assets/index-CZbZv2fh.js +0 -64
- package/dist-web/assets/index-CqUuHQBQ.js +0 -22
- package/dist-web/assets/index-CzeTP5MJ.js +0 -173
- package/dist-web/assets/index-DgNxTM_8.js +0 -2
- package/dist-web/assets/index-DosG7LmJ.css +0 -1
- /package/dist/{chunk-ULNRUS55.mjs.map → chunk-E6W2RURS.mjs.map} +0 -0
- /package/dist/{chunk-326OCJOT.mjs.map → chunk-ZVWUR7ET.mjs.map} +0 -0
- /package/dist/{http-L4AMELPZ.mjs.map → http-M6NHWJZD.mjs.map} +0 -0
- /package/dist/{import-WWCSXGVS.mjs.map → import-SEYHFXS5.mjs.map} +0 -0
- /package/dist/{src-MPNBTFOE.mjs.map → src-7OD6RD5V.mjs.map} +0 -0
- /package/dist/{stream-import-HIE7PNL4.mjs.map → stream-import-AWEHGQJY.mjs.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createEmptyContactsDiagnostics,
|
|
10
10
|
normalizeContactsTimeRangePreset,
|
|
11
11
|
resolveContactsTimeRange
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-7HBVGVOB.mjs";
|
|
13
13
|
import {
|
|
14
14
|
PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
|
|
15
15
|
buildPeopleRelationshipsNeighborhoodGraph,
|
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
computePeopleRelationshipsSnapshot,
|
|
19
19
|
normalizePeopleRelationshipsTimeRangePreset,
|
|
20
20
|
resolvePeopleRelationshipsTimeRange
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-LJXSUN25.mjs";
|
|
22
|
+
import {
|
|
23
|
+
ANNUAL_SUMMARY_ALGORITHM_VERSION
|
|
24
|
+
} from "./chunk-YAQLYL52.mjs";
|
|
22
25
|
import {
|
|
23
26
|
BUILTIN_MODELS,
|
|
24
27
|
BUILTIN_PROVIDERS,
|
|
@@ -34,18 +37,23 @@ import {
|
|
|
34
37
|
batchSegmentWithFrequency,
|
|
35
38
|
buildExcludeKeywordsConditions,
|
|
36
39
|
buildSessionInfo,
|
|
40
|
+
cleanupAnnualSummarySnapshotTempFiles,
|
|
37
41
|
clearJiebaInstance,
|
|
38
42
|
clearSessionIndex,
|
|
39
43
|
collectPosTagStats,
|
|
44
|
+
deleteAnnualSummarySnapshots,
|
|
40
45
|
deleteMember,
|
|
41
46
|
generateIncrementalSessionIndex,
|
|
42
47
|
generateMessageKey,
|
|
43
48
|
generateSessionIndex,
|
|
49
|
+
getAnnualSummarySnapshotPath,
|
|
44
50
|
getBuiltinModelsByProvider,
|
|
45
51
|
getChatSessionList,
|
|
46
52
|
getContactsDir,
|
|
47
53
|
getContactsFactsCacheDir,
|
|
48
54
|
getDbFileVersion,
|
|
55
|
+
getGlobalInsightDir,
|
|
56
|
+
getGlobalInsightFactsCacheDir,
|
|
49
57
|
getJieba,
|
|
50
58
|
getMemberNameHistory,
|
|
51
59
|
getMembersPaginated,
|
|
@@ -67,20 +75,25 @@ import {
|
|
|
67
75
|
mergeConfirmedNames,
|
|
68
76
|
mergeMembers,
|
|
69
77
|
needsStreamingThinkParsing,
|
|
78
|
+
normalizeAnnualSummaryRange,
|
|
70
79
|
normalizeBuiltinToolNames,
|
|
71
80
|
openBetterSqliteDatabase,
|
|
81
|
+
readAnnualSummarySnapshot,
|
|
72
82
|
renameSession,
|
|
73
83
|
resolveApiKey,
|
|
74
84
|
saveSegmentSummary,
|
|
75
85
|
segment,
|
|
76
86
|
snapshotPathProvider,
|
|
87
|
+
toAnnualSummaryLocalDate,
|
|
88
|
+
toAnnualSummaryRangeKey,
|
|
77
89
|
tokenizeForFts,
|
|
78
90
|
tokenizeQueryForFts,
|
|
79
91
|
truncateToolResultText,
|
|
80
92
|
updateMemberAliases,
|
|
81
93
|
updateSessionOwnerId,
|
|
94
|
+
writeAnnualSummarySnapshot,
|
|
82
95
|
writeAuthProfile
|
|
83
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-DQ45LDKZ.mjs";
|
|
84
97
|
import {
|
|
85
98
|
__commonJS,
|
|
86
99
|
__export,
|
|
@@ -696,8 +709,8 @@ var require_FilterBase = __commonJS({
|
|
|
696
709
|
var FilterBase = class _FilterBase extends Transform {
|
|
697
710
|
static stringFilter(string, separator) {
|
|
698
711
|
return (stack) => {
|
|
699
|
-
const
|
|
700
|
-
return
|
|
712
|
+
const path31 = stack.join(separator);
|
|
713
|
+
return path31.length === string.length && path31 === string || path31.length > string.length && path31.substr(0, string.length) === string && path31.substr(string.length, separator.length) === separator;
|
|
701
714
|
};
|
|
702
715
|
}
|
|
703
716
|
static regExpFilter(regExp, separator) {
|
|
@@ -1151,12 +1164,12 @@ var require_Assembler = __commonJS({
|
|
|
1151
1164
|
return (this.stack.length >> 1) + (this.done ? 0 : 1);
|
|
1152
1165
|
}
|
|
1153
1166
|
get path() {
|
|
1154
|
-
const
|
|
1167
|
+
const path31 = [];
|
|
1155
1168
|
for (let i = 0; i < this.stack.length; i += 2) {
|
|
1156
1169
|
const key = this.stack[i + 1];
|
|
1157
|
-
|
|
1170
|
+
path31.push(key === null ? this.stack[i].length : key);
|
|
1158
1171
|
}
|
|
1159
|
-
return
|
|
1172
|
+
return path31;
|
|
1160
1173
|
}
|
|
1161
1174
|
dropToLevel(level) {
|
|
1162
1175
|
if (level < this.depth) {
|
|
@@ -1438,7 +1451,7 @@ var require_pend = __commonJS({
|
|
|
1438
1451
|
var require_fd_slicer = __commonJS({
|
|
1439
1452
|
"../../packages/node-runtime/node_modules/yauzl/fd-slicer.js"(exports) {
|
|
1440
1453
|
"use strict";
|
|
1441
|
-
var
|
|
1454
|
+
var fs35 = __require("fs");
|
|
1442
1455
|
var util = __require("util");
|
|
1443
1456
|
var stream = __require("stream");
|
|
1444
1457
|
var Readable = stream.Readable;
|
|
@@ -1458,7 +1471,7 @@ var require_fd_slicer = __commonJS({
|
|
|
1458
1471
|
FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
|
|
1459
1472
|
var self = this;
|
|
1460
1473
|
self.pend.go(function(cb) {
|
|
1461
|
-
|
|
1474
|
+
fs35.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
|
|
1462
1475
|
cb();
|
|
1463
1476
|
callback(err, bytesRead, buffer2);
|
|
1464
1477
|
});
|
|
@@ -1475,7 +1488,7 @@ var require_fd_slicer = __commonJS({
|
|
|
1475
1488
|
self.refCount -= 1;
|
|
1476
1489
|
if (self.refCount < 0) throw new Error("invalid unref");
|
|
1477
1490
|
if (self.refCount > 0) return;
|
|
1478
|
-
|
|
1491
|
+
fs35.close(self.fd, onCloseDone);
|
|
1479
1492
|
function onCloseDone(err) {
|
|
1480
1493
|
if (err) {
|
|
1481
1494
|
self.emit("error", err);
|
|
@@ -1507,7 +1520,7 @@ var require_fd_slicer = __commonJS({
|
|
|
1507
1520
|
}
|
|
1508
1521
|
self.context.pend.go(function(cb) {
|
|
1509
1522
|
var buffer = Buffer.allocUnsafe(toRead);
|
|
1510
|
-
|
|
1523
|
+
fs35.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
|
|
1511
1524
|
if (err) {
|
|
1512
1525
|
self.destroy(err);
|
|
1513
1526
|
} else if (bytesRead === 0) {
|
|
@@ -1869,7 +1882,7 @@ var require_crc32 = __commonJS({
|
|
|
1869
1882
|
var require_yauzl = __commonJS({
|
|
1870
1883
|
"../../packages/node-runtime/node_modules/yauzl/index.js"(exports) {
|
|
1871
1884
|
"use strict";
|
|
1872
|
-
var
|
|
1885
|
+
var fs35 = __require("fs");
|
|
1873
1886
|
var zlib = __require("zlib");
|
|
1874
1887
|
var fd_slicer = require_fd_slicer();
|
|
1875
1888
|
var util = __require("util");
|
|
@@ -1894,9 +1907,9 @@ var require_yauzl = __commonJS({
|
|
|
1894
1907
|
exports.Entry = Entry;
|
|
1895
1908
|
exports.LocalFileHeader = LocalFileHeader;
|
|
1896
1909
|
exports.RandomAccessReader = RandomAccessReader;
|
|
1897
|
-
function openPromise2(
|
|
1910
|
+
function openPromise2(path31, options2) {
|
|
1898
1911
|
return new Promise((resolve2, reject) => {
|
|
1899
|
-
open(
|
|
1912
|
+
open(path31, { ...options2, lazyEntries: true }, function(err, zipfile) {
|
|
1900
1913
|
if (err) return reject(err);
|
|
1901
1914
|
resolve2(zipfile);
|
|
1902
1915
|
});
|
|
@@ -1926,7 +1939,7 @@ var require_yauzl = __commonJS({
|
|
|
1926
1939
|
});
|
|
1927
1940
|
});
|
|
1928
1941
|
}
|
|
1929
|
-
function open(
|
|
1942
|
+
function open(path31, options2, callback) {
|
|
1930
1943
|
if (typeof options2 === "function") {
|
|
1931
1944
|
callback = options2;
|
|
1932
1945
|
options2 = null;
|
|
@@ -1938,10 +1951,10 @@ var require_yauzl = __commonJS({
|
|
|
1938
1951
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
1939
1952
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
1940
1953
|
if (callback == null) callback = defaultCallback;
|
|
1941
|
-
|
|
1954
|
+
fs35.open(path31, "r", function(err, fd) {
|
|
1942
1955
|
if (err) return callback(err);
|
|
1943
1956
|
fromFd(fd, options2, function(err2, zipfile) {
|
|
1944
|
-
if (err2)
|
|
1957
|
+
if (err2) fs35.close(fd, defaultCallback);
|
|
1945
1958
|
callback(err2, zipfile);
|
|
1946
1959
|
});
|
|
1947
1960
|
});
|
|
@@ -1958,7 +1971,7 @@ var require_yauzl = __commonJS({
|
|
|
1958
1971
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
1959
1972
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
1960
1973
|
if (callback == null) callback = defaultCallback;
|
|
1961
|
-
|
|
1974
|
+
fs35.fstat(fd, function(err, stats) {
|
|
1962
1975
|
if (err) return callback(err);
|
|
1963
1976
|
var reader = new fd_slicer.FdSlicer(fd);
|
|
1964
1977
|
fromRandomAccessReader(reader, stats.size, options2, callback);
|
|
@@ -2743,7 +2756,7 @@ function writeParseResultToDb(db, meta, members, messages) {
|
|
|
2743
2756
|
meta.ownerId || null
|
|
2744
2757
|
);
|
|
2745
2758
|
const insertMember = db.prepare(
|
|
2746
|
-
`INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, avatar, roles) VALUES (?, ?, ?, ?, ?)`
|
|
2759
|
+
`INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles) VALUES (?, ?, ?, ?, ?, ?)`
|
|
2747
2760
|
);
|
|
2748
2761
|
const getMemberId = db.prepare(`SELECT id FROM member WHERE platform_id = ?`);
|
|
2749
2762
|
const memberIdMap = /* @__PURE__ */ new Map();
|
|
@@ -2752,6 +2765,7 @@ function writeParseResultToDb(db, meta, members, messages) {
|
|
|
2752
2765
|
member.platformId,
|
|
2753
2766
|
member.accountName || null,
|
|
2754
2767
|
member.groupNickname || null,
|
|
2768
|
+
member.aliases ? JSON.stringify(member.aliases) : "[]",
|
|
2755
2769
|
member.avatar || null,
|
|
2756
2770
|
member.roles ? JSON.stringify(member.roles) : "[]"
|
|
2757
2771
|
);
|
|
@@ -3178,6 +3192,7 @@ function toParsedMember(member, fromHead) {
|
|
|
3178
3192
|
platformId: member.platformId,
|
|
3179
3193
|
accountName: member.accountName,
|
|
3180
3194
|
groupNickname: member.groupNickname,
|
|
3195
|
+
aliases: member.aliases,
|
|
3181
3196
|
avatar: member.avatar,
|
|
3182
3197
|
// Role objects pass through with their original key set ({id} or {id, name}).
|
|
3183
3198
|
roles: member.roles?.map((role) => role.name !== void 0 ? { id: role.id, name: role.name } : { id: role.id })
|
|
@@ -3193,7 +3208,9 @@ var chatlabAdapter = {
|
|
|
3193
3208
|
platform: meta.platform,
|
|
3194
3209
|
type: meta.chatType,
|
|
3195
3210
|
groupId: meta.groupId ?? void 0,
|
|
3196
|
-
groupAvatar: meta.groupAvatar ?? void 0
|
|
3211
|
+
groupAvatar: meta.groupAvatar ?? void 0,
|
|
3212
|
+
ownerId: meta.ownerId ?? void 0,
|
|
3213
|
+
sourceSessionId: meta.sourceSessionId ?? void 0
|
|
3197
3214
|
};
|
|
3198
3215
|
},
|
|
3199
3216
|
mapMembers(members, metaJson) {
|
|
@@ -3285,7 +3302,9 @@ async function* parseChatLab(options2) {
|
|
|
3285
3302
|
platform: metaObj.platform || KNOWN_PLATFORMS.UNKNOWN,
|
|
3286
3303
|
type: metaObj.type || "group" /* GROUP */,
|
|
3287
3304
|
groupId: metaObj.groupId,
|
|
3288
|
-
groupAvatar: metaObj.groupAvatar
|
|
3305
|
+
groupAvatar: metaObj.groupAvatar,
|
|
3306
|
+
ownerId: metaObj.ownerId,
|
|
3307
|
+
sourceSessionId: metaObj.sourceSessionId
|
|
3289
3308
|
};
|
|
3290
3309
|
}
|
|
3291
3310
|
}
|
|
@@ -3296,22 +3315,6 @@ async function* parseChatLab(options2) {
|
|
|
3296
3315
|
}
|
|
3297
3316
|
yield { type: "meta", data: meta };
|
|
3298
3317
|
const members = [];
|
|
3299
|
-
try {
|
|
3300
|
-
const membersMatch = headContent.match(/"members"\s*:\s*\[([\s\S]*?)\]/);
|
|
3301
|
-
if (membersMatch) {
|
|
3302
|
-
const membersJson = JSON.parse(`[${membersMatch[1]}]`);
|
|
3303
|
-
for (const m of membersJson) {
|
|
3304
|
-
members.push({
|
|
3305
|
-
platformId: m.platformId,
|
|
3306
|
-
accountName: m.accountName,
|
|
3307
|
-
groupNickname: m.groupNickname,
|
|
3308
|
-
avatar: m.avatar,
|
|
3309
|
-
roles: m.roles
|
|
3310
|
-
});
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
} catch {
|
|
3314
|
-
}
|
|
3315
3318
|
const memberMapFromMessages = /* @__PURE__ */ new Map();
|
|
3316
3319
|
const messageBatch = [];
|
|
3317
3320
|
await new Promise((resolve2, reject) => {
|
|
@@ -3319,9 +3322,20 @@ async function* parseChatLab(options2) {
|
|
|
3319
3322
|
readStream.on("data", (chunk) => {
|
|
3320
3323
|
bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
3321
3324
|
});
|
|
3322
|
-
const pipeline2 = chain([readStream, parser(), pick({ filter: /^messages\.\d+$/ }), streamValues()]);
|
|
3325
|
+
const pipeline2 = chain([readStream, parser(), pick({ filter: /^(?:members|messages)\.\d+$/ }), streamValues()]);
|
|
3323
3326
|
const batchCollector = [];
|
|
3324
3327
|
pipeline2.on("data", ({ value }) => {
|
|
3328
|
+
if (!("sender" in value)) {
|
|
3329
|
+
members.push({
|
|
3330
|
+
platformId: value.platformId,
|
|
3331
|
+
accountName: value.accountName,
|
|
3332
|
+
groupNickname: value.groupNickname,
|
|
3333
|
+
aliases: value.aliases,
|
|
3334
|
+
avatar: value.avatar,
|
|
3335
|
+
roles: value.roles
|
|
3336
|
+
});
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3325
3339
|
const msg = value;
|
|
3326
3340
|
if (members.length === 0) {
|
|
3327
3341
|
memberMapFromMessages.set(msg.sender, {
|
|
@@ -3460,7 +3474,9 @@ async function* parseChatLabJsonl(options2) {
|
|
|
3460
3474
|
platform: parsed.meta.platform || KNOWN_PLATFORMS.UNKNOWN,
|
|
3461
3475
|
type: parsed.meta.type || "group" /* GROUP */,
|
|
3462
3476
|
groupId: parsed.meta.groupId,
|
|
3463
|
-
groupAvatar: parsed.meta.groupAvatar
|
|
3477
|
+
groupAvatar: parsed.meta.groupAvatar,
|
|
3478
|
+
ownerId: parsed.meta.ownerId,
|
|
3479
|
+
sourceSessionId: parsed.meta.sourceSessionId
|
|
3464
3480
|
};
|
|
3465
3481
|
headerParsed = true;
|
|
3466
3482
|
yield { type: "meta", data: meta };
|
|
@@ -3865,6 +3881,9 @@ function extractNameFromFilePath3(filePath) {
|
|
|
3865
3881
|
const basename13 = path5.basename(filePath);
|
|
3866
3882
|
return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
|
|
3867
3883
|
}
|
|
3884
|
+
function isPlaceholderSenderId(id) {
|
|
3885
|
+
return id === "0" || id === "\u672A\u77E5";
|
|
3886
|
+
}
|
|
3868
3887
|
function countSenders(content) {
|
|
3869
3888
|
const sendersStart = content.indexOf('"senders"');
|
|
3870
3889
|
if (sendersStart === -1) return 0;
|
|
@@ -3879,8 +3898,19 @@ function countSenders(content) {
|
|
|
3879
3898
|
}
|
|
3880
3899
|
if (depth !== 0) return 0;
|
|
3881
3900
|
const sendersContent = content.slice(arrayStart + 1, i - 1);
|
|
3882
|
-
const uidMatches = sendersContent.
|
|
3883
|
-
|
|
3901
|
+
const uidMatches = sendersContent.matchAll(/"uid"\s*:\s*"([^"]*)"/g);
|
|
3902
|
+
let count = 0;
|
|
3903
|
+
for (const match of uidMatches) {
|
|
3904
|
+
const uid = match[1];
|
|
3905
|
+
if (uid && !isPlaceholderSenderId(uid)) count++;
|
|
3906
|
+
}
|
|
3907
|
+
return count;
|
|
3908
|
+
}
|
|
3909
|
+
function resolveChatType(chatInfoType, headContent) {
|
|
3910
|
+
if (chatInfoType === "group") return "group" /* GROUP */;
|
|
3911
|
+
const sendersCount = countSenders(headContent);
|
|
3912
|
+
if (chatInfoType === "private") return sendersCount > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
|
|
3913
|
+
return sendersCount > 2 ? "group" /* GROUP */ : sendersCount > 0 ? "private" /* PRIVATE */ : "group" /* GROUP */;
|
|
3884
3914
|
}
|
|
3885
3915
|
function convertMessageType(messageType, content, isRecalled) {
|
|
3886
3916
|
if (isRecalled) return 81 /* RECALL */;
|
|
@@ -3934,8 +3964,7 @@ async function* parseV4(options2) {
|
|
|
3934
3964
|
}
|
|
3935
3965
|
} catch {
|
|
3936
3966
|
}
|
|
3937
|
-
const
|
|
3938
|
-
const chatType = sendersCount > 2 ? "group" /* GROUP */ : sendersCount > 0 ? "private" /* PRIVATE */ : "group" /* GROUP */;
|
|
3967
|
+
const chatType = resolveChatType(chatInfo.type, headContent);
|
|
3939
3968
|
const meta = {
|
|
3940
3969
|
name: chatInfo.name || extractNameFromFilePath3(filePath),
|
|
3941
3970
|
platform: KNOWN_PLATFORMS.QQ,
|
|
@@ -3954,7 +3983,7 @@ async function* parseV4(options2) {
|
|
|
3954
3983
|
const pipeline2 = chain3([readStream, parser4(), pick3({ filter: /^messages\.\d+$/ }), streamValues3()]);
|
|
3955
3984
|
pipeline2.on("data", ({ value }) => {
|
|
3956
3985
|
const platformId = value.sender.uin || value.sender.uid || value.rawMessage?.senderUin || value.rawMessage?.senderUid;
|
|
3957
|
-
if (!platformId) {
|
|
3986
|
+
if (!platformId || isPlaceholderSenderId(platformId)) {
|
|
3958
3987
|
skippedMessages++;
|
|
3959
3988
|
return;
|
|
3960
3989
|
}
|
|
@@ -3973,9 +4002,11 @@ async function* parseV4(options2) {
|
|
|
3973
4002
|
skippedMessages++;
|
|
3974
4003
|
return;
|
|
3975
4004
|
}
|
|
3976
|
-
const
|
|
4005
|
+
const isSystem = value.system ?? value.isSystemMessage;
|
|
4006
|
+
const isRecalled = value.recalled ?? value.isRecalled;
|
|
4007
|
+
const type = isSystem ? 80 /* SYSTEM */ : convertMessageType(value.messageType, value.content, isRecalled);
|
|
3977
4008
|
let textContent = value.content?.text || "";
|
|
3978
|
-
if (
|
|
4009
|
+
if (isRecalled) textContent = "[\u5DF2\u64A4\u56DE] " + textContent;
|
|
3979
4010
|
messageBatch.push({
|
|
3980
4011
|
platformMessageId: value.messageId,
|
|
3981
4012
|
// 消息的平台原始 ID
|
|
@@ -5502,6 +5533,14 @@ function extractNameFromFilePath5(filePath) {
|
|
|
5502
5533
|
if (matchZhTw) return matchZhTw[1].trim();
|
|
5503
5534
|
return basename13.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
|
|
5504
5535
|
}
|
|
5536
|
+
function shouldInferPrivateChatName(filePath, fallbackName) {
|
|
5537
|
+
const basename13 = path8.basename(filePath);
|
|
5538
|
+
return /^_chat\.txt$/i.test(basename13) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
|
|
5539
|
+
}
|
|
5540
|
+
function inferPrivateChatName(memberMap, fallbackName) {
|
|
5541
|
+
const member = Array.from(memberMap.values()).find((item) => item.platformId !== "system");
|
|
5542
|
+
return member?.nickname || fallbackName;
|
|
5543
|
+
}
|
|
5505
5544
|
var feature10 = {
|
|
5506
5545
|
id: "whatsapp-native-txt",
|
|
5507
5546
|
name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
|
|
@@ -5520,9 +5559,9 @@ var feature10 = {
|
|
|
5520
5559
|
// 英文加密提示(WhatsApp 独有)
|
|
5521
5560
|
/你发送给自己的消息已进行端到端加密/,
|
|
5522
5561
|
// 简体中文自己对话提示(WhatsApp 独有)
|
|
5523
|
-
/\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}
|
|
5562
|
+
/\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|上午|下午|午前|午後|오전|오후))?\s+- /i,
|
|
5524
5563
|
// 消息行格式特征(无方括号,含 " - " 分隔符,WhatsApp 独有)
|
|
5525
|
-
/\[\d{1,4}\/\d{1,2}\/\d{
|
|
5564
|
+
/\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
|
|
5526
5565
|
// 消息行格式特征(方括号 + 含日期和时间的时间戳,兼容各地区变体)
|
|
5527
5566
|
],
|
|
5528
5567
|
// 文件名特征:与xxx的 WhatsApp 聊天.txt
|
|
@@ -5532,9 +5571,9 @@ var feature10 = {
|
|
|
5532
5571
|
function cleanLine(line) {
|
|
5533
5572
|
return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
|
|
5534
5573
|
}
|
|
5535
|
-
var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}) - (.+)
|
|
5574
|
+
var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|上午|下午|午前|午後|오전|오후))?) - (.+)$/i;
|
|
5536
5575
|
var MESSAGE_LINE_REGEX_V2 = /^\[([^\]]+)\] (.+)$/;
|
|
5537
|
-
var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]
|
|
5576
|
+
var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]*(.*)$/;
|
|
5538
5577
|
var SYSTEM_MESSAGE_PATTERNS = [
|
|
5539
5578
|
// 简体中文系统消息
|
|
5540
5579
|
/消息和通话已进行端到端加密/,
|
|
@@ -5586,12 +5625,12 @@ function detectMessageType2(content) {
|
|
|
5586
5625
|
}
|
|
5587
5626
|
var AMPM_MARKERS = [
|
|
5588
5627
|
[/\bPM\b/i, true],
|
|
5589
|
-
[/\bP\.M
|
|
5628
|
+
[/\bP\.M\.(?!\w)/i, true],
|
|
5590
5629
|
[/下午/, true],
|
|
5591
5630
|
[/午後/, true],
|
|
5592
5631
|
[/오후/, true],
|
|
5593
5632
|
[/\bAM\b/i, false],
|
|
5594
|
-
[/\bA\.M
|
|
5633
|
+
[/\bA\.M\.(?!\w)/i, false],
|
|
5595
5634
|
[/上午/, false],
|
|
5596
5635
|
[/午前/, false],
|
|
5597
5636
|
[/오전/, false]
|
|
@@ -5607,7 +5646,7 @@ function parseFlexibleV2Timestamp(raw) {
|
|
|
5607
5646
|
}
|
|
5608
5647
|
}
|
|
5609
5648
|
str = str.replace(/,/g, " ").replace(/\s+/g, " ").trim();
|
|
5610
|
-
const match = str.match(/^(\d{1,4}\/\d{1,2}\/\d{
|
|
5649
|
+
const match = str.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
|
|
5611
5650
|
if (!match) return null;
|
|
5612
5651
|
const [, datePart, timePart] = match;
|
|
5613
5652
|
const dateParts = datePart.split("/").map((s) => parseInt(s, 10));
|
|
@@ -5636,32 +5675,7 @@ function parseFlexibleV2Timestamp(raw) {
|
|
|
5636
5675
|
return isNaN(ts) ? null : ts;
|
|
5637
5676
|
}
|
|
5638
5677
|
function parseV1Timestamp(timeStr) {
|
|
5639
|
-
|
|
5640
|
-
const match = str.match(/^(\d{1,4})\/(\d{1,2})\/(\d{2,4})\s+(\d{1,2}):(\d{2})$/);
|
|
5641
|
-
if (!match) {
|
|
5642
|
-
const normalized = timeStr.replace(/,/g, "").replace(/\//g, "-").replace(/\s+/, "T") + ":00";
|
|
5643
|
-
return Math.floor(new Date(normalized).getTime() / 1e3);
|
|
5644
|
-
}
|
|
5645
|
-
const parts = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
|
|
5646
|
-
const hour = parseInt(match[4], 10);
|
|
5647
|
-
const minute = parseInt(match[5], 10);
|
|
5648
|
-
let year, month, day;
|
|
5649
|
-
if (parts[0] > 31) {
|
|
5650
|
-
year = parts[0];
|
|
5651
|
-
month = parts[1];
|
|
5652
|
-
day = parts[2];
|
|
5653
|
-
} else if (parts[2] > 31) {
|
|
5654
|
-
day = parts[0];
|
|
5655
|
-
month = parts[1];
|
|
5656
|
-
year = parts[2];
|
|
5657
|
-
} else {
|
|
5658
|
-
month = parts[0];
|
|
5659
|
-
day = parts[1];
|
|
5660
|
-
year = 2e3 + parts[2];
|
|
5661
|
-
}
|
|
5662
|
-
const date = new Date(year, month - 1, day, hour, minute, 0);
|
|
5663
|
-
const ts = Math.floor(date.getTime() / 1e3);
|
|
5664
|
-
return isNaN(ts) ? 0 : ts;
|
|
5678
|
+
return parseFlexibleV2Timestamp(timeStr) ?? 0;
|
|
5665
5679
|
}
|
|
5666
5680
|
async function* parseWhatsApp(options2) {
|
|
5667
5681
|
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
@@ -5673,7 +5687,7 @@ async function* parseWhatsApp(options2) {
|
|
|
5673
5687
|
yield { type: "progress", data: initialProgress };
|
|
5674
5688
|
onProgress?.(initialProgress);
|
|
5675
5689
|
onLog?.("info", `\u5F00\u59CB\u89E3\u6790 WhatsApp TXT \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
|
|
5676
|
-
const
|
|
5690
|
+
const fileChatName = extractNameFromFilePath5(filePath);
|
|
5677
5691
|
const memberMap = /* @__PURE__ */ new Map();
|
|
5678
5692
|
const messages = [];
|
|
5679
5693
|
let currentMessage = null;
|
|
@@ -5772,6 +5786,7 @@ async function* parseWhatsApp(options2) {
|
|
|
5772
5786
|
const hasSystemMember = memberMap.has("system");
|
|
5773
5787
|
const realMemberCount = hasSystemMember ? memberMap.size - 1 : memberMap.size;
|
|
5774
5788
|
const chatType = realMemberCount > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
|
|
5789
|
+
const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(filePath, fileChatName) ? inferPrivateChatName(memberMap, fileChatName) : fileChatName;
|
|
5775
5790
|
const meta = {
|
|
5776
5791
|
name: chatName,
|
|
5777
5792
|
platform: KNOWN_PLATFORMS.WHATSAPP,
|
|
@@ -7410,6 +7425,35 @@ function searchByFts(db, keywords, limit = 1e3, offset = 0) {
|
|
|
7410
7425
|
|
|
7411
7426
|
// ../../packages/node-runtime/src/import/streaming-importer.ts
|
|
7412
7427
|
import * as fs20 from "fs";
|
|
7428
|
+
|
|
7429
|
+
// ../../packages/node-runtime/src/import/message-deduplicator.ts
|
|
7430
|
+
function generateFallbackMessageKey(message) {
|
|
7431
|
+
const contentKey = generateMessageKey(message.timestamp, message.senderPlatformId, message.content);
|
|
7432
|
+
return JSON.stringify([contentKey, message.type, message.replyToMessageId || null]);
|
|
7433
|
+
}
|
|
7434
|
+
function createMessageDedupState(platformMessageIds = [], fallbackKeys = [], fallbackOnlyKeys = []) {
|
|
7435
|
+
return {
|
|
7436
|
+
platformMessageIds: new Set(platformMessageIds),
|
|
7437
|
+
fallbackKeys: new Set(fallbackKeys),
|
|
7438
|
+
fallbackOnlyKeys: new Set(fallbackOnlyKeys)
|
|
7439
|
+
};
|
|
7440
|
+
}
|
|
7441
|
+
function registerMessageAndCheckDuplicate(message, state) {
|
|
7442
|
+
const key = generateFallbackMessageKey(message);
|
|
7443
|
+
if (message.platformMessageId) {
|
|
7444
|
+
if (state.platformMessageIds.has(message.platformMessageId)) return true;
|
|
7445
|
+
state.platformMessageIds.add(message.platformMessageId);
|
|
7446
|
+
if (state.fallbackOnlyKeys.delete(key)) return true;
|
|
7447
|
+
state.fallbackKeys.add(key);
|
|
7448
|
+
return false;
|
|
7449
|
+
}
|
|
7450
|
+
if (state.fallbackKeys.has(key)) return true;
|
|
7451
|
+
state.fallbackKeys.add(key);
|
|
7452
|
+
state.fallbackOnlyKeys.add(key);
|
|
7453
|
+
return false;
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
// ../../packages/node-runtime/src/import/streaming-importer.ts
|
|
7413
7457
|
var BATCH_COMMIT_SIZE = 5e4;
|
|
7414
7458
|
var CHECKPOINT_INTERVAL = 2e5;
|
|
7415
7459
|
var yieldToEventLoop = () => new Promise((resolve2) => setImmediate(resolve2));
|
|
@@ -7418,6 +7462,28 @@ function defaultGenerateSessionId() {
|
|
|
7418
7462
|
const rand = Math.random().toString(36).substring(2, 8);
|
|
7419
7463
|
return `chat_${ts}_${rand}`;
|
|
7420
7464
|
}
|
|
7465
|
+
function prepareMessageForCreate(message) {
|
|
7466
|
+
if (!message.senderPlatformId) return { skipCounter: "skippedNoSenderId" };
|
|
7467
|
+
if (!message.senderAccountName) return { skipCounter: "skippedNoAccountName" };
|
|
7468
|
+
if (message.timestamp === void 0 || message.timestamp === null || isNaN(message.timestamp)) {
|
|
7469
|
+
return { skipCounter: "skippedInvalidTimestamp" };
|
|
7470
|
+
}
|
|
7471
|
+
if (message.type === void 0 || message.type === null) return { skipCounter: "skippedNoType" };
|
|
7472
|
+
let content = null;
|
|
7473
|
+
if (message.content != null) {
|
|
7474
|
+
content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
|
|
7475
|
+
}
|
|
7476
|
+
return {
|
|
7477
|
+
message: {
|
|
7478
|
+
platformMessageId: message.platformMessageId,
|
|
7479
|
+
timestamp: message.timestamp,
|
|
7480
|
+
senderPlatformId: message.senderPlatformId,
|
|
7481
|
+
type: message.type,
|
|
7482
|
+
content,
|
|
7483
|
+
replyToMessageId: message.replyToMessageId
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
7486
|
+
}
|
|
7421
7487
|
async function streamingImport(filePath, deps, formatOptions, externalSessionId) {
|
|
7422
7488
|
if (formatOptions?.formatId) {
|
|
7423
7489
|
const formatId = formatOptions.formatId;
|
|
@@ -7494,11 +7560,12 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7494
7560
|
`INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
7495
7561
|
);
|
|
7496
7562
|
const insertMember = db.prepare(
|
|
7497
|
-
`INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)
|
|
7498
|
-
VALUES (?, ?, ?, ?, ?)
|
|
7563
|
+
`INSERT INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles)
|
|
7564
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7499
7565
|
ON CONFLICT(platform_id) DO UPDATE SET
|
|
7500
7566
|
account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),
|
|
7501
7567
|
group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),
|
|
7568
|
+
aliases = CASE WHEN excluded.aliases != '[]' THEN excluded.aliases ELSE aliases END,
|
|
7502
7569
|
avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),
|
|
7503
7570
|
roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END`
|
|
7504
7571
|
);
|
|
@@ -7518,6 +7585,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7518
7585
|
let metaInserted = false;
|
|
7519
7586
|
let messageCountInBatch = 0;
|
|
7520
7587
|
let totalMessageCount = 0;
|
|
7588
|
+
let duplicateCount = 0;
|
|
7521
7589
|
let lastCheckpointCount = 0;
|
|
7522
7590
|
let inTransaction = false;
|
|
7523
7591
|
const beginTransaction = () => {
|
|
@@ -7569,6 +7637,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7569
7637
|
skippedInvalidTimestamp: 0,
|
|
7570
7638
|
skippedNoType: 0
|
|
7571
7639
|
};
|
|
7640
|
+
const dedupState = createMessageDedupState();
|
|
7572
7641
|
logger?.info("Starting streamParseFile...");
|
|
7573
7642
|
try {
|
|
7574
7643
|
await streamParseFile(
|
|
@@ -7613,6 +7682,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7613
7682
|
member.platformId,
|
|
7614
7683
|
member.accountName || null,
|
|
7615
7684
|
member.groupNickname || null,
|
|
7685
|
+
member.aliases ? JSON.stringify(member.aliases) : "[]",
|
|
7616
7686
|
member.avatar || null,
|
|
7617
7687
|
member.roles ? JSON.stringify(member.roles) : "[]"
|
|
7618
7688
|
);
|
|
@@ -7634,20 +7704,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7634
7704
|
let memberInsertCount = 0;
|
|
7635
7705
|
let nicknameChangeCount = 0;
|
|
7636
7706
|
for (const msg of messages) {
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
}
|
|
7641
|
-
if (!msg.senderAccountName) {
|
|
7642
|
-
callbackStats.skippedNoAccountName++;
|
|
7643
|
-
continue;
|
|
7644
|
-
}
|
|
7645
|
-
if (msg.timestamp === void 0 || msg.timestamp === null || isNaN(msg.timestamp)) {
|
|
7646
|
-
callbackStats.skippedInvalidTimestamp++;
|
|
7707
|
+
const prepared = prepareMessageForCreate(msg);
|
|
7708
|
+
if ("skipCounter" in prepared) {
|
|
7709
|
+
callbackStats[prepared.skipCounter]++;
|
|
7647
7710
|
continue;
|
|
7648
7711
|
}
|
|
7649
|
-
|
|
7650
|
-
|
|
7712
|
+
const dedupMessage = prepared.message;
|
|
7713
|
+
if (registerMessageAndCheckDuplicate(dedupMessage, dedupState)) {
|
|
7714
|
+
duplicateCount++;
|
|
7651
7715
|
continue;
|
|
7652
7716
|
}
|
|
7653
7717
|
let t0 = Date.now();
|
|
@@ -7656,6 +7720,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7656
7720
|
msg.senderPlatformId,
|
|
7657
7721
|
msg.senderAccountName || null,
|
|
7658
7722
|
msg.senderGroupNickname || null,
|
|
7723
|
+
"[]",
|
|
7659
7724
|
null,
|
|
7660
7725
|
"[]"
|
|
7661
7726
|
);
|
|
@@ -7669,18 +7734,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7669
7734
|
}
|
|
7670
7735
|
const senderId = memberIdMap.get(msg.senderPlatformId);
|
|
7671
7736
|
if (senderId === void 0) continue;
|
|
7672
|
-
let safeContent = null;
|
|
7673
|
-
if (msg.content != null) {
|
|
7674
|
-
safeContent = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
7675
|
-
}
|
|
7676
7737
|
t0 = Date.now();
|
|
7677
7738
|
insertMessage.run(
|
|
7678
7739
|
senderId,
|
|
7679
7740
|
msg.senderAccountName || null,
|
|
7680
7741
|
msg.senderGroupNickname || null,
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7742
|
+
dedupMessage.timestamp,
|
|
7743
|
+
dedupMessage.type,
|
|
7744
|
+
dedupMessage.content,
|
|
7684
7745
|
msg.replyToMessageId || null,
|
|
7685
7746
|
msg.platformMessageId || null
|
|
7686
7747
|
);
|
|
@@ -7797,6 +7858,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7797
7858
|
logger?.info(` invalid timestamp: ${callbackStats.skippedInvalidTimestamp}`);
|
|
7798
7859
|
if (callbackStats.skippedNoType > 0) logger?.info(` missing type: ${callbackStats.skippedNoType}`);
|
|
7799
7860
|
}
|
|
7861
|
+
if (duplicateCount > 0) logger?.info(`Duplicate messages skipped: ${duplicateCount}`);
|
|
7800
7862
|
logger?.summary(totalMessageCount, memberIdMap.size);
|
|
7801
7863
|
if (totalMessageCount === 0) {
|
|
7802
7864
|
logger?.error(
|
|
@@ -7829,6 +7891,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7829
7891
|
detectedFormat: formatFeature ? `${formatFeature.name} (${formatFeature.id})` : null,
|
|
7830
7892
|
messagesReceived: callbackStats.totalMessagesReceived,
|
|
7831
7893
|
messagesWritten: totalMessageCount,
|
|
7894
|
+
duplicateCount,
|
|
7832
7895
|
messagesSkipped: callbackStats.skippedNoSenderId + callbackStats.skippedNoAccountName + callbackStats.skippedInvalidTimestamp + callbackStats.skippedNoType,
|
|
7833
7896
|
skipReasons: {
|
|
7834
7897
|
noSenderId: callbackStats.skippedNoSenderId,
|
|
@@ -7842,32 +7905,54 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7842
7905
|
}
|
|
7843
7906
|
return { success: true, sessionId, diagnostics };
|
|
7844
7907
|
}
|
|
7845
|
-
async function analyzeNewImport(filePath, onProgress) {
|
|
7846
|
-
const formatFeature = detectFormat(filePath);
|
|
7908
|
+
async function analyzeNewImport(filePath, onProgress, options2) {
|
|
7909
|
+
const formatFeature = options2?.formatId ? getFormatFeatureById(options2.formatId) : detectFormat(filePath);
|
|
7847
7910
|
if (!formatFeature) {
|
|
7848
|
-
return {
|
|
7911
|
+
return {
|
|
7912
|
+
totalMessages: 0,
|
|
7913
|
+
newMessageCount: 0,
|
|
7914
|
+
duplicateCount: 0,
|
|
7915
|
+
totalMembers: 0,
|
|
7916
|
+
meta: null,
|
|
7917
|
+
error: "error.unrecognized_format"
|
|
7918
|
+
};
|
|
7849
7919
|
}
|
|
7850
7920
|
let meta = null;
|
|
7851
7921
|
const memberSet = /* @__PURE__ */ new Set();
|
|
7922
|
+
const dedupState = createMessageDedupState();
|
|
7852
7923
|
let totalMessages = 0;
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7924
|
+
let newMessageCount = 0;
|
|
7925
|
+
let duplicateCount = 0;
|
|
7926
|
+
await streamParseFile(
|
|
7927
|
+
filePath,
|
|
7928
|
+
{
|
|
7929
|
+
formatOptions: options2?.chatIndex === void 0 ? void 0 : { chatIndex: options2.chatIndex },
|
|
7930
|
+
onMeta: (parsedMeta) => {
|
|
7931
|
+
meta = { name: parsedMeta.name, platform: parsedMeta.platform, type: parsedMeta.type };
|
|
7932
|
+
},
|
|
7933
|
+
onMembers: (members) => {
|
|
7934
|
+
for (const m of members) memberSet.add(m.platformId);
|
|
7935
|
+
},
|
|
7936
|
+
onProgress: (progress) => {
|
|
7937
|
+
onProgress(progress);
|
|
7938
|
+
},
|
|
7939
|
+
onMessageBatch: (batch) => {
|
|
7940
|
+
for (const msg of batch) {
|
|
7941
|
+
totalMessages++;
|
|
7942
|
+
if (!memberSet.has(msg.senderPlatformId)) memberSet.add(msg.senderPlatformId);
|
|
7943
|
+
const prepared = prepareMessageForCreate(msg);
|
|
7944
|
+
if ("skipCounter" in prepared) continue;
|
|
7945
|
+
if (registerMessageAndCheckDuplicate(prepared.message, dedupState)) {
|
|
7946
|
+
duplicateCount++;
|
|
7947
|
+
} else {
|
|
7948
|
+
newMessageCount++;
|
|
7949
|
+
}
|
|
7950
|
+
}
|
|
7867
7951
|
}
|
|
7868
|
-
}
|
|
7869
|
-
|
|
7870
|
-
|
|
7952
|
+
},
|
|
7953
|
+
options2?.formatId
|
|
7954
|
+
);
|
|
7955
|
+
return { totalMessages, newMessageCount, duplicateCount, totalMembers: memberSet.size, meta };
|
|
7871
7956
|
}
|
|
7872
7957
|
async function streamParseFileInfo(filePath, deps) {
|
|
7873
7958
|
const formatFeature = detectFormat(filePath);
|
|
@@ -8028,37 +8113,43 @@ function countHistory(tracker) {
|
|
|
8028
8113
|
function loadExistingDedup(db) {
|
|
8029
8114
|
const existingPlatformMsgIds = /* @__PURE__ */ new Set();
|
|
8030
8115
|
const existingKeys = /* @__PURE__ */ new Set();
|
|
8116
|
+
const existingFallbackOnlyKeys = /* @__PURE__ */ new Set();
|
|
8031
8117
|
const pmidRows = db.prepare("SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL").all();
|
|
8032
8118
|
for (const row of pmidRows) {
|
|
8033
8119
|
existingPlatformMsgIds.add(row.platform_message_id);
|
|
8034
8120
|
}
|
|
8035
8121
|
const hashRows = db.prepare(
|
|
8036
|
-
`SELECT ts, m.platform_id as sender_platform_id, content
|
|
8122
|
+
`SELECT ts, m.platform_id as sender_platform_id, msg.type, content, reply_to_message_id,
|
|
8123
|
+
platform_message_id
|
|
8037
8124
|
FROM message msg
|
|
8038
8125
|
JOIN member m ON msg.sender_id = m.id`
|
|
8039
8126
|
).all();
|
|
8040
8127
|
for (const row of hashRows) {
|
|
8041
|
-
|
|
8128
|
+
const key = generateFallbackMessageKey({
|
|
8129
|
+
timestamp: row.ts,
|
|
8130
|
+
senderPlatformId: row.sender_platform_id,
|
|
8131
|
+
type: row.type,
|
|
8132
|
+
content: row.content,
|
|
8133
|
+
replyToMessageId: row.reply_to_message_id ?? void 0
|
|
8134
|
+
});
|
|
8135
|
+
existingKeys.add(key);
|
|
8136
|
+
if (!row.platform_message_id) existingFallbackOnlyKeys.add(key);
|
|
8042
8137
|
}
|
|
8043
|
-
return { existingPlatformMsgIds, existingKeys };
|
|
8138
|
+
return { existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys };
|
|
8044
8139
|
}
|
|
8045
|
-
function isDuplicate(msg, existingPlatformMsgIds, existingKeys) {
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
}
|
|
8051
|
-
const key = generateMessageKey(msg.timestamp, msg.senderPlatformId, msg.content);
|
|
8052
|
-
if (existingKeys.has(key)) return true;
|
|
8053
|
-
existingKeys.add(key);
|
|
8054
|
-
return false;
|
|
8140
|
+
function isDuplicate(msg, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys) {
|
|
8141
|
+
return registerMessageAndCheckDuplicate(msg, {
|
|
8142
|
+
platformMessageIds: existingPlatformMsgIds,
|
|
8143
|
+
fallbackKeys: existingKeys,
|
|
8144
|
+
fallbackOnlyKeys: existingFallbackOnlyKeys
|
|
8145
|
+
});
|
|
8055
8146
|
}
|
|
8056
|
-
function
|
|
8147
|
+
function normalizeImportTimestamp(timestamp) {
|
|
8057
8148
|
const value = typeof timestamp === "string" && timestamp.trim() !== "" ? Number(timestamp) : timestamp;
|
|
8058
8149
|
return typeof value === "number" && value > 0 && Number.isFinite(value) ? value : null;
|
|
8059
8150
|
}
|
|
8060
|
-
async function analyzeIncrementalImport(sessionId, filePath, deps) {
|
|
8061
|
-
const formatFeature = detectFormat(filePath);
|
|
8151
|
+
async function analyzeIncrementalImport(sessionId, filePath, deps, options2) {
|
|
8152
|
+
const formatFeature = options2?.formatId ? getFormatFeatureById(options2.formatId) : detectFormat(filePath);
|
|
8062
8153
|
if (!formatFeature) {
|
|
8063
8154
|
return { error: "error.unrecognized_format", newMessageCount: 0, duplicateCount: 0, totalInFile: 0 };
|
|
8064
8155
|
}
|
|
@@ -8068,36 +8159,41 @@ async function analyzeIncrementalImport(sessionId, filePath, deps) {
|
|
|
8068
8159
|
} catch {
|
|
8069
8160
|
return { error: "error.session_not_found", newMessageCount: 0, duplicateCount: 0, totalInFile: 0 };
|
|
8070
8161
|
}
|
|
8071
|
-
const { existingPlatformMsgIds, existingKeys } = loadExistingDedup(db);
|
|
8162
|
+
const { existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys } = loadExistingDedup(db);
|
|
8072
8163
|
db.close();
|
|
8073
8164
|
let totalInFile = 0;
|
|
8074
8165
|
let newMessageCount = 0;
|
|
8075
8166
|
let duplicateCount = 0;
|
|
8076
|
-
await streamParseFile(
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8167
|
+
await streamParseFile(
|
|
8168
|
+
filePath,
|
|
8169
|
+
{
|
|
8170
|
+
formatOptions: options2?.chatIndex === void 0 ? void 0 : { chatIndex: options2.chatIndex },
|
|
8171
|
+
onMeta: () => {
|
|
8172
|
+
},
|
|
8173
|
+
onMembers: () => {
|
|
8174
|
+
},
|
|
8175
|
+
onProgress: (progress) => {
|
|
8176
|
+
deps.onProgress(progress);
|
|
8177
|
+
},
|
|
8178
|
+
onMessageBatch: (batch) => {
|
|
8179
|
+
for (const msg of batch) {
|
|
8180
|
+
totalInFile++;
|
|
8181
|
+
const timestamp = normalizeImportTimestamp(msg.timestamp);
|
|
8182
|
+
if (timestamp === null) continue;
|
|
8183
|
+
if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys)) {
|
|
8184
|
+
duplicateCount++;
|
|
8185
|
+
} else {
|
|
8186
|
+
newMessageCount++;
|
|
8187
|
+
}
|
|
8093
8188
|
}
|
|
8094
8189
|
}
|
|
8095
|
-
}
|
|
8096
|
-
|
|
8190
|
+
},
|
|
8191
|
+
options2?.formatId
|
|
8192
|
+
);
|
|
8097
8193
|
return { newMessageCount, duplicateCount, totalInFile };
|
|
8098
8194
|
}
|
|
8099
8195
|
async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
8100
|
-
const formatFeature = detectFormat(filePath);
|
|
8196
|
+
const formatFeature = options2?.formatId ? getFormatFeatureById(options2.formatId) : detectFormat(filePath);
|
|
8101
8197
|
if (!formatFeature) {
|
|
8102
8198
|
return { success: false, newMessageCount: 0, error: "error.unrecognized_format" };
|
|
8103
8199
|
}
|
|
@@ -8118,18 +8214,19 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8118
8214
|
}
|
|
8119
8215
|
};
|
|
8120
8216
|
var trackError = trackError2;
|
|
8121
|
-
const { existingPlatformMsgIds, existingKeys } = loadExistingDedup(db);
|
|
8217
|
+
const { existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys } = loadExistingDedup(db);
|
|
8122
8218
|
const memberIdMap = /* @__PURE__ */ new Map();
|
|
8123
8219
|
const existingMembers = db.prepare("SELECT id, platform_id FROM member").all();
|
|
8124
8220
|
for (const m of existingMembers) {
|
|
8125
8221
|
memberIdMap.set(m.platform_id, m.id);
|
|
8126
8222
|
}
|
|
8127
8223
|
const upsertMember = db.prepare(`
|
|
8128
|
-
INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)
|
|
8129
|
-
VALUES (?, ?, ?, ?, ?)
|
|
8224
|
+
INSERT INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles)
|
|
8225
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
8130
8226
|
ON CONFLICT(platform_id) DO UPDATE SET
|
|
8131
8227
|
account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),
|
|
8132
8228
|
group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),
|
|
8229
|
+
aliases = CASE WHEN excluded.aliases != '[]' THEN excluded.aliases ELSE aliases END,
|
|
8133
8230
|
avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),
|
|
8134
8231
|
roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END
|
|
8135
8232
|
`);
|
|
@@ -8165,106 +8262,112 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8165
8262
|
const MAX_ERROR_SAMPLES = 5;
|
|
8166
8263
|
const BATCH_SIZE2 = 5e3;
|
|
8167
8264
|
const newFtsEntries = [];
|
|
8168
|
-
await streamParseFile(
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
onMembers: (members) => {
|
|
8181
|
-
if (memberUpdateMode === "none") return;
|
|
8182
|
-
for (const m of members) {
|
|
8183
|
-
const existed = memberIdMap.has(m.platformId);
|
|
8184
|
-
upsertMember.run(
|
|
8185
|
-
m.platformId,
|
|
8186
|
-
m.accountName || null,
|
|
8187
|
-
m.groupNickname || null,
|
|
8188
|
-
m.avatar || null,
|
|
8189
|
-
m.roles ? JSON.stringify(m.roles) : "[]"
|
|
8265
|
+
await streamParseFile(
|
|
8266
|
+
filePath,
|
|
8267
|
+
{
|
|
8268
|
+
formatOptions: options2?.chatIndex === void 0 ? void 0 : { chatIndex: options2.chatIndex },
|
|
8269
|
+
onMeta: (meta) => {
|
|
8270
|
+
if (metaUpdateMode === "none") return;
|
|
8271
|
+
updateMeta.run(
|
|
8272
|
+
meta.name || "",
|
|
8273
|
+
meta.groupId || "",
|
|
8274
|
+
meta.groupAvatar || "",
|
|
8275
|
+
meta.ownerId || "",
|
|
8276
|
+
Math.floor(Date.now() / 1e3)
|
|
8190
8277
|
);
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
continue;
|
|
8213
|
-
}
|
|
8214
|
-
const timestamp = normalizeTimestamp(msg.timestamp);
|
|
8215
|
-
if (timestamp === null) {
|
|
8216
|
-
trackError2(processedCount, "INVALID_TIMESTAMP", `timestamp value: ${msg.timestamp}`);
|
|
8217
|
-
continue;
|
|
8218
|
-
}
|
|
8219
|
-
if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys)) {
|
|
8220
|
-
duplicateCount++;
|
|
8221
|
-
continue;
|
|
8278
|
+
metaUpdated = true;
|
|
8279
|
+
},
|
|
8280
|
+
onMembers: (members) => {
|
|
8281
|
+
if (memberUpdateMode === "none") return;
|
|
8282
|
+
for (const m of members) {
|
|
8283
|
+
const existed = memberIdMap.has(m.platformId);
|
|
8284
|
+
upsertMember.run(
|
|
8285
|
+
m.platformId,
|
|
8286
|
+
m.accountName || null,
|
|
8287
|
+
m.groupNickname || null,
|
|
8288
|
+
m.aliases ? JSON.stringify(m.aliases) : "[]",
|
|
8289
|
+
m.avatar || null,
|
|
8290
|
+
m.roles ? JSON.stringify(m.roles) : "[]"
|
|
8291
|
+
);
|
|
8292
|
+
if (!existed) {
|
|
8293
|
+
const row = getMemberId.get(m.platformId);
|
|
8294
|
+
if (row) memberIdMap.set(m.platformId, row.id);
|
|
8295
|
+
membersAdded++;
|
|
8296
|
+
} else {
|
|
8297
|
+
membersUpdated++;
|
|
8298
|
+
}
|
|
8222
8299
|
}
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8300
|
+
},
|
|
8301
|
+
onProgress: (progress) => {
|
|
8302
|
+
deps.onProgress(progress);
|
|
8303
|
+
},
|
|
8304
|
+
onMessageBatch: (batch) => {
|
|
8305
|
+
for (const msg of batch) {
|
|
8306
|
+
processedCount++;
|
|
8307
|
+
if (!msg.senderPlatformId) {
|
|
8308
|
+
trackError2(processedCount, "MISSING_SENDER", "sender field is empty");
|
|
8309
|
+
continue;
|
|
8310
|
+
}
|
|
8311
|
+
if (msg.timestamp === void 0 || msg.timestamp === null) {
|
|
8312
|
+
trackError2(processedCount, "MISSING_TIMESTAMP", "timestamp field is missing");
|
|
8313
|
+
continue;
|
|
8314
|
+
}
|
|
8315
|
+
const timestamp = normalizeImportTimestamp(msg.timestamp);
|
|
8316
|
+
if (timestamp === null) {
|
|
8317
|
+
trackError2(processedCount, "INVALID_TIMESTAMP", `timestamp value: ${msg.timestamp}`);
|
|
8318
|
+
continue;
|
|
8319
|
+
}
|
|
8320
|
+
if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys)) {
|
|
8321
|
+
duplicateCount++;
|
|
8322
|
+
continue;
|
|
8323
|
+
}
|
|
8324
|
+
let memberId = memberIdMap.get(msg.senderPlatformId);
|
|
8325
|
+
if (!memberId) {
|
|
8326
|
+
insertMemberMinimal.run(
|
|
8327
|
+
msg.senderPlatformId,
|
|
8328
|
+
msg.senderAccountName || null,
|
|
8329
|
+
msg.senderGroupNickname || null,
|
|
8330
|
+
null
|
|
8331
|
+
);
|
|
8332
|
+
const row = getMemberId.get(msg.senderPlatformId);
|
|
8333
|
+
if (row) {
|
|
8334
|
+
memberId = row.id;
|
|
8335
|
+
memberIdMap.set(msg.senderPlatformId, memberId);
|
|
8336
|
+
membersAdded++;
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
if (!memberId) continue;
|
|
8340
|
+
const msgResult = insertMessage.run(
|
|
8341
|
+
memberId,
|
|
8227
8342
|
msg.senderAccountName || null,
|
|
8228
8343
|
msg.senderGroupNickname || null,
|
|
8229
|
-
|
|
8344
|
+
timestamp,
|
|
8345
|
+
msg.type,
|
|
8346
|
+
msg.content || null,
|
|
8347
|
+
msg.replyToMessageId || null,
|
|
8348
|
+
msg.platformMessageId || null
|
|
8230
8349
|
);
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8350
|
+
newFtsEntries.push({
|
|
8351
|
+
id: Number(msgResult.lastInsertRowid ?? 0),
|
|
8352
|
+
content: msg.content || null
|
|
8353
|
+
});
|
|
8354
|
+
if (timestamp < minWrittenTs) minWrittenTs = timestamp;
|
|
8355
|
+
newMessageCount++;
|
|
8356
|
+
}
|
|
8357
|
+
if (processedCount % BATCH_SIZE2 === 0) {
|
|
8358
|
+
deps.onProgress({
|
|
8359
|
+
stage: "saving",
|
|
8360
|
+
bytesRead: 0,
|
|
8361
|
+
totalBytes: 0,
|
|
8362
|
+
messagesProcessed: processedCount,
|
|
8363
|
+
percentage: 50,
|
|
8364
|
+
message: `Processed ${processedCount}, added ${newMessageCount}`
|
|
8365
|
+
});
|
|
8237
8366
|
}
|
|
8238
|
-
if (!memberId) continue;
|
|
8239
|
-
const msgResult = insertMessage.run(
|
|
8240
|
-
memberId,
|
|
8241
|
-
msg.senderAccountName || null,
|
|
8242
|
-
msg.senderGroupNickname || null,
|
|
8243
|
-
timestamp,
|
|
8244
|
-
msg.type,
|
|
8245
|
-
msg.content || null,
|
|
8246
|
-
msg.replyToMessageId || null,
|
|
8247
|
-
msg.platformMessageId || null
|
|
8248
|
-
);
|
|
8249
|
-
newFtsEntries.push({
|
|
8250
|
-
id: Number(msgResult.lastInsertRowid ?? 0),
|
|
8251
|
-
content: msg.content || null
|
|
8252
|
-
});
|
|
8253
|
-
if (timestamp < minWrittenTs) minWrittenTs = timestamp;
|
|
8254
|
-
newMessageCount++;
|
|
8255
|
-
}
|
|
8256
|
-
if (processedCount % BATCH_SIZE2 === 0) {
|
|
8257
|
-
deps.onProgress({
|
|
8258
|
-
stage: "saving",
|
|
8259
|
-
bytesRead: 0,
|
|
8260
|
-
totalBytes: 0,
|
|
8261
|
-
messagesProcessed: processedCount,
|
|
8262
|
-
percentage: 50,
|
|
8263
|
-
message: `Processed ${processedCount}, added ${newMessageCount}`
|
|
8264
|
-
});
|
|
8265
8367
|
}
|
|
8266
|
-
}
|
|
8267
|
-
|
|
8368
|
+
},
|
|
8369
|
+
options2?.formatId
|
|
8370
|
+
);
|
|
8268
8371
|
db.exec("COMMIT");
|
|
8269
8372
|
if (!metaUpdated) {
|
|
8270
8373
|
db.prepare("UPDATE meta SET imported_at = ?").run(Math.floor(Date.now() / 1e3));
|
|
@@ -8340,56 +8443,408 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8340
8443
|
}
|
|
8341
8444
|
}
|
|
8342
8445
|
|
|
8343
|
-
// ../../packages/node-runtime/src/import/
|
|
8344
|
-
var
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
this.name = "ArchiveImportError";
|
|
8349
|
-
}
|
|
8350
|
-
code;
|
|
8351
|
-
};
|
|
8446
|
+
// ../../packages/node-runtime/src/import/session-id.ts
|
|
8447
|
+
var SAFE_IMPORT_SESSION_ID_RE = /^[A-Za-z0-9_@-][A-Za-z0-9._@-]{0,127}$/;
|
|
8448
|
+
function isValidImportSessionId(sessionId) {
|
|
8449
|
+
return SAFE_IMPORT_SESSION_ID_RE.test(sessionId) && !sessionId.includes("..");
|
|
8450
|
+
}
|
|
8352
8451
|
|
|
8353
|
-
// ../../packages/node-runtime/src/import/
|
|
8354
|
-
var
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8452
|
+
// ../../packages/node-runtime/src/import/auto-import-matcher.ts
|
|
8453
|
+
var MATCH_WINDOW_SIZE = 5;
|
|
8454
|
+
function buildPrivateIdentity(ownerId, memberIds) {
|
|
8455
|
+
if (!ownerId) return null;
|
|
8456
|
+
const ids = [...new Set(memberIds)].filter((id) => id && id.toLowerCase() !== "system").sort();
|
|
8457
|
+
return ids.length > 0 ? `${ownerId}\0${ids.join("\0")}` : null;
|
|
8458
|
+
}
|
|
8459
|
+
function isBusinessMessage(type, content) {
|
|
8460
|
+
return type !== 80 /* SYSTEM */ && type !== 81 /* RECALL */ && Boolean(content?.trim());
|
|
8461
|
+
}
|
|
8462
|
+
function addMessageWindow(recentKeys, windows, message) {
|
|
8463
|
+
if (!message.senderPlatformId || !isBusinessMessage(message.type, message.content)) return;
|
|
8464
|
+
const timestamp = normalizeImportTimestamp(message.timestamp);
|
|
8465
|
+
if (timestamp === null) return;
|
|
8466
|
+
recentKeys.push(generateMessageKey(timestamp, message.senderPlatformId, message.content));
|
|
8467
|
+
if (recentKeys.length > MATCH_WINDOW_SIZE) recentKeys.shift();
|
|
8468
|
+
if (recentKeys.length === MATCH_WINDOW_SIZE) windows.add(recentKeys.join(""));
|
|
8469
|
+
}
|
|
8470
|
+
async function resolveAutoImportTarget(filePath, deps, formatOptions) {
|
|
8471
|
+
let sourceMeta = null;
|
|
8472
|
+
const sourceMemberIds = /* @__PURE__ */ new Set();
|
|
8473
|
+
const sourceWindows = /* @__PURE__ */ new Set();
|
|
8474
|
+
const recentSourceKeys = [];
|
|
8475
|
+
const { formatId, ...parserOptions } = formatOptions ?? {};
|
|
8476
|
+
await streamParseFile(
|
|
8477
|
+
filePath,
|
|
8478
|
+
{
|
|
8479
|
+
formatOptions: parserOptions,
|
|
8480
|
+
onProgress: deps.onProgress ?? (() => {
|
|
8481
|
+
}),
|
|
8482
|
+
onMeta: (meta2) => {
|
|
8483
|
+
sourceMeta = meta2;
|
|
8484
|
+
},
|
|
8485
|
+
onMembers: (members) => {
|
|
8486
|
+
for (const member of members) sourceMemberIds.add(member.platformId);
|
|
8487
|
+
},
|
|
8488
|
+
onMessageBatch: (messages) => {
|
|
8489
|
+
for (const message of messages) {
|
|
8490
|
+
sourceMemberIds.add(message.senderPlatformId);
|
|
8491
|
+
addMessageWindow(recentSourceKeys, sourceWindows, message);
|
|
8492
|
+
}
|
|
8493
|
+
}
|
|
8494
|
+
},
|
|
8495
|
+
typeof formatId === "string" ? formatId : void 0
|
|
8496
|
+
);
|
|
8497
|
+
if (!sourceMeta) throw new Error("Import source did not provide metadata");
|
|
8498
|
+
const meta = sourceMeta;
|
|
8499
|
+
const privateIdentity = meta.type === "private" ? buildPrivateIdentity(meta.ownerId, sourceMemberIds) : null;
|
|
8500
|
+
const hasStableIdentity = Boolean(meta.groupId || privateIdentity);
|
|
8501
|
+
const stableMatches = [];
|
|
8502
|
+
const trailingMatches = [];
|
|
8503
|
+
for (const sessionId of deps.listSessionIds()) {
|
|
8504
|
+
let db = null;
|
|
8505
|
+
try {
|
|
8506
|
+
db = deps.openReadonly(sessionId);
|
|
8507
|
+
if (!isChatSessionDb(db)) continue;
|
|
8508
|
+
const candidate = getSessionMeta(db);
|
|
8509
|
+
if (candidate?.platform !== meta.platform || candidate.type !== meta.type) continue;
|
|
8510
|
+
const groupMatches = Boolean(meta.groupId && candidate.groupId === meta.groupId);
|
|
8511
|
+
const memberRows = privateIdentity ? db.prepare("SELECT platform_id FROM member WHERE LOWER(platform_id) != 'system'").all() : [];
|
|
8512
|
+
const candidatePrivateIdentity = privateIdentity ? buildPrivateIdentity(
|
|
8513
|
+
candidate.ownerId,
|
|
8514
|
+
memberRows.map((row) => row.platform_id)
|
|
8515
|
+
) : null;
|
|
8516
|
+
if (groupMatches || privateIdentity && candidatePrivateIdentity === privateIdentity) {
|
|
8517
|
+
stableMatches.push(sessionId);
|
|
8518
|
+
}
|
|
8519
|
+
if (sourceWindows.size > 0) {
|
|
8520
|
+
const rows = db.prepare(
|
|
8521
|
+
`SELECT msg.ts, member.platform_id, msg.type, msg.content
|
|
8522
|
+
FROM message msg
|
|
8523
|
+
JOIN member ON member.id = msg.sender_id
|
|
8524
|
+
WHERE msg.type NOT IN (?, ?)
|
|
8525
|
+
AND NULLIF(TRIM(msg.content), '') IS NOT NULL
|
|
8526
|
+
ORDER BY msg.ts DESC, msg.id DESC
|
|
8527
|
+
LIMIT ?`
|
|
8528
|
+
).all(80 /* SYSTEM */, 81 /* RECALL */, MATCH_WINDOW_SIZE);
|
|
8529
|
+
if (rows.length === MATCH_WINDOW_SIZE) {
|
|
8530
|
+
const signature = rows.reverse().map((row) => generateMessageKey(row.ts, row.platform_id, row.content)).join("");
|
|
8531
|
+
if (sourceWindows.has(signature)) trailingMatches.push(sessionId);
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8534
|
+
} catch (error) {
|
|
8535
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8536
|
+
throw new Error(`Failed to inspect import candidate ${JSON.stringify(sessionId)}: ${detail}`, {
|
|
8537
|
+
cause: error
|
|
8538
|
+
});
|
|
8539
|
+
} finally {
|
|
8540
|
+
db?.close();
|
|
8541
|
+
}
|
|
8542
|
+
}
|
|
8543
|
+
if (hasStableIdentity) {
|
|
8544
|
+
if (meta.sourceSessionId && stableMatches.includes(meta.sourceSessionId)) {
|
|
8545
|
+
return { action: "incremental", sessionId: meta.sourceSessionId, matchedBy: "source-session-id" };
|
|
8546
|
+
}
|
|
8547
|
+
if (stableMatches.length === 1) {
|
|
8548
|
+
return { action: "incremental", sessionId: stableMatches[0], matchedBy: "stable-id" };
|
|
8549
|
+
}
|
|
8364
8550
|
}
|
|
8551
|
+
if (meta.sourceSessionId && trailingMatches.includes(meta.sourceSessionId)) {
|
|
8552
|
+
return { action: "incremental", sessionId: meta.sourceSessionId, matchedBy: "source-session-id" };
|
|
8553
|
+
}
|
|
8554
|
+
if (trailingMatches.length === 1) {
|
|
8555
|
+
return { action: "incremental", sessionId: trailingMatches[0], matchedBy: "trailing-messages" };
|
|
8556
|
+
}
|
|
8557
|
+
const ambiguous = stableMatches.length > 1 || trailingMatches.length > 1;
|
|
8558
|
+
return { action: "create", reason: ambiguous ? "ambiguous" : "no-match" };
|
|
8365
8559
|
}
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8560
|
+
|
|
8561
|
+
// ../../packages/node-runtime/src/import/auto-importer.ts
|
|
8562
|
+
async function planAutoImport(filePath, deps, options2) {
|
|
8563
|
+
if (options2.explicitSessionId) {
|
|
8564
|
+
if (!isValidImportSessionId(options2.explicitSessionId)) {
|
|
8565
|
+
throw new Error("sessionId contains invalid characters");
|
|
8566
|
+
}
|
|
8567
|
+
return deps.sessionExists(options2.explicitSessionId) ? { action: "incremental", sessionId: options2.explicitSessionId } : { action: "create", sessionId: options2.explicitSessionId };
|
|
8568
|
+
}
|
|
8569
|
+
const decision = await (deps.resolveTarget ?? resolveAutoImportTarget)(
|
|
8570
|
+
filePath,
|
|
8571
|
+
deps,
|
|
8572
|
+
options2.formatOptions
|
|
8573
|
+
);
|
|
8574
|
+
return decision.action === "incremental" ? { action: "incremental", sessionId: decision.sessionId, matchedBy: decision.matchedBy } : { action: "create", reason: decision.reason };
|
|
8372
8575
|
}
|
|
8373
|
-
function
|
|
8576
|
+
function mapCreateResult(result, createReason) {
|
|
8577
|
+
if (!result.success || !result.sessionId) {
|
|
8578
|
+
return { success: false, error: result.error, diagnostics: result.diagnostics };
|
|
8579
|
+
}
|
|
8374
8580
|
return {
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8581
|
+
success: true,
|
|
8582
|
+
sessionId: result.sessionId,
|
|
8583
|
+
importMode: "created",
|
|
8584
|
+
...createReason ? { createReason } : {},
|
|
8585
|
+
newMessageCount: result.diagnostics?.messagesWritten ?? 0,
|
|
8586
|
+
duplicateCount: result.diagnostics?.duplicateCount ?? 0,
|
|
8587
|
+
batch: result.diagnostics ? {
|
|
8588
|
+
receivedCount: result.diagnostics.messagesReceived,
|
|
8589
|
+
writtenCount: result.diagnostics.messagesWritten,
|
|
8590
|
+
duplicateCount: result.diagnostics.duplicateCount
|
|
8591
|
+
} : void 0,
|
|
8592
|
+
diagnostics: result.diagnostics
|
|
8380
8593
|
};
|
|
8381
8594
|
}
|
|
8382
|
-
function
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8595
|
+
function mapIncrementalResult(sessionId, result, matchedBy) {
|
|
8596
|
+
if (!result.success) return { success: false, sessionId, error: result.error };
|
|
8597
|
+
return {
|
|
8598
|
+
success: true,
|
|
8599
|
+
sessionId,
|
|
8600
|
+
importMode: "incremental",
|
|
8601
|
+
...matchedBy ? { matchedBy } : {},
|
|
8602
|
+
newMessageCount: result.newMessageCount,
|
|
8603
|
+
duplicateCount: result.batch?.duplicateCount ?? 0,
|
|
8604
|
+
batch: result.batch,
|
|
8605
|
+
session: result.session,
|
|
8606
|
+
updates: result.updates
|
|
8607
|
+
};
|
|
8608
|
+
}
|
|
8609
|
+
async function autoImportFile(filePath, deps, options2 = {}) {
|
|
8610
|
+
try {
|
|
8611
|
+
if (!options2.explicitSessionId) {
|
|
8612
|
+
appLogger.info("import", "Automatic session matching started", {
|
|
8613
|
+
candidateCount: deps.listSessionIds().length
|
|
8614
|
+
});
|
|
8615
|
+
}
|
|
8616
|
+
const plan = await planAutoImport(filePath, deps, options2);
|
|
8617
|
+
if (plan.action === "incremental") {
|
|
8618
|
+
const result2 = mapIncrementalResult(
|
|
8619
|
+
plan.sessionId,
|
|
8620
|
+
await deps.appendSession(plan.sessionId, filePath, options2.formatOptions),
|
|
8621
|
+
plan.matchedBy
|
|
8622
|
+
);
|
|
8623
|
+
appLogger.info("import", "Incremental import completed", {
|
|
8624
|
+
sessionId: plan.sessionId,
|
|
8625
|
+
matchedBy: plan.matchedBy,
|
|
8626
|
+
success: result2.success,
|
|
8627
|
+
newMessageCount: result2.newMessageCount,
|
|
8628
|
+
duplicateCount: result2.duplicateCount
|
|
8629
|
+
});
|
|
8630
|
+
return result2;
|
|
8631
|
+
}
|
|
8632
|
+
const result = mapCreateResult(
|
|
8633
|
+
await deps.createSession(filePath, options2.formatOptions, plan.sessionId),
|
|
8634
|
+
plan.reason
|
|
8391
8635
|
);
|
|
8392
|
-
|
|
8636
|
+
appLogger.info("import", "Import created a new session", {
|
|
8637
|
+
reason: plan.reason,
|
|
8638
|
+
sessionId: result.sessionId,
|
|
8639
|
+
success: result.success
|
|
8640
|
+
});
|
|
8641
|
+
return result;
|
|
8642
|
+
} catch (error) {
|
|
8643
|
+
appLogger.error("import", "Automatic import failed", error);
|
|
8644
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8647
|
+
async function analyzeAutoImportFile(filePath, deps, options2 = {}) {
|
|
8648
|
+
try {
|
|
8649
|
+
const plan = await planAutoImport(filePath, deps, options2);
|
|
8650
|
+
if (plan.action === "incremental") {
|
|
8651
|
+
const analysis2 = await deps.analyzeAppendSession(plan.sessionId, filePath, options2.formatOptions);
|
|
8652
|
+
if (analysis2.error) return { success: false, error: analysis2.error };
|
|
8653
|
+
const result2 = {
|
|
8654
|
+
success: true,
|
|
8655
|
+
importMode: "incremental",
|
|
8656
|
+
sessionId: plan.sessionId,
|
|
8657
|
+
...plan.matchedBy ? { matchedBy: plan.matchedBy } : {},
|
|
8658
|
+
totalMessageCount: analysis2.totalInFile,
|
|
8659
|
+
newMessageCount: analysis2.newMessageCount,
|
|
8660
|
+
duplicateCount: analysis2.duplicateCount
|
|
8661
|
+
};
|
|
8662
|
+
appLogger.info("import", "Incremental import analysis completed", {
|
|
8663
|
+
sessionId: plan.sessionId,
|
|
8664
|
+
matchedBy: plan.matchedBy,
|
|
8665
|
+
newMessageCount: analysis2.newMessageCount,
|
|
8666
|
+
duplicateCount: analysis2.duplicateCount
|
|
8667
|
+
});
|
|
8668
|
+
return result2;
|
|
8669
|
+
}
|
|
8670
|
+
const analysis = await deps.analyzeCreateSession(filePath, options2.formatOptions);
|
|
8671
|
+
if (analysis.error) return { success: false, error: analysis.error };
|
|
8672
|
+
const result = {
|
|
8673
|
+
success: true,
|
|
8674
|
+
importMode: "created",
|
|
8675
|
+
...plan.sessionId ? { sessionId: plan.sessionId } : {},
|
|
8676
|
+
...plan.reason ? { createReason: plan.reason } : {},
|
|
8677
|
+
totalMessageCount: analysis.totalMessages,
|
|
8678
|
+
newMessageCount: analysis.newMessageCount,
|
|
8679
|
+
duplicateCount: analysis.duplicateCount,
|
|
8680
|
+
totalMemberCount: analysis.totalMembers,
|
|
8681
|
+
meta: analysis.meta
|
|
8682
|
+
};
|
|
8683
|
+
appLogger.info("import", "New session import analysis completed", {
|
|
8684
|
+
reason: plan.reason,
|
|
8685
|
+
messageCount: analysis.totalMessages,
|
|
8686
|
+
memberCount: analysis.totalMembers
|
|
8687
|
+
});
|
|
8688
|
+
return result;
|
|
8689
|
+
} catch (error) {
|
|
8690
|
+
appLogger.error("import", "Automatic import analysis failed", error);
|
|
8691
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
|
|
8695
|
+
// ../../packages/node-runtime/src/import/import-lock.ts
|
|
8696
|
+
import * as fs21 from "fs";
|
|
8697
|
+
import * as path16 from "path";
|
|
8698
|
+
import { randomUUID } from "crypto";
|
|
8699
|
+
var IMPORT_LOCK_FILENAME = ".chatlab-import.lock";
|
|
8700
|
+
var IMPORT_IN_PROGRESS_ERROR_KEY = "error.import_in_progress";
|
|
8701
|
+
var ImportInProgressError = class extends Error {
|
|
8702
|
+
code = "IMPORT_IN_PROGRESS";
|
|
8703
|
+
errorKey = IMPORT_IN_PROGRESS_ERROR_KEY;
|
|
8704
|
+
constructor() {
|
|
8705
|
+
super("Another import is already in progress");
|
|
8706
|
+
this.name = "ImportInProgressError";
|
|
8707
|
+
}
|
|
8708
|
+
};
|
|
8709
|
+
function parseOwner(lockPath) {
|
|
8710
|
+
try {
|
|
8711
|
+
const parsed = JSON.parse(fs21.readFileSync(lockPath, "utf-8"));
|
|
8712
|
+
if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.token !== "string" || parsed.token.length === 0 || typeof parsed.startedAt !== "number") {
|
|
8713
|
+
return null;
|
|
8714
|
+
}
|
|
8715
|
+
return parsed;
|
|
8716
|
+
} catch {
|
|
8717
|
+
return null;
|
|
8718
|
+
}
|
|
8719
|
+
}
|
|
8720
|
+
function isProcessRunning(pid) {
|
|
8721
|
+
try {
|
|
8722
|
+
process.kill(pid, 0);
|
|
8723
|
+
return true;
|
|
8724
|
+
} catch (error) {
|
|
8725
|
+
return error.code !== "ESRCH";
|
|
8726
|
+
}
|
|
8727
|
+
}
|
|
8728
|
+
function removeStaleLock(lockPath) {
|
|
8729
|
+
const owner = parseOwner(lockPath);
|
|
8730
|
+
if (owner) {
|
|
8731
|
+
if (isProcessRunning(owner.pid)) return false;
|
|
8732
|
+
} else {
|
|
8733
|
+
try {
|
|
8734
|
+
if (Date.now() - fs21.statSync(lockPath).mtimeMs < 5e3) return false;
|
|
8735
|
+
} catch {
|
|
8736
|
+
return true;
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
try {
|
|
8740
|
+
fs21.unlinkSync(lockPath);
|
|
8741
|
+
appLogger.warn("import-lock", "Removed stale import lock", { ownerPid: owner?.pid });
|
|
8742
|
+
return true;
|
|
8743
|
+
} catch {
|
|
8744
|
+
return false;
|
|
8745
|
+
}
|
|
8746
|
+
}
|
|
8747
|
+
function releaseLock(lockPath, fd, owner) {
|
|
8748
|
+
try {
|
|
8749
|
+
fs21.closeSync(fd);
|
|
8750
|
+
} catch (error) {
|
|
8751
|
+
appLogger.error("import-lock", "Failed to close import lock", error);
|
|
8752
|
+
}
|
|
8753
|
+
try {
|
|
8754
|
+
if (parseOwner(lockPath)?.token !== owner.token) {
|
|
8755
|
+
appLogger.warn("import-lock", "Import lock ownership changed before release");
|
|
8756
|
+
return;
|
|
8757
|
+
}
|
|
8758
|
+
fs21.unlinkSync(lockPath);
|
|
8759
|
+
appLogger.info("import-lock", "Import lock released");
|
|
8760
|
+
} catch (error) {
|
|
8761
|
+
appLogger.error("import-lock", "Failed to release import lock", error);
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
async function withDataDirImportLock(userDataDir, task) {
|
|
8765
|
+
fs21.mkdirSync(userDataDir, { recursive: true });
|
|
8766
|
+
const lockPath = path16.join(userDataDir, IMPORT_LOCK_FILENAME);
|
|
8767
|
+
let fd = null;
|
|
8768
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8769
|
+
try {
|
|
8770
|
+
fd = fs21.openSync(lockPath, "wx");
|
|
8771
|
+
break;
|
|
8772
|
+
} catch (error) {
|
|
8773
|
+
const code = error.code;
|
|
8774
|
+
if (code === "EEXIST" && attempt === 0 && removeStaleLock(lockPath)) continue;
|
|
8775
|
+
if (code === "EEXIST") {
|
|
8776
|
+
appLogger.warn("import-lock", "Rejected concurrent import");
|
|
8777
|
+
throw new ImportInProgressError();
|
|
8778
|
+
}
|
|
8779
|
+
throw error;
|
|
8780
|
+
}
|
|
8781
|
+
}
|
|
8782
|
+
if (fd === null) throw new ImportInProgressError();
|
|
8783
|
+
const owner = {
|
|
8784
|
+
pid: process.pid,
|
|
8785
|
+
token: randomUUID(),
|
|
8786
|
+
startedAt: Date.now()
|
|
8787
|
+
};
|
|
8788
|
+
try {
|
|
8789
|
+
fs21.writeFileSync(fd, JSON.stringify(owner), "utf-8");
|
|
8790
|
+
fs21.fsyncSync(fd);
|
|
8791
|
+
appLogger.info("import-lock", "Import lock acquired");
|
|
8792
|
+
return await task();
|
|
8793
|
+
} finally {
|
|
8794
|
+
releaseLock(lockPath, fd, owner);
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
|
|
8798
|
+
// ../../packages/node-runtime/src/import/archive/errors.ts
|
|
8799
|
+
var ArchiveImportError = class extends Error {
|
|
8800
|
+
constructor(code, message, options2) {
|
|
8801
|
+
super(message, options2);
|
|
8802
|
+
this.code = code;
|
|
8803
|
+
this.name = "ArchiveImportError";
|
|
8804
|
+
}
|
|
8805
|
+
code;
|
|
8806
|
+
};
|
|
8807
|
+
|
|
8808
|
+
// ../../packages/node-runtime/src/import/archive/archive-reader.ts
|
|
8809
|
+
var import_yauzl = __toESM(require_yauzl(), 1);
|
|
8810
|
+
import * as fs22 from "fs";
|
|
8811
|
+
import * as path17 from "path";
|
|
8812
|
+
import { pipeline } from "stream/promises";
|
|
8813
|
+
var DEFAULT_MAX_ENTRIES = 1e5;
|
|
8814
|
+
var DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024;
|
|
8815
|
+
var DEFAULT_MAX_COMPRESSION_RATIO = 1e4;
|
|
8816
|
+
function validateArchiveEntryName(fileName) {
|
|
8817
|
+
if (fileName.startsWith("/") || /^[A-Za-z]:\//.test(fileName) || fileName.includes("\\") || fileName.split("/").includes("..")) {
|
|
8818
|
+
throw new ArchiveImportError("error.archive_unsafe_path", `Unsafe archive entry path: ${fileName}`);
|
|
8819
|
+
}
|
|
8820
|
+
}
|
|
8821
|
+
function mapArchiveError(error) {
|
|
8822
|
+
if (error instanceof ArchiveImportError) return error;
|
|
8823
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8824
|
+
return new ArchiveImportError("error.archive_corrupt", `Invalid ZIP archive: ${message}`, {
|
|
8825
|
+
cause: error
|
|
8826
|
+
});
|
|
8827
|
+
}
|
|
8828
|
+
function toSummary(entry) {
|
|
8829
|
+
return {
|
|
8830
|
+
name: entry.fileName,
|
|
8831
|
+
compressedSize: entry.compressedSize,
|
|
8832
|
+
uncompressedSize: entry.uncompressedSize,
|
|
8833
|
+
compressionMethod: entry.compressionMethod,
|
|
8834
|
+
isDirectory: entry.fileName.endsWith("/")
|
|
8835
|
+
};
|
|
8836
|
+
}
|
|
8837
|
+
function validateEntry(entry, options2) {
|
|
8838
|
+
validateArchiveEntryName(entry.fileName);
|
|
8839
|
+
if (entry.isEncrypted()) {
|
|
8840
|
+
throw new ArchiveImportError("error.archive_encrypted", `Encrypted ZIP entry is not supported: ${entry.fileName}`);
|
|
8841
|
+
}
|
|
8842
|
+
if (!entry.canDecodeFileData() || entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
|
|
8843
|
+
throw new ArchiveImportError(
|
|
8844
|
+
"error.archive_unsupported",
|
|
8845
|
+
`Unsupported ZIP compression method for entry: ${entry.fileName}`
|
|
8846
|
+
);
|
|
8847
|
+
}
|
|
8393
8848
|
if (entry.uncompressedSize > options2.maxEntryBytes) {
|
|
8394
8849
|
throw new ArchiveImportError("error.archive_limit_exceeded", `ZIP entry exceeds the size limit: ${entry.fileName}`);
|
|
8395
8850
|
}
|
|
@@ -8472,8 +8927,8 @@ var ZipArchiveReader = class {
|
|
|
8472
8927
|
await this.visitEntries(async (entry, openStream) => {
|
|
8473
8928
|
const destination = targets.get(entry.name);
|
|
8474
8929
|
if (!destination || entry.isDirectory) return;
|
|
8475
|
-
|
|
8476
|
-
await pipeline(await openStream(),
|
|
8930
|
+
fs22.mkdirSync(path17.dirname(destination), { recursive: true });
|
|
8931
|
+
await pipeline(await openStream(), fs22.createWriteStream(destination, { flags: "wx" }));
|
|
8477
8932
|
remaining.delete(entry.name);
|
|
8478
8933
|
});
|
|
8479
8934
|
if (remaining.size > 0) {
|
|
@@ -8490,8 +8945,8 @@ var import_stream_chain9 = __toESM(require_stream_chain(), 1);
|
|
|
8490
8945
|
var import_stream_json9 = __toESM(require_stream_json(), 1);
|
|
8491
8946
|
var import_Pick8 = __toESM(require_Pick(), 1);
|
|
8492
8947
|
var import_StreamValues9 = __toESM(require_StreamValues(), 1);
|
|
8493
|
-
import * as
|
|
8494
|
-
import * as
|
|
8948
|
+
import * as fs23 from "fs";
|
|
8949
|
+
import * as path18 from "path";
|
|
8495
8950
|
var { chain: chain9 } = import_stream_chain9.default;
|
|
8496
8951
|
var { parser: parser10 } = import_stream_json9.default;
|
|
8497
8952
|
var { pick: pick8 } = import_Pick8.default;
|
|
@@ -8620,16 +9075,16 @@ var GoogleChatTakeoutResolver = class {
|
|
|
8620
9075
|
`Google Chat conversation is missing required files: ${chat.chatId}`
|
|
8621
9076
|
);
|
|
8622
9077
|
}
|
|
8623
|
-
|
|
9078
|
+
fs23.mkdirSync(targetDir, { recursive: true });
|
|
8624
9079
|
await reader.pipeEntries(
|
|
8625
9080
|
/* @__PURE__ */ new Map([
|
|
8626
|
-
[userInfoEntry,
|
|
8627
|
-
[groupInfoEntry,
|
|
8628
|
-
[messagesEntry,
|
|
9081
|
+
[userInfoEntry, path18.join(targetDir, "user_info.json")],
|
|
9082
|
+
[groupInfoEntry, path18.join(targetDir, "group_info.json")],
|
|
9083
|
+
[messagesEntry, path18.join(targetDir, "messages.json")]
|
|
8629
9084
|
])
|
|
8630
9085
|
);
|
|
8631
|
-
const manifestPath =
|
|
8632
|
-
|
|
9086
|
+
const manifestPath = path18.join(targetDir, "google-chat-import.json");
|
|
9087
|
+
fs23.writeFileSync(
|
|
8633
9088
|
manifestPath,
|
|
8634
9089
|
JSON.stringify(
|
|
8635
9090
|
{
|
|
@@ -8652,9 +9107,9 @@ var GoogleChatTakeoutResolver = class {
|
|
|
8652
9107
|
};
|
|
8653
9108
|
|
|
8654
9109
|
// ../../packages/node-runtime/src/import/archive/source-manager.ts
|
|
8655
|
-
import * as
|
|
8656
|
-
import * as
|
|
8657
|
-
import { randomUUID } from "crypto";
|
|
9110
|
+
import * as fs24 from "fs";
|
|
9111
|
+
import * as path19 from "path";
|
|
9112
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
8658
9113
|
var DEFAULT_TTL_MS = 30 * 60 * 1e3;
|
|
8659
9114
|
var ArchiveImportSourceManager = class {
|
|
8660
9115
|
records = /* @__PURE__ */ new Map();
|
|
@@ -8664,11 +9119,11 @@ var ArchiveImportSourceManager = class {
|
|
|
8664
9119
|
now;
|
|
8665
9120
|
resolvers;
|
|
8666
9121
|
constructor(options2 = {}) {
|
|
8667
|
-
this.tempRoot = options2.tempRoot ??
|
|
9122
|
+
this.tempRoot = options2.tempRoot ?? path19.join(process.cwd(), ".chatlab-import-sources");
|
|
8668
9123
|
this.ttlMs = options2.ttlMs ?? DEFAULT_TTL_MS;
|
|
8669
9124
|
this.now = options2.now ?? Date.now;
|
|
8670
9125
|
this.resolvers = options2.resolvers ?? [new GoogleChatTakeoutResolver()];
|
|
8671
|
-
|
|
9126
|
+
fs24.mkdirSync(this.tempRoot, { recursive: true });
|
|
8672
9127
|
}
|
|
8673
9128
|
prepareLocalArchive(archivePath) {
|
|
8674
9129
|
return this.prepareArchive(archivePath, false);
|
|
@@ -8691,7 +9146,7 @@ var ArchiveImportSourceManager = class {
|
|
|
8691
9146
|
const resolver = matches[0];
|
|
8692
9147
|
const chats = await resolver.scan(reader);
|
|
8693
9148
|
const createdAt = this.now();
|
|
8694
|
-
const sourceId =
|
|
9149
|
+
const sourceId = randomUUID2();
|
|
8695
9150
|
const descriptor = {
|
|
8696
9151
|
sourceId,
|
|
8697
9152
|
formatId: resolver.id,
|
|
@@ -8725,7 +9180,7 @@ var ArchiveImportSourceManager = class {
|
|
|
8725
9180
|
`Archive conversation was not found: ${chatId}`
|
|
8726
9181
|
);
|
|
8727
9182
|
}
|
|
8728
|
-
const materializedDir =
|
|
9183
|
+
const materializedDir = fs24.mkdtempSync(path19.join(this.tempRoot, `materialized-${sourceId}-`));
|
|
8729
9184
|
try {
|
|
8730
9185
|
const reader = new ZipArchiveReader(record.archivePath);
|
|
8731
9186
|
const materialized = await record.resolver.materialize(reader, chat, materializedDir);
|
|
@@ -8776,7 +9231,7 @@ var ArchiveImportSourceManager = class {
|
|
|
8776
9231
|
}
|
|
8777
9232
|
removePath(targetPath) {
|
|
8778
9233
|
try {
|
|
8779
|
-
|
|
9234
|
+
fs24.rmSync(targetPath, { recursive: true, force: true });
|
|
8780
9235
|
} catch {
|
|
8781
9236
|
}
|
|
8782
9237
|
}
|
|
@@ -8846,12 +9301,12 @@ ${rawErrorLabel}: ${details}`;
|
|
|
8846
9301
|
}
|
|
8847
9302
|
|
|
8848
9303
|
// ../../packages/node-runtime/src/ai/assistant-parser.ts
|
|
8849
|
-
import * as
|
|
9304
|
+
import * as path20 from "path";
|
|
8850
9305
|
import matter from "gray-matter";
|
|
8851
9306
|
function parseAssistantFile(content, filePath) {
|
|
8852
9307
|
try {
|
|
8853
9308
|
const { data: fm, content: body } = matter(content);
|
|
8854
|
-
const id = fm.id ??
|
|
9309
|
+
const id = fm.id ?? path20.basename(filePath, ".md");
|
|
8855
9310
|
const name = fm.name;
|
|
8856
9311
|
if (!name) return null;
|
|
8857
9312
|
return {
|
|
@@ -8930,8 +9385,8 @@ var AssistantManager = class {
|
|
|
8930
9385
|
}
|
|
8931
9386
|
// ==================== Init ====================
|
|
8932
9387
|
init() {
|
|
8933
|
-
const { fs:
|
|
8934
|
-
|
|
9388
|
+
const { fs: fs35, assistantsDir } = this.deps;
|
|
9389
|
+
fs35.ensureDir(assistantsDir);
|
|
8935
9390
|
const generalCreated = this.ensureGeneralAssistants();
|
|
8936
9391
|
this.loadAll();
|
|
8937
9392
|
this.initialized = true;
|
|
@@ -8942,26 +9397,26 @@ var AssistantManager = class {
|
|
|
8942
9397
|
return { total: this.cache.size, generalCreated };
|
|
8943
9398
|
}
|
|
8944
9399
|
ensureGeneralAssistants() {
|
|
8945
|
-
const { fs:
|
|
9400
|
+
const { fs: fs35, assistantsDir } = this.deps;
|
|
8946
9401
|
let anyCreated = false;
|
|
8947
9402
|
for (const id of this.generalIds) {
|
|
8948
9403
|
const config = this.getBuiltinConfig(id);
|
|
8949
9404
|
if (!config) continue;
|
|
8950
|
-
const filePath =
|
|
8951
|
-
if (
|
|
8952
|
-
|
|
9405
|
+
const filePath = fs35.joinPath(assistantsDir, `${id}.md`);
|
|
9406
|
+
if (fs35.fileExists(filePath)) continue;
|
|
9407
|
+
fs35.writeFile(filePath, serializeAssistant({ ...config, builtinId: config.id }));
|
|
8953
9408
|
anyCreated = true;
|
|
8954
9409
|
}
|
|
8955
9410
|
return anyCreated;
|
|
8956
9411
|
}
|
|
8957
9412
|
loadAll() {
|
|
8958
|
-
const { fs:
|
|
9413
|
+
const { fs: fs35, assistantsDir } = this.deps;
|
|
8959
9414
|
this.cache.clear();
|
|
8960
|
-
const files =
|
|
9415
|
+
const files = fs35.listFiles(assistantsDir, ".md");
|
|
8961
9416
|
for (const file of files) {
|
|
8962
9417
|
try {
|
|
8963
|
-
const filePath =
|
|
8964
|
-
const content =
|
|
9418
|
+
const filePath = fs35.joinPath(assistantsDir, file);
|
|
9419
|
+
const content = fs35.readFile(filePath);
|
|
8965
9420
|
const config = parseAssistantFile(content, filePath);
|
|
8966
9421
|
if (config) {
|
|
8967
9422
|
this.cache.set(config.id, config);
|
|
@@ -9073,12 +9528,12 @@ var AssistantManager = class {
|
|
|
9073
9528
|
};
|
|
9074
9529
|
|
|
9075
9530
|
// ../../packages/node-runtime/src/ai/skill-parser.ts
|
|
9076
|
-
import * as
|
|
9531
|
+
import * as path21 from "path";
|
|
9077
9532
|
import matter2 from "gray-matter";
|
|
9078
9533
|
function parseSkillFile(content, filePath) {
|
|
9079
9534
|
try {
|
|
9080
9535
|
const { data: fm, content: prompt } = matter2(content);
|
|
9081
|
-
const id = fm.id ??
|
|
9536
|
+
const id = fm.id ?? path21.basename(filePath, ".md");
|
|
9082
9537
|
const name = fm.name;
|
|
9083
9538
|
if (!name) return null;
|
|
9084
9539
|
return {
|
|
@@ -9097,7 +9552,7 @@ function parseSkillFile(content, filePath) {
|
|
|
9097
9552
|
function extractSkillId(content, filePath) {
|
|
9098
9553
|
try {
|
|
9099
9554
|
const { data: fm } = matter2(content);
|
|
9100
|
-
return fm.id ??
|
|
9555
|
+
return fm.id ?? path21.basename(filePath, ".md");
|
|
9101
9556
|
} catch {
|
|
9102
9557
|
return null;
|
|
9103
9558
|
}
|
|
@@ -9116,8 +9571,8 @@ function validateChatScope(raw) {
|
|
|
9116
9571
|
|
|
9117
9572
|
// ../../packages/node-runtime/src/ai/chats.ts
|
|
9118
9573
|
import Database from "better-sqlite3";
|
|
9119
|
-
import * as
|
|
9120
|
-
import * as
|
|
9574
|
+
import * as fs25 from "fs";
|
|
9575
|
+
import * as path22 from "path";
|
|
9121
9576
|
var DEFAULT_GENERAL_ID = "general_cn";
|
|
9122
9577
|
var defaultLogger = {
|
|
9123
9578
|
warn(_category, message, extra) {
|
|
@@ -9136,14 +9591,14 @@ var AIChatManager = class {
|
|
|
9136
9591
|
this.nativeBinding = options2?.nativeBinding;
|
|
9137
9592
|
}
|
|
9138
9593
|
ensureDir(dir) {
|
|
9139
|
-
if (!
|
|
9140
|
-
|
|
9594
|
+
if (!fs25.existsSync(dir)) {
|
|
9595
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
9141
9596
|
}
|
|
9142
9597
|
}
|
|
9143
9598
|
getDb() {
|
|
9144
9599
|
if (this.db) return this.db;
|
|
9145
9600
|
this.ensureDir(this.aiDataDir);
|
|
9146
|
-
const dbPath =
|
|
9601
|
+
const dbPath = path22.join(this.aiDataDir, "conversations.db");
|
|
9147
9602
|
this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath);
|
|
9148
9603
|
this.db.pragma("journal_mode = WAL");
|
|
9149
9604
|
this.db.exec(`
|
|
@@ -9386,16 +9841,16 @@ var AIChatManager = class {
|
|
|
9386
9841
|
if (allRows.length === 0) return [];
|
|
9387
9842
|
const rowMap = new Map(allRows.map((row) => [row.id, row]));
|
|
9388
9843
|
let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
|
|
9389
|
-
const
|
|
9844
|
+
const path31 = [];
|
|
9390
9845
|
const seen = /* @__PURE__ */ new Set();
|
|
9391
9846
|
while (currentId && !seen.has(currentId)) {
|
|
9392
9847
|
seen.add(currentId);
|
|
9393
9848
|
const row = rowMap.get(currentId);
|
|
9394
9849
|
if (!row) break;
|
|
9395
|
-
|
|
9850
|
+
path31.push(row);
|
|
9396
9851
|
currentId = row.parentId;
|
|
9397
9852
|
}
|
|
9398
|
-
return
|
|
9853
|
+
return path31.length > 0 ? path31.reverse() : allRows;
|
|
9399
9854
|
}
|
|
9400
9855
|
// ==================== 生命周期 ====================
|
|
9401
9856
|
close() {
|
|
@@ -9965,21 +10420,21 @@ var SkillManagerCore = class {
|
|
|
9965
10420
|
}
|
|
9966
10421
|
// ==================== Init ====================
|
|
9967
10422
|
init() {
|
|
9968
|
-
const { fs:
|
|
9969
|
-
|
|
10423
|
+
const { fs: fs35, skillsDir } = this.deps;
|
|
10424
|
+
fs35.ensureDir(skillsDir);
|
|
9970
10425
|
this.loadAll();
|
|
9971
10426
|
this.initialized = true;
|
|
9972
10427
|
this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
|
|
9973
10428
|
return { total: this.cache.size };
|
|
9974
10429
|
}
|
|
9975
10430
|
loadAll() {
|
|
9976
|
-
const { fs:
|
|
10431
|
+
const { fs: fs35, skillsDir } = this.deps;
|
|
9977
10432
|
this.cache.clear();
|
|
9978
|
-
const files =
|
|
10433
|
+
const files = fs35.listFiles(skillsDir, ".md");
|
|
9979
10434
|
for (const file of files) {
|
|
9980
10435
|
try {
|
|
9981
|
-
const filePath =
|
|
9982
|
-
const content =
|
|
10436
|
+
const filePath = fs35.joinPath(skillsDir, file);
|
|
10437
|
+
const content = fs35.readFile(filePath);
|
|
9983
10438
|
const def = parseSkillFile(content, filePath);
|
|
9984
10439
|
if (def) {
|
|
9985
10440
|
this.cache.set(def.id, def);
|
|
@@ -10131,8 +10586,8 @@ var SkillManagerCore = class {
|
|
|
10131
10586
|
};
|
|
10132
10587
|
|
|
10133
10588
|
// ../../packages/node-runtime/src/ai/skill-manager.ts
|
|
10134
|
-
import * as
|
|
10135
|
-
import * as
|
|
10589
|
+
import * as fs26 from "fs";
|
|
10590
|
+
import * as path23 from "path";
|
|
10136
10591
|
var SKILLS_DIR_NAME = "skills";
|
|
10137
10592
|
var defaultLogger2 = {
|
|
10138
10593
|
info: () => {
|
|
@@ -10142,26 +10597,26 @@ var defaultLogger2 = {
|
|
|
10142
10597
|
};
|
|
10143
10598
|
var nodeFs = {
|
|
10144
10599
|
ensureDir(dir) {
|
|
10145
|
-
|
|
10600
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
10146
10601
|
},
|
|
10147
10602
|
listFiles(dir, ext) {
|
|
10148
|
-
if (!
|
|
10149
|
-
return
|
|
10603
|
+
if (!fs26.existsSync(dir)) return [];
|
|
10604
|
+
return fs26.readdirSync(dir).filter((file) => file.endsWith(ext));
|
|
10150
10605
|
},
|
|
10151
10606
|
readFile(filePath) {
|
|
10152
|
-
return
|
|
10607
|
+
return fs26.readFileSync(filePath, "utf-8");
|
|
10153
10608
|
},
|
|
10154
10609
|
writeFile(filePath, content) {
|
|
10155
|
-
|
|
10610
|
+
fs26.writeFileSync(filePath, content, "utf-8");
|
|
10156
10611
|
},
|
|
10157
10612
|
deleteFile(filePath) {
|
|
10158
|
-
|
|
10613
|
+
fs26.unlinkSync(filePath);
|
|
10159
10614
|
},
|
|
10160
10615
|
fileExists(filePath) {
|
|
10161
|
-
return
|
|
10616
|
+
return fs26.existsSync(filePath);
|
|
10162
10617
|
},
|
|
10163
10618
|
joinPath(...parts) {
|
|
10164
|
-
return
|
|
10619
|
+
return path23.join(...parts);
|
|
10165
10620
|
}
|
|
10166
10621
|
};
|
|
10167
10622
|
var SkillManager = class {
|
|
@@ -10171,7 +10626,7 @@ var SkillManager = class {
|
|
|
10171
10626
|
this.logger = logger ?? defaultLogger2;
|
|
10172
10627
|
this.core = new SkillManagerCore({
|
|
10173
10628
|
fs: nodeFs,
|
|
10174
|
-
skillsDir:
|
|
10629
|
+
skillsDir: path23.join(aiDataDir, SKILLS_DIR_NAME),
|
|
10175
10630
|
builtinRawSkills: [],
|
|
10176
10631
|
logger: {
|
|
10177
10632
|
info: () => {
|
|
@@ -14001,9 +14456,9 @@ var localeMap = {
|
|
|
14001
14456
|
"ja-JP": ja_JP_default,
|
|
14002
14457
|
"zh-TW": zh_TW_default
|
|
14003
14458
|
};
|
|
14004
|
-
function getNestedValue(obj,
|
|
14459
|
+
function getNestedValue(obj, path31) {
|
|
14005
14460
|
let current = obj;
|
|
14006
|
-
for (const segment2 of
|
|
14461
|
+
for (const segment2 of path31.split(".")) {
|
|
14007
14462
|
if (current == null || typeof current !== "object") return void 0;
|
|
14008
14463
|
current = current[segment2];
|
|
14009
14464
|
}
|
|
@@ -15156,7 +15611,13 @@ function queryMessages(db, timeFilter) {
|
|
|
15156
15611
|
const sql = `
|
|
15157
15612
|
SELECT msg.ts,
|
|
15158
15613
|
COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,
|
|
15159
|
-
|
|
15614
|
+
m.platform_id as sender,
|
|
15615
|
+
COALESCE(msg.sender_account_name, m.account_name, m.platform_id) as accountName,
|
|
15616
|
+
COALESCE(msg.sender_group_nickname, m.group_nickname) as groupNickname,
|
|
15617
|
+
msg.type,
|
|
15618
|
+
msg.content,
|
|
15619
|
+
msg.platform_message_id as platformMessageId,
|
|
15620
|
+
msg.reply_to_message_id as replyToMessageId
|
|
15160
15621
|
FROM message msg
|
|
15161
15622
|
JOIN member m ON msg.sender_id = m.id
|
|
15162
15623
|
${hasFilter ? "WHERE msg.ts >= ? AND msg.ts <= ?" : ""}
|
|
@@ -15168,9 +15629,6 @@ function queryMessages(db, timeFilter) {
|
|
|
15168
15629
|
}
|
|
15169
15630
|
return db.prepare(sql).all(...params);
|
|
15170
15631
|
}
|
|
15171
|
-
function formatTime(ts) {
|
|
15172
|
-
return new Date(ts * 1e3).toLocaleString();
|
|
15173
|
-
}
|
|
15174
15632
|
function formatTimeShort(ts) {
|
|
15175
15633
|
return new Date(ts * 1e3).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
15176
15634
|
}
|
|
@@ -15190,16 +15648,69 @@ function exportAsTxt(messages, sessionName) {
|
|
|
15190
15648
|
}
|
|
15191
15649
|
return lines.join("\n");
|
|
15192
15650
|
}
|
|
15193
|
-
function
|
|
15651
|
+
function parseOptionalArray(value) {
|
|
15652
|
+
if (!value) return void 0;
|
|
15653
|
+
try {
|
|
15654
|
+
const parsed = JSON.parse(value);
|
|
15655
|
+
return Array.isArray(parsed) && parsed.length > 0 ? parsed : void 0;
|
|
15656
|
+
} catch {
|
|
15657
|
+
return void 0;
|
|
15658
|
+
}
|
|
15659
|
+
}
|
|
15660
|
+
function exportAsJson(db, messages, sessionName, sourceSessionId) {
|
|
15661
|
+
const meta = db.prepare(
|
|
15662
|
+
`SELECT name,
|
|
15663
|
+
platform,
|
|
15664
|
+
type,
|
|
15665
|
+
group_id as groupId,
|
|
15666
|
+
group_avatar as groupAvatar,
|
|
15667
|
+
owner_id as ownerId
|
|
15668
|
+
FROM meta
|
|
15669
|
+
LIMIT 1`
|
|
15670
|
+
).get();
|
|
15671
|
+
if (!meta) throw new Error("Cannot read session metadata");
|
|
15672
|
+
const members = db.prepare(
|
|
15673
|
+
`SELECT platform_id as platformId,
|
|
15674
|
+
account_name as accountName,
|
|
15675
|
+
group_nickname as groupNickname,
|
|
15676
|
+
aliases,
|
|
15677
|
+
avatar,
|
|
15678
|
+
roles
|
|
15679
|
+
FROM member
|
|
15680
|
+
ORDER BY id ASC`
|
|
15681
|
+
).all();
|
|
15194
15682
|
const data = {
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15683
|
+
chatlab: {
|
|
15684
|
+
version: "0.0.2",
|
|
15685
|
+
exportedAt: Math.floor(Date.now() / 1e3),
|
|
15686
|
+
generator: "ChatLab"
|
|
15687
|
+
},
|
|
15688
|
+
meta: {
|
|
15689
|
+
name: meta.name || sessionName,
|
|
15690
|
+
platform: meta.platform,
|
|
15691
|
+
type: meta.type,
|
|
15692
|
+
groupId: meta.groupId || void 0,
|
|
15693
|
+
groupAvatar: meta.groupAvatar || void 0,
|
|
15694
|
+
ownerId: meta.ownerId || void 0,
|
|
15695
|
+
sourceSessionId
|
|
15696
|
+
},
|
|
15697
|
+
members: members.map((member) => ({
|
|
15698
|
+
platformId: member.platformId,
|
|
15699
|
+
accountName: member.accountName || member.platformId,
|
|
15700
|
+
groupNickname: member.groupNickname || void 0,
|
|
15701
|
+
aliases: parseOptionalArray(member.aliases),
|
|
15702
|
+
avatar: member.avatar || void 0,
|
|
15703
|
+
roles: parseOptionalArray(member.roles)
|
|
15704
|
+
})),
|
|
15198
15705
|
messages: messages.map((msg) => ({
|
|
15706
|
+
sender: msg.sender,
|
|
15707
|
+
accountName: msg.accountName,
|
|
15708
|
+
groupNickname: msg.groupNickname || void 0,
|
|
15199
15709
|
timestamp: msg.ts,
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15710
|
+
type: msg.type,
|
|
15711
|
+
content: msg.content,
|
|
15712
|
+
platformMessageId: msg.platformMessageId || void 0,
|
|
15713
|
+
replyToMessageId: msg.replyToMessageId || void 0
|
|
15203
15714
|
}))
|
|
15204
15715
|
};
|
|
15205
15716
|
return JSON.stringify(data, null, 2);
|
|
@@ -15250,7 +15761,7 @@ function exportWithFormat(params, openDatabase) {
|
|
|
15250
15761
|
content = exportAsTxt(messages, params.sessionName);
|
|
15251
15762
|
break;
|
|
15252
15763
|
case "json":
|
|
15253
|
-
content = exportAsJson(messages, params.sessionName);
|
|
15764
|
+
content = exportAsJson(db, messages, params.sessionName, params.sessionId);
|
|
15254
15765
|
break;
|
|
15255
15766
|
case "markdown":
|
|
15256
15767
|
content = exportAsMarkdown(messages, params.sessionName);
|
|
@@ -15258,8 +15769,15 @@ function exportWithFormat(params, openDatabase) {
|
|
|
15258
15769
|
}
|
|
15259
15770
|
const timestamp = Date.now();
|
|
15260
15771
|
const filename = `${params.sessionName}_export_${timestamp}.${ext}`;
|
|
15772
|
+
appLogger.info("export", "chat export generated", {
|
|
15773
|
+
sessionId: params.sessionId,
|
|
15774
|
+
format: params.format,
|
|
15775
|
+
totalMessages: messages.length,
|
|
15776
|
+
filteredByTime: !!params.timeFilter
|
|
15777
|
+
});
|
|
15261
15778
|
return { success: true, totalMessages: messages.length, content, filename, mimeType: mime };
|
|
15262
15779
|
} catch (error) {
|
|
15780
|
+
appLogger.error("export", "chat export failed", error);
|
|
15263
15781
|
return {
|
|
15264
15782
|
success: false,
|
|
15265
15783
|
error: String(error),
|
|
@@ -15272,8 +15790,8 @@ function exportWithFormat(params, openDatabase) {
|
|
|
15272
15790
|
}
|
|
15273
15791
|
|
|
15274
15792
|
// ../../packages/node-runtime/src/merger/temp-db.ts
|
|
15275
|
-
import * as
|
|
15276
|
-
import * as
|
|
15793
|
+
import * as fs27 from "fs";
|
|
15794
|
+
import * as path24 from "path";
|
|
15277
15795
|
var TEMP_DB_SCHEMA = `
|
|
15278
15796
|
CREATE TABLE IF NOT EXISTS meta (
|
|
15279
15797
|
name TEXT NOT NULL,
|
|
@@ -15518,21 +16036,21 @@ function exportSessionToJson(db) {
|
|
|
15518
16036
|
}
|
|
15519
16037
|
function deleteTempDatabase(dbPath) {
|
|
15520
16038
|
try {
|
|
15521
|
-
if (
|
|
16039
|
+
if (fs27.existsSync(dbPath)) fs27.unlinkSync(dbPath);
|
|
15522
16040
|
const walPath = dbPath + "-wal";
|
|
15523
16041
|
const shmPath = dbPath + "-shm";
|
|
15524
|
-
if (
|
|
15525
|
-
if (
|
|
16042
|
+
if (fs27.existsSync(walPath)) fs27.unlinkSync(walPath);
|
|
16043
|
+
if (fs27.existsSync(shmPath)) fs27.unlinkSync(shmPath);
|
|
15526
16044
|
} catch {
|
|
15527
16045
|
}
|
|
15528
16046
|
}
|
|
15529
16047
|
function cleanupTempDatabases(tempDir) {
|
|
15530
16048
|
try {
|
|
15531
|
-
if (!
|
|
15532
|
-
const files =
|
|
16049
|
+
if (!fs27.existsSync(tempDir)) return;
|
|
16050
|
+
const files = fs27.readdirSync(tempDir);
|
|
15533
16051
|
for (const file of files) {
|
|
15534
16052
|
if (file.startsWith("merge_") && file.endsWith(".db")) {
|
|
15535
|
-
deleteTempDatabase(
|
|
16053
|
+
deleteTempDatabase(path24.join(tempDir, file));
|
|
15536
16054
|
}
|
|
15537
16055
|
}
|
|
15538
16056
|
} catch {
|
|
@@ -15558,7 +16076,21 @@ function buildSession(db, id, dbPath, options2) {
|
|
|
15558
16076
|
if (meta.type === "private") {
|
|
15559
16077
|
memberAvatar = getPrivateChatMemberAvatar(db, meta.name, meta.ownerId);
|
|
15560
16078
|
}
|
|
15561
|
-
|
|
16079
|
+
const ownerMember = meta.ownerId ? db.prepare(
|
|
16080
|
+
`SELECT COALESCE(NULLIF(group_nickname, ''), NULLIF(account_name, ''), platform_id) as displayName
|
|
16081
|
+
FROM member
|
|
16082
|
+
WHERE platform_id = ?
|
|
16083
|
+
LIMIT 1`
|
|
16084
|
+
).get(meta.ownerId) : void 0;
|
|
16085
|
+
let dto = {
|
|
16086
|
+
...info,
|
|
16087
|
+
id,
|
|
16088
|
+
dbPath,
|
|
16089
|
+
memberAvatar,
|
|
16090
|
+
aiConversationCount: 0,
|
|
16091
|
+
ownerName: ownerMember?.displayName ?? null,
|
|
16092
|
+
ownerStatus: !meta.ownerId ? "missing" : ownerMember ? "resolved" : "unresolved"
|
|
16093
|
+
};
|
|
15562
16094
|
if (options2?.enrichSession) {
|
|
15563
16095
|
dto = options2.enrichSession(dto);
|
|
15564
16096
|
}
|
|
@@ -15898,8 +16430,8 @@ function clearSessionOwner(adapter, sessionId) {
|
|
|
15898
16430
|
}
|
|
15899
16431
|
|
|
15900
16432
|
// ../../packages/node-runtime/src/services/contacts/overrides.ts
|
|
15901
|
-
import
|
|
15902
|
-
import
|
|
16433
|
+
import fs28 from "fs";
|
|
16434
|
+
import path25 from "path";
|
|
15903
16435
|
var CONTACT_OVERRIDES_FILE_NAME = "contact-overrides.json";
|
|
15904
16436
|
var CONTACT_OVERRIDES_TMP_PREFIX = "contact-overrides.tmp-";
|
|
15905
16437
|
var CONTACT_OVERRIDES_VERSION = 1;
|
|
@@ -15910,23 +16442,23 @@ function createEmptyContactOverridesFile() {
|
|
|
15910
16442
|
};
|
|
15911
16443
|
}
|
|
15912
16444
|
function getContactOverridesPath(snapshotDir) {
|
|
15913
|
-
return
|
|
16445
|
+
return path25.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
|
|
15914
16446
|
}
|
|
15915
16447
|
function readContactOverrides(snapshotDir) {
|
|
15916
16448
|
const filePath = getContactOverridesPath(snapshotDir);
|
|
15917
|
-
if (!
|
|
16449
|
+
if (!fs28.existsSync(filePath)) return createEmptyContactOverridesFile();
|
|
15918
16450
|
try {
|
|
15919
|
-
return normalizeContactOverrides(JSON.parse(
|
|
16451
|
+
return normalizeContactOverrides(JSON.parse(fs28.readFileSync(filePath, "utf-8")));
|
|
15920
16452
|
} catch (error) {
|
|
15921
16453
|
appLogger.warn("contacts", "contact overrides file is unreadable", error);
|
|
15922
16454
|
return createEmptyContactOverridesFile();
|
|
15923
16455
|
}
|
|
15924
16456
|
}
|
|
15925
16457
|
function writeContactOverrides(snapshotDir, overrides) {
|
|
15926
|
-
if (!
|
|
15927
|
-
const tmpPath =
|
|
15928
|
-
|
|
15929
|
-
|
|
16458
|
+
if (!fs28.existsSync(snapshotDir)) fs28.mkdirSync(snapshotDir, { recursive: true });
|
|
16459
|
+
const tmpPath = path25.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
16460
|
+
fs28.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
|
|
16461
|
+
fs28.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
|
|
15930
16462
|
}
|
|
15931
16463
|
function normalizeContactOverrides(value) {
|
|
15932
16464
|
const input = isRecord2(value) ? value : {};
|
|
@@ -15961,23 +16493,23 @@ function buildContactsSignature(adapter, timeRangePreset) {
|
|
|
15961
16493
|
}
|
|
15962
16494
|
|
|
15963
16495
|
// ../../packages/node-runtime/src/services/contacts/snapshot.ts
|
|
15964
|
-
import
|
|
15965
|
-
import
|
|
16496
|
+
import fs29 from "fs";
|
|
16497
|
+
import path26 from "path";
|
|
15966
16498
|
var CONTACTS_SNAPSHOT_TMP_PREFIX = "contacts-snapshot.tmp-";
|
|
15967
16499
|
function getContactsSnapshotPath(snapshotDir, timeRangePreset) {
|
|
15968
16500
|
const preset = normalizeContactsTimeRangePreset(timeRangePreset);
|
|
15969
|
-
return
|
|
16501
|
+
return path26.join(snapshotDir, `contacts-snapshot-${preset}.json`);
|
|
15970
16502
|
}
|
|
15971
16503
|
function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
15972
16504
|
const snapshotPath = getContactsSnapshotPath(snapshotDir, timeRangePreset);
|
|
15973
|
-
if (!
|
|
16505
|
+
if (!fs29.existsSync(snapshotPath)) return null;
|
|
15974
16506
|
try {
|
|
15975
|
-
return JSON.parse(
|
|
16507
|
+
return JSON.parse(fs29.readFileSync(snapshotPath, "utf-8"));
|
|
15976
16508
|
} catch (error) {
|
|
15977
16509
|
const ts = options2.now?.() ?? Date.now();
|
|
15978
|
-
const backupPath =
|
|
16510
|
+
const backupPath = path26.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
|
|
15979
16511
|
try {
|
|
15980
|
-
|
|
16512
|
+
fs29.renameSync(snapshotPath, backupPath);
|
|
15981
16513
|
} catch (renameError) {
|
|
15982
16514
|
appLogger.warn("contacts", "failed to backup corrupt contacts snapshot", renameError);
|
|
15983
16515
|
}
|
|
@@ -15986,17 +16518,17 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
|
15986
16518
|
}
|
|
15987
16519
|
}
|
|
15988
16520
|
function writeContactsSnapshot(snapshotDir, snapshot) {
|
|
15989
|
-
if (!
|
|
15990
|
-
const tmpPath =
|
|
15991
|
-
|
|
15992
|
-
|
|
16521
|
+
if (!fs29.existsSync(snapshotDir)) fs29.mkdirSync(snapshotDir, { recursive: true });
|
|
16522
|
+
const tmpPath = path26.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
16523
|
+
fs29.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
16524
|
+
fs29.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
|
|
15993
16525
|
}
|
|
15994
16526
|
function cleanupContactsSnapshotTempFiles(snapshotDir) {
|
|
15995
|
-
if (!
|
|
15996
|
-
for (const name of
|
|
16527
|
+
if (!fs29.existsSync(snapshotDir)) return;
|
|
16528
|
+
for (const name of fs29.readdirSync(snapshotDir)) {
|
|
15997
16529
|
if (!name.startsWith(CONTACTS_SNAPSHOT_TMP_PREFIX)) continue;
|
|
15998
16530
|
try {
|
|
15999
|
-
|
|
16531
|
+
fs29.rmSync(path26.join(snapshotDir, name), { force: true });
|
|
16000
16532
|
} catch (error) {
|
|
16001
16533
|
appLogger.warn("contacts", "failed to remove contacts snapshot temp file", error);
|
|
16002
16534
|
}
|
|
@@ -16494,23 +17026,23 @@ function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
|
|
|
16494
17026
|
}
|
|
16495
17027
|
|
|
16496
17028
|
// ../../packages/node-runtime/src/services/people/relationships/snapshot.ts
|
|
16497
|
-
import
|
|
16498
|
-
import
|
|
17029
|
+
import fs30 from "fs";
|
|
17030
|
+
import path27 from "path";
|
|
16499
17031
|
var PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX = "people-relationships-snapshot.tmp-";
|
|
16500
17032
|
function getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset) {
|
|
16501
17033
|
const preset = normalizePeopleRelationshipsTimeRangePreset(timeRangePreset);
|
|
16502
|
-
return
|
|
17034
|
+
return path27.join(snapshotDir, `graph-snapshot-${preset}.json`);
|
|
16503
17035
|
}
|
|
16504
17036
|
function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
16505
17037
|
const snapshotPath = getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset);
|
|
16506
|
-
if (!
|
|
17038
|
+
if (!fs30.existsSync(snapshotPath)) return null;
|
|
16507
17039
|
try {
|
|
16508
|
-
return JSON.parse(
|
|
17040
|
+
return JSON.parse(fs30.readFileSync(snapshotPath, "utf-8"));
|
|
16509
17041
|
} catch (error) {
|
|
16510
17042
|
const ts = options2.now?.() ?? Date.now();
|
|
16511
|
-
const backupPath =
|
|
17043
|
+
const backupPath = path27.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
|
|
16512
17044
|
try {
|
|
16513
|
-
|
|
17045
|
+
fs30.renameSync(snapshotPath, backupPath);
|
|
16514
17046
|
} catch (renameError) {
|
|
16515
17047
|
appLogger.warn("people-relationships", "failed to backup corrupt people relationships snapshot", renameError);
|
|
16516
17048
|
}
|
|
@@ -16519,17 +17051,17 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
|
|
|
16519
17051
|
}
|
|
16520
17052
|
}
|
|
16521
17053
|
function writePeopleRelationshipsSnapshot(snapshotDir, snapshot) {
|
|
16522
|
-
if (!
|
|
16523
|
-
const tmpPath =
|
|
16524
|
-
|
|
16525
|
-
|
|
17054
|
+
if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
|
|
17055
|
+
const tmpPath = path27.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
17056
|
+
fs30.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
17057
|
+
fs30.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
|
|
16526
17058
|
}
|
|
16527
17059
|
function cleanupPeopleRelationshipsSnapshotTempFiles(snapshotDir) {
|
|
16528
|
-
if (!
|
|
16529
|
-
for (const name of
|
|
17060
|
+
if (!fs30.existsSync(snapshotDir)) return;
|
|
17061
|
+
for (const name of fs30.readdirSync(snapshotDir)) {
|
|
16530
17062
|
if (!name.startsWith(PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX)) continue;
|
|
16531
17063
|
try {
|
|
16532
|
-
|
|
17064
|
+
fs30.rmSync(path27.join(snapshotDir, name), { force: true });
|
|
16533
17065
|
} catch (error) {
|
|
16534
17066
|
appLogger.warn("people-relationships", "failed to remove people relationships snapshot temp file", error);
|
|
16535
17067
|
}
|
|
@@ -17050,9 +17582,310 @@ __export(relationships_exports, {
|
|
|
17050
17582
|
getPeopleRelationshipsFactsCacheDir: () => getPeopleRelationshipsFactsCacheDir
|
|
17051
17583
|
});
|
|
17052
17584
|
|
|
17585
|
+
// ../../packages/node-runtime/src/services/global-insight/service.ts
|
|
17586
|
+
import fs31 from "fs";
|
|
17587
|
+
|
|
17588
|
+
// ../../packages/node-runtime/src/services/global-insight/signature.ts
|
|
17589
|
+
function buildAnnualSummarySignature(adapter, range) {
|
|
17590
|
+
const parts = [
|
|
17591
|
+
`algorithm:${ANNUAL_SUMMARY_ALGORITHM_VERSION}`,
|
|
17592
|
+
`range:${toAnnualSummaryRangeKey(range)}`,
|
|
17593
|
+
`start:${range.startTs}`,
|
|
17594
|
+
`day:${toAnnualSummaryLocalDate(range)}`
|
|
17595
|
+
];
|
|
17596
|
+
for (const sessionId of [...adapter.listSessionIds()].sort()) {
|
|
17597
|
+
parts.push(`${sessionId}:${getDbFileVersion(adapter.getDbPath(sessionId))}`);
|
|
17598
|
+
}
|
|
17599
|
+
return parts.join("|");
|
|
17600
|
+
}
|
|
17601
|
+
|
|
17602
|
+
// ../../packages/node-runtime/src/services/global-insight/worker-runner.ts
|
|
17603
|
+
import { existsSync as existsSync11 } from "fs";
|
|
17604
|
+
import { Worker as Worker3 } from "worker_threads";
|
|
17605
|
+
function resolveDefaultAnnualSummaryWorkerEntryUrl(currentModuleUrl = import.meta.url, entryExists = (url) => existsSync11(url)) {
|
|
17606
|
+
const moduleUrl = typeof currentModuleUrl === "string" ? currentModuleUrl : currentModuleUrl.href;
|
|
17607
|
+
if (moduleUrl.endsWith(".ts")) return new URL("./worker-entry.ts", moduleUrl);
|
|
17608
|
+
if (moduleUrl.endsWith(".mjs")) return new URL("./global-insight-worker.mjs", moduleUrl);
|
|
17609
|
+
const sibling = new URL("./worker-entry.js", moduleUrl);
|
|
17610
|
+
return entryExists(sibling) ? sibling : new URL("./global-insight-worker.js", moduleUrl);
|
|
17611
|
+
}
|
|
17612
|
+
function createAnnualSummaryWorkerRunner(options2) {
|
|
17613
|
+
return ({ signature, range, signal, onProgress }) => new Promise((resolve2, reject) => {
|
|
17614
|
+
if (signal.aborted) {
|
|
17615
|
+
reject(new Error("annual summary worker aborted"));
|
|
17616
|
+
return;
|
|
17617
|
+
}
|
|
17618
|
+
const worker = createWorker3(
|
|
17619
|
+
{
|
|
17620
|
+
paths: snapshotPathProvider(options2.pathProvider),
|
|
17621
|
+
runtimeIdentity: options2.runtimeIdentity,
|
|
17622
|
+
nativeBinding: options2.nativeBinding,
|
|
17623
|
+
signature,
|
|
17624
|
+
range
|
|
17625
|
+
},
|
|
17626
|
+
options2.workerEntryUrl
|
|
17627
|
+
);
|
|
17628
|
+
let settled = false;
|
|
17629
|
+
const abort = () => {
|
|
17630
|
+
if (settled) return;
|
|
17631
|
+
settled = true;
|
|
17632
|
+
void worker.terminate();
|
|
17633
|
+
reject(new Error("annual summary worker aborted"));
|
|
17634
|
+
};
|
|
17635
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
17636
|
+
worker.on("message", (message) => {
|
|
17637
|
+
if (message.type === "progress" && message.progress) {
|
|
17638
|
+
onProgress(message.progress);
|
|
17639
|
+
return;
|
|
17640
|
+
}
|
|
17641
|
+
if (message.type === "success" && message.snapshot) {
|
|
17642
|
+
settled = true;
|
|
17643
|
+
signal.removeEventListener("abort", abort);
|
|
17644
|
+
resolve2(message.snapshot);
|
|
17645
|
+
void worker.terminate();
|
|
17646
|
+
return;
|
|
17647
|
+
}
|
|
17648
|
+
if (message.type === "error") {
|
|
17649
|
+
settled = true;
|
|
17650
|
+
signal.removeEventListener("abort", abort);
|
|
17651
|
+
reject(new Error(message.error ?? "annual summary worker failed"));
|
|
17652
|
+
void worker.terminate();
|
|
17653
|
+
}
|
|
17654
|
+
});
|
|
17655
|
+
worker.on("error", (error) => {
|
|
17656
|
+
if (settled) return;
|
|
17657
|
+
settled = true;
|
|
17658
|
+
signal.removeEventListener("abort", abort);
|
|
17659
|
+
reject(error);
|
|
17660
|
+
});
|
|
17661
|
+
worker.on("exit", (code) => {
|
|
17662
|
+
if (settled || code === 0) return;
|
|
17663
|
+
settled = true;
|
|
17664
|
+
signal.removeEventListener("abort", abort);
|
|
17665
|
+
reject(new Error(`annual summary worker exited with code ${code}`));
|
|
17666
|
+
});
|
|
17667
|
+
});
|
|
17668
|
+
}
|
|
17669
|
+
function createWorker3(workerData, entryUrlInput) {
|
|
17670
|
+
const entryUrl = entryUrlInput ? typeof entryUrlInput === "string" ? new URL(entryUrlInput) : entryUrlInput : resolveDefaultAnnualSummaryWorkerEntryUrl();
|
|
17671
|
+
if (!entryUrl.href.endsWith(".ts")) return new Worker3(entryUrl, { workerData });
|
|
17672
|
+
const bootstrap = `
|
|
17673
|
+
import { register } from 'tsx/esm/api';
|
|
17674
|
+
register();
|
|
17675
|
+
await import(${JSON.stringify(entryUrl.href)});
|
|
17676
|
+
`;
|
|
17677
|
+
const workerOptions = { eval: true, type: "module", workerData, execArgv: [] };
|
|
17678
|
+
return new Worker3(bootstrap, workerOptions);
|
|
17679
|
+
}
|
|
17680
|
+
|
|
17681
|
+
// ../../packages/node-runtime/src/services/global-insight/service.ts
|
|
17682
|
+
function createGlobalInsightService(deps) {
|
|
17683
|
+
return new DefaultGlobalInsightService(deps);
|
|
17684
|
+
}
|
|
17685
|
+
var DefaultGlobalInsightService = class {
|
|
17686
|
+
constructor(deps) {
|
|
17687
|
+
this.deps = deps;
|
|
17688
|
+
this.snapshotDir = deps.userDataDir ? getGlobalInsightDir(deps.userDataDir) : getGlobalInsightDir(requirePathProvider3(deps).getUserDataDir());
|
|
17689
|
+
cleanupAnnualSummarySnapshotTempFiles(this.snapshotDir);
|
|
17690
|
+
this.runner = deps.runner ?? createAnnualSummaryWorkerRunner({
|
|
17691
|
+
pathProvider: requirePathProvider3(deps),
|
|
17692
|
+
runtimeIdentity: deps.runtimeIdentity,
|
|
17693
|
+
nativeBinding: deps.nativeBinding,
|
|
17694
|
+
workerEntryUrl: deps.workerEntryUrl
|
|
17695
|
+
});
|
|
17696
|
+
}
|
|
17697
|
+
deps;
|
|
17698
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
17699
|
+
snapshotDir;
|
|
17700
|
+
runner;
|
|
17701
|
+
inFlight = null;
|
|
17702
|
+
task = createIdleTaskState3();
|
|
17703
|
+
getAnnualSummary(options2 = {}) {
|
|
17704
|
+
const range = this.normalizeRange(options2);
|
|
17705
|
+
const signature = buildAnnualSummarySignature(this.deps.adapter, range);
|
|
17706
|
+
const status = this.getCacheStatus(signature, range);
|
|
17707
|
+
if (this.shouldStartTask(options2, status)) this.ensureTaskStarted(signature, range);
|
|
17708
|
+
return this.toResponse(signature, range, options2.acceptStale === true);
|
|
17709
|
+
}
|
|
17710
|
+
startRecompute(options2 = {}) {
|
|
17711
|
+
const range = this.normalizeRange(options2);
|
|
17712
|
+
const signature = buildAnnualSummarySignature(this.deps.adapter, range);
|
|
17713
|
+
this.ensureTaskStarted(signature, range);
|
|
17714
|
+
return this.toResponse(signature, range, true);
|
|
17715
|
+
}
|
|
17716
|
+
invalidateCache() {
|
|
17717
|
+
this.snapshots.clear();
|
|
17718
|
+
}
|
|
17719
|
+
normalizeRange(input = {}) {
|
|
17720
|
+
return normalizeAnnualSummaryRange(input, new Date(this.now()));
|
|
17721
|
+
}
|
|
17722
|
+
replaceSnapshotForTests(snapshot) {
|
|
17723
|
+
writeAnnualSummarySnapshot(this.snapshotDir, snapshot);
|
|
17724
|
+
this.snapshots.set(toAnnualSummaryRangeKey(snapshot.range), snapshot);
|
|
17725
|
+
}
|
|
17726
|
+
async close() {
|
|
17727
|
+
const inFlight = this.inFlight;
|
|
17728
|
+
if (!inFlight) return;
|
|
17729
|
+
this.inFlight = null;
|
|
17730
|
+
inFlight.abortController.abort();
|
|
17731
|
+
this.task = {
|
|
17732
|
+
...this.task,
|
|
17733
|
+
status: "failed",
|
|
17734
|
+
finishedAt: this.now(),
|
|
17735
|
+
lastError: "annual summary task aborted"
|
|
17736
|
+
};
|
|
17737
|
+
}
|
|
17738
|
+
shouldStartTask(options2, status) {
|
|
17739
|
+
if (options2.forceRecompute) return true;
|
|
17740
|
+
if (status === "fresh") return false;
|
|
17741
|
+
return this.task.status !== "failed";
|
|
17742
|
+
}
|
|
17743
|
+
ensureTaskStarted(signature, range) {
|
|
17744
|
+
if (this.inFlight) return;
|
|
17745
|
+
const id = `annual_summary_${this.now()}_${Math.random().toString(36).slice(2)}`;
|
|
17746
|
+
this.task = {
|
|
17747
|
+
id,
|
|
17748
|
+
status: "running",
|
|
17749
|
+
startedAt: this.now(),
|
|
17750
|
+
finishedAt: null,
|
|
17751
|
+
processedSessions: 0,
|
|
17752
|
+
totalSessions: this.deps.adapter.listSessionIds().length
|
|
17753
|
+
};
|
|
17754
|
+
const abortController = new AbortController();
|
|
17755
|
+
const promise = this.runner({
|
|
17756
|
+
signature,
|
|
17757
|
+
range,
|
|
17758
|
+
signal: abortController.signal,
|
|
17759
|
+
onProgress: (progress) => {
|
|
17760
|
+
if (this.task.id !== id || this.task.status !== "running") return;
|
|
17761
|
+
this.task = { ...this.task, ...progress };
|
|
17762
|
+
}
|
|
17763
|
+
});
|
|
17764
|
+
this.inFlight = { id, signature, promise, abortController };
|
|
17765
|
+
promise.then((snapshot) => this.handleTaskSuccess(id, signature, snapshot)).catch((error) => this.handleTaskFailure(id, error));
|
|
17766
|
+
}
|
|
17767
|
+
handleTaskSuccess(id, inputSignature, snapshot) {
|
|
17768
|
+
if (this.inFlight?.id !== id) return;
|
|
17769
|
+
const latestSignature = buildAnnualSummarySignature(this.deps.adapter, snapshot.range);
|
|
17770
|
+
const finishedAt = this.now();
|
|
17771
|
+
if (inputSignature !== latestSignature || snapshot.signature !== latestSignature) {
|
|
17772
|
+
this.inFlight = null;
|
|
17773
|
+
this.task = { ...this.task, status: "superseded", finishedAt, currentSessionId: void 0 };
|
|
17774
|
+
appLogger.info("global-insight", "annual summary worker result discarded because signature changed");
|
|
17775
|
+
return;
|
|
17776
|
+
}
|
|
17777
|
+
try {
|
|
17778
|
+
writeAnnualSummarySnapshot(this.snapshotDir, snapshot);
|
|
17779
|
+
this.inFlight = null;
|
|
17780
|
+
this.snapshots.set(toAnnualSummaryRangeKey(snapshot.range), snapshot);
|
|
17781
|
+
this.task = {
|
|
17782
|
+
...this.task,
|
|
17783
|
+
status: "succeeded",
|
|
17784
|
+
finishedAt,
|
|
17785
|
+
processedSessions: snapshot.workerStats.processedSessions,
|
|
17786
|
+
totalSessions: snapshot.workerStats.totalSessions,
|
|
17787
|
+
currentSessionId: void 0
|
|
17788
|
+
};
|
|
17789
|
+
appLogger.info("global-insight", "annual summary snapshot persisted", {
|
|
17790
|
+
analyzedSessions: snapshot.coverage.analyzedSessions,
|
|
17791
|
+
durationMs: snapshot.workerStats.durationMs
|
|
17792
|
+
});
|
|
17793
|
+
} catch (error) {
|
|
17794
|
+
this.handleTaskFailure(id, error);
|
|
17795
|
+
}
|
|
17796
|
+
}
|
|
17797
|
+
handleTaskFailure(id, error) {
|
|
17798
|
+
if (this.inFlight?.id !== id) return;
|
|
17799
|
+
this.inFlight = null;
|
|
17800
|
+
this.task = {
|
|
17801
|
+
...this.task,
|
|
17802
|
+
status: "failed",
|
|
17803
|
+
finishedAt: this.now(),
|
|
17804
|
+
currentSessionId: void 0,
|
|
17805
|
+
lastError: error instanceof Error ? error.message : String(error)
|
|
17806
|
+
};
|
|
17807
|
+
appLogger.error("global-insight", "annual summary worker failed", error);
|
|
17808
|
+
}
|
|
17809
|
+
toResponse(signature, range, acceptStale) {
|
|
17810
|
+
const snapshot = this.getSnapshot(range);
|
|
17811
|
+
const status = this.getCacheStatus(signature, range);
|
|
17812
|
+
const includeSnapshot = status === "fresh" || status === "stale" && acceptStale;
|
|
17813
|
+
return {
|
|
17814
|
+
range,
|
|
17815
|
+
availableDataYears: includeSnapshot ? snapshot?.availableDataYears ?? [] : [],
|
|
17816
|
+
latestDataYear: includeSnapshot ? snapshot?.latestDataYear ?? null : null,
|
|
17817
|
+
metrics: includeSnapshot ? snapshot?.metrics ?? null : null,
|
|
17818
|
+
monthlyActivity: includeSnapshot ? snapshot?.monthlyActivity ?? [] : [],
|
|
17819
|
+
dailyActivity: includeSnapshot ? snapshot?.dailyActivity ?? [] : [],
|
|
17820
|
+
messageTypes: includeSnapshot ? snapshot?.messageTypes ?? [] : [],
|
|
17821
|
+
textLength: includeSnapshot ? snapshot?.textLength ?? null : null,
|
|
17822
|
+
coverage: includeSnapshot ? snapshot?.coverage ?? emptyCoverage() : emptyCoverage(),
|
|
17823
|
+
cache: {
|
|
17824
|
+
status,
|
|
17825
|
+
computedAt: snapshot?.computedAt ?? null,
|
|
17826
|
+
signature: snapshot?.signature,
|
|
17827
|
+
staleReason: status === "stale" ? "signature_changed" : void 0
|
|
17828
|
+
},
|
|
17829
|
+
task: this.task
|
|
17830
|
+
};
|
|
17831
|
+
}
|
|
17832
|
+
getCacheStatus(signature, range) {
|
|
17833
|
+
const snapshot = this.getSnapshot(range);
|
|
17834
|
+
if (!snapshot) return "missing";
|
|
17835
|
+
return snapshot.signature === signature ? "fresh" : "stale";
|
|
17836
|
+
}
|
|
17837
|
+
getSnapshot(range) {
|
|
17838
|
+
const key = toAnnualSummaryRangeKey(range);
|
|
17839
|
+
if (this.snapshots.has(key) && !fs31.existsSync(getAnnualSummarySnapshotPath(this.snapshotDir, range))) {
|
|
17840
|
+
this.snapshots.delete(key);
|
|
17841
|
+
}
|
|
17842
|
+
if (!this.snapshots.has(key)) {
|
|
17843
|
+
this.snapshots.set(key, readAnnualSummarySnapshot(this.snapshotDir, range, { now: this.deps.now }));
|
|
17844
|
+
}
|
|
17845
|
+
return this.snapshots.get(key) ?? null;
|
|
17846
|
+
}
|
|
17847
|
+
now() {
|
|
17848
|
+
return this.deps.now?.() ?? Date.now();
|
|
17849
|
+
}
|
|
17850
|
+
};
|
|
17851
|
+
function requirePathProvider3(deps) {
|
|
17852
|
+
if (!deps.pathProvider)
|
|
17853
|
+
throw new Error("GlobalInsightService requires pathProvider unless userDataDir and runner are provided");
|
|
17854
|
+
return deps.pathProvider;
|
|
17855
|
+
}
|
|
17856
|
+
function createIdleTaskState3() {
|
|
17857
|
+
return {
|
|
17858
|
+
id: null,
|
|
17859
|
+
status: "idle",
|
|
17860
|
+
startedAt: null,
|
|
17861
|
+
finishedAt: null,
|
|
17862
|
+
processedSessions: 0,
|
|
17863
|
+
totalSessions: 0
|
|
17864
|
+
};
|
|
17865
|
+
}
|
|
17866
|
+
function emptyCoverage() {
|
|
17867
|
+
return {
|
|
17868
|
+
totalSessions: 0,
|
|
17869
|
+
analyzedSessions: 0,
|
|
17870
|
+
missingOwnerSessions: 0,
|
|
17871
|
+
unresolvedOwnerSessions: 0,
|
|
17872
|
+
failedSessions: 0
|
|
17873
|
+
};
|
|
17874
|
+
}
|
|
17875
|
+
|
|
17876
|
+
// ../../packages/node-runtime/src/services/global-insight/index.ts
|
|
17877
|
+
var global_insight_exports = {};
|
|
17878
|
+
__export(global_insight_exports, {
|
|
17879
|
+
ANNUAL_SUMMARY_ALGORITHM_VERSION: () => ANNUAL_SUMMARY_ALGORITHM_VERSION,
|
|
17880
|
+
createGlobalInsightService: () => createGlobalInsightService,
|
|
17881
|
+
deleteAnnualSummarySnapshots: () => deleteAnnualSummarySnapshots,
|
|
17882
|
+
getGlobalInsightDir: () => getGlobalInsightDir,
|
|
17883
|
+
getGlobalInsightFactsCacheDir: () => getGlobalInsightFactsCacheDir
|
|
17884
|
+
});
|
|
17885
|
+
|
|
17053
17886
|
// ../../packages/node-runtime/src/semantic-index/config.ts
|
|
17054
|
-
import
|
|
17055
|
-
import
|
|
17887
|
+
import fs32 from "fs";
|
|
17888
|
+
import path28 from "path";
|
|
17056
17889
|
var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
|
|
17057
17890
|
var SEMANTIC_INDEX_CONFIG_VERSION = 1;
|
|
17058
17891
|
var SEARCH_MAX_RESULTS_DEFAULT = 10;
|
|
@@ -17141,9 +17974,9 @@ var SemanticIndexConfigStore = class {
|
|
|
17141
17974
|
return this.set({ ...this.get(), enabled });
|
|
17142
17975
|
}
|
|
17143
17976
|
get() {
|
|
17144
|
-
if (!
|
|
17977
|
+
if (!fs32.existsSync(this.filePath)) return defaultSemanticIndexConfig();
|
|
17145
17978
|
try {
|
|
17146
|
-
const raw = JSON.parse(
|
|
17979
|
+
const raw = JSON.parse(fs32.readFileSync(this.filePath, "utf-8"));
|
|
17147
17980
|
return normalizeConfig(raw);
|
|
17148
17981
|
} catch {
|
|
17149
17982
|
return defaultSemanticIndexConfig();
|
|
@@ -17151,9 +17984,9 @@ var SemanticIndexConfigStore = class {
|
|
|
17151
17984
|
}
|
|
17152
17985
|
set(config) {
|
|
17153
17986
|
const normalized = normalizeConfig(config);
|
|
17154
|
-
const dir =
|
|
17155
|
-
if (!
|
|
17156
|
-
|
|
17987
|
+
const dir = path28.dirname(this.filePath);
|
|
17988
|
+
if (!fs32.existsSync(dir)) fs32.mkdirSync(dir, { recursive: true });
|
|
17989
|
+
fs32.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
17157
17990
|
return normalized;
|
|
17158
17991
|
}
|
|
17159
17992
|
resolveModelId() {
|
|
@@ -17162,7 +17995,7 @@ var SemanticIndexConfigStore = class {
|
|
|
17162
17995
|
};
|
|
17163
17996
|
|
|
17164
17997
|
// ../../packages/node-runtime/src/semantic-index/service.ts
|
|
17165
|
-
import
|
|
17998
|
+
import path29 from "path";
|
|
17166
17999
|
|
|
17167
18000
|
// ../../packages/node-runtime/src/semantic-index/chunker-config.ts
|
|
17168
18001
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -18143,7 +18976,7 @@ function formatDateTime(ts) {
|
|
|
18143
18976
|
const d = new Date(ts);
|
|
18144
18977
|
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
18145
18978
|
}
|
|
18146
|
-
function
|
|
18979
|
+
function formatTime(ts) {
|
|
18147
18980
|
const d = new Date(ts);
|
|
18148
18981
|
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
18149
18982
|
}
|
|
@@ -18151,7 +18984,7 @@ function formatTimeRange2(startTs, endTs) {
|
|
|
18151
18984
|
const start = new Date(startTs);
|
|
18152
18985
|
const end = new Date(endTs);
|
|
18153
18986
|
const sameDay = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth() && start.getDate() === end.getDate();
|
|
18154
|
-
return `${formatDateTime(startTs)} - ${sameDay ?
|
|
18987
|
+
return `${formatDateTime(startTs)} - ${sameDay ? formatTime(endTs) : formatDateTime(endTs)}`;
|
|
18155
18988
|
}
|
|
18156
18989
|
function resolveSenderNameTokenCap(nameTokenCounts, totalLimit) {
|
|
18157
18990
|
const total = nameTokenCounts.reduce((sum, count) => sum + count, 0);
|
|
@@ -18693,16 +19526,16 @@ function resolveSearchToolEvidenceTokens(maxResultTokens) {
|
|
|
18693
19526
|
return maxResultTokens && maxResultTokens > 0 ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens) : SEARCH_TOOL_EVIDENCE_TOKENS;
|
|
18694
19527
|
}
|
|
18695
19528
|
function sessionIdFromDbPath(dbPath) {
|
|
18696
|
-
return
|
|
19529
|
+
return path29.basename(dbPath, ".db");
|
|
18697
19530
|
}
|
|
18698
19531
|
var SEMANTIC_INDEX_DB_FILE = "embedding_index.db";
|
|
18699
19532
|
var SEMANTIC_INDEX_CONFIG_FILE = "semantic-index-config.json";
|
|
18700
19533
|
function createSemanticIndexService(params) {
|
|
18701
19534
|
const { pathProvider } = params;
|
|
18702
19535
|
return new SemanticIndexService({
|
|
18703
|
-
vectorDbPath:
|
|
18704
|
-
configPath:
|
|
18705
|
-
modelsCacheDir:
|
|
19536
|
+
vectorDbPath: path29.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
|
|
19537
|
+
configPath: path29.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
|
|
19538
|
+
modelsCacheDir: path29.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
|
|
18706
19539
|
modelDownloadProxyUrl: params.modelDownloadProxyUrl,
|
|
18707
19540
|
sessionAdapter: params.sessionAdapter,
|
|
18708
19541
|
nativeBinding: params.nativeBinding,
|
|
@@ -19252,8 +20085,8 @@ ${text}`;
|
|
|
19252
20085
|
};
|
|
19253
20086
|
|
|
19254
20087
|
// ../../packages/node-runtime/src/services/merge-cache.ts
|
|
19255
|
-
import * as
|
|
19256
|
-
import * as
|
|
20088
|
+
import * as fs33 from "fs";
|
|
20089
|
+
import * as path30 from "path";
|
|
19257
20090
|
import * as crypto from "crypto";
|
|
19258
20091
|
var MergeSessionCache = class {
|
|
19259
20092
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -19262,15 +20095,15 @@ var MergeSessionCache = class {
|
|
|
19262
20095
|
constructor(pathProvider, options2) {
|
|
19263
20096
|
this.tempDir = pathProvider.getTempDir();
|
|
19264
20097
|
this.nativeBinding = options2?.nativeBinding;
|
|
19265
|
-
if (!
|
|
19266
|
-
|
|
20098
|
+
if (!fs33.existsSync(this.tempDir)) {
|
|
20099
|
+
fs33.mkdirSync(this.tempDir, { recursive: true });
|
|
19267
20100
|
}
|
|
19268
20101
|
}
|
|
19269
20102
|
generateTempDbPath(filename) {
|
|
19270
20103
|
const ts = Date.now();
|
|
19271
20104
|
const rand = Math.random().toString(36).slice(2, 8);
|
|
19272
20105
|
const safeName = filename.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 40);
|
|
19273
|
-
return
|
|
20106
|
+
return path30.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
|
|
19274
20107
|
}
|
|
19275
20108
|
/** Store a parsed file's temp DB and return a handle for subsequent requests. */
|
|
19276
20109
|
store(filename, tempDbPath) {
|
|
@@ -19281,7 +20114,7 @@ var MergeSessionCache = class {
|
|
|
19281
20114
|
/** Open a TempDbReader for a given handle. Caller must close the reader. */
|
|
19282
20115
|
openReader(handle) {
|
|
19283
20116
|
const entry = this.cache.get(handle);
|
|
19284
|
-
if (!entry || !
|
|
20117
|
+
if (!entry || !fs33.existsSync(entry.tempDbPath)) return null;
|
|
19285
20118
|
const adapter = openBetterSqliteDatabase(entry.tempDbPath, {
|
|
19286
20119
|
readonly: true,
|
|
19287
20120
|
nativeBinding: this.nativeBinding
|
|
@@ -19326,21 +20159,44 @@ var MergeSessionCache = class {
|
|
|
19326
20159
|
};
|
|
19327
20160
|
|
|
19328
20161
|
// ../../packages/node-runtime/src/services/push-importer.ts
|
|
19329
|
-
import * as
|
|
19330
|
-
var importInProgress = /* @__PURE__ */ new Set();
|
|
20162
|
+
import * as fs34 from "fs";
|
|
19331
20163
|
var SYSTEM_SENDER_ID = "SYSTEM";
|
|
19332
20164
|
var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
|
|
20165
|
+
function isRecord3(value) {
|
|
20166
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20167
|
+
}
|
|
20168
|
+
function validateOptionalString(value, field) {
|
|
20169
|
+
return value !== void 0 && typeof value !== "string" ? `${field} must be a string` : null;
|
|
20170
|
+
}
|
|
19333
20171
|
function validatePayload(payload, isNew) {
|
|
20172
|
+
if (!isRecord3(payload)) return "payload must be an object";
|
|
19334
20173
|
const messages = payload.messages;
|
|
19335
20174
|
if (!Array.isArray(messages) || messages.length === 0) return "messages is required and must be a non-empty array";
|
|
20175
|
+
if (payload.chatlab !== void 0) {
|
|
20176
|
+
if (!isRecord3(payload.chatlab)) return "chatlab must be an object";
|
|
20177
|
+
if (typeof payload.chatlab.version !== "string" || payload.chatlab.version.length === 0)
|
|
20178
|
+
return "chatlab.version must be a string";
|
|
20179
|
+
if (typeof payload.chatlab.exportedAt !== "number" || !Number.isFinite(payload.chatlab.exportedAt))
|
|
20180
|
+
return "chatlab.exportedAt must be a number";
|
|
20181
|
+
const generatorError = validateOptionalString(payload.chatlab.generator, "chatlab.generator");
|
|
20182
|
+
if (generatorError) return generatorError;
|
|
20183
|
+
}
|
|
19336
20184
|
if (isNew) {
|
|
19337
20185
|
if (!payload.chatlab) return "chatlab is required for new sessions";
|
|
19338
20186
|
if (!payload.meta) return "meta is required for new sessions";
|
|
19339
|
-
const m = payload.meta;
|
|
19340
|
-
if (!m.name) return "meta.name is required";
|
|
19341
|
-
if (!m.platform) return "meta.platform is required";
|
|
19342
|
-
if (!m.type) return "meta.type is required";
|
|
19343
20187
|
}
|
|
20188
|
+
if (payload.meta !== void 0) {
|
|
20189
|
+
if (!isRecord3(payload.meta)) return "meta must be an object";
|
|
20190
|
+
const metaFields = ["name", "platform", "type", "groupId", "groupAvatar", "ownerId"];
|
|
20191
|
+
for (const field of metaFields) {
|
|
20192
|
+
const error = validateOptionalString(payload.meta[field], `meta.${field}`);
|
|
20193
|
+
if (error) return error;
|
|
20194
|
+
}
|
|
20195
|
+
if (isNew && !payload.meta.name) return "meta.name is required";
|
|
20196
|
+
if (isNew && !payload.meta.platform) return "meta.platform is required";
|
|
20197
|
+
if (isNew && !payload.meta.type) return "meta.type is required";
|
|
20198
|
+
}
|
|
20199
|
+
if (payload.options !== void 0 && !isRecord3(payload.options)) return "options must be an object";
|
|
19344
20200
|
const { metaUpdateMode, memberUpdateMode } = payload.options ?? {};
|
|
19345
20201
|
if (metaUpdateMode !== void 0 && metaUpdateMode !== "patch" && metaUpdateMode !== "none") {
|
|
19346
20202
|
return `options.metaUpdateMode must be 'patch' or 'none'`;
|
|
@@ -19351,16 +20207,35 @@ function validatePayload(payload, isNew) {
|
|
|
19351
20207
|
if (payload.members !== void 0 && !Array.isArray(payload.members)) return "members must be an array";
|
|
19352
20208
|
if (payload.members) {
|
|
19353
20209
|
for (let i = 0; i < payload.members.length; i++) {
|
|
19354
|
-
|
|
20210
|
+
const member = payload.members[i];
|
|
20211
|
+
if (!isRecord3(member)) return `members[${i}] must be an object`;
|
|
20212
|
+
if (typeof member.platformId !== "string" || member.platformId.length === 0)
|
|
19355
20213
|
return `members[${i}].platformId must be a string`;
|
|
20214
|
+
for (const field of ["accountName", "groupNickname", "avatar"]) {
|
|
20215
|
+
const error = validateOptionalString(member[field], `members[${i}].${field}`);
|
|
20216
|
+
if (error) return error;
|
|
20217
|
+
}
|
|
20218
|
+
if (member.roles !== void 0) {
|
|
20219
|
+
if (!Array.isArray(member.roles)) return `members[${i}].roles must be an array`;
|
|
20220
|
+
for (let j = 0; j < member.roles.length; j++) {
|
|
20221
|
+
const role = member.roles[j];
|
|
20222
|
+
if (!isRecord3(role) || typeof role.id !== "string" || role.id.length === 0)
|
|
20223
|
+
return `members[${i}].roles[${j}].id must be a string`;
|
|
20224
|
+
}
|
|
20225
|
+
}
|
|
19356
20226
|
}
|
|
19357
20227
|
}
|
|
19358
20228
|
for (let i = 0; i < messages.length; i++) {
|
|
19359
20229
|
const msg = messages[i];
|
|
20230
|
+
if (!isRecord3(msg)) return `messages[${i}] must be an object`;
|
|
19360
20231
|
if (typeof msg.sender !== "string" || msg.sender.length === 0) return `messages[${i}].sender must be a string`;
|
|
19361
|
-
if (typeof msg.timestamp !== "number" || msg.timestamp <= 0)
|
|
20232
|
+
if (typeof msg.timestamp !== "number" || !Number.isFinite(msg.timestamp) || msg.timestamp <= 0)
|
|
19362
20233
|
return `messages[${i}].timestamp must be a positive number`;
|
|
19363
|
-
if (typeof msg.type !== "number") return `messages[${i}].type must be a number`;
|
|
20234
|
+
if (typeof msg.type !== "number" || !Number.isFinite(msg.type)) return `messages[${i}].type must be a number`;
|
|
20235
|
+
for (const field of ["accountName", "groupNickname"]) {
|
|
20236
|
+
const error = validateOptionalString(msg[field], `messages[${i}].${field}`);
|
|
20237
|
+
if (error) return error;
|
|
20238
|
+
}
|
|
19364
20239
|
if (msg.content !== void 0 && msg.content !== null && typeof msg.content !== "string")
|
|
19365
20240
|
return `messages[${i}].content must be a string or null`;
|
|
19366
20241
|
if (msg.platformMessageId !== void 0 && typeof msg.platformMessageId !== "string")
|
|
@@ -19381,47 +20256,79 @@ function normalizeSenderAccountName(sender, accountName) {
|
|
|
19381
20256
|
function normalizeSenderGroupNickname(sender, groupNickname) {
|
|
19382
20257
|
return sender === SYSTEM_SENDER_ID ? SYSTEM_MEMBER_NAME : groupNickname;
|
|
19383
20258
|
}
|
|
19384
|
-
function
|
|
20259
|
+
function isCountedMember(platformId) {
|
|
20260
|
+
return platformId !== SYSTEM_SENDER_ID;
|
|
20261
|
+
}
|
|
20262
|
+
function registerPushMessageAndCheckDuplicate(message, dedupState) {
|
|
20263
|
+
return registerMessageAndCheckDuplicate(
|
|
20264
|
+
{
|
|
20265
|
+
platformMessageId: message.platformMessageId,
|
|
20266
|
+
timestamp: message.timestamp,
|
|
20267
|
+
senderPlatformId: message.sender,
|
|
20268
|
+
type: message.type,
|
|
20269
|
+
content: message.content ?? null,
|
|
20270
|
+
replyToMessageId: message.replyToMessageId
|
|
20271
|
+
},
|
|
20272
|
+
dedupState
|
|
20273
|
+
);
|
|
20274
|
+
}
|
|
20275
|
+
function createExistingMessageDedupState(db) {
|
|
20276
|
+
const existingPmids = /* @__PURE__ */ new Set();
|
|
20277
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
20278
|
+
const existingFallbackOnlyKeys = /* @__PURE__ */ new Set();
|
|
20279
|
+
const existingMessages = db.prepare(
|
|
20280
|
+
`SELECT msg.ts, m.platform_id, msg.type, msg.content, msg.reply_to_message_id, msg.platform_message_id
|
|
20281
|
+
FROM message msg
|
|
20282
|
+
JOIN member m ON msg.sender_id = m.id`
|
|
20283
|
+
).all();
|
|
20284
|
+
for (const message of existingMessages) {
|
|
20285
|
+
if (message.platform_message_id) existingPmids.add(message.platform_message_id);
|
|
20286
|
+
const key = generateFallbackMessageKey({
|
|
20287
|
+
timestamp: message.ts,
|
|
20288
|
+
senderPlatformId: message.platform_id,
|
|
20289
|
+
type: message.type,
|
|
20290
|
+
content: message.content,
|
|
20291
|
+
replyToMessageId: message.reply_to_message_id ?? void 0
|
|
20292
|
+
});
|
|
20293
|
+
existingKeys.add(key);
|
|
20294
|
+
if (!message.platform_message_id) existingFallbackOnlyKeys.add(key);
|
|
20295
|
+
}
|
|
20296
|
+
return createMessageDedupState(existingPmids, existingKeys, existingFallbackOnlyKeys);
|
|
20297
|
+
}
|
|
20298
|
+
function writeMessages(db, messages, dedupState) {
|
|
19385
20299
|
const insertMsg = db.prepare(
|
|
19386
20300
|
`INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)
|
|
19387
20301
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
19388
20302
|
);
|
|
19389
20303
|
const getMemberId = db.prepare("SELECT id FROM member WHERE platform_id = ?");
|
|
19390
|
-
const insertMinimalMember = db.prepare(
|
|
20304
|
+
const insertMinimalMember = db.prepare(
|
|
20305
|
+
"INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname) VALUES (?, ?, ?)"
|
|
20306
|
+
);
|
|
19391
20307
|
const memberIdCache = /* @__PURE__ */ new Map();
|
|
19392
20308
|
let writtenCount = 0;
|
|
19393
20309
|
let duplicateCount = 0;
|
|
20310
|
+
let membersAdded = 0;
|
|
19394
20311
|
let minWrittenTs = Infinity;
|
|
19395
20312
|
const ftsEntries = [];
|
|
19396
20313
|
const sorted = [...messages].sort((a, b) => a.timestamp - b.timestamp);
|
|
19397
20314
|
db.transaction(() => {
|
|
19398
20315
|
for (const msg of sorted) {
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
duplicateCount++;
|
|
19403
|
-
continue;
|
|
19404
|
-
}
|
|
19405
|
-
if (existingKeys.has(key)) {
|
|
19406
|
-
duplicateCount++;
|
|
19407
|
-
continue;
|
|
19408
|
-
}
|
|
19409
|
-
existingPmids.add(msg.platformMessageId);
|
|
19410
|
-
existingKeys.add(key);
|
|
19411
|
-
} else {
|
|
19412
|
-
if (existingKeys.has(key)) {
|
|
19413
|
-
duplicateCount++;
|
|
19414
|
-
continue;
|
|
19415
|
-
}
|
|
19416
|
-
existingKeys.add(key);
|
|
20316
|
+
if (registerPushMessageAndCheckDuplicate(msg, dedupState)) {
|
|
20317
|
+
duplicateCount++;
|
|
20318
|
+
continue;
|
|
19417
20319
|
}
|
|
19418
20320
|
let memberId = memberIdCache.get(msg.sender);
|
|
19419
20321
|
if (!memberId) {
|
|
19420
|
-
insertMinimalMember.run(
|
|
20322
|
+
const insertResult = insertMinimalMember.run(
|
|
20323
|
+
msg.sender,
|
|
20324
|
+
normalizeSenderAccountName(msg.sender, msg.accountName) || null,
|
|
20325
|
+
normalizeSenderGroupNickname(msg.sender, msg.groupNickname) || null
|
|
20326
|
+
);
|
|
19421
20327
|
const row = getMemberId.get(msg.sender);
|
|
19422
20328
|
if (row) {
|
|
19423
20329
|
memberId = row.id;
|
|
19424
20330
|
memberIdCache.set(msg.sender, memberId);
|
|
20331
|
+
if (isCountedMember(msg.sender)) membersAdded += insertResult.changes;
|
|
19425
20332
|
}
|
|
19426
20333
|
}
|
|
19427
20334
|
if (!memberId) continue;
|
|
@@ -19440,7 +20347,7 @@ function writeMessages(db, messages, existingPmids, existingKeys) {
|
|
|
19440
20347
|
writtenCount++;
|
|
19441
20348
|
}
|
|
19442
20349
|
});
|
|
19443
|
-
return { writtenCount, duplicateCount, minWrittenTs, ftsEntries };
|
|
20350
|
+
return { writtenCount, duplicateCount, membersAdded, minWrittenTs, ftsEntries };
|
|
19444
20351
|
}
|
|
19445
20352
|
function fullImport(db, meta, members, messages) {
|
|
19446
20353
|
db.exec(CHAT_DB_SCHEMA);
|
|
@@ -19457,29 +20364,13 @@ function fullImport(db, meta, members, messages) {
|
|
|
19457
20364
|
}
|
|
19458
20365
|
}
|
|
19459
20366
|
const allMembers = extraMembers.length > 0 ? [...members, ...extraMembers] : members;
|
|
19460
|
-
const
|
|
19461
|
-
const hashKeys = /* @__PURE__ */ new Set();
|
|
20367
|
+
const dedupState = createMessageDedupState();
|
|
19462
20368
|
let duplicateCount = 0;
|
|
19463
20369
|
const dedupedMessages = [];
|
|
19464
20370
|
for (const msg of messages) {
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
duplicateCount++;
|
|
19469
|
-
continue;
|
|
19470
|
-
}
|
|
19471
|
-
if (hashKeys.has(key)) {
|
|
19472
|
-
duplicateCount++;
|
|
19473
|
-
continue;
|
|
19474
|
-
}
|
|
19475
|
-
pmids.add(msg.platformMessageId);
|
|
19476
|
-
hashKeys.add(key);
|
|
19477
|
-
} else {
|
|
19478
|
-
if (hashKeys.has(key)) {
|
|
19479
|
-
duplicateCount++;
|
|
19480
|
-
continue;
|
|
19481
|
-
}
|
|
19482
|
-
hashKeys.add(key);
|
|
20371
|
+
if (registerPushMessageAndCheckDuplicate(msg, dedupState)) {
|
|
20372
|
+
duplicateCount++;
|
|
20373
|
+
continue;
|
|
19483
20374
|
}
|
|
19484
20375
|
dedupedMessages.push(msg);
|
|
19485
20376
|
}
|
|
@@ -19507,15 +20398,12 @@ function fullImport(db, meta, members, messages) {
|
|
|
19507
20398
|
generateSessionIndex(db);
|
|
19508
20399
|
} catch {
|
|
19509
20400
|
}
|
|
19510
|
-
return { writtenCount: stats.messageCount, duplicateCount
|
|
20401
|
+
return { writtenCount: stats.messageCount, duplicateCount };
|
|
19511
20402
|
}
|
|
19512
|
-
function
|
|
20403
|
+
function writeIncrementalImport(db, payload) {
|
|
19513
20404
|
const metaUpdateMode = payload.options?.metaUpdateMode ?? "patch";
|
|
19514
20405
|
const memberUpdateMode = payload.options?.memberUpdateMode ?? "upsert";
|
|
19515
|
-
const
|
|
19516
|
-
const existingKeys = /* @__PURE__ */ new Set();
|
|
19517
|
-
db.prepare("SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL").all().forEach((r) => existingPmids.add(r.platform_message_id));
|
|
19518
|
-
db.prepare(`SELECT msg.ts, m.platform_id, msg.content FROM message msg JOIN member m ON msg.sender_id = m.id`).all().forEach((r) => existingKeys.add(generateMessageKey(r.ts, r.platform_id, r.content)));
|
|
20406
|
+
const dedupState = createExistingMessageDedupState(db);
|
|
19519
20407
|
let metaUpdated = false;
|
|
19520
20408
|
let membersAdded = 0;
|
|
19521
20409
|
let membersUpdated = 0;
|
|
@@ -19552,22 +20440,23 @@ function incrementalImport2(db, payload) {
|
|
|
19552
20440
|
m.roles ? JSON.stringify(m.roles) : "[]"
|
|
19553
20441
|
);
|
|
19554
20442
|
if (!existed) {
|
|
19555
|
-
membersAdded++;
|
|
20443
|
+
if (isCountedMember(m.platformId)) membersAdded++;
|
|
19556
20444
|
const row = getMemberId.get(m.platformId);
|
|
19557
20445
|
if (row) existingMemberIds.add(m.platformId);
|
|
19558
|
-
} else {
|
|
20446
|
+
} else if (isCountedMember(m.platformId)) {
|
|
19559
20447
|
membersUpdated++;
|
|
19560
20448
|
}
|
|
19561
20449
|
}
|
|
19562
20450
|
});
|
|
19563
20451
|
}
|
|
19564
20452
|
const preWriteMaxTs = db.prepare("SELECT MAX(ts) as max_ts FROM message").get()?.max_ts ?? 0;
|
|
19565
|
-
const {
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
20453
|
+
const {
|
|
20454
|
+
writtenCount,
|
|
20455
|
+
duplicateCount,
|
|
20456
|
+
membersAdded: autoCreatedMembersAdded,
|
|
20457
|
+
minWrittenTs,
|
|
20458
|
+
ftsEntries
|
|
20459
|
+
} = writeMessages(db, payload.messages, dedupState);
|
|
19571
20460
|
if (ftsEntries.length > 0) {
|
|
19572
20461
|
try {
|
|
19573
20462
|
insertFtsEntries(db, ftsEntries);
|
|
@@ -19587,40 +20476,69 @@ function incrementalImport2(db, payload) {
|
|
|
19587
20476
|
if (!metaUpdated) {
|
|
19588
20477
|
db.prepare("UPDATE meta SET imported_at = ?").run(Math.floor(Date.now() / 1e3));
|
|
19589
20478
|
}
|
|
19590
|
-
return {
|
|
20479
|
+
return {
|
|
20480
|
+
writtenCount,
|
|
20481
|
+
duplicateCount,
|
|
20482
|
+
metaUpdated,
|
|
20483
|
+
membersAdded: membersAdded + autoCreatedMembersAdded,
|
|
20484
|
+
membersUpdated
|
|
20485
|
+
};
|
|
20486
|
+
}
|
|
20487
|
+
function incrementalImport2(db, payload) {
|
|
20488
|
+
return db.transaction(() => writeIncrementalImport(db, payload));
|
|
19591
20489
|
}
|
|
19592
|
-
var SAFE_SESSION_ID_RE = /^[a-zA-Z0-9_@-][a-zA-Z0-9_@.-]*$/;
|
|
19593
20490
|
async function pushImport(dbManager, sessionId, payload) {
|
|
19594
|
-
|
|
19595
|
-
return
|
|
20491
|
+
try {
|
|
20492
|
+
return await withDataDirImportLock(dbManager.getUserDataDir(), async () => {
|
|
20493
|
+
const outcome = await executePushImportUnlocked(
|
|
20494
|
+
{
|
|
20495
|
+
getDbPath: (id) => dbManager.getDbPath(id),
|
|
20496
|
+
openDatabase: (id, options2) => dbManager.openRawSessionDatabase(id, options2),
|
|
20497
|
+
deleteDatabase: (id) => {
|
|
20498
|
+
dbManager.deleteSessionDatabaseFiles(id);
|
|
20499
|
+
}
|
|
20500
|
+
},
|
|
20501
|
+
sessionId,
|
|
20502
|
+
payload
|
|
20503
|
+
);
|
|
20504
|
+
if (!outcome.ok) return outcome;
|
|
20505
|
+
try {
|
|
20506
|
+
dbManager.raiseCurrentChatDbCompatibilityGate();
|
|
20507
|
+
} catch (error) {
|
|
20508
|
+
if (outcome.result.created) dbManager.deleteSessionDatabaseFiles(sessionId);
|
|
20509
|
+
throw error;
|
|
20510
|
+
}
|
|
20511
|
+
return outcome;
|
|
20512
|
+
});
|
|
20513
|
+
} catch (error) {
|
|
20514
|
+
if (error instanceof ImportInProgressError) {
|
|
20515
|
+
return {
|
|
20516
|
+
ok: false,
|
|
20517
|
+
reason: "import_in_progress",
|
|
20518
|
+
message: "Another import is already in progress"
|
|
20519
|
+
};
|
|
20520
|
+
}
|
|
20521
|
+
throw error;
|
|
19596
20522
|
}
|
|
19597
|
-
|
|
19598
|
-
|
|
19599
|
-
|
|
19600
|
-
|
|
19601
|
-
message: "Another import is already in progress for this session"
|
|
19602
|
-
};
|
|
20523
|
+
}
|
|
20524
|
+
async function executePushImportUnlocked(deps, sessionId, payload) {
|
|
20525
|
+
if (!isValidImportSessionId(sessionId)) {
|
|
20526
|
+
return { ok: false, reason: "invalid_payload", message: "sessionId contains invalid characters" };
|
|
19603
20527
|
}
|
|
19604
|
-
const dbPath =
|
|
19605
|
-
const isNew = !
|
|
20528
|
+
const dbPath = deps.getDbPath(sessionId);
|
|
20529
|
+
const isNew = !fs34.existsSync(dbPath);
|
|
19606
20530
|
const validationError = validatePayload(payload, isNew);
|
|
19607
20531
|
if (validationError) {
|
|
19608
20532
|
return { ok: false, reason: "invalid_payload", message: validationError };
|
|
19609
20533
|
}
|
|
19610
|
-
importInProgress.add(sessionId);
|
|
19611
20534
|
try {
|
|
19612
20535
|
if (isNew) {
|
|
19613
|
-
const db2 =
|
|
20536
|
+
const db2 = deps.openDatabase(sessionId, { create: true });
|
|
19614
20537
|
try {
|
|
19615
|
-
const { writtenCount, duplicateCount
|
|
19616
|
-
db2,
|
|
19617
|
-
payload.meta,
|
|
19618
|
-
payload.members ?? [],
|
|
19619
|
-
payload.messages
|
|
19620
|
-
);
|
|
20538
|
+
const { writtenCount, duplicateCount } = fullImport(db2, payload.meta, payload.members ?? [], payload.messages);
|
|
19621
20539
|
const session = queryStats(db2);
|
|
19622
|
-
|
|
19623
|
-
|
|
20540
|
+
const membersAdded = session.memberCount;
|
|
20541
|
+
const outcome = {
|
|
19624
20542
|
ok: true,
|
|
19625
20543
|
result: {
|
|
19626
20544
|
sessionId,
|
|
@@ -19630,16 +20548,17 @@ async function pushImport(dbManager, sessionId, payload) {
|
|
|
19630
20548
|
updates: { metaUpdated: true, membersAdded, membersUpdated: 0 }
|
|
19631
20549
|
}
|
|
19632
20550
|
};
|
|
20551
|
+
appLogger.info("push-import", "Push import completed", outcome.result);
|
|
20552
|
+
return outcome;
|
|
19633
20553
|
} finally {
|
|
19634
20554
|
db2.close();
|
|
19635
20555
|
}
|
|
19636
20556
|
}
|
|
19637
|
-
const db =
|
|
20557
|
+
const db = deps.openDatabase(sessionId, { readonly: false });
|
|
19638
20558
|
try {
|
|
19639
20559
|
const { writtenCount, duplicateCount, metaUpdated, membersAdded, membersUpdated } = incrementalImport2(db, payload);
|
|
19640
20560
|
const session = queryStats(db);
|
|
19641
|
-
|
|
19642
|
-
return {
|
|
20561
|
+
const outcome = {
|
|
19643
20562
|
ok: true,
|
|
19644
20563
|
result: {
|
|
19645
20564
|
sessionId,
|
|
@@ -19649,6 +20568,8 @@ async function pushImport(dbManager, sessionId, payload) {
|
|
|
19649
20568
|
updates: { metaUpdated, membersAdded, membersUpdated }
|
|
19650
20569
|
}
|
|
19651
20570
|
};
|
|
20571
|
+
appLogger.info("push-import", "Push import completed", outcome.result);
|
|
20572
|
+
return outcome;
|
|
19652
20573
|
} finally {
|
|
19653
20574
|
db.close();
|
|
19654
20575
|
}
|
|
@@ -19656,16 +20577,54 @@ async function pushImport(dbManager, sessionId, payload) {
|
|
|
19656
20577
|
if (err instanceof DataDirCompatibilityError) throw err;
|
|
19657
20578
|
if (isNew) {
|
|
19658
20579
|
try {
|
|
19659
|
-
|
|
20580
|
+
deps.deleteDatabase(sessionId);
|
|
19660
20581
|
} catch {
|
|
19661
20582
|
}
|
|
19662
20583
|
}
|
|
20584
|
+
appLogger.error("push-import", "Push import failed", err);
|
|
19663
20585
|
return { ok: false, reason: "import_failed", message: err instanceof Error ? err.message : String(err) };
|
|
19664
|
-
} finally {
|
|
19665
|
-
importInProgress.delete(sessionId);
|
|
19666
20586
|
}
|
|
19667
20587
|
}
|
|
19668
20588
|
|
|
20589
|
+
// ../../packages/node-runtime/src/services/import-idempotency.ts
|
|
20590
|
+
import { createHash as createHash4 } from "crypto";
|
|
20591
|
+
var DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS = 60 * 60 * 1e3;
|
|
20592
|
+
function hashImportBody(body) {
|
|
20593
|
+
return createHash4("sha256").update(JSON.stringify(body ?? "")).digest("hex");
|
|
20594
|
+
}
|
|
20595
|
+
var ImportIdempotencyCache = class {
|
|
20596
|
+
constructor(ttlMs = DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS) {
|
|
20597
|
+
this.ttlMs = ttlMs;
|
|
20598
|
+
}
|
|
20599
|
+
ttlMs;
|
|
20600
|
+
entries = /* @__PURE__ */ new Map();
|
|
20601
|
+
start(key, bodyHash, now = Date.now()) {
|
|
20602
|
+
this.prune(now);
|
|
20603
|
+
const entry = this.entries.get(key);
|
|
20604
|
+
if (!entry) {
|
|
20605
|
+
this.entries.set(key, { bodyHash, status: "pending", timestamp: now });
|
|
20606
|
+
return { status: "started" };
|
|
20607
|
+
}
|
|
20608
|
+
if (entry.bodyHash !== bodyHash) return { status: "conflict" };
|
|
20609
|
+
if (entry.status === "pending") return { status: "pending" };
|
|
20610
|
+
return { status: "success", response: entry.response };
|
|
20611
|
+
}
|
|
20612
|
+
success(key, response) {
|
|
20613
|
+
const entry = this.entries.get(key);
|
|
20614
|
+
if (!entry) return;
|
|
20615
|
+
entry.status = "success";
|
|
20616
|
+
entry.response = response;
|
|
20617
|
+
}
|
|
20618
|
+
fail(key) {
|
|
20619
|
+
this.entries.delete(key);
|
|
20620
|
+
}
|
|
20621
|
+
prune(now) {
|
|
20622
|
+
for (const [key, entry] of this.entries) {
|
|
20623
|
+
if (now - entry.timestamp > this.ttlMs) this.entries.delete(key);
|
|
20624
|
+
}
|
|
20625
|
+
}
|
|
20626
|
+
};
|
|
20627
|
+
|
|
19669
20628
|
export {
|
|
19670
20629
|
writeParseResultToDb,
|
|
19671
20630
|
detectFormat,
|
|
@@ -19704,6 +20663,14 @@ export {
|
|
|
19704
20663
|
streamParseFileInfo,
|
|
19705
20664
|
analyzeIncrementalImport,
|
|
19706
20665
|
incrementalImport,
|
|
20666
|
+
isValidImportSessionId,
|
|
20667
|
+
resolveAutoImportTarget,
|
|
20668
|
+
autoImportFile,
|
|
20669
|
+
analyzeAutoImportFile,
|
|
20670
|
+
IMPORT_LOCK_FILENAME,
|
|
20671
|
+
IMPORT_IN_PROGRESS_ERROR_KEY,
|
|
20672
|
+
ImportInProgressError,
|
|
20673
|
+
withDataDirImportLock,
|
|
19707
20674
|
ArchiveImportError,
|
|
19708
20675
|
validateArchiveEntryName,
|
|
19709
20676
|
ZipArchiveReader,
|
|
@@ -19809,6 +20776,8 @@ export {
|
|
|
19809
20776
|
contacts_exports,
|
|
19810
20777
|
createPeopleRelationshipsService,
|
|
19811
20778
|
relationships_exports,
|
|
20779
|
+
createGlobalInsightService,
|
|
20780
|
+
global_insight_exports,
|
|
19812
20781
|
CHUNK_VECTOR_INDEX_TABLE,
|
|
19813
20782
|
CHUNK_VECTOR_INDEX_INDEXES,
|
|
19814
20783
|
EMBEDDING_INDEX_SCHEMA,
|
|
@@ -19866,6 +20835,10 @@ export {
|
|
|
19866
20835
|
createSemanticIndexService,
|
|
19867
20836
|
SemanticIndexService,
|
|
19868
20837
|
MergeSessionCache,
|
|
19869
|
-
pushImport
|
|
20838
|
+
pushImport,
|
|
20839
|
+
executePushImportUnlocked,
|
|
20840
|
+
DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS,
|
|
20841
|
+
hashImportBody,
|
|
20842
|
+
ImportIdempotencyCache
|
|
19870
20843
|
};
|
|
19871
|
-
//# sourceMappingURL=chunk-
|
|
20844
|
+
//# sourceMappingURL=chunk-KCPGEILH.mjs.map
|