chatlab-cli 0.30.2 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chat-command-VBDMWOKL.mjs → chat-command-CHV7RLBH.mjs} +9 -8
- package/dist/{chat-command-VBDMWOKL.mjs.map → chat-command-CHV7RLBH.mjs.map} +1 -1
- package/dist/{chunk-326OCJOT.mjs → chunk-DA5T64RY.mjs} +2 -2
- package/dist/{chunk-M2PXDX4G.mjs → chunk-DBBL74RK.mjs} +31 -22
- package/dist/chunk-DBBL74RK.mjs.map +1 -0
- package/dist/{chunk-KBSAVUFV.mjs → chunk-EXD2SXWL.mjs} +89 -15
- package/dist/chunk-EXD2SXWL.mjs.map +1 -0
- package/dist/{chunk-D3DPCDII.mjs → chunk-JH5RAJT2.mjs} +83 -34
- package/dist/chunk-JH5RAJT2.mjs.map +1 -0
- package/dist/{chunk-5EE2EFPE.mjs → chunk-RD335EMA.mjs} +4 -4
- package/dist/{chunk-WV5GSRQM.mjs → chunk-RDCUGC24.mjs} +5 -15
- package/dist/chunk-RDCUGC24.mjs.map +1 -0
- package/dist/{chunk-TEX44HNL.mjs → chunk-SNCXU2QL.mjs} +5 -15
- package/dist/chunk-SNCXU2QL.mjs.map +1 -0
- package/dist/{chunk-36JGKW6K.mjs → chunk-T64GA5HP.mjs} +1134 -364
- package/dist/chunk-T64GA5HP.mjs.map +1 -0
- package/dist/{chunk-LVEJHB3X.mjs → chunk-UWWFOEGF.mjs} +494 -97
- package/dist/chunk-UWWFOEGF.mjs.map +1 -0
- package/dist/{chunk-ULNRUS55.mjs → chunk-WQCY45U2.mjs} +3 -3
- package/dist/chunk-YAQLYL52.mjs +14 -0
- package/dist/chunk-YAQLYL52.mjs.map +1 -0
- package/dist/{chunk-TCZHG3G7.mjs → chunk-YPQ72HMX.mjs} +2 -2
- package/dist/cli.mjs +8 -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-3B5ARN7A.mjs} +12 -11
- package/dist/{import-WWCSXGVS.mjs → import-2DZGNSEX.mjs} +10 -7
- package/dist/index.mjs +12 -11
- package/dist/{mcp-A4M3LT5O.mjs → mcp-44I6FUUQ.mjs} +12 -11
- package/dist/{mcp-A4M3LT5O.mjs.map → mcp-44I6FUUQ.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-R3QX6L3L.mjs} +26 -6
- package/dist/{stream-import-HIE7PNL4.mjs → stream-import-VUEL2TTU.mjs} +10 -7
- package/dist/{worker-runtime-BJXO24XX.mjs → worker-runtime-GJAOIIZW.mjs} +6 -5
- package/dist/{worker-runtime-BJXO24XX.mjs.map → worker-runtime-GJAOIIZW.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-CdJT0t2f.js +110 -0
- package/dist-web/assets/{BufferResource-q3rzaWWd.js → BufferResource-BPGkshPa.js} +1 -1
- package/dist-web/assets/{CanvasRenderer-BRk1y8tp.js → CanvasRenderer-DTKkZhq4.js} +1 -1
- package/dist-web/assets/{EChartWordcloud-Clg60FMo.js → EChartWordcloud-DU3ZYdgz.js} +1 -1
- package/dist-web/assets/{Filter-BJwode73.js → Filter-CHXKzS2l.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-h-zl0dx1.js +1 -0
- package/dist-web/assets/PageHeader-CK0hTsdx.js +1 -0
- package/dist-web/assets/PageHeader-XIXMTomn.css +1 -0
- package/dist-web/assets/{RelationshipGalaxyCanvas-CORMD7vL.js → RelationshipGalaxyCanvas-Dwdy5flh.js} +3 -3
- package/dist-web/assets/{RelationshipGalaxyThreeCanvas-B_w_P4ob.js → RelationshipGalaxyThreeCanvas-CtY4TGen.js} +1 -1
- package/dist-web/assets/{RenderTargetSystem-CGhV9R9E.js → RenderTargetSystem-BE56mKRG.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-27hMyAUE.js +1 -0
- package/dist-web/assets/{WebGLRenderer-CAokTE5X.js → WebGLRenderer-Dm18dxOf.js} +1 -1
- package/dist-web/assets/{WebGPURenderer-DzP4ggSy.js → WebGPURenderer-BF6TF-is.js} +1 -1
- package/dist-web/assets/annual-summary-time-range-DVLXK1r4.js +1 -0
- package/dist-web/assets/base-CFPyD9Gy.js +1 -0
- package/dist-web/assets/{browserAll-CAZCK6dd.js → browserAll-Cn_CfkXl.js} +1 -1
- package/dist-web/assets/dateFormat-BcqNKTQ1.js +1 -0
- package/dist-web/assets/{electron-CkPDbAaS.js → electron-B6xOabTq.js} +1 -1
- package/dist-web/assets/electron-hcWiB7Gg.js +1 -0
- package/dist-web/assets/fetch-BPkgnY0R.js +1 -0
- package/dist-web/assets/{fetch-LX9jrJCC.js → fetch-BRXmDI_2.js} +1 -1
- package/dist-web/assets/{fetch-Apd5OLJw.js → fetch-BUm1isWe.js} +1 -1
- package/dist-web/assets/{fetch-B8rik0TM.js → fetch-CBOJwwJp.js} +3 -3
- package/dist-web/assets/{fetch-wqMSiaK3.js → fetch-CEhUu1Yu.js} +1 -1
- package/dist-web/assets/fetch-C_EfVFJi.js +3 -0
- package/dist-web/assets/{fetch-DQ9UNNAy.js → fetch-DL_u25Ui.js} +1 -1
- package/dist-web/assets/{fetch-BIEXkvDq.js → fetch-DMLq3xPG.js} +1 -1
- package/dist-web/assets/{fetch--UbRoBT7.js → fetch-DylpNKCR.js} +1 -1
- package/dist-web/assets/fetch-etgwVKIw.js +1 -0
- package/dist-web/assets/index-0x1QPzKZ.js +1 -0
- package/dist-web/assets/index-B5S9FnfP.js +1 -0
- package/dist-web/assets/index-BS6loLHW.js +1 -0
- package/dist-web/assets/index-BfO_LpDv.css +1 -0
- package/dist-web/assets/index-BrTlAl8b.js +1 -0
- package/dist-web/assets/index-ByUaiVQK.css +1 -0
- package/dist-web/assets/{index-S-YQRH1E.js → index-C3TnydSt.js} +1 -1
- package/dist-web/assets/index-CAWO10k1.js +2 -0
- package/dist-web/assets/index-C_0GHEcb.css +1 -0
- package/dist-web/assets/index-Chm17wNe.js +2 -0
- package/dist-web/assets/index-DAr_2DlV.js +173 -0
- package/dist-web/assets/index-Em6upenM.js +64 -0
- package/dist-web/assets/index-FoTy2Cce.js +1 -0
- package/dist-web/assets/index-YADL4XIp.js +22 -0
- package/dist-web/assets/index-ZsZwPLWy.css +1 -0
- package/dist-web/assets/index-_6lck7Bv.css +1 -0
- package/dist-web/assets/index-aMIgqj43.js +1 -0
- package/dist-web/assets/index-y7cxH1Et.css +1 -0
- package/dist-web/assets/index-zZVsV5HB.js +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-BUZ-dZy0.js} +1 -1
- package/dist-web/assets/{webworkerAll-Bhv-hBqR.js → webworkerAll-BKGP--A4.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-D3DPCDII.mjs.map +0 -1
- 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-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-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-326OCJOT.mjs.map → chunk-DA5T64RY.mjs.map} +0 -0
- /package/dist/{chunk-5EE2EFPE.mjs.map → chunk-RD335EMA.mjs.map} +0 -0
- /package/dist/{chunk-ULNRUS55.mjs.map → chunk-WQCY45U2.mjs.map} +0 -0
- /package/dist/{chunk-TCZHG3G7.mjs.map → chunk-YPQ72HMX.mjs.map} +0 -0
- /package/dist/{http-L4AMELPZ.mjs.map → http-3B5ARN7A.mjs.map} +0 -0
- /package/dist/{import-WWCSXGVS.mjs.map → import-2DZGNSEX.mjs.map} +0 -0
- /package/dist/{src-MPNBTFOE.mjs.map → src-R3QX6L3L.mjs.map} +0 -0
- /package/dist/{stream-import-HIE7PNL4.mjs.map → stream-import-VUEL2TTU.mjs.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createEmptyContactsDiagnostics,
|
|
10
10
|
normalizeContactsTimeRangePreset,
|
|
11
11
|
resolveContactsTimeRange
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SNCXU2QL.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-RDCUGC24.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-UWWFOEGF.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 };
|
|
@@ -5502,6 +5518,14 @@ function extractNameFromFilePath5(filePath) {
|
|
|
5502
5518
|
if (matchZhTw) return matchZhTw[1].trim();
|
|
5503
5519
|
return basename13.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
|
|
5504
5520
|
}
|
|
5521
|
+
function shouldInferPrivateChatName(filePath, fallbackName) {
|
|
5522
|
+
const basename13 = path8.basename(filePath);
|
|
5523
|
+
return /^_chat\.txt$/i.test(basename13) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
|
|
5524
|
+
}
|
|
5525
|
+
function inferPrivateChatName(memberMap, fallbackName) {
|
|
5526
|
+
const member = Array.from(memberMap.values()).find((item) => item.platformId !== "system");
|
|
5527
|
+
return member?.nickname || fallbackName;
|
|
5528
|
+
}
|
|
5505
5529
|
var feature10 = {
|
|
5506
5530
|
id: "whatsapp-native-txt",
|
|
5507
5531
|
name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
|
|
@@ -5520,9 +5544,9 @@ var feature10 = {
|
|
|
5520
5544
|
// 英文加密提示(WhatsApp 独有)
|
|
5521
5545
|
/你发送给自己的消息已进行端到端加密/,
|
|
5522
5546
|
// 简体中文自己对话提示(WhatsApp 独有)
|
|
5523
|
-
/\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}
|
|
5547
|
+
/\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
5548
|
// 消息行格式特征(无方括号,含 " - " 分隔符,WhatsApp 独有)
|
|
5525
|
-
/\[\d{1,4}\/\d{1,2}\/\d{
|
|
5549
|
+
/\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
|
|
5526
5550
|
// 消息行格式特征(方括号 + 含日期和时间的时间戳,兼容各地区变体)
|
|
5527
5551
|
],
|
|
5528
5552
|
// 文件名特征:与xxx的 WhatsApp 聊天.txt
|
|
@@ -5532,9 +5556,9 @@ var feature10 = {
|
|
|
5532
5556
|
function cleanLine(line) {
|
|
5533
5557
|
return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
|
|
5534
5558
|
}
|
|
5535
|
-
var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}) - (.+)
|
|
5559
|
+
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
5560
|
var MESSAGE_LINE_REGEX_V2 = /^\[([^\]]+)\] (.+)$/;
|
|
5537
|
-
var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]
|
|
5561
|
+
var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]*(.*)$/;
|
|
5538
5562
|
var SYSTEM_MESSAGE_PATTERNS = [
|
|
5539
5563
|
// 简体中文系统消息
|
|
5540
5564
|
/消息和通话已进行端到端加密/,
|
|
@@ -5586,12 +5610,12 @@ function detectMessageType2(content) {
|
|
|
5586
5610
|
}
|
|
5587
5611
|
var AMPM_MARKERS = [
|
|
5588
5612
|
[/\bPM\b/i, true],
|
|
5589
|
-
[/\bP\.M
|
|
5613
|
+
[/\bP\.M\.(?!\w)/i, true],
|
|
5590
5614
|
[/下午/, true],
|
|
5591
5615
|
[/午後/, true],
|
|
5592
5616
|
[/오후/, true],
|
|
5593
5617
|
[/\bAM\b/i, false],
|
|
5594
|
-
[/\bA\.M
|
|
5618
|
+
[/\bA\.M\.(?!\w)/i, false],
|
|
5595
5619
|
[/上午/, false],
|
|
5596
5620
|
[/午前/, false],
|
|
5597
5621
|
[/오전/, false]
|
|
@@ -5607,7 +5631,7 @@ function parseFlexibleV2Timestamp(raw) {
|
|
|
5607
5631
|
}
|
|
5608
5632
|
}
|
|
5609
5633
|
str = str.replace(/,/g, " ").replace(/\s+/g, " ").trim();
|
|
5610
|
-
const match = str.match(/^(\d{1,4}\/\d{1,2}\/\d{
|
|
5634
|
+
const match = str.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
|
|
5611
5635
|
if (!match) return null;
|
|
5612
5636
|
const [, datePart, timePart] = match;
|
|
5613
5637
|
const dateParts = datePart.split("/").map((s) => parseInt(s, 10));
|
|
@@ -5636,32 +5660,7 @@ function parseFlexibleV2Timestamp(raw) {
|
|
|
5636
5660
|
return isNaN(ts) ? null : ts;
|
|
5637
5661
|
}
|
|
5638
5662
|
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;
|
|
5663
|
+
return parseFlexibleV2Timestamp(timeStr) ?? 0;
|
|
5665
5664
|
}
|
|
5666
5665
|
async function* parseWhatsApp(options2) {
|
|
5667
5666
|
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
@@ -5673,7 +5672,7 @@ async function* parseWhatsApp(options2) {
|
|
|
5673
5672
|
yield { type: "progress", data: initialProgress };
|
|
5674
5673
|
onProgress?.(initialProgress);
|
|
5675
5674
|
onLog?.("info", `\u5F00\u59CB\u89E3\u6790 WhatsApp TXT \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
|
|
5676
|
-
const
|
|
5675
|
+
const fileChatName = extractNameFromFilePath5(filePath);
|
|
5677
5676
|
const memberMap = /* @__PURE__ */ new Map();
|
|
5678
5677
|
const messages = [];
|
|
5679
5678
|
let currentMessage = null;
|
|
@@ -5772,6 +5771,7 @@ async function* parseWhatsApp(options2) {
|
|
|
5772
5771
|
const hasSystemMember = memberMap.has("system");
|
|
5773
5772
|
const realMemberCount = hasSystemMember ? memberMap.size - 1 : memberMap.size;
|
|
5774
5773
|
const chatType = realMemberCount > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
|
|
5774
|
+
const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(filePath, fileChatName) ? inferPrivateChatName(memberMap, fileChatName) : fileChatName;
|
|
5775
5775
|
const meta = {
|
|
5776
5776
|
name: chatName,
|
|
5777
5777
|
platform: KNOWN_PLATFORMS.WHATSAPP,
|
|
@@ -7410,6 +7410,35 @@ function searchByFts(db, keywords, limit = 1e3, offset = 0) {
|
|
|
7410
7410
|
|
|
7411
7411
|
// ../../packages/node-runtime/src/import/streaming-importer.ts
|
|
7412
7412
|
import * as fs20 from "fs";
|
|
7413
|
+
|
|
7414
|
+
// ../../packages/node-runtime/src/import/message-deduplicator.ts
|
|
7415
|
+
function generateFallbackMessageKey(message) {
|
|
7416
|
+
const contentKey = generateMessageKey(message.timestamp, message.senderPlatformId, message.content);
|
|
7417
|
+
return JSON.stringify([contentKey, message.type, message.replyToMessageId || null]);
|
|
7418
|
+
}
|
|
7419
|
+
function createMessageDedupState(platformMessageIds = [], fallbackKeys = [], fallbackOnlyKeys = []) {
|
|
7420
|
+
return {
|
|
7421
|
+
platformMessageIds: new Set(platformMessageIds),
|
|
7422
|
+
fallbackKeys: new Set(fallbackKeys),
|
|
7423
|
+
fallbackOnlyKeys: new Set(fallbackOnlyKeys)
|
|
7424
|
+
};
|
|
7425
|
+
}
|
|
7426
|
+
function registerMessageAndCheckDuplicate(message, state) {
|
|
7427
|
+
const key = generateFallbackMessageKey(message);
|
|
7428
|
+
if (message.platformMessageId) {
|
|
7429
|
+
if (state.platformMessageIds.has(message.platformMessageId)) return true;
|
|
7430
|
+
state.platformMessageIds.add(message.platformMessageId);
|
|
7431
|
+
if (state.fallbackOnlyKeys.delete(key)) return true;
|
|
7432
|
+
state.fallbackKeys.add(key);
|
|
7433
|
+
return false;
|
|
7434
|
+
}
|
|
7435
|
+
if (state.fallbackKeys.has(key)) return true;
|
|
7436
|
+
state.fallbackKeys.add(key);
|
|
7437
|
+
state.fallbackOnlyKeys.add(key);
|
|
7438
|
+
return false;
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7441
|
+
// ../../packages/node-runtime/src/import/streaming-importer.ts
|
|
7413
7442
|
var BATCH_COMMIT_SIZE = 5e4;
|
|
7414
7443
|
var CHECKPOINT_INTERVAL = 2e5;
|
|
7415
7444
|
var yieldToEventLoop = () => new Promise((resolve2) => setImmediate(resolve2));
|
|
@@ -7418,6 +7447,28 @@ function defaultGenerateSessionId() {
|
|
|
7418
7447
|
const rand = Math.random().toString(36).substring(2, 8);
|
|
7419
7448
|
return `chat_${ts}_${rand}`;
|
|
7420
7449
|
}
|
|
7450
|
+
function prepareMessageForCreate(message) {
|
|
7451
|
+
if (!message.senderPlatformId) return { skipCounter: "skippedNoSenderId" };
|
|
7452
|
+
if (!message.senderAccountName) return { skipCounter: "skippedNoAccountName" };
|
|
7453
|
+
if (message.timestamp === void 0 || message.timestamp === null || isNaN(message.timestamp)) {
|
|
7454
|
+
return { skipCounter: "skippedInvalidTimestamp" };
|
|
7455
|
+
}
|
|
7456
|
+
if (message.type === void 0 || message.type === null) return { skipCounter: "skippedNoType" };
|
|
7457
|
+
let content = null;
|
|
7458
|
+
if (message.content != null) {
|
|
7459
|
+
content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
|
|
7460
|
+
}
|
|
7461
|
+
return {
|
|
7462
|
+
message: {
|
|
7463
|
+
platformMessageId: message.platformMessageId,
|
|
7464
|
+
timestamp: message.timestamp,
|
|
7465
|
+
senderPlatformId: message.senderPlatformId,
|
|
7466
|
+
type: message.type,
|
|
7467
|
+
content,
|
|
7468
|
+
replyToMessageId: message.replyToMessageId
|
|
7469
|
+
}
|
|
7470
|
+
};
|
|
7471
|
+
}
|
|
7421
7472
|
async function streamingImport(filePath, deps, formatOptions, externalSessionId) {
|
|
7422
7473
|
if (formatOptions?.formatId) {
|
|
7423
7474
|
const formatId = formatOptions.formatId;
|
|
@@ -7494,11 +7545,12 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7494
7545
|
`INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
7495
7546
|
);
|
|
7496
7547
|
const insertMember = db.prepare(
|
|
7497
|
-
`INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)
|
|
7498
|
-
VALUES (?, ?, ?, ?, ?)
|
|
7548
|
+
`INSERT INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles)
|
|
7549
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7499
7550
|
ON CONFLICT(platform_id) DO UPDATE SET
|
|
7500
7551
|
account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),
|
|
7501
7552
|
group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),
|
|
7553
|
+
aliases = CASE WHEN excluded.aliases != '[]' THEN excluded.aliases ELSE aliases END,
|
|
7502
7554
|
avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),
|
|
7503
7555
|
roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END`
|
|
7504
7556
|
);
|
|
@@ -7518,6 +7570,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7518
7570
|
let metaInserted = false;
|
|
7519
7571
|
let messageCountInBatch = 0;
|
|
7520
7572
|
let totalMessageCount = 0;
|
|
7573
|
+
let duplicateCount = 0;
|
|
7521
7574
|
let lastCheckpointCount = 0;
|
|
7522
7575
|
let inTransaction = false;
|
|
7523
7576
|
const beginTransaction = () => {
|
|
@@ -7569,6 +7622,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7569
7622
|
skippedInvalidTimestamp: 0,
|
|
7570
7623
|
skippedNoType: 0
|
|
7571
7624
|
};
|
|
7625
|
+
const dedupState = createMessageDedupState();
|
|
7572
7626
|
logger?.info("Starting streamParseFile...");
|
|
7573
7627
|
try {
|
|
7574
7628
|
await streamParseFile(
|
|
@@ -7613,6 +7667,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7613
7667
|
member.platformId,
|
|
7614
7668
|
member.accountName || null,
|
|
7615
7669
|
member.groupNickname || null,
|
|
7670
|
+
member.aliases ? JSON.stringify(member.aliases) : "[]",
|
|
7616
7671
|
member.avatar || null,
|
|
7617
7672
|
member.roles ? JSON.stringify(member.roles) : "[]"
|
|
7618
7673
|
);
|
|
@@ -7634,20 +7689,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7634
7689
|
let memberInsertCount = 0;
|
|
7635
7690
|
let nicknameChangeCount = 0;
|
|
7636
7691
|
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++;
|
|
7692
|
+
const prepared = prepareMessageForCreate(msg);
|
|
7693
|
+
if ("skipCounter" in prepared) {
|
|
7694
|
+
callbackStats[prepared.skipCounter]++;
|
|
7647
7695
|
continue;
|
|
7648
7696
|
}
|
|
7649
|
-
|
|
7650
|
-
|
|
7697
|
+
const dedupMessage = prepared.message;
|
|
7698
|
+
if (registerMessageAndCheckDuplicate(dedupMessage, dedupState)) {
|
|
7699
|
+
duplicateCount++;
|
|
7651
7700
|
continue;
|
|
7652
7701
|
}
|
|
7653
7702
|
let t0 = Date.now();
|
|
@@ -7656,6 +7705,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7656
7705
|
msg.senderPlatformId,
|
|
7657
7706
|
msg.senderAccountName || null,
|
|
7658
7707
|
msg.senderGroupNickname || null,
|
|
7708
|
+
"[]",
|
|
7659
7709
|
null,
|
|
7660
7710
|
"[]"
|
|
7661
7711
|
);
|
|
@@ -7669,18 +7719,14 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7669
7719
|
}
|
|
7670
7720
|
const senderId = memberIdMap.get(msg.senderPlatformId);
|
|
7671
7721
|
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
7722
|
t0 = Date.now();
|
|
7677
7723
|
insertMessage.run(
|
|
7678
7724
|
senderId,
|
|
7679
7725
|
msg.senderAccountName || null,
|
|
7680
7726
|
msg.senderGroupNickname || null,
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7727
|
+
dedupMessage.timestamp,
|
|
7728
|
+
dedupMessage.type,
|
|
7729
|
+
dedupMessage.content,
|
|
7684
7730
|
msg.replyToMessageId || null,
|
|
7685
7731
|
msg.platformMessageId || null
|
|
7686
7732
|
);
|
|
@@ -7797,6 +7843,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7797
7843
|
logger?.info(` invalid timestamp: ${callbackStats.skippedInvalidTimestamp}`);
|
|
7798
7844
|
if (callbackStats.skippedNoType > 0) logger?.info(` missing type: ${callbackStats.skippedNoType}`);
|
|
7799
7845
|
}
|
|
7846
|
+
if (duplicateCount > 0) logger?.info(`Duplicate messages skipped: ${duplicateCount}`);
|
|
7800
7847
|
logger?.summary(totalMessageCount, memberIdMap.size);
|
|
7801
7848
|
if (totalMessageCount === 0) {
|
|
7802
7849
|
logger?.error(
|
|
@@ -7829,6 +7876,7 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7829
7876
|
detectedFormat: formatFeature ? `${formatFeature.name} (${formatFeature.id})` : null,
|
|
7830
7877
|
messagesReceived: callbackStats.totalMessagesReceived,
|
|
7831
7878
|
messagesWritten: totalMessageCount,
|
|
7879
|
+
duplicateCount,
|
|
7832
7880
|
messagesSkipped: callbackStats.skippedNoSenderId + callbackStats.skippedNoAccountName + callbackStats.skippedInvalidTimestamp + callbackStats.skippedNoType,
|
|
7833
7881
|
skipReasons: {
|
|
7834
7882
|
noSenderId: callbackStats.skippedNoSenderId,
|
|
@@ -7845,11 +7893,21 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
7845
7893
|
async function analyzeNewImport(filePath, onProgress) {
|
|
7846
7894
|
const formatFeature = detectFormat(filePath);
|
|
7847
7895
|
if (!formatFeature) {
|
|
7848
|
-
return {
|
|
7896
|
+
return {
|
|
7897
|
+
totalMessages: 0,
|
|
7898
|
+
newMessageCount: 0,
|
|
7899
|
+
duplicateCount: 0,
|
|
7900
|
+
totalMembers: 0,
|
|
7901
|
+
meta: null,
|
|
7902
|
+
error: "error.unrecognized_format"
|
|
7903
|
+
};
|
|
7849
7904
|
}
|
|
7850
7905
|
let meta = null;
|
|
7851
7906
|
const memberSet = /* @__PURE__ */ new Set();
|
|
7907
|
+
const dedupState = createMessageDedupState();
|
|
7852
7908
|
let totalMessages = 0;
|
|
7909
|
+
let newMessageCount = 0;
|
|
7910
|
+
let duplicateCount = 0;
|
|
7853
7911
|
await streamParseFile(filePath, {
|
|
7854
7912
|
onMeta: (parsedMeta) => {
|
|
7855
7913
|
meta = { name: parsedMeta.name, platform: parsedMeta.platform, type: parsedMeta.type };
|
|
@@ -7864,10 +7922,17 @@ async function analyzeNewImport(filePath, onProgress) {
|
|
|
7864
7922
|
for (const msg of batch) {
|
|
7865
7923
|
totalMessages++;
|
|
7866
7924
|
if (!memberSet.has(msg.senderPlatformId)) memberSet.add(msg.senderPlatformId);
|
|
7925
|
+
const prepared = prepareMessageForCreate(msg);
|
|
7926
|
+
if ("skipCounter" in prepared) continue;
|
|
7927
|
+
if (registerMessageAndCheckDuplicate(prepared.message, dedupState)) {
|
|
7928
|
+
duplicateCount++;
|
|
7929
|
+
} else {
|
|
7930
|
+
newMessageCount++;
|
|
7931
|
+
}
|
|
7867
7932
|
}
|
|
7868
7933
|
}
|
|
7869
7934
|
});
|
|
7870
|
-
return { totalMessages, totalMembers: memberSet.size, meta };
|
|
7935
|
+
return { totalMessages, newMessageCount, duplicateCount, totalMembers: memberSet.size, meta };
|
|
7871
7936
|
}
|
|
7872
7937
|
async function streamParseFileInfo(filePath, deps) {
|
|
7873
7938
|
const formatFeature = detectFormat(filePath);
|
|
@@ -8028,32 +8093,38 @@ function countHistory(tracker) {
|
|
|
8028
8093
|
function loadExistingDedup(db) {
|
|
8029
8094
|
const existingPlatformMsgIds = /* @__PURE__ */ new Set();
|
|
8030
8095
|
const existingKeys = /* @__PURE__ */ new Set();
|
|
8096
|
+
const existingFallbackOnlyKeys = /* @__PURE__ */ new Set();
|
|
8031
8097
|
const pmidRows = db.prepare("SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL").all();
|
|
8032
8098
|
for (const row of pmidRows) {
|
|
8033
8099
|
existingPlatformMsgIds.add(row.platform_message_id);
|
|
8034
8100
|
}
|
|
8035
8101
|
const hashRows = db.prepare(
|
|
8036
|
-
`SELECT ts, m.platform_id as sender_platform_id, content
|
|
8102
|
+
`SELECT ts, m.platform_id as sender_platform_id, msg.type, content, reply_to_message_id,
|
|
8103
|
+
platform_message_id
|
|
8037
8104
|
FROM message msg
|
|
8038
8105
|
JOIN member m ON msg.sender_id = m.id`
|
|
8039
8106
|
).all();
|
|
8040
8107
|
for (const row of hashRows) {
|
|
8041
|
-
|
|
8108
|
+
const key = generateFallbackMessageKey({
|
|
8109
|
+
timestamp: row.ts,
|
|
8110
|
+
senderPlatformId: row.sender_platform_id,
|
|
8111
|
+
type: row.type,
|
|
8112
|
+
content: row.content,
|
|
8113
|
+
replyToMessageId: row.reply_to_message_id ?? void 0
|
|
8114
|
+
});
|
|
8115
|
+
existingKeys.add(key);
|
|
8116
|
+
if (!row.platform_message_id) existingFallbackOnlyKeys.add(key);
|
|
8042
8117
|
}
|
|
8043
|
-
return { existingPlatformMsgIds, existingKeys };
|
|
8118
|
+
return { existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys };
|
|
8044
8119
|
}
|
|
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;
|
|
8120
|
+
function isDuplicate(msg, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys) {
|
|
8121
|
+
return registerMessageAndCheckDuplicate(msg, {
|
|
8122
|
+
platformMessageIds: existingPlatformMsgIds,
|
|
8123
|
+
fallbackKeys: existingKeys,
|
|
8124
|
+
fallbackOnlyKeys: existingFallbackOnlyKeys
|
|
8125
|
+
});
|
|
8055
8126
|
}
|
|
8056
|
-
function
|
|
8127
|
+
function normalizeImportTimestamp(timestamp) {
|
|
8057
8128
|
const value = typeof timestamp === "string" && timestamp.trim() !== "" ? Number(timestamp) : timestamp;
|
|
8058
8129
|
return typeof value === "number" && value > 0 && Number.isFinite(value) ? value : null;
|
|
8059
8130
|
}
|
|
@@ -8068,7 +8139,7 @@ async function analyzeIncrementalImport(sessionId, filePath, deps) {
|
|
|
8068
8139
|
} catch {
|
|
8069
8140
|
return { error: "error.session_not_found", newMessageCount: 0, duplicateCount: 0, totalInFile: 0 };
|
|
8070
8141
|
}
|
|
8071
|
-
const { existingPlatformMsgIds, existingKeys } = loadExistingDedup(db);
|
|
8142
|
+
const { existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys } = loadExistingDedup(db);
|
|
8072
8143
|
db.close();
|
|
8073
8144
|
let totalInFile = 0;
|
|
8074
8145
|
let newMessageCount = 0;
|
|
@@ -8084,9 +8155,9 @@ async function analyzeIncrementalImport(sessionId, filePath, deps) {
|
|
|
8084
8155
|
onMessageBatch: (batch) => {
|
|
8085
8156
|
for (const msg of batch) {
|
|
8086
8157
|
totalInFile++;
|
|
8087
|
-
const timestamp =
|
|
8158
|
+
const timestamp = normalizeImportTimestamp(msg.timestamp);
|
|
8088
8159
|
if (timestamp === null) continue;
|
|
8089
|
-
if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys)) {
|
|
8160
|
+
if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys)) {
|
|
8090
8161
|
duplicateCount++;
|
|
8091
8162
|
} else {
|
|
8092
8163
|
newMessageCount++;
|
|
@@ -8097,7 +8168,7 @@ async function analyzeIncrementalImport(sessionId, filePath, deps) {
|
|
|
8097
8168
|
return { newMessageCount, duplicateCount, totalInFile };
|
|
8098
8169
|
}
|
|
8099
8170
|
async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
8100
|
-
const formatFeature = detectFormat(filePath);
|
|
8171
|
+
const formatFeature = options2?.formatId ? getFormatFeatureById(options2.formatId) : detectFormat(filePath);
|
|
8101
8172
|
if (!formatFeature) {
|
|
8102
8173
|
return { success: false, newMessageCount: 0, error: "error.unrecognized_format" };
|
|
8103
8174
|
}
|
|
@@ -8118,18 +8189,19 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8118
8189
|
}
|
|
8119
8190
|
};
|
|
8120
8191
|
var trackError = trackError2;
|
|
8121
|
-
const { existingPlatformMsgIds, existingKeys } = loadExistingDedup(db);
|
|
8192
|
+
const { existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys } = loadExistingDedup(db);
|
|
8122
8193
|
const memberIdMap = /* @__PURE__ */ new Map();
|
|
8123
8194
|
const existingMembers = db.prepare("SELECT id, platform_id FROM member").all();
|
|
8124
8195
|
for (const m of existingMembers) {
|
|
8125
8196
|
memberIdMap.set(m.platform_id, m.id);
|
|
8126
8197
|
}
|
|
8127
8198
|
const upsertMember = db.prepare(`
|
|
8128
|
-
INSERT INTO member (platform_id, account_name, group_nickname, avatar, roles)
|
|
8129
|
-
VALUES (?, ?, ?, ?, ?)
|
|
8199
|
+
INSERT INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles)
|
|
8200
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
8130
8201
|
ON CONFLICT(platform_id) DO UPDATE SET
|
|
8131
8202
|
account_name = COALESCE(NULLIF(excluded.account_name, ''), account_name),
|
|
8132
8203
|
group_nickname = COALESCE(NULLIF(excluded.group_nickname, ''), group_nickname),
|
|
8204
|
+
aliases = CASE WHEN excluded.aliases != '[]' THEN excluded.aliases ELSE aliases END,
|
|
8133
8205
|
avatar = COALESCE(NULLIF(excluded.avatar, ''), avatar),
|
|
8134
8206
|
roles = CASE WHEN excluded.roles != '[]' THEN excluded.roles ELSE roles END
|
|
8135
8207
|
`);
|
|
@@ -8165,106 +8237,112 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8165
8237
|
const MAX_ERROR_SAMPLES = 5;
|
|
8166
8238
|
const BATCH_SIZE2 = 5e3;
|
|
8167
8239
|
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) : "[]"
|
|
8240
|
+
await streamParseFile(
|
|
8241
|
+
filePath,
|
|
8242
|
+
{
|
|
8243
|
+
formatOptions: options2?.chatIndex === void 0 ? void 0 : { chatIndex: options2.chatIndex },
|
|
8244
|
+
onMeta: (meta) => {
|
|
8245
|
+
if (metaUpdateMode === "none") return;
|
|
8246
|
+
updateMeta.run(
|
|
8247
|
+
meta.name || "",
|
|
8248
|
+
meta.groupId || "",
|
|
8249
|
+
meta.groupAvatar || "",
|
|
8250
|
+
meta.ownerId || "",
|
|
8251
|
+
Math.floor(Date.now() / 1e3)
|
|
8190
8252
|
);
|
|
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;
|
|
8253
|
+
metaUpdated = true;
|
|
8254
|
+
},
|
|
8255
|
+
onMembers: (members) => {
|
|
8256
|
+
if (memberUpdateMode === "none") return;
|
|
8257
|
+
for (const m of members) {
|
|
8258
|
+
const existed = memberIdMap.has(m.platformId);
|
|
8259
|
+
upsertMember.run(
|
|
8260
|
+
m.platformId,
|
|
8261
|
+
m.accountName || null,
|
|
8262
|
+
m.groupNickname || null,
|
|
8263
|
+
m.aliases ? JSON.stringify(m.aliases) : "[]",
|
|
8264
|
+
m.avatar || null,
|
|
8265
|
+
m.roles ? JSON.stringify(m.roles) : "[]"
|
|
8266
|
+
);
|
|
8267
|
+
if (!existed) {
|
|
8268
|
+
const row = getMemberId.get(m.platformId);
|
|
8269
|
+
if (row) memberIdMap.set(m.platformId, row.id);
|
|
8270
|
+
membersAdded++;
|
|
8271
|
+
} else {
|
|
8272
|
+
membersUpdated++;
|
|
8273
|
+
}
|
|
8222
8274
|
}
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8275
|
+
},
|
|
8276
|
+
onProgress: (progress) => {
|
|
8277
|
+
deps.onProgress(progress);
|
|
8278
|
+
},
|
|
8279
|
+
onMessageBatch: (batch) => {
|
|
8280
|
+
for (const msg of batch) {
|
|
8281
|
+
processedCount++;
|
|
8282
|
+
if (!msg.senderPlatformId) {
|
|
8283
|
+
trackError2(processedCount, "MISSING_SENDER", "sender field is empty");
|
|
8284
|
+
continue;
|
|
8285
|
+
}
|
|
8286
|
+
if (msg.timestamp === void 0 || msg.timestamp === null) {
|
|
8287
|
+
trackError2(processedCount, "MISSING_TIMESTAMP", "timestamp field is missing");
|
|
8288
|
+
continue;
|
|
8289
|
+
}
|
|
8290
|
+
const timestamp = normalizeImportTimestamp(msg.timestamp);
|
|
8291
|
+
if (timestamp === null) {
|
|
8292
|
+
trackError2(processedCount, "INVALID_TIMESTAMP", `timestamp value: ${msg.timestamp}`);
|
|
8293
|
+
continue;
|
|
8294
|
+
}
|
|
8295
|
+
if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys)) {
|
|
8296
|
+
duplicateCount++;
|
|
8297
|
+
continue;
|
|
8298
|
+
}
|
|
8299
|
+
let memberId = memberIdMap.get(msg.senderPlatformId);
|
|
8300
|
+
if (!memberId) {
|
|
8301
|
+
insertMemberMinimal.run(
|
|
8302
|
+
msg.senderPlatformId,
|
|
8303
|
+
msg.senderAccountName || null,
|
|
8304
|
+
msg.senderGroupNickname || null,
|
|
8305
|
+
null
|
|
8306
|
+
);
|
|
8307
|
+
const row = getMemberId.get(msg.senderPlatformId);
|
|
8308
|
+
if (row) {
|
|
8309
|
+
memberId = row.id;
|
|
8310
|
+
memberIdMap.set(msg.senderPlatformId, memberId);
|
|
8311
|
+
membersAdded++;
|
|
8312
|
+
}
|
|
8313
|
+
}
|
|
8314
|
+
if (!memberId) continue;
|
|
8315
|
+
const msgResult = insertMessage.run(
|
|
8316
|
+
memberId,
|
|
8227
8317
|
msg.senderAccountName || null,
|
|
8228
8318
|
msg.senderGroupNickname || null,
|
|
8229
|
-
|
|
8319
|
+
timestamp,
|
|
8320
|
+
msg.type,
|
|
8321
|
+
msg.content || null,
|
|
8322
|
+
msg.replyToMessageId || null,
|
|
8323
|
+
msg.platformMessageId || null
|
|
8230
8324
|
);
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8325
|
+
newFtsEntries.push({
|
|
8326
|
+
id: Number(msgResult.lastInsertRowid ?? 0),
|
|
8327
|
+
content: msg.content || null
|
|
8328
|
+
});
|
|
8329
|
+
if (timestamp < minWrittenTs) minWrittenTs = timestamp;
|
|
8330
|
+
newMessageCount++;
|
|
8331
|
+
}
|
|
8332
|
+
if (processedCount % BATCH_SIZE2 === 0) {
|
|
8333
|
+
deps.onProgress({
|
|
8334
|
+
stage: "saving",
|
|
8335
|
+
bytesRead: 0,
|
|
8336
|
+
totalBytes: 0,
|
|
8337
|
+
messagesProcessed: processedCount,
|
|
8338
|
+
percentage: 50,
|
|
8339
|
+
message: `Processed ${processedCount}, added ${newMessageCount}`
|
|
8340
|
+
});
|
|
8237
8341
|
}
|
|
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
8342
|
}
|
|
8266
|
-
}
|
|
8267
|
-
|
|
8343
|
+
},
|
|
8344
|
+
options2?.formatId
|
|
8345
|
+
);
|
|
8268
8346
|
db.exec("COMMIT");
|
|
8269
8347
|
if (!metaUpdated) {
|
|
8270
8348
|
db.prepare("UPDATE meta SET imported_at = ?").run(Math.floor(Date.now() / 1e3));
|
|
@@ -8340,6 +8418,306 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8340
8418
|
}
|
|
8341
8419
|
}
|
|
8342
8420
|
|
|
8421
|
+
// ../../packages/node-runtime/src/import/auto-import-matcher.ts
|
|
8422
|
+
var MATCH_WINDOW_SIZE = 5;
|
|
8423
|
+
function buildPrivateIdentity(ownerId, memberIds) {
|
|
8424
|
+
if (!ownerId) return null;
|
|
8425
|
+
const ids = [...new Set(memberIds)].filter((id) => id && id.toLowerCase() !== "system").sort();
|
|
8426
|
+
return ids.length > 0 ? `${ownerId}\0${ids.join("\0")}` : null;
|
|
8427
|
+
}
|
|
8428
|
+
function isBusinessMessage(type, content) {
|
|
8429
|
+
return type !== 80 /* SYSTEM */ && type !== 81 /* RECALL */ && Boolean(content?.trim());
|
|
8430
|
+
}
|
|
8431
|
+
function addMessageWindow(recentKeys, windows, message) {
|
|
8432
|
+
if (!message.senderPlatformId || !isBusinessMessage(message.type, message.content)) return;
|
|
8433
|
+
const timestamp = normalizeImportTimestamp(message.timestamp);
|
|
8434
|
+
if (timestamp === null) return;
|
|
8435
|
+
recentKeys.push(generateMessageKey(timestamp, message.senderPlatformId, message.content));
|
|
8436
|
+
if (recentKeys.length > MATCH_WINDOW_SIZE) recentKeys.shift();
|
|
8437
|
+
if (recentKeys.length === MATCH_WINDOW_SIZE) windows.add(recentKeys.join(""));
|
|
8438
|
+
}
|
|
8439
|
+
async function resolveAutoImportTarget(filePath, deps, formatOptions) {
|
|
8440
|
+
let sourceMeta = null;
|
|
8441
|
+
const sourceMemberIds = /* @__PURE__ */ new Set();
|
|
8442
|
+
const sourceWindows = /* @__PURE__ */ new Set();
|
|
8443
|
+
const recentSourceKeys = [];
|
|
8444
|
+
const { formatId, ...parserOptions } = formatOptions ?? {};
|
|
8445
|
+
await streamParseFile(
|
|
8446
|
+
filePath,
|
|
8447
|
+
{
|
|
8448
|
+
formatOptions: parserOptions,
|
|
8449
|
+
onProgress: deps.onProgress ?? (() => {
|
|
8450
|
+
}),
|
|
8451
|
+
onMeta: (meta2) => {
|
|
8452
|
+
sourceMeta = meta2;
|
|
8453
|
+
},
|
|
8454
|
+
onMembers: (members) => {
|
|
8455
|
+
for (const member of members) sourceMemberIds.add(member.platformId);
|
|
8456
|
+
},
|
|
8457
|
+
onMessageBatch: (messages) => {
|
|
8458
|
+
for (const message of messages) {
|
|
8459
|
+
sourceMemberIds.add(message.senderPlatformId);
|
|
8460
|
+
addMessageWindow(recentSourceKeys, sourceWindows, message);
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
},
|
|
8464
|
+
typeof formatId === "string" ? formatId : void 0
|
|
8465
|
+
);
|
|
8466
|
+
if (!sourceMeta) throw new Error("Import source did not provide metadata");
|
|
8467
|
+
const meta = sourceMeta;
|
|
8468
|
+
const privateIdentity = meta.type === "private" ? buildPrivateIdentity(meta.ownerId, sourceMemberIds) : null;
|
|
8469
|
+
const hasStableIdentity = Boolean(meta.groupId || privateIdentity);
|
|
8470
|
+
const stableMatches = [];
|
|
8471
|
+
const trailingMatches = [];
|
|
8472
|
+
for (const sessionId of deps.listSessionIds()) {
|
|
8473
|
+
let db = null;
|
|
8474
|
+
try {
|
|
8475
|
+
db = deps.openReadonly(sessionId);
|
|
8476
|
+
if (!isChatSessionDb(db)) continue;
|
|
8477
|
+
const candidate = getSessionMeta(db);
|
|
8478
|
+
if (candidate?.platform !== meta.platform || candidate.type !== meta.type) continue;
|
|
8479
|
+
const groupMatches = Boolean(meta.groupId && candidate.groupId === meta.groupId);
|
|
8480
|
+
const memberRows = privateIdentity ? db.prepare("SELECT platform_id FROM member WHERE LOWER(platform_id) != 'system'").all() : [];
|
|
8481
|
+
const candidatePrivateIdentity = privateIdentity ? buildPrivateIdentity(
|
|
8482
|
+
candidate.ownerId,
|
|
8483
|
+
memberRows.map((row) => row.platform_id)
|
|
8484
|
+
) : null;
|
|
8485
|
+
if (groupMatches || privateIdentity && candidatePrivateIdentity === privateIdentity) {
|
|
8486
|
+
stableMatches.push(sessionId);
|
|
8487
|
+
}
|
|
8488
|
+
if (sourceWindows.size > 0) {
|
|
8489
|
+
const rows = db.prepare(
|
|
8490
|
+
`SELECT msg.ts, member.platform_id, msg.type, msg.content
|
|
8491
|
+
FROM message msg
|
|
8492
|
+
JOIN member ON member.id = msg.sender_id
|
|
8493
|
+
WHERE msg.type NOT IN (?, ?)
|
|
8494
|
+
AND NULLIF(TRIM(msg.content), '') IS NOT NULL
|
|
8495
|
+
ORDER BY msg.ts DESC, msg.id DESC
|
|
8496
|
+
LIMIT ?`
|
|
8497
|
+
).all(80 /* SYSTEM */, 81 /* RECALL */, MATCH_WINDOW_SIZE);
|
|
8498
|
+
if (rows.length === MATCH_WINDOW_SIZE) {
|
|
8499
|
+
const signature = rows.reverse().map((row) => generateMessageKey(row.ts, row.platform_id, row.content)).join("");
|
|
8500
|
+
if (sourceWindows.has(signature)) trailingMatches.push(sessionId);
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
} catch (error) {
|
|
8504
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8505
|
+
throw new Error(`Failed to inspect import candidate ${JSON.stringify(sessionId)}: ${detail}`, {
|
|
8506
|
+
cause: error
|
|
8507
|
+
});
|
|
8508
|
+
} finally {
|
|
8509
|
+
db?.close();
|
|
8510
|
+
}
|
|
8511
|
+
}
|
|
8512
|
+
if (hasStableIdentity) {
|
|
8513
|
+
if (meta.sourceSessionId && stableMatches.includes(meta.sourceSessionId)) {
|
|
8514
|
+
return { action: "incremental", sessionId: meta.sourceSessionId, matchedBy: "source-session-id" };
|
|
8515
|
+
}
|
|
8516
|
+
if (stableMatches.length === 1) {
|
|
8517
|
+
return { action: "incremental", sessionId: stableMatches[0], matchedBy: "stable-id" };
|
|
8518
|
+
}
|
|
8519
|
+
}
|
|
8520
|
+
if (meta.sourceSessionId && trailingMatches.includes(meta.sourceSessionId)) {
|
|
8521
|
+
return { action: "incremental", sessionId: meta.sourceSessionId, matchedBy: "source-session-id" };
|
|
8522
|
+
}
|
|
8523
|
+
if (trailingMatches.length === 1) {
|
|
8524
|
+
return { action: "incremental", sessionId: trailingMatches[0], matchedBy: "trailing-messages" };
|
|
8525
|
+
}
|
|
8526
|
+
const ambiguous = stableMatches.length > 1 || trailingMatches.length > 1;
|
|
8527
|
+
return { action: "create", reason: ambiguous ? "ambiguous" : "no-match" };
|
|
8528
|
+
}
|
|
8529
|
+
|
|
8530
|
+
// ../../packages/node-runtime/src/import/auto-importer.ts
|
|
8531
|
+
function mapCreateResult(result, createReason) {
|
|
8532
|
+
if (!result.success || !result.sessionId) {
|
|
8533
|
+
return { success: false, error: result.error, diagnostics: result.diagnostics };
|
|
8534
|
+
}
|
|
8535
|
+
return {
|
|
8536
|
+
success: true,
|
|
8537
|
+
sessionId: result.sessionId,
|
|
8538
|
+
importMode: "created",
|
|
8539
|
+
...createReason ? { createReason } : {},
|
|
8540
|
+
newMessageCount: result.diagnostics?.messagesWritten ?? 0,
|
|
8541
|
+
duplicateCount: result.diagnostics?.duplicateCount ?? 0,
|
|
8542
|
+
diagnostics: result.diagnostics
|
|
8543
|
+
};
|
|
8544
|
+
}
|
|
8545
|
+
function mapIncrementalResult(sessionId, result, matchedBy) {
|
|
8546
|
+
if (!result.success) return { success: false, sessionId, error: result.error };
|
|
8547
|
+
return {
|
|
8548
|
+
success: true,
|
|
8549
|
+
sessionId,
|
|
8550
|
+
importMode: "incremental",
|
|
8551
|
+
...matchedBy ? { matchedBy } : {},
|
|
8552
|
+
newMessageCount: result.newMessageCount,
|
|
8553
|
+
duplicateCount: result.batch?.duplicateCount ?? 0
|
|
8554
|
+
};
|
|
8555
|
+
}
|
|
8556
|
+
async function autoImportFile(filePath, deps, options2 = {}) {
|
|
8557
|
+
try {
|
|
8558
|
+
if (options2.explicitSessionId) {
|
|
8559
|
+
if (deps.sessionExists(options2.explicitSessionId)) {
|
|
8560
|
+
const result3 = mapIncrementalResult(
|
|
8561
|
+
options2.explicitSessionId,
|
|
8562
|
+
await deps.appendSession(options2.explicitSessionId, filePath, options2.formatOptions)
|
|
8563
|
+
);
|
|
8564
|
+
appLogger.info("import", "Explicit incremental import completed", {
|
|
8565
|
+
sessionId: options2.explicitSessionId,
|
|
8566
|
+
success: result3.success,
|
|
8567
|
+
newMessageCount: result3.newMessageCount,
|
|
8568
|
+
duplicateCount: result3.duplicateCount
|
|
8569
|
+
});
|
|
8570
|
+
return result3;
|
|
8571
|
+
}
|
|
8572
|
+
const result2 = mapCreateResult(
|
|
8573
|
+
await deps.createSession(filePath, options2.formatOptions, options2.explicitSessionId)
|
|
8574
|
+
);
|
|
8575
|
+
appLogger.info("import", "Explicit session import completed", {
|
|
8576
|
+
sessionId: result2.sessionId,
|
|
8577
|
+
success: result2.success,
|
|
8578
|
+
importMode: result2.importMode
|
|
8579
|
+
});
|
|
8580
|
+
return result2;
|
|
8581
|
+
}
|
|
8582
|
+
appLogger.info("import", "Automatic session matching started", {
|
|
8583
|
+
candidateCount: deps.listSessionIds().length
|
|
8584
|
+
});
|
|
8585
|
+
const decision = await (deps.resolveTarget ?? resolveAutoImportTarget)(
|
|
8586
|
+
filePath,
|
|
8587
|
+
deps,
|
|
8588
|
+
options2.formatOptions
|
|
8589
|
+
);
|
|
8590
|
+
if (decision.action === "incremental") {
|
|
8591
|
+
const result2 = mapIncrementalResult(
|
|
8592
|
+
decision.sessionId,
|
|
8593
|
+
await deps.appendSession(decision.sessionId, filePath, options2.formatOptions),
|
|
8594
|
+
decision.matchedBy
|
|
8595
|
+
);
|
|
8596
|
+
appLogger.info("import", "Automatic incremental import completed", {
|
|
8597
|
+
sessionId: decision.sessionId,
|
|
8598
|
+
matchedBy: decision.matchedBy,
|
|
8599
|
+
success: result2.success,
|
|
8600
|
+
newMessageCount: result2.newMessageCount,
|
|
8601
|
+
duplicateCount: result2.duplicateCount
|
|
8602
|
+
});
|
|
8603
|
+
return result2;
|
|
8604
|
+
}
|
|
8605
|
+
const result = mapCreateResult(await deps.createSession(filePath, options2.formatOptions), decision.reason);
|
|
8606
|
+
appLogger.info("import", "Automatic import created a new session", {
|
|
8607
|
+
reason: decision.reason,
|
|
8608
|
+
sessionId: result.sessionId,
|
|
8609
|
+
success: result.success
|
|
8610
|
+
});
|
|
8611
|
+
return result;
|
|
8612
|
+
} catch (error) {
|
|
8613
|
+
appLogger.error("import", "Automatic import failed", error);
|
|
8614
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
|
|
8618
|
+
// ../../packages/node-runtime/src/import/import-lock.ts
|
|
8619
|
+
import * as fs21 from "fs";
|
|
8620
|
+
import * as path16 from "path";
|
|
8621
|
+
import { randomUUID } from "crypto";
|
|
8622
|
+
var IMPORT_LOCK_FILENAME = ".chatlab-import.lock";
|
|
8623
|
+
var IMPORT_IN_PROGRESS_ERROR_KEY = "error.import_in_progress";
|
|
8624
|
+
var ImportInProgressError = class extends Error {
|
|
8625
|
+
code = "IMPORT_IN_PROGRESS";
|
|
8626
|
+
errorKey = IMPORT_IN_PROGRESS_ERROR_KEY;
|
|
8627
|
+
constructor() {
|
|
8628
|
+
super("Another import is already in progress");
|
|
8629
|
+
this.name = "ImportInProgressError";
|
|
8630
|
+
}
|
|
8631
|
+
};
|
|
8632
|
+
function parseOwner(lockPath) {
|
|
8633
|
+
try {
|
|
8634
|
+
const parsed = JSON.parse(fs21.readFileSync(lockPath, "utf-8"));
|
|
8635
|
+
if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.token !== "string" || parsed.token.length === 0 || typeof parsed.startedAt !== "number") {
|
|
8636
|
+
return null;
|
|
8637
|
+
}
|
|
8638
|
+
return parsed;
|
|
8639
|
+
} catch {
|
|
8640
|
+
return null;
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
function isProcessRunning(pid) {
|
|
8644
|
+
try {
|
|
8645
|
+
process.kill(pid, 0);
|
|
8646
|
+
return true;
|
|
8647
|
+
} catch (error) {
|
|
8648
|
+
return error.code !== "ESRCH";
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
function removeStaleLock(lockPath) {
|
|
8652
|
+
const owner = parseOwner(lockPath);
|
|
8653
|
+
if (owner) {
|
|
8654
|
+
if (isProcessRunning(owner.pid)) return false;
|
|
8655
|
+
} else {
|
|
8656
|
+
try {
|
|
8657
|
+
if (Date.now() - fs21.statSync(lockPath).mtimeMs < 5e3) return false;
|
|
8658
|
+
} catch {
|
|
8659
|
+
return true;
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
try {
|
|
8663
|
+
fs21.unlinkSync(lockPath);
|
|
8664
|
+
appLogger.warn("import-lock", "Removed stale import lock", { ownerPid: owner?.pid });
|
|
8665
|
+
return true;
|
|
8666
|
+
} catch {
|
|
8667
|
+
return false;
|
|
8668
|
+
}
|
|
8669
|
+
}
|
|
8670
|
+
function releaseLock(lockPath, fd, owner) {
|
|
8671
|
+
try {
|
|
8672
|
+
fs21.closeSync(fd);
|
|
8673
|
+
} catch (error) {
|
|
8674
|
+
appLogger.error("import-lock", "Failed to close import lock", error);
|
|
8675
|
+
}
|
|
8676
|
+
try {
|
|
8677
|
+
if (parseOwner(lockPath)?.token !== owner.token) {
|
|
8678
|
+
appLogger.warn("import-lock", "Import lock ownership changed before release");
|
|
8679
|
+
return;
|
|
8680
|
+
}
|
|
8681
|
+
fs21.unlinkSync(lockPath);
|
|
8682
|
+
appLogger.info("import-lock", "Import lock released");
|
|
8683
|
+
} catch (error) {
|
|
8684
|
+
appLogger.error("import-lock", "Failed to release import lock", error);
|
|
8685
|
+
}
|
|
8686
|
+
}
|
|
8687
|
+
async function withDataDirImportLock(userDataDir, task) {
|
|
8688
|
+
fs21.mkdirSync(userDataDir, { recursive: true });
|
|
8689
|
+
const lockPath = path16.join(userDataDir, IMPORT_LOCK_FILENAME);
|
|
8690
|
+
let fd = null;
|
|
8691
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8692
|
+
try {
|
|
8693
|
+
fd = fs21.openSync(lockPath, "wx");
|
|
8694
|
+
break;
|
|
8695
|
+
} catch (error) {
|
|
8696
|
+
const code = error.code;
|
|
8697
|
+
if (code === "EEXIST" && attempt === 0 && removeStaleLock(lockPath)) continue;
|
|
8698
|
+
if (code === "EEXIST") {
|
|
8699
|
+
appLogger.warn("import-lock", "Rejected concurrent import");
|
|
8700
|
+
throw new ImportInProgressError();
|
|
8701
|
+
}
|
|
8702
|
+
throw error;
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
if (fd === null) throw new ImportInProgressError();
|
|
8706
|
+
const owner = {
|
|
8707
|
+
pid: process.pid,
|
|
8708
|
+
token: randomUUID(),
|
|
8709
|
+
startedAt: Date.now()
|
|
8710
|
+
};
|
|
8711
|
+
try {
|
|
8712
|
+
fs21.writeFileSync(fd, JSON.stringify(owner), "utf-8");
|
|
8713
|
+
fs21.fsyncSync(fd);
|
|
8714
|
+
appLogger.info("import-lock", "Import lock acquired");
|
|
8715
|
+
return await task();
|
|
8716
|
+
} finally {
|
|
8717
|
+
releaseLock(lockPath, fd, owner);
|
|
8718
|
+
}
|
|
8719
|
+
}
|
|
8720
|
+
|
|
8343
8721
|
// ../../packages/node-runtime/src/import/archive/errors.ts
|
|
8344
8722
|
var ArchiveImportError = class extends Error {
|
|
8345
8723
|
constructor(code, message, options2) {
|
|
@@ -8352,8 +8730,8 @@ var ArchiveImportError = class extends Error {
|
|
|
8352
8730
|
|
|
8353
8731
|
// ../../packages/node-runtime/src/import/archive/archive-reader.ts
|
|
8354
8732
|
var import_yauzl = __toESM(require_yauzl(), 1);
|
|
8355
|
-
import * as
|
|
8356
|
-
import * as
|
|
8733
|
+
import * as fs22 from "fs";
|
|
8734
|
+
import * as path17 from "path";
|
|
8357
8735
|
import { pipeline } from "stream/promises";
|
|
8358
8736
|
var DEFAULT_MAX_ENTRIES = 1e5;
|
|
8359
8737
|
var DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024;
|
|
@@ -8472,8 +8850,8 @@ var ZipArchiveReader = class {
|
|
|
8472
8850
|
await this.visitEntries(async (entry, openStream) => {
|
|
8473
8851
|
const destination = targets.get(entry.name);
|
|
8474
8852
|
if (!destination || entry.isDirectory) return;
|
|
8475
|
-
|
|
8476
|
-
await pipeline(await openStream(),
|
|
8853
|
+
fs22.mkdirSync(path17.dirname(destination), { recursive: true });
|
|
8854
|
+
await pipeline(await openStream(), fs22.createWriteStream(destination, { flags: "wx" }));
|
|
8477
8855
|
remaining.delete(entry.name);
|
|
8478
8856
|
});
|
|
8479
8857
|
if (remaining.size > 0) {
|
|
@@ -8490,8 +8868,8 @@ var import_stream_chain9 = __toESM(require_stream_chain(), 1);
|
|
|
8490
8868
|
var import_stream_json9 = __toESM(require_stream_json(), 1);
|
|
8491
8869
|
var import_Pick8 = __toESM(require_Pick(), 1);
|
|
8492
8870
|
var import_StreamValues9 = __toESM(require_StreamValues(), 1);
|
|
8493
|
-
import * as
|
|
8494
|
-
import * as
|
|
8871
|
+
import * as fs23 from "fs";
|
|
8872
|
+
import * as path18 from "path";
|
|
8495
8873
|
var { chain: chain9 } = import_stream_chain9.default;
|
|
8496
8874
|
var { parser: parser10 } = import_stream_json9.default;
|
|
8497
8875
|
var { pick: pick8 } = import_Pick8.default;
|
|
@@ -8620,16 +8998,16 @@ var GoogleChatTakeoutResolver = class {
|
|
|
8620
8998
|
`Google Chat conversation is missing required files: ${chat.chatId}`
|
|
8621
8999
|
);
|
|
8622
9000
|
}
|
|
8623
|
-
|
|
9001
|
+
fs23.mkdirSync(targetDir, { recursive: true });
|
|
8624
9002
|
await reader.pipeEntries(
|
|
8625
9003
|
/* @__PURE__ */ new Map([
|
|
8626
|
-
[userInfoEntry,
|
|
8627
|
-
[groupInfoEntry,
|
|
8628
|
-
[messagesEntry,
|
|
9004
|
+
[userInfoEntry, path18.join(targetDir, "user_info.json")],
|
|
9005
|
+
[groupInfoEntry, path18.join(targetDir, "group_info.json")],
|
|
9006
|
+
[messagesEntry, path18.join(targetDir, "messages.json")]
|
|
8629
9007
|
])
|
|
8630
9008
|
);
|
|
8631
|
-
const manifestPath =
|
|
8632
|
-
|
|
9009
|
+
const manifestPath = path18.join(targetDir, "google-chat-import.json");
|
|
9010
|
+
fs23.writeFileSync(
|
|
8633
9011
|
manifestPath,
|
|
8634
9012
|
JSON.stringify(
|
|
8635
9013
|
{
|
|
@@ -8652,9 +9030,9 @@ var GoogleChatTakeoutResolver = class {
|
|
|
8652
9030
|
};
|
|
8653
9031
|
|
|
8654
9032
|
// ../../packages/node-runtime/src/import/archive/source-manager.ts
|
|
8655
|
-
import * as
|
|
8656
|
-
import * as
|
|
8657
|
-
import { randomUUID } from "crypto";
|
|
9033
|
+
import * as fs24 from "fs";
|
|
9034
|
+
import * as path19 from "path";
|
|
9035
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
8658
9036
|
var DEFAULT_TTL_MS = 30 * 60 * 1e3;
|
|
8659
9037
|
var ArchiveImportSourceManager = class {
|
|
8660
9038
|
records = /* @__PURE__ */ new Map();
|
|
@@ -8664,11 +9042,11 @@ var ArchiveImportSourceManager = class {
|
|
|
8664
9042
|
now;
|
|
8665
9043
|
resolvers;
|
|
8666
9044
|
constructor(options2 = {}) {
|
|
8667
|
-
this.tempRoot = options2.tempRoot ??
|
|
9045
|
+
this.tempRoot = options2.tempRoot ?? path19.join(process.cwd(), ".chatlab-import-sources");
|
|
8668
9046
|
this.ttlMs = options2.ttlMs ?? DEFAULT_TTL_MS;
|
|
8669
9047
|
this.now = options2.now ?? Date.now;
|
|
8670
9048
|
this.resolvers = options2.resolvers ?? [new GoogleChatTakeoutResolver()];
|
|
8671
|
-
|
|
9049
|
+
fs24.mkdirSync(this.tempRoot, { recursive: true });
|
|
8672
9050
|
}
|
|
8673
9051
|
prepareLocalArchive(archivePath) {
|
|
8674
9052
|
return this.prepareArchive(archivePath, false);
|
|
@@ -8691,7 +9069,7 @@ var ArchiveImportSourceManager = class {
|
|
|
8691
9069
|
const resolver = matches[0];
|
|
8692
9070
|
const chats = await resolver.scan(reader);
|
|
8693
9071
|
const createdAt = this.now();
|
|
8694
|
-
const sourceId =
|
|
9072
|
+
const sourceId = randomUUID2();
|
|
8695
9073
|
const descriptor = {
|
|
8696
9074
|
sourceId,
|
|
8697
9075
|
formatId: resolver.id,
|
|
@@ -8725,7 +9103,7 @@ var ArchiveImportSourceManager = class {
|
|
|
8725
9103
|
`Archive conversation was not found: ${chatId}`
|
|
8726
9104
|
);
|
|
8727
9105
|
}
|
|
8728
|
-
const materializedDir =
|
|
9106
|
+
const materializedDir = fs24.mkdtempSync(path19.join(this.tempRoot, `materialized-${sourceId}-`));
|
|
8729
9107
|
try {
|
|
8730
9108
|
const reader = new ZipArchiveReader(record.archivePath);
|
|
8731
9109
|
const materialized = await record.resolver.materialize(reader, chat, materializedDir);
|
|
@@ -8776,7 +9154,7 @@ var ArchiveImportSourceManager = class {
|
|
|
8776
9154
|
}
|
|
8777
9155
|
removePath(targetPath) {
|
|
8778
9156
|
try {
|
|
8779
|
-
|
|
9157
|
+
fs24.rmSync(targetPath, { recursive: true, force: true });
|
|
8780
9158
|
} catch {
|
|
8781
9159
|
}
|
|
8782
9160
|
}
|
|
@@ -8846,12 +9224,12 @@ ${rawErrorLabel}: ${details}`;
|
|
|
8846
9224
|
}
|
|
8847
9225
|
|
|
8848
9226
|
// ../../packages/node-runtime/src/ai/assistant-parser.ts
|
|
8849
|
-
import * as
|
|
9227
|
+
import * as path20 from "path";
|
|
8850
9228
|
import matter from "gray-matter";
|
|
8851
9229
|
function parseAssistantFile(content, filePath) {
|
|
8852
9230
|
try {
|
|
8853
9231
|
const { data: fm, content: body } = matter(content);
|
|
8854
|
-
const id = fm.id ??
|
|
9232
|
+
const id = fm.id ?? path20.basename(filePath, ".md");
|
|
8855
9233
|
const name = fm.name;
|
|
8856
9234
|
if (!name) return null;
|
|
8857
9235
|
return {
|
|
@@ -8930,8 +9308,8 @@ var AssistantManager = class {
|
|
|
8930
9308
|
}
|
|
8931
9309
|
// ==================== Init ====================
|
|
8932
9310
|
init() {
|
|
8933
|
-
const { fs:
|
|
8934
|
-
|
|
9311
|
+
const { fs: fs35, assistantsDir } = this.deps;
|
|
9312
|
+
fs35.ensureDir(assistantsDir);
|
|
8935
9313
|
const generalCreated = this.ensureGeneralAssistants();
|
|
8936
9314
|
this.loadAll();
|
|
8937
9315
|
this.initialized = true;
|
|
@@ -8942,26 +9320,26 @@ var AssistantManager = class {
|
|
|
8942
9320
|
return { total: this.cache.size, generalCreated };
|
|
8943
9321
|
}
|
|
8944
9322
|
ensureGeneralAssistants() {
|
|
8945
|
-
const { fs:
|
|
9323
|
+
const { fs: fs35, assistantsDir } = this.deps;
|
|
8946
9324
|
let anyCreated = false;
|
|
8947
9325
|
for (const id of this.generalIds) {
|
|
8948
9326
|
const config = this.getBuiltinConfig(id);
|
|
8949
9327
|
if (!config) continue;
|
|
8950
|
-
const filePath =
|
|
8951
|
-
if (
|
|
8952
|
-
|
|
9328
|
+
const filePath = fs35.joinPath(assistantsDir, `${id}.md`);
|
|
9329
|
+
if (fs35.fileExists(filePath)) continue;
|
|
9330
|
+
fs35.writeFile(filePath, serializeAssistant({ ...config, builtinId: config.id }));
|
|
8953
9331
|
anyCreated = true;
|
|
8954
9332
|
}
|
|
8955
9333
|
return anyCreated;
|
|
8956
9334
|
}
|
|
8957
9335
|
loadAll() {
|
|
8958
|
-
const { fs:
|
|
9336
|
+
const { fs: fs35, assistantsDir } = this.deps;
|
|
8959
9337
|
this.cache.clear();
|
|
8960
|
-
const files =
|
|
9338
|
+
const files = fs35.listFiles(assistantsDir, ".md");
|
|
8961
9339
|
for (const file of files) {
|
|
8962
9340
|
try {
|
|
8963
|
-
const filePath =
|
|
8964
|
-
const content =
|
|
9341
|
+
const filePath = fs35.joinPath(assistantsDir, file);
|
|
9342
|
+
const content = fs35.readFile(filePath);
|
|
8965
9343
|
const config = parseAssistantFile(content, filePath);
|
|
8966
9344
|
if (config) {
|
|
8967
9345
|
this.cache.set(config.id, config);
|
|
@@ -9073,12 +9451,12 @@ var AssistantManager = class {
|
|
|
9073
9451
|
};
|
|
9074
9452
|
|
|
9075
9453
|
// ../../packages/node-runtime/src/ai/skill-parser.ts
|
|
9076
|
-
import * as
|
|
9454
|
+
import * as path21 from "path";
|
|
9077
9455
|
import matter2 from "gray-matter";
|
|
9078
9456
|
function parseSkillFile(content, filePath) {
|
|
9079
9457
|
try {
|
|
9080
9458
|
const { data: fm, content: prompt } = matter2(content);
|
|
9081
|
-
const id = fm.id ??
|
|
9459
|
+
const id = fm.id ?? path21.basename(filePath, ".md");
|
|
9082
9460
|
const name = fm.name;
|
|
9083
9461
|
if (!name) return null;
|
|
9084
9462
|
return {
|
|
@@ -9097,7 +9475,7 @@ function parseSkillFile(content, filePath) {
|
|
|
9097
9475
|
function extractSkillId(content, filePath) {
|
|
9098
9476
|
try {
|
|
9099
9477
|
const { data: fm } = matter2(content);
|
|
9100
|
-
return fm.id ??
|
|
9478
|
+
return fm.id ?? path21.basename(filePath, ".md");
|
|
9101
9479
|
} catch {
|
|
9102
9480
|
return null;
|
|
9103
9481
|
}
|
|
@@ -9116,8 +9494,8 @@ function validateChatScope(raw) {
|
|
|
9116
9494
|
|
|
9117
9495
|
// ../../packages/node-runtime/src/ai/chats.ts
|
|
9118
9496
|
import Database from "better-sqlite3";
|
|
9119
|
-
import * as
|
|
9120
|
-
import * as
|
|
9497
|
+
import * as fs25 from "fs";
|
|
9498
|
+
import * as path22 from "path";
|
|
9121
9499
|
var DEFAULT_GENERAL_ID = "general_cn";
|
|
9122
9500
|
var defaultLogger = {
|
|
9123
9501
|
warn(_category, message, extra) {
|
|
@@ -9136,14 +9514,14 @@ var AIChatManager = class {
|
|
|
9136
9514
|
this.nativeBinding = options2?.nativeBinding;
|
|
9137
9515
|
}
|
|
9138
9516
|
ensureDir(dir) {
|
|
9139
|
-
if (!
|
|
9140
|
-
|
|
9517
|
+
if (!fs25.existsSync(dir)) {
|
|
9518
|
+
fs25.mkdirSync(dir, { recursive: true });
|
|
9141
9519
|
}
|
|
9142
9520
|
}
|
|
9143
9521
|
getDb() {
|
|
9144
9522
|
if (this.db) return this.db;
|
|
9145
9523
|
this.ensureDir(this.aiDataDir);
|
|
9146
|
-
const dbPath =
|
|
9524
|
+
const dbPath = path22.join(this.aiDataDir, "conversations.db");
|
|
9147
9525
|
this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath);
|
|
9148
9526
|
this.db.pragma("journal_mode = WAL");
|
|
9149
9527
|
this.db.exec(`
|
|
@@ -9386,16 +9764,16 @@ var AIChatManager = class {
|
|
|
9386
9764
|
if (allRows.length === 0) return [];
|
|
9387
9765
|
const rowMap = new Map(allRows.map((row) => [row.id, row]));
|
|
9388
9766
|
let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
|
|
9389
|
-
const
|
|
9767
|
+
const path31 = [];
|
|
9390
9768
|
const seen = /* @__PURE__ */ new Set();
|
|
9391
9769
|
while (currentId && !seen.has(currentId)) {
|
|
9392
9770
|
seen.add(currentId);
|
|
9393
9771
|
const row = rowMap.get(currentId);
|
|
9394
9772
|
if (!row) break;
|
|
9395
|
-
|
|
9773
|
+
path31.push(row);
|
|
9396
9774
|
currentId = row.parentId;
|
|
9397
9775
|
}
|
|
9398
|
-
return
|
|
9776
|
+
return path31.length > 0 ? path31.reverse() : allRows;
|
|
9399
9777
|
}
|
|
9400
9778
|
// ==================== 生命周期 ====================
|
|
9401
9779
|
close() {
|
|
@@ -9965,21 +10343,21 @@ var SkillManagerCore = class {
|
|
|
9965
10343
|
}
|
|
9966
10344
|
// ==================== Init ====================
|
|
9967
10345
|
init() {
|
|
9968
|
-
const { fs:
|
|
9969
|
-
|
|
10346
|
+
const { fs: fs35, skillsDir } = this.deps;
|
|
10347
|
+
fs35.ensureDir(skillsDir);
|
|
9970
10348
|
this.loadAll();
|
|
9971
10349
|
this.initialized = true;
|
|
9972
10350
|
this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
|
|
9973
10351
|
return { total: this.cache.size };
|
|
9974
10352
|
}
|
|
9975
10353
|
loadAll() {
|
|
9976
|
-
const { fs:
|
|
10354
|
+
const { fs: fs35, skillsDir } = this.deps;
|
|
9977
10355
|
this.cache.clear();
|
|
9978
|
-
const files =
|
|
10356
|
+
const files = fs35.listFiles(skillsDir, ".md");
|
|
9979
10357
|
for (const file of files) {
|
|
9980
10358
|
try {
|
|
9981
|
-
const filePath =
|
|
9982
|
-
const content =
|
|
10359
|
+
const filePath = fs35.joinPath(skillsDir, file);
|
|
10360
|
+
const content = fs35.readFile(filePath);
|
|
9983
10361
|
const def = parseSkillFile(content, filePath);
|
|
9984
10362
|
if (def) {
|
|
9985
10363
|
this.cache.set(def.id, def);
|
|
@@ -10131,8 +10509,8 @@ var SkillManagerCore = class {
|
|
|
10131
10509
|
};
|
|
10132
10510
|
|
|
10133
10511
|
// ../../packages/node-runtime/src/ai/skill-manager.ts
|
|
10134
|
-
import * as
|
|
10135
|
-
import * as
|
|
10512
|
+
import * as fs26 from "fs";
|
|
10513
|
+
import * as path23 from "path";
|
|
10136
10514
|
var SKILLS_DIR_NAME = "skills";
|
|
10137
10515
|
var defaultLogger2 = {
|
|
10138
10516
|
info: () => {
|
|
@@ -10142,26 +10520,26 @@ var defaultLogger2 = {
|
|
|
10142
10520
|
};
|
|
10143
10521
|
var nodeFs = {
|
|
10144
10522
|
ensureDir(dir) {
|
|
10145
|
-
|
|
10523
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
10146
10524
|
},
|
|
10147
10525
|
listFiles(dir, ext) {
|
|
10148
|
-
if (!
|
|
10149
|
-
return
|
|
10526
|
+
if (!fs26.existsSync(dir)) return [];
|
|
10527
|
+
return fs26.readdirSync(dir).filter((file) => file.endsWith(ext));
|
|
10150
10528
|
},
|
|
10151
10529
|
readFile(filePath) {
|
|
10152
|
-
return
|
|
10530
|
+
return fs26.readFileSync(filePath, "utf-8");
|
|
10153
10531
|
},
|
|
10154
10532
|
writeFile(filePath, content) {
|
|
10155
|
-
|
|
10533
|
+
fs26.writeFileSync(filePath, content, "utf-8");
|
|
10156
10534
|
},
|
|
10157
10535
|
deleteFile(filePath) {
|
|
10158
|
-
|
|
10536
|
+
fs26.unlinkSync(filePath);
|
|
10159
10537
|
},
|
|
10160
10538
|
fileExists(filePath) {
|
|
10161
|
-
return
|
|
10539
|
+
return fs26.existsSync(filePath);
|
|
10162
10540
|
},
|
|
10163
10541
|
joinPath(...parts) {
|
|
10164
|
-
return
|
|
10542
|
+
return path23.join(...parts);
|
|
10165
10543
|
}
|
|
10166
10544
|
};
|
|
10167
10545
|
var SkillManager = class {
|
|
@@ -10171,7 +10549,7 @@ var SkillManager = class {
|
|
|
10171
10549
|
this.logger = logger ?? defaultLogger2;
|
|
10172
10550
|
this.core = new SkillManagerCore({
|
|
10173
10551
|
fs: nodeFs,
|
|
10174
|
-
skillsDir:
|
|
10552
|
+
skillsDir: path23.join(aiDataDir, SKILLS_DIR_NAME),
|
|
10175
10553
|
builtinRawSkills: [],
|
|
10176
10554
|
logger: {
|
|
10177
10555
|
info: () => {
|
|
@@ -14001,9 +14379,9 @@ var localeMap = {
|
|
|
14001
14379
|
"ja-JP": ja_JP_default,
|
|
14002
14380
|
"zh-TW": zh_TW_default
|
|
14003
14381
|
};
|
|
14004
|
-
function getNestedValue(obj,
|
|
14382
|
+
function getNestedValue(obj, path31) {
|
|
14005
14383
|
let current = obj;
|
|
14006
|
-
for (const segment2 of
|
|
14384
|
+
for (const segment2 of path31.split(".")) {
|
|
14007
14385
|
if (current == null || typeof current !== "object") return void 0;
|
|
14008
14386
|
current = current[segment2];
|
|
14009
14387
|
}
|
|
@@ -15156,7 +15534,13 @@ function queryMessages(db, timeFilter) {
|
|
|
15156
15534
|
const sql = `
|
|
15157
15535
|
SELECT msg.ts,
|
|
15158
15536
|
COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,
|
|
15159
|
-
|
|
15537
|
+
m.platform_id as sender,
|
|
15538
|
+
COALESCE(msg.sender_account_name, m.account_name, m.platform_id) as accountName,
|
|
15539
|
+
COALESCE(msg.sender_group_nickname, m.group_nickname) as groupNickname,
|
|
15540
|
+
msg.type,
|
|
15541
|
+
msg.content,
|
|
15542
|
+
msg.platform_message_id as platformMessageId,
|
|
15543
|
+
msg.reply_to_message_id as replyToMessageId
|
|
15160
15544
|
FROM message msg
|
|
15161
15545
|
JOIN member m ON msg.sender_id = m.id
|
|
15162
15546
|
${hasFilter ? "WHERE msg.ts >= ? AND msg.ts <= ?" : ""}
|
|
@@ -15168,9 +15552,6 @@ function queryMessages(db, timeFilter) {
|
|
|
15168
15552
|
}
|
|
15169
15553
|
return db.prepare(sql).all(...params);
|
|
15170
15554
|
}
|
|
15171
|
-
function formatTime(ts) {
|
|
15172
|
-
return new Date(ts * 1e3).toLocaleString();
|
|
15173
|
-
}
|
|
15174
15555
|
function formatTimeShort(ts) {
|
|
15175
15556
|
return new Date(ts * 1e3).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
15176
15557
|
}
|
|
@@ -15190,16 +15571,69 @@ function exportAsTxt(messages, sessionName) {
|
|
|
15190
15571
|
}
|
|
15191
15572
|
return lines.join("\n");
|
|
15192
15573
|
}
|
|
15193
|
-
function
|
|
15574
|
+
function parseOptionalArray(value) {
|
|
15575
|
+
if (!value) return void 0;
|
|
15576
|
+
try {
|
|
15577
|
+
const parsed = JSON.parse(value);
|
|
15578
|
+
return Array.isArray(parsed) && parsed.length > 0 ? parsed : void 0;
|
|
15579
|
+
} catch {
|
|
15580
|
+
return void 0;
|
|
15581
|
+
}
|
|
15582
|
+
}
|
|
15583
|
+
function exportAsJson(db, messages, sessionName, sourceSessionId) {
|
|
15584
|
+
const meta = db.prepare(
|
|
15585
|
+
`SELECT name,
|
|
15586
|
+
platform,
|
|
15587
|
+
type,
|
|
15588
|
+
group_id as groupId,
|
|
15589
|
+
group_avatar as groupAvatar,
|
|
15590
|
+
owner_id as ownerId
|
|
15591
|
+
FROM meta
|
|
15592
|
+
LIMIT 1`
|
|
15593
|
+
).get();
|
|
15594
|
+
if (!meta) throw new Error("Cannot read session metadata");
|
|
15595
|
+
const members = db.prepare(
|
|
15596
|
+
`SELECT platform_id as platformId,
|
|
15597
|
+
account_name as accountName,
|
|
15598
|
+
group_nickname as groupNickname,
|
|
15599
|
+
aliases,
|
|
15600
|
+
avatar,
|
|
15601
|
+
roles
|
|
15602
|
+
FROM member
|
|
15603
|
+
ORDER BY id ASC`
|
|
15604
|
+
).all();
|
|
15194
15605
|
const data = {
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15606
|
+
chatlab: {
|
|
15607
|
+
version: "0.0.2",
|
|
15608
|
+
exportedAt: Math.floor(Date.now() / 1e3),
|
|
15609
|
+
generator: "ChatLab"
|
|
15610
|
+
},
|
|
15611
|
+
meta: {
|
|
15612
|
+
name: meta.name || sessionName,
|
|
15613
|
+
platform: meta.platform,
|
|
15614
|
+
type: meta.type,
|
|
15615
|
+
groupId: meta.groupId || void 0,
|
|
15616
|
+
groupAvatar: meta.groupAvatar || void 0,
|
|
15617
|
+
ownerId: meta.ownerId || void 0,
|
|
15618
|
+
sourceSessionId
|
|
15619
|
+
},
|
|
15620
|
+
members: members.map((member) => ({
|
|
15621
|
+
platformId: member.platformId,
|
|
15622
|
+
accountName: member.accountName || member.platformId,
|
|
15623
|
+
groupNickname: member.groupNickname || void 0,
|
|
15624
|
+
aliases: parseOptionalArray(member.aliases),
|
|
15625
|
+
avatar: member.avatar || void 0,
|
|
15626
|
+
roles: parseOptionalArray(member.roles)
|
|
15627
|
+
})),
|
|
15198
15628
|
messages: messages.map((msg) => ({
|
|
15629
|
+
sender: msg.sender,
|
|
15630
|
+
accountName: msg.accountName,
|
|
15631
|
+
groupNickname: msg.groupNickname || void 0,
|
|
15199
15632
|
timestamp: msg.ts,
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15633
|
+
type: msg.type,
|
|
15634
|
+
content: msg.content,
|
|
15635
|
+
platformMessageId: msg.platformMessageId || void 0,
|
|
15636
|
+
replyToMessageId: msg.replyToMessageId || void 0
|
|
15203
15637
|
}))
|
|
15204
15638
|
};
|
|
15205
15639
|
return JSON.stringify(data, null, 2);
|
|
@@ -15250,7 +15684,7 @@ function exportWithFormat(params, openDatabase) {
|
|
|
15250
15684
|
content = exportAsTxt(messages, params.sessionName);
|
|
15251
15685
|
break;
|
|
15252
15686
|
case "json":
|
|
15253
|
-
content = exportAsJson(messages, params.sessionName);
|
|
15687
|
+
content = exportAsJson(db, messages, params.sessionName, params.sessionId);
|
|
15254
15688
|
break;
|
|
15255
15689
|
case "markdown":
|
|
15256
15690
|
content = exportAsMarkdown(messages, params.sessionName);
|
|
@@ -15258,8 +15692,15 @@ function exportWithFormat(params, openDatabase) {
|
|
|
15258
15692
|
}
|
|
15259
15693
|
const timestamp = Date.now();
|
|
15260
15694
|
const filename = `${params.sessionName}_export_${timestamp}.${ext}`;
|
|
15695
|
+
appLogger.info("export", "chat export generated", {
|
|
15696
|
+
sessionId: params.sessionId,
|
|
15697
|
+
format: params.format,
|
|
15698
|
+
totalMessages: messages.length,
|
|
15699
|
+
filteredByTime: !!params.timeFilter
|
|
15700
|
+
});
|
|
15261
15701
|
return { success: true, totalMessages: messages.length, content, filename, mimeType: mime };
|
|
15262
15702
|
} catch (error) {
|
|
15703
|
+
appLogger.error("export", "chat export failed", error);
|
|
15263
15704
|
return {
|
|
15264
15705
|
success: false,
|
|
15265
15706
|
error: String(error),
|
|
@@ -15272,8 +15713,8 @@ function exportWithFormat(params, openDatabase) {
|
|
|
15272
15713
|
}
|
|
15273
15714
|
|
|
15274
15715
|
// ../../packages/node-runtime/src/merger/temp-db.ts
|
|
15275
|
-
import * as
|
|
15276
|
-
import * as
|
|
15716
|
+
import * as fs27 from "fs";
|
|
15717
|
+
import * as path24 from "path";
|
|
15277
15718
|
var TEMP_DB_SCHEMA = `
|
|
15278
15719
|
CREATE TABLE IF NOT EXISTS meta (
|
|
15279
15720
|
name TEXT NOT NULL,
|
|
@@ -15518,21 +15959,21 @@ function exportSessionToJson(db) {
|
|
|
15518
15959
|
}
|
|
15519
15960
|
function deleteTempDatabase(dbPath) {
|
|
15520
15961
|
try {
|
|
15521
|
-
if (
|
|
15962
|
+
if (fs27.existsSync(dbPath)) fs27.unlinkSync(dbPath);
|
|
15522
15963
|
const walPath = dbPath + "-wal";
|
|
15523
15964
|
const shmPath = dbPath + "-shm";
|
|
15524
|
-
if (
|
|
15525
|
-
if (
|
|
15965
|
+
if (fs27.existsSync(walPath)) fs27.unlinkSync(walPath);
|
|
15966
|
+
if (fs27.existsSync(shmPath)) fs27.unlinkSync(shmPath);
|
|
15526
15967
|
} catch {
|
|
15527
15968
|
}
|
|
15528
15969
|
}
|
|
15529
15970
|
function cleanupTempDatabases(tempDir) {
|
|
15530
15971
|
try {
|
|
15531
|
-
if (!
|
|
15532
|
-
const files =
|
|
15972
|
+
if (!fs27.existsSync(tempDir)) return;
|
|
15973
|
+
const files = fs27.readdirSync(tempDir);
|
|
15533
15974
|
for (const file of files) {
|
|
15534
15975
|
if (file.startsWith("merge_") && file.endsWith(".db")) {
|
|
15535
|
-
deleteTempDatabase(
|
|
15976
|
+
deleteTempDatabase(path24.join(tempDir, file));
|
|
15536
15977
|
}
|
|
15537
15978
|
}
|
|
15538
15979
|
} catch {
|
|
@@ -15558,7 +15999,21 @@ function buildSession(db, id, dbPath, options2) {
|
|
|
15558
15999
|
if (meta.type === "private") {
|
|
15559
16000
|
memberAvatar = getPrivateChatMemberAvatar(db, meta.name, meta.ownerId);
|
|
15560
16001
|
}
|
|
15561
|
-
|
|
16002
|
+
const ownerMember = meta.ownerId ? db.prepare(
|
|
16003
|
+
`SELECT COALESCE(NULLIF(group_nickname, ''), NULLIF(account_name, ''), platform_id) as displayName
|
|
16004
|
+
FROM member
|
|
16005
|
+
WHERE platform_id = ?
|
|
16006
|
+
LIMIT 1`
|
|
16007
|
+
).get(meta.ownerId) : void 0;
|
|
16008
|
+
let dto = {
|
|
16009
|
+
...info,
|
|
16010
|
+
id,
|
|
16011
|
+
dbPath,
|
|
16012
|
+
memberAvatar,
|
|
16013
|
+
aiConversationCount: 0,
|
|
16014
|
+
ownerName: ownerMember?.displayName ?? null,
|
|
16015
|
+
ownerStatus: !meta.ownerId ? "missing" : ownerMember ? "resolved" : "unresolved"
|
|
16016
|
+
};
|
|
15562
16017
|
if (options2?.enrichSession) {
|
|
15563
16018
|
dto = options2.enrichSession(dto);
|
|
15564
16019
|
}
|
|
@@ -15898,8 +16353,8 @@ function clearSessionOwner(adapter, sessionId) {
|
|
|
15898
16353
|
}
|
|
15899
16354
|
|
|
15900
16355
|
// ../../packages/node-runtime/src/services/contacts/overrides.ts
|
|
15901
|
-
import
|
|
15902
|
-
import
|
|
16356
|
+
import fs28 from "fs";
|
|
16357
|
+
import path25 from "path";
|
|
15903
16358
|
var CONTACT_OVERRIDES_FILE_NAME = "contact-overrides.json";
|
|
15904
16359
|
var CONTACT_OVERRIDES_TMP_PREFIX = "contact-overrides.tmp-";
|
|
15905
16360
|
var CONTACT_OVERRIDES_VERSION = 1;
|
|
@@ -15910,23 +16365,23 @@ function createEmptyContactOverridesFile() {
|
|
|
15910
16365
|
};
|
|
15911
16366
|
}
|
|
15912
16367
|
function getContactOverridesPath(snapshotDir) {
|
|
15913
|
-
return
|
|
16368
|
+
return path25.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
|
|
15914
16369
|
}
|
|
15915
16370
|
function readContactOverrides(snapshotDir) {
|
|
15916
16371
|
const filePath = getContactOverridesPath(snapshotDir);
|
|
15917
|
-
if (!
|
|
16372
|
+
if (!fs28.existsSync(filePath)) return createEmptyContactOverridesFile();
|
|
15918
16373
|
try {
|
|
15919
|
-
return normalizeContactOverrides(JSON.parse(
|
|
16374
|
+
return normalizeContactOverrides(JSON.parse(fs28.readFileSync(filePath, "utf-8")));
|
|
15920
16375
|
} catch (error) {
|
|
15921
16376
|
appLogger.warn("contacts", "contact overrides file is unreadable", error);
|
|
15922
16377
|
return createEmptyContactOverridesFile();
|
|
15923
16378
|
}
|
|
15924
16379
|
}
|
|
15925
16380
|
function writeContactOverrides(snapshotDir, overrides) {
|
|
15926
|
-
if (!
|
|
15927
|
-
const tmpPath =
|
|
15928
|
-
|
|
15929
|
-
|
|
16381
|
+
if (!fs28.existsSync(snapshotDir)) fs28.mkdirSync(snapshotDir, { recursive: true });
|
|
16382
|
+
const tmpPath = path25.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
16383
|
+
fs28.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
|
|
16384
|
+
fs28.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
|
|
15930
16385
|
}
|
|
15931
16386
|
function normalizeContactOverrides(value) {
|
|
15932
16387
|
const input = isRecord2(value) ? value : {};
|
|
@@ -15961,23 +16416,23 @@ function buildContactsSignature(adapter, timeRangePreset) {
|
|
|
15961
16416
|
}
|
|
15962
16417
|
|
|
15963
16418
|
// ../../packages/node-runtime/src/services/contacts/snapshot.ts
|
|
15964
|
-
import
|
|
15965
|
-
import
|
|
16419
|
+
import fs29 from "fs";
|
|
16420
|
+
import path26 from "path";
|
|
15966
16421
|
var CONTACTS_SNAPSHOT_TMP_PREFIX = "contacts-snapshot.tmp-";
|
|
15967
16422
|
function getContactsSnapshotPath(snapshotDir, timeRangePreset) {
|
|
15968
16423
|
const preset = normalizeContactsTimeRangePreset(timeRangePreset);
|
|
15969
|
-
return
|
|
16424
|
+
return path26.join(snapshotDir, `contacts-snapshot-${preset}.json`);
|
|
15970
16425
|
}
|
|
15971
16426
|
function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
15972
16427
|
const snapshotPath = getContactsSnapshotPath(snapshotDir, timeRangePreset);
|
|
15973
|
-
if (!
|
|
16428
|
+
if (!fs29.existsSync(snapshotPath)) return null;
|
|
15974
16429
|
try {
|
|
15975
|
-
return JSON.parse(
|
|
16430
|
+
return JSON.parse(fs29.readFileSync(snapshotPath, "utf-8"));
|
|
15976
16431
|
} catch (error) {
|
|
15977
16432
|
const ts = options2.now?.() ?? Date.now();
|
|
15978
|
-
const backupPath =
|
|
16433
|
+
const backupPath = path26.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
|
|
15979
16434
|
try {
|
|
15980
|
-
|
|
16435
|
+
fs29.renameSync(snapshotPath, backupPath);
|
|
15981
16436
|
} catch (renameError) {
|
|
15982
16437
|
appLogger.warn("contacts", "failed to backup corrupt contacts snapshot", renameError);
|
|
15983
16438
|
}
|
|
@@ -15986,17 +16441,17 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
|
15986
16441
|
}
|
|
15987
16442
|
}
|
|
15988
16443
|
function writeContactsSnapshot(snapshotDir, snapshot) {
|
|
15989
|
-
if (!
|
|
15990
|
-
const tmpPath =
|
|
15991
|
-
|
|
15992
|
-
|
|
16444
|
+
if (!fs29.existsSync(snapshotDir)) fs29.mkdirSync(snapshotDir, { recursive: true });
|
|
16445
|
+
const tmpPath = path26.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
16446
|
+
fs29.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
16447
|
+
fs29.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
|
|
15993
16448
|
}
|
|
15994
16449
|
function cleanupContactsSnapshotTempFiles(snapshotDir) {
|
|
15995
|
-
if (!
|
|
15996
|
-
for (const name of
|
|
16450
|
+
if (!fs29.existsSync(snapshotDir)) return;
|
|
16451
|
+
for (const name of fs29.readdirSync(snapshotDir)) {
|
|
15997
16452
|
if (!name.startsWith(CONTACTS_SNAPSHOT_TMP_PREFIX)) continue;
|
|
15998
16453
|
try {
|
|
15999
|
-
|
|
16454
|
+
fs29.rmSync(path26.join(snapshotDir, name), { force: true });
|
|
16000
16455
|
} catch (error) {
|
|
16001
16456
|
appLogger.warn("contacts", "failed to remove contacts snapshot temp file", error);
|
|
16002
16457
|
}
|
|
@@ -16494,23 +16949,23 @@ function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
|
|
|
16494
16949
|
}
|
|
16495
16950
|
|
|
16496
16951
|
// ../../packages/node-runtime/src/services/people/relationships/snapshot.ts
|
|
16497
|
-
import
|
|
16498
|
-
import
|
|
16952
|
+
import fs30 from "fs";
|
|
16953
|
+
import path27 from "path";
|
|
16499
16954
|
var PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX = "people-relationships-snapshot.tmp-";
|
|
16500
16955
|
function getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset) {
|
|
16501
16956
|
const preset = normalizePeopleRelationshipsTimeRangePreset(timeRangePreset);
|
|
16502
|
-
return
|
|
16957
|
+
return path27.join(snapshotDir, `graph-snapshot-${preset}.json`);
|
|
16503
16958
|
}
|
|
16504
16959
|
function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
16505
16960
|
const snapshotPath = getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset);
|
|
16506
|
-
if (!
|
|
16961
|
+
if (!fs30.existsSync(snapshotPath)) return null;
|
|
16507
16962
|
try {
|
|
16508
|
-
return JSON.parse(
|
|
16963
|
+
return JSON.parse(fs30.readFileSync(snapshotPath, "utf-8"));
|
|
16509
16964
|
} catch (error) {
|
|
16510
16965
|
const ts = options2.now?.() ?? Date.now();
|
|
16511
|
-
const backupPath =
|
|
16966
|
+
const backupPath = path27.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
|
|
16512
16967
|
try {
|
|
16513
|
-
|
|
16968
|
+
fs30.renameSync(snapshotPath, backupPath);
|
|
16514
16969
|
} catch (renameError) {
|
|
16515
16970
|
appLogger.warn("people-relationships", "failed to backup corrupt people relationships snapshot", renameError);
|
|
16516
16971
|
}
|
|
@@ -16519,17 +16974,17 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
|
|
|
16519
16974
|
}
|
|
16520
16975
|
}
|
|
16521
16976
|
function writePeopleRelationshipsSnapshot(snapshotDir, snapshot) {
|
|
16522
|
-
if (!
|
|
16523
|
-
const tmpPath =
|
|
16524
|
-
|
|
16525
|
-
|
|
16977
|
+
if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
|
|
16978
|
+
const tmpPath = path27.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
16979
|
+
fs30.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
16980
|
+
fs30.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
|
|
16526
16981
|
}
|
|
16527
16982
|
function cleanupPeopleRelationshipsSnapshotTempFiles(snapshotDir) {
|
|
16528
|
-
if (!
|
|
16529
|
-
for (const name of
|
|
16983
|
+
if (!fs30.existsSync(snapshotDir)) return;
|
|
16984
|
+
for (const name of fs30.readdirSync(snapshotDir)) {
|
|
16530
16985
|
if (!name.startsWith(PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX)) continue;
|
|
16531
16986
|
try {
|
|
16532
|
-
|
|
16987
|
+
fs30.rmSync(path27.join(snapshotDir, name), { force: true });
|
|
16533
16988
|
} catch (error) {
|
|
16534
16989
|
appLogger.warn("people-relationships", "failed to remove people relationships snapshot temp file", error);
|
|
16535
16990
|
}
|
|
@@ -17050,9 +17505,310 @@ __export(relationships_exports, {
|
|
|
17050
17505
|
getPeopleRelationshipsFactsCacheDir: () => getPeopleRelationshipsFactsCacheDir
|
|
17051
17506
|
});
|
|
17052
17507
|
|
|
17508
|
+
// ../../packages/node-runtime/src/services/global-insight/service.ts
|
|
17509
|
+
import fs31 from "fs";
|
|
17510
|
+
|
|
17511
|
+
// ../../packages/node-runtime/src/services/global-insight/signature.ts
|
|
17512
|
+
function buildAnnualSummarySignature(adapter, range) {
|
|
17513
|
+
const parts = [
|
|
17514
|
+
`algorithm:${ANNUAL_SUMMARY_ALGORITHM_VERSION}`,
|
|
17515
|
+
`range:${toAnnualSummaryRangeKey(range)}`,
|
|
17516
|
+
`start:${range.startTs}`,
|
|
17517
|
+
`day:${toAnnualSummaryLocalDate(range)}`
|
|
17518
|
+
];
|
|
17519
|
+
for (const sessionId of [...adapter.listSessionIds()].sort()) {
|
|
17520
|
+
parts.push(`${sessionId}:${getDbFileVersion(adapter.getDbPath(sessionId))}`);
|
|
17521
|
+
}
|
|
17522
|
+
return parts.join("|");
|
|
17523
|
+
}
|
|
17524
|
+
|
|
17525
|
+
// ../../packages/node-runtime/src/services/global-insight/worker-runner.ts
|
|
17526
|
+
import { existsSync as existsSync11 } from "fs";
|
|
17527
|
+
import { Worker as Worker3 } from "worker_threads";
|
|
17528
|
+
function resolveDefaultAnnualSummaryWorkerEntryUrl(currentModuleUrl = import.meta.url, entryExists = (url) => existsSync11(url)) {
|
|
17529
|
+
const moduleUrl = typeof currentModuleUrl === "string" ? currentModuleUrl : currentModuleUrl.href;
|
|
17530
|
+
if (moduleUrl.endsWith(".ts")) return new URL("./worker-entry.ts", moduleUrl);
|
|
17531
|
+
if (moduleUrl.endsWith(".mjs")) return new URL("./global-insight-worker.mjs", moduleUrl);
|
|
17532
|
+
const sibling = new URL("./worker-entry.js", moduleUrl);
|
|
17533
|
+
return entryExists(sibling) ? sibling : new URL("./global-insight-worker.js", moduleUrl);
|
|
17534
|
+
}
|
|
17535
|
+
function createAnnualSummaryWorkerRunner(options2) {
|
|
17536
|
+
return ({ signature, range, signal, onProgress }) => new Promise((resolve2, reject) => {
|
|
17537
|
+
if (signal.aborted) {
|
|
17538
|
+
reject(new Error("annual summary worker aborted"));
|
|
17539
|
+
return;
|
|
17540
|
+
}
|
|
17541
|
+
const worker = createWorker3(
|
|
17542
|
+
{
|
|
17543
|
+
paths: snapshotPathProvider(options2.pathProvider),
|
|
17544
|
+
runtimeIdentity: options2.runtimeIdentity,
|
|
17545
|
+
nativeBinding: options2.nativeBinding,
|
|
17546
|
+
signature,
|
|
17547
|
+
range
|
|
17548
|
+
},
|
|
17549
|
+
options2.workerEntryUrl
|
|
17550
|
+
);
|
|
17551
|
+
let settled = false;
|
|
17552
|
+
const abort = () => {
|
|
17553
|
+
if (settled) return;
|
|
17554
|
+
settled = true;
|
|
17555
|
+
void worker.terminate();
|
|
17556
|
+
reject(new Error("annual summary worker aborted"));
|
|
17557
|
+
};
|
|
17558
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
17559
|
+
worker.on("message", (message) => {
|
|
17560
|
+
if (message.type === "progress" && message.progress) {
|
|
17561
|
+
onProgress(message.progress);
|
|
17562
|
+
return;
|
|
17563
|
+
}
|
|
17564
|
+
if (message.type === "success" && message.snapshot) {
|
|
17565
|
+
settled = true;
|
|
17566
|
+
signal.removeEventListener("abort", abort);
|
|
17567
|
+
resolve2(message.snapshot);
|
|
17568
|
+
void worker.terminate();
|
|
17569
|
+
return;
|
|
17570
|
+
}
|
|
17571
|
+
if (message.type === "error") {
|
|
17572
|
+
settled = true;
|
|
17573
|
+
signal.removeEventListener("abort", abort);
|
|
17574
|
+
reject(new Error(message.error ?? "annual summary worker failed"));
|
|
17575
|
+
void worker.terminate();
|
|
17576
|
+
}
|
|
17577
|
+
});
|
|
17578
|
+
worker.on("error", (error) => {
|
|
17579
|
+
if (settled) return;
|
|
17580
|
+
settled = true;
|
|
17581
|
+
signal.removeEventListener("abort", abort);
|
|
17582
|
+
reject(error);
|
|
17583
|
+
});
|
|
17584
|
+
worker.on("exit", (code) => {
|
|
17585
|
+
if (settled || code === 0) return;
|
|
17586
|
+
settled = true;
|
|
17587
|
+
signal.removeEventListener("abort", abort);
|
|
17588
|
+
reject(new Error(`annual summary worker exited with code ${code}`));
|
|
17589
|
+
});
|
|
17590
|
+
});
|
|
17591
|
+
}
|
|
17592
|
+
function createWorker3(workerData, entryUrlInput) {
|
|
17593
|
+
const entryUrl = entryUrlInput ? typeof entryUrlInput === "string" ? new URL(entryUrlInput) : entryUrlInput : resolveDefaultAnnualSummaryWorkerEntryUrl();
|
|
17594
|
+
if (!entryUrl.href.endsWith(".ts")) return new Worker3(entryUrl, { workerData });
|
|
17595
|
+
const bootstrap = `
|
|
17596
|
+
import { register } from 'tsx/esm/api';
|
|
17597
|
+
register();
|
|
17598
|
+
await import(${JSON.stringify(entryUrl.href)});
|
|
17599
|
+
`;
|
|
17600
|
+
const workerOptions = { eval: true, type: "module", workerData, execArgv: [] };
|
|
17601
|
+
return new Worker3(bootstrap, workerOptions);
|
|
17602
|
+
}
|
|
17603
|
+
|
|
17604
|
+
// ../../packages/node-runtime/src/services/global-insight/service.ts
|
|
17605
|
+
function createGlobalInsightService(deps) {
|
|
17606
|
+
return new DefaultGlobalInsightService(deps);
|
|
17607
|
+
}
|
|
17608
|
+
var DefaultGlobalInsightService = class {
|
|
17609
|
+
constructor(deps) {
|
|
17610
|
+
this.deps = deps;
|
|
17611
|
+
this.snapshotDir = deps.userDataDir ? getGlobalInsightDir(deps.userDataDir) : getGlobalInsightDir(requirePathProvider3(deps).getUserDataDir());
|
|
17612
|
+
cleanupAnnualSummarySnapshotTempFiles(this.snapshotDir);
|
|
17613
|
+
this.runner = deps.runner ?? createAnnualSummaryWorkerRunner({
|
|
17614
|
+
pathProvider: requirePathProvider3(deps),
|
|
17615
|
+
runtimeIdentity: deps.runtimeIdentity,
|
|
17616
|
+
nativeBinding: deps.nativeBinding,
|
|
17617
|
+
workerEntryUrl: deps.workerEntryUrl
|
|
17618
|
+
});
|
|
17619
|
+
}
|
|
17620
|
+
deps;
|
|
17621
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
17622
|
+
snapshotDir;
|
|
17623
|
+
runner;
|
|
17624
|
+
inFlight = null;
|
|
17625
|
+
task = createIdleTaskState3();
|
|
17626
|
+
getAnnualSummary(options2 = {}) {
|
|
17627
|
+
const range = this.normalizeRange(options2);
|
|
17628
|
+
const signature = buildAnnualSummarySignature(this.deps.adapter, range);
|
|
17629
|
+
const status = this.getCacheStatus(signature, range);
|
|
17630
|
+
if (this.shouldStartTask(options2, status)) this.ensureTaskStarted(signature, range);
|
|
17631
|
+
return this.toResponse(signature, range, options2.acceptStale === true);
|
|
17632
|
+
}
|
|
17633
|
+
startRecompute(options2 = {}) {
|
|
17634
|
+
const range = this.normalizeRange(options2);
|
|
17635
|
+
const signature = buildAnnualSummarySignature(this.deps.adapter, range);
|
|
17636
|
+
this.ensureTaskStarted(signature, range);
|
|
17637
|
+
return this.toResponse(signature, range, true);
|
|
17638
|
+
}
|
|
17639
|
+
invalidateCache() {
|
|
17640
|
+
this.snapshots.clear();
|
|
17641
|
+
}
|
|
17642
|
+
normalizeRange(input = {}) {
|
|
17643
|
+
return normalizeAnnualSummaryRange(input, new Date(this.now()));
|
|
17644
|
+
}
|
|
17645
|
+
replaceSnapshotForTests(snapshot) {
|
|
17646
|
+
writeAnnualSummarySnapshot(this.snapshotDir, snapshot);
|
|
17647
|
+
this.snapshots.set(toAnnualSummaryRangeKey(snapshot.range), snapshot);
|
|
17648
|
+
}
|
|
17649
|
+
async close() {
|
|
17650
|
+
const inFlight = this.inFlight;
|
|
17651
|
+
if (!inFlight) return;
|
|
17652
|
+
this.inFlight = null;
|
|
17653
|
+
inFlight.abortController.abort();
|
|
17654
|
+
this.task = {
|
|
17655
|
+
...this.task,
|
|
17656
|
+
status: "failed",
|
|
17657
|
+
finishedAt: this.now(),
|
|
17658
|
+
lastError: "annual summary task aborted"
|
|
17659
|
+
};
|
|
17660
|
+
}
|
|
17661
|
+
shouldStartTask(options2, status) {
|
|
17662
|
+
if (options2.forceRecompute) return true;
|
|
17663
|
+
if (status === "fresh") return false;
|
|
17664
|
+
return this.task.status !== "failed";
|
|
17665
|
+
}
|
|
17666
|
+
ensureTaskStarted(signature, range) {
|
|
17667
|
+
if (this.inFlight) return;
|
|
17668
|
+
const id = `annual_summary_${this.now()}_${Math.random().toString(36).slice(2)}`;
|
|
17669
|
+
this.task = {
|
|
17670
|
+
id,
|
|
17671
|
+
status: "running",
|
|
17672
|
+
startedAt: this.now(),
|
|
17673
|
+
finishedAt: null,
|
|
17674
|
+
processedSessions: 0,
|
|
17675
|
+
totalSessions: this.deps.adapter.listSessionIds().length
|
|
17676
|
+
};
|
|
17677
|
+
const abortController = new AbortController();
|
|
17678
|
+
const promise = this.runner({
|
|
17679
|
+
signature,
|
|
17680
|
+
range,
|
|
17681
|
+
signal: abortController.signal,
|
|
17682
|
+
onProgress: (progress) => {
|
|
17683
|
+
if (this.task.id !== id || this.task.status !== "running") return;
|
|
17684
|
+
this.task = { ...this.task, ...progress };
|
|
17685
|
+
}
|
|
17686
|
+
});
|
|
17687
|
+
this.inFlight = { id, signature, promise, abortController };
|
|
17688
|
+
promise.then((snapshot) => this.handleTaskSuccess(id, signature, snapshot)).catch((error) => this.handleTaskFailure(id, error));
|
|
17689
|
+
}
|
|
17690
|
+
handleTaskSuccess(id, inputSignature, snapshot) {
|
|
17691
|
+
if (this.inFlight?.id !== id) return;
|
|
17692
|
+
const latestSignature = buildAnnualSummarySignature(this.deps.adapter, snapshot.range);
|
|
17693
|
+
const finishedAt = this.now();
|
|
17694
|
+
if (inputSignature !== latestSignature || snapshot.signature !== latestSignature) {
|
|
17695
|
+
this.inFlight = null;
|
|
17696
|
+
this.task = { ...this.task, status: "superseded", finishedAt, currentSessionId: void 0 };
|
|
17697
|
+
appLogger.info("global-insight", "annual summary worker result discarded because signature changed");
|
|
17698
|
+
return;
|
|
17699
|
+
}
|
|
17700
|
+
try {
|
|
17701
|
+
writeAnnualSummarySnapshot(this.snapshotDir, snapshot);
|
|
17702
|
+
this.inFlight = null;
|
|
17703
|
+
this.snapshots.set(toAnnualSummaryRangeKey(snapshot.range), snapshot);
|
|
17704
|
+
this.task = {
|
|
17705
|
+
...this.task,
|
|
17706
|
+
status: "succeeded",
|
|
17707
|
+
finishedAt,
|
|
17708
|
+
processedSessions: snapshot.workerStats.processedSessions,
|
|
17709
|
+
totalSessions: snapshot.workerStats.totalSessions,
|
|
17710
|
+
currentSessionId: void 0
|
|
17711
|
+
};
|
|
17712
|
+
appLogger.info("global-insight", "annual summary snapshot persisted", {
|
|
17713
|
+
analyzedSessions: snapshot.coverage.analyzedSessions,
|
|
17714
|
+
durationMs: snapshot.workerStats.durationMs
|
|
17715
|
+
});
|
|
17716
|
+
} catch (error) {
|
|
17717
|
+
this.handleTaskFailure(id, error);
|
|
17718
|
+
}
|
|
17719
|
+
}
|
|
17720
|
+
handleTaskFailure(id, error) {
|
|
17721
|
+
if (this.inFlight?.id !== id) return;
|
|
17722
|
+
this.inFlight = null;
|
|
17723
|
+
this.task = {
|
|
17724
|
+
...this.task,
|
|
17725
|
+
status: "failed",
|
|
17726
|
+
finishedAt: this.now(),
|
|
17727
|
+
currentSessionId: void 0,
|
|
17728
|
+
lastError: error instanceof Error ? error.message : String(error)
|
|
17729
|
+
};
|
|
17730
|
+
appLogger.error("global-insight", "annual summary worker failed", error);
|
|
17731
|
+
}
|
|
17732
|
+
toResponse(signature, range, acceptStale) {
|
|
17733
|
+
const snapshot = this.getSnapshot(range);
|
|
17734
|
+
const status = this.getCacheStatus(signature, range);
|
|
17735
|
+
const includeSnapshot = status === "fresh" || status === "stale" && acceptStale;
|
|
17736
|
+
return {
|
|
17737
|
+
range,
|
|
17738
|
+
availableDataYears: includeSnapshot ? snapshot?.availableDataYears ?? [] : [],
|
|
17739
|
+
latestDataYear: includeSnapshot ? snapshot?.latestDataYear ?? null : null,
|
|
17740
|
+
metrics: includeSnapshot ? snapshot?.metrics ?? null : null,
|
|
17741
|
+
monthlyActivity: includeSnapshot ? snapshot?.monthlyActivity ?? [] : [],
|
|
17742
|
+
dailyActivity: includeSnapshot ? snapshot?.dailyActivity ?? [] : [],
|
|
17743
|
+
messageTypes: includeSnapshot ? snapshot?.messageTypes ?? [] : [],
|
|
17744
|
+
textLength: includeSnapshot ? snapshot?.textLength ?? null : null,
|
|
17745
|
+
coverage: includeSnapshot ? snapshot?.coverage ?? emptyCoverage() : emptyCoverage(),
|
|
17746
|
+
cache: {
|
|
17747
|
+
status,
|
|
17748
|
+
computedAt: snapshot?.computedAt ?? null,
|
|
17749
|
+
signature: snapshot?.signature,
|
|
17750
|
+
staleReason: status === "stale" ? "signature_changed" : void 0
|
|
17751
|
+
},
|
|
17752
|
+
task: this.task
|
|
17753
|
+
};
|
|
17754
|
+
}
|
|
17755
|
+
getCacheStatus(signature, range) {
|
|
17756
|
+
const snapshot = this.getSnapshot(range);
|
|
17757
|
+
if (!snapshot) return "missing";
|
|
17758
|
+
return snapshot.signature === signature ? "fresh" : "stale";
|
|
17759
|
+
}
|
|
17760
|
+
getSnapshot(range) {
|
|
17761
|
+
const key = toAnnualSummaryRangeKey(range);
|
|
17762
|
+
if (this.snapshots.has(key) && !fs31.existsSync(getAnnualSummarySnapshotPath(this.snapshotDir, range))) {
|
|
17763
|
+
this.snapshots.delete(key);
|
|
17764
|
+
}
|
|
17765
|
+
if (!this.snapshots.has(key)) {
|
|
17766
|
+
this.snapshots.set(key, readAnnualSummarySnapshot(this.snapshotDir, range, { now: this.deps.now }));
|
|
17767
|
+
}
|
|
17768
|
+
return this.snapshots.get(key) ?? null;
|
|
17769
|
+
}
|
|
17770
|
+
now() {
|
|
17771
|
+
return this.deps.now?.() ?? Date.now();
|
|
17772
|
+
}
|
|
17773
|
+
};
|
|
17774
|
+
function requirePathProvider3(deps) {
|
|
17775
|
+
if (!deps.pathProvider)
|
|
17776
|
+
throw new Error("GlobalInsightService requires pathProvider unless userDataDir and runner are provided");
|
|
17777
|
+
return deps.pathProvider;
|
|
17778
|
+
}
|
|
17779
|
+
function createIdleTaskState3() {
|
|
17780
|
+
return {
|
|
17781
|
+
id: null,
|
|
17782
|
+
status: "idle",
|
|
17783
|
+
startedAt: null,
|
|
17784
|
+
finishedAt: null,
|
|
17785
|
+
processedSessions: 0,
|
|
17786
|
+
totalSessions: 0
|
|
17787
|
+
};
|
|
17788
|
+
}
|
|
17789
|
+
function emptyCoverage() {
|
|
17790
|
+
return {
|
|
17791
|
+
totalSessions: 0,
|
|
17792
|
+
analyzedSessions: 0,
|
|
17793
|
+
missingOwnerSessions: 0,
|
|
17794
|
+
unresolvedOwnerSessions: 0,
|
|
17795
|
+
failedSessions: 0
|
|
17796
|
+
};
|
|
17797
|
+
}
|
|
17798
|
+
|
|
17799
|
+
// ../../packages/node-runtime/src/services/global-insight/index.ts
|
|
17800
|
+
var global_insight_exports = {};
|
|
17801
|
+
__export(global_insight_exports, {
|
|
17802
|
+
ANNUAL_SUMMARY_ALGORITHM_VERSION: () => ANNUAL_SUMMARY_ALGORITHM_VERSION,
|
|
17803
|
+
createGlobalInsightService: () => createGlobalInsightService,
|
|
17804
|
+
deleteAnnualSummarySnapshots: () => deleteAnnualSummarySnapshots,
|
|
17805
|
+
getGlobalInsightDir: () => getGlobalInsightDir,
|
|
17806
|
+
getGlobalInsightFactsCacheDir: () => getGlobalInsightFactsCacheDir
|
|
17807
|
+
});
|
|
17808
|
+
|
|
17053
17809
|
// ../../packages/node-runtime/src/semantic-index/config.ts
|
|
17054
|
-
import
|
|
17055
|
-
import
|
|
17810
|
+
import fs32 from "fs";
|
|
17811
|
+
import path28 from "path";
|
|
17056
17812
|
var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
|
|
17057
17813
|
var SEMANTIC_INDEX_CONFIG_VERSION = 1;
|
|
17058
17814
|
var SEARCH_MAX_RESULTS_DEFAULT = 10;
|
|
@@ -17141,9 +17897,9 @@ var SemanticIndexConfigStore = class {
|
|
|
17141
17897
|
return this.set({ ...this.get(), enabled });
|
|
17142
17898
|
}
|
|
17143
17899
|
get() {
|
|
17144
|
-
if (!
|
|
17900
|
+
if (!fs32.existsSync(this.filePath)) return defaultSemanticIndexConfig();
|
|
17145
17901
|
try {
|
|
17146
|
-
const raw = JSON.parse(
|
|
17902
|
+
const raw = JSON.parse(fs32.readFileSync(this.filePath, "utf-8"));
|
|
17147
17903
|
return normalizeConfig(raw);
|
|
17148
17904
|
} catch {
|
|
17149
17905
|
return defaultSemanticIndexConfig();
|
|
@@ -17151,9 +17907,9 @@ var SemanticIndexConfigStore = class {
|
|
|
17151
17907
|
}
|
|
17152
17908
|
set(config) {
|
|
17153
17909
|
const normalized = normalizeConfig(config);
|
|
17154
|
-
const dir =
|
|
17155
|
-
if (!
|
|
17156
|
-
|
|
17910
|
+
const dir = path28.dirname(this.filePath);
|
|
17911
|
+
if (!fs32.existsSync(dir)) fs32.mkdirSync(dir, { recursive: true });
|
|
17912
|
+
fs32.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
17157
17913
|
return normalized;
|
|
17158
17914
|
}
|
|
17159
17915
|
resolveModelId() {
|
|
@@ -17162,7 +17918,7 @@ var SemanticIndexConfigStore = class {
|
|
|
17162
17918
|
};
|
|
17163
17919
|
|
|
17164
17920
|
// ../../packages/node-runtime/src/semantic-index/service.ts
|
|
17165
|
-
import
|
|
17921
|
+
import path29 from "path";
|
|
17166
17922
|
|
|
17167
17923
|
// ../../packages/node-runtime/src/semantic-index/chunker-config.ts
|
|
17168
17924
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -18143,7 +18899,7 @@ function formatDateTime(ts) {
|
|
|
18143
18899
|
const d = new Date(ts);
|
|
18144
18900
|
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
18145
18901
|
}
|
|
18146
|
-
function
|
|
18902
|
+
function formatTime(ts) {
|
|
18147
18903
|
const d = new Date(ts);
|
|
18148
18904
|
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
18149
18905
|
}
|
|
@@ -18151,7 +18907,7 @@ function formatTimeRange2(startTs, endTs) {
|
|
|
18151
18907
|
const start = new Date(startTs);
|
|
18152
18908
|
const end = new Date(endTs);
|
|
18153
18909
|
const sameDay = start.getFullYear() === end.getFullYear() && start.getMonth() === end.getMonth() && start.getDate() === end.getDate();
|
|
18154
|
-
return `${formatDateTime(startTs)} - ${sameDay ?
|
|
18910
|
+
return `${formatDateTime(startTs)} - ${sameDay ? formatTime(endTs) : formatDateTime(endTs)}`;
|
|
18155
18911
|
}
|
|
18156
18912
|
function resolveSenderNameTokenCap(nameTokenCounts, totalLimit) {
|
|
18157
18913
|
const total = nameTokenCounts.reduce((sum, count) => sum + count, 0);
|
|
@@ -18693,16 +19449,16 @@ function resolveSearchToolEvidenceTokens(maxResultTokens) {
|
|
|
18693
19449
|
return maxResultTokens && maxResultTokens > 0 ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens) : SEARCH_TOOL_EVIDENCE_TOKENS;
|
|
18694
19450
|
}
|
|
18695
19451
|
function sessionIdFromDbPath(dbPath) {
|
|
18696
|
-
return
|
|
19452
|
+
return path29.basename(dbPath, ".db");
|
|
18697
19453
|
}
|
|
18698
19454
|
var SEMANTIC_INDEX_DB_FILE = "embedding_index.db";
|
|
18699
19455
|
var SEMANTIC_INDEX_CONFIG_FILE = "semantic-index-config.json";
|
|
18700
19456
|
function createSemanticIndexService(params) {
|
|
18701
19457
|
const { pathProvider } = params;
|
|
18702
19458
|
return new SemanticIndexService({
|
|
18703
|
-
vectorDbPath:
|
|
18704
|
-
configPath:
|
|
18705
|
-
modelsCacheDir:
|
|
19459
|
+
vectorDbPath: path29.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
|
|
19460
|
+
configPath: path29.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
|
|
19461
|
+
modelsCacheDir: path29.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
|
|
18706
19462
|
modelDownloadProxyUrl: params.modelDownloadProxyUrl,
|
|
18707
19463
|
sessionAdapter: params.sessionAdapter,
|
|
18708
19464
|
nativeBinding: params.nativeBinding,
|
|
@@ -19252,8 +20008,8 @@ ${text}`;
|
|
|
19252
20008
|
};
|
|
19253
20009
|
|
|
19254
20010
|
// ../../packages/node-runtime/src/services/merge-cache.ts
|
|
19255
|
-
import * as
|
|
19256
|
-
import * as
|
|
20011
|
+
import * as fs33 from "fs";
|
|
20012
|
+
import * as path30 from "path";
|
|
19257
20013
|
import * as crypto from "crypto";
|
|
19258
20014
|
var MergeSessionCache = class {
|
|
19259
20015
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -19262,15 +20018,15 @@ var MergeSessionCache = class {
|
|
|
19262
20018
|
constructor(pathProvider, options2) {
|
|
19263
20019
|
this.tempDir = pathProvider.getTempDir();
|
|
19264
20020
|
this.nativeBinding = options2?.nativeBinding;
|
|
19265
|
-
if (!
|
|
19266
|
-
|
|
20021
|
+
if (!fs33.existsSync(this.tempDir)) {
|
|
20022
|
+
fs33.mkdirSync(this.tempDir, { recursive: true });
|
|
19267
20023
|
}
|
|
19268
20024
|
}
|
|
19269
20025
|
generateTempDbPath(filename) {
|
|
19270
20026
|
const ts = Date.now();
|
|
19271
20027
|
const rand = Math.random().toString(36).slice(2, 8);
|
|
19272
20028
|
const safeName = filename.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 40);
|
|
19273
|
-
return
|
|
20029
|
+
return path30.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
|
|
19274
20030
|
}
|
|
19275
20031
|
/** Store a parsed file's temp DB and return a handle for subsequent requests. */
|
|
19276
20032
|
store(filename, tempDbPath) {
|
|
@@ -19281,7 +20037,7 @@ var MergeSessionCache = class {
|
|
|
19281
20037
|
/** Open a TempDbReader for a given handle. Caller must close the reader. */
|
|
19282
20038
|
openReader(handle) {
|
|
19283
20039
|
const entry = this.cache.get(handle);
|
|
19284
|
-
if (!entry || !
|
|
20040
|
+
if (!entry || !fs33.existsSync(entry.tempDbPath)) return null;
|
|
19285
20041
|
const adapter = openBetterSqliteDatabase(entry.tempDbPath, {
|
|
19286
20042
|
readonly: true,
|
|
19287
20043
|
nativeBinding: this.nativeBinding
|
|
@@ -19326,8 +20082,7 @@ var MergeSessionCache = class {
|
|
|
19326
20082
|
};
|
|
19327
20083
|
|
|
19328
20084
|
// ../../packages/node-runtime/src/services/push-importer.ts
|
|
19329
|
-
import * as
|
|
19330
|
-
var importInProgress = /* @__PURE__ */ new Set();
|
|
20085
|
+
import * as fs34 from "fs";
|
|
19331
20086
|
var SYSTEM_SENDER_ID = "SYSTEM";
|
|
19332
20087
|
var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
|
|
19333
20088
|
function validatePayload(payload, isNew) {
|
|
@@ -19594,20 +20349,29 @@ async function pushImport(dbManager, sessionId, payload) {
|
|
|
19594
20349
|
if (!SAFE_SESSION_ID_RE.test(sessionId) || sessionId.includes("..")) {
|
|
19595
20350
|
return { ok: false, reason: "invalid_payload", message: "sessionId contains invalid characters" };
|
|
19596
20351
|
}
|
|
19597
|
-
|
|
19598
|
-
return
|
|
19599
|
-
|
|
19600
|
-
|
|
19601
|
-
|
|
19602
|
-
|
|
20352
|
+
try {
|
|
20353
|
+
return await withDataDirImportLock(
|
|
20354
|
+
dbManager.getUserDataDir(),
|
|
20355
|
+
() => pushImportUnlocked(dbManager, sessionId, payload)
|
|
20356
|
+
);
|
|
20357
|
+
} catch (error) {
|
|
20358
|
+
if (error instanceof ImportInProgressError) {
|
|
20359
|
+
return {
|
|
20360
|
+
ok: false,
|
|
20361
|
+
reason: "import_in_progress",
|
|
20362
|
+
message: "Another import is already in progress"
|
|
20363
|
+
};
|
|
20364
|
+
}
|
|
20365
|
+
throw error;
|
|
19603
20366
|
}
|
|
20367
|
+
}
|
|
20368
|
+
async function pushImportUnlocked(dbManager, sessionId, payload) {
|
|
19604
20369
|
const dbPath = dbManager.getDbPath(sessionId);
|
|
19605
|
-
const isNew = !
|
|
20370
|
+
const isNew = !fs34.existsSync(dbPath);
|
|
19606
20371
|
const validationError = validatePayload(payload, isNew);
|
|
19607
20372
|
if (validationError) {
|
|
19608
20373
|
return { ok: false, reason: "invalid_payload", message: validationError };
|
|
19609
20374
|
}
|
|
19610
|
-
importInProgress.add(sessionId);
|
|
19611
20375
|
try {
|
|
19612
20376
|
if (isNew) {
|
|
19613
20377
|
const db2 = dbManager.openRawSessionDatabase(sessionId, { create: true });
|
|
@@ -19661,8 +20425,6 @@ async function pushImport(dbManager, sessionId, payload) {
|
|
|
19661
20425
|
}
|
|
19662
20426
|
}
|
|
19663
20427
|
return { ok: false, reason: "import_failed", message: err instanceof Error ? err.message : String(err) };
|
|
19664
|
-
} finally {
|
|
19665
|
-
importInProgress.delete(sessionId);
|
|
19666
20428
|
}
|
|
19667
20429
|
}
|
|
19668
20430
|
|
|
@@ -19704,6 +20466,12 @@ export {
|
|
|
19704
20466
|
streamParseFileInfo,
|
|
19705
20467
|
analyzeIncrementalImport,
|
|
19706
20468
|
incrementalImport,
|
|
20469
|
+
resolveAutoImportTarget,
|
|
20470
|
+
autoImportFile,
|
|
20471
|
+
IMPORT_LOCK_FILENAME,
|
|
20472
|
+
IMPORT_IN_PROGRESS_ERROR_KEY,
|
|
20473
|
+
ImportInProgressError,
|
|
20474
|
+
withDataDirImportLock,
|
|
19707
20475
|
ArchiveImportError,
|
|
19708
20476
|
validateArchiveEntryName,
|
|
19709
20477
|
ZipArchiveReader,
|
|
@@ -19809,6 +20577,8 @@ export {
|
|
|
19809
20577
|
contacts_exports,
|
|
19810
20578
|
createPeopleRelationshipsService,
|
|
19811
20579
|
relationships_exports,
|
|
20580
|
+
createGlobalInsightService,
|
|
20581
|
+
global_insight_exports,
|
|
19812
20582
|
CHUNK_VECTOR_INDEX_TABLE,
|
|
19813
20583
|
CHUNK_VECTOR_INDEX_INDEXES,
|
|
19814
20584
|
EMBEDDING_INDEX_SCHEMA,
|
|
@@ -19868,4 +20638,4 @@ export {
|
|
|
19868
20638
|
MergeSessionCache,
|
|
19869
20639
|
pushImport
|
|
19870
20640
|
};
|
|
19871
|
-
//# sourceMappingURL=chunk-
|
|
20641
|
+
//# sourceMappingURL=chunk-T64GA5HP.mjs.map
|