chatlab-cli 0.28.1 → 0.30.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-ISJ2DC7R.mjs → chat-command-UZ23GKO7.mjs} +8 -5
- package/dist/{chat-command-ISJ2DC7R.mjs.map → chat-command-UZ23GKO7.mjs.map} +1 -1
- package/dist/{chunk-AB6TAXTQ.mjs → chunk-4IZ3WKYU.mjs} +591 -310
- package/dist/chunk-4IZ3WKYU.mjs.map +1 -0
- package/dist/{chunk-3QBVO4J7.mjs → chunk-5UBHNQFZ.mjs} +101 -41
- package/dist/chunk-5UBHNQFZ.mjs.map +1 -0
- package/dist/chunk-7BZLS2KW.mjs +8965 -0
- package/dist/chunk-7BZLS2KW.mjs.map +1 -0
- package/dist/{chunk-ZVDOFWL7.mjs → chunk-A2SANKG4.mjs} +4043 -10895
- package/dist/chunk-A2SANKG4.mjs.map +1 -0
- package/dist/chunk-CNSYXRJP.mjs +526 -0
- package/dist/chunk-CNSYXRJP.mjs.map +1 -0
- package/dist/{chunk-T7IETWHN.mjs → chunk-FCCL5Z4W.mjs} +5 -3
- package/dist/{chunk-T7IETWHN.mjs.map → chunk-FCCL5Z4W.mjs.map} +1 -1
- package/dist/chunk-M55KW2HA.mjs +2323 -0
- package/dist/chunk-M55KW2HA.mjs.map +1 -0
- package/dist/chunk-MUPILWAZ.mjs +1339 -0
- package/dist/chunk-MUPILWAZ.mjs.map +1 -0
- package/dist/{chunk-YBS3YQB6.mjs → chunk-P3ZOLTGS.mjs} +31 -57
- package/dist/chunk-P3ZOLTGS.mjs.map +1 -0
- package/dist/{chunk-NJB2NNB2.mjs → chunk-RDR6PS2R.mjs} +2 -2
- package/dist/{chunk-ERAAWLEF.mjs → chunk-ZFU2CHW3.mjs} +11 -8
- package/dist/{chunk-ERAAWLEF.mjs.map → chunk-ZFU2CHW3.mjs.map} +1 -1
- package/dist/cli.mjs +7 -4
- package/dist/contacts-worker.mjs +48 -0
- package/dist/contacts-worker.mjs.map +1 -0
- package/dist/{http-MBILV37U.mjs → http-UWRA5FLB.mjs} +11 -8
- package/dist/{import-EYV3GXHE.mjs → import-745KQL7N.mjs} +7 -4
- package/dist/index.mjs +11 -8
- package/dist/{mcp-H55AEXU5.mjs → mcp-UHN5JOHC.mjs} +8 -5
- package/dist/{mcp-H55AEXU5.mjs.map → mcp-UHN5JOHC.mjs.map} +1 -1
- package/dist/people-relationships-worker.mjs +48 -0
- package/dist/people-relationships-worker.mjs.map +1 -0
- package/dist/semantic-index-worker.mjs +1 -1
- package/dist/{src-36ZIR65K.mjs → src-Y7MDRXLJ.mjs} +68 -44
- package/dist/{stream-import-YZMZ56B5.mjs → stream-import-3JMXQD7P.mjs} +7 -4
- package/dist/{worker-runtime-YDYS2C6T.mjs → worker-runtime-63RYMGZI.mjs} +7 -3
- package/dist/{worker-runtime-YDYS2C6T.mjs.map → worker-runtime-63RYMGZI.mjs.map} +1 -1
- package/dist-web/assets/BufferResource-BVdJ_I5X.js +185 -0
- package/dist-web/assets/CanvasRenderer-BaRuT54R.js +1 -0
- package/dist-web/assets/DebugToolsPanel-D_lFIW6j.css +1 -0
- package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-N5CoiJzN.js +110 -0
- package/dist-web/assets/{EChartWordcloud-B_YisOW5.js → EChartWordcloud-Clg60FMo.js} +1 -1
- package/dist-web/assets/Filter-B7cNFtuE.js +1 -0
- package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js +1 -0
- package/dist-web/assets/MessageExportModal-B2Qm6VVj.js +1 -0
- package/dist-web/assets/PageHeader-Bbn87QZS.css +1 -0
- package/dist-web/assets/PageHeader-PjlWy9fC.js +1 -0
- package/dist-web/assets/RelationshipGalaxyCanvas-11MafciW.js +244 -0
- package/dist-web/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css +1 -0
- package/dist-web/assets/RelationshipGalaxyThreeCanvas-C1I4B8NX.js +4108 -0
- package/dist-web/assets/RenderTargetSystem-Chkz1WlI.js +184 -0
- package/dist-web/assets/WebGLRenderer-xK19ru58.js +156 -0
- package/dist-web/assets/WebGPURenderer-CL5ZaKsn.js +41 -0
- package/dist-web/assets/browserAll-CyyNzRD_.js +14 -0
- package/dist-web/assets/electron-MXaTKaMu.js +1 -0
- package/dist-web/assets/{fetch-29ZBtGV0.js → fetch-D1dDiFO4.js} +1 -1
- package/dist-web/assets/{fetch-TCeykOkO.js → fetch-DDnVXGte.js} +1 -1
- package/dist-web/assets/{fetch-Bh1fo3Df.js → fetch-DTwdRTXM.js} +1 -1
- package/dist-web/assets/{fetch-BcxrU2mr.js → fetch-DaaJwT-5.js} +1 -1
- package/dist-web/assets/{fetch-tIIVdXSv.js → fetch-DeA0ztwT.js} +1 -1
- package/dist-web/assets/fetch-DpnIGZSN.js +17 -0
- package/dist-web/assets/{fetch-C3yhWO2S.js → fetch-DsE-xcP2.js} +1 -1
- package/dist-web/assets/{fetch-BYczbT4C.js → fetch-DsTgYu_X.js} +1 -1
- package/dist-web/assets/fetch-hKE53UO4.js +1 -0
- package/dist-web/assets/fetch-u9GTMhMF.js +1 -0
- package/dist-web/assets/index---rXG1hZ.css +1 -0
- package/dist-web/assets/index-BVkE4qis.js +22 -0
- package/dist-web/assets/index-BcXLcFnV.css +1 -0
- package/dist-web/assets/index-BnHKyESZ.js +1 -0
- package/dist-web/assets/index-Btr5H7d-.js +2 -0
- package/dist-web/assets/{index-BX3tqfLz.css → index-C1p9CbmN.css} +1 -1
- package/dist-web/assets/index-COpza1A4.css +1 -0
- package/dist-web/assets/index-D04LWJop.js +173 -0
- package/dist-web/assets/index-DFCYmzGE.js +2 -0
- package/dist-web/assets/index-DosG7LmJ.css +1 -0
- package/dist-web/assets/index-DqXHRIhI.js +1 -0
- package/dist-web/assets/{index-fbWrQpgH.js → index-DqtXt3wl.js} +1 -1
- package/dist-web/assets/index-Dszv-QFp.js +64 -0
- package/dist-web/assets/index-DzLrWGXu.js +1 -0
- package/dist-web/assets/people-page-header-Mupn-U_b.js +1 -0
- package/dist-web/assets/relationship-galaxy-viewport-Cwfa51ER.js +1 -0
- package/dist-web/assets/{vendor-echarts-CITQeskI.js → vendor-echarts-hJVnODyU.js} +2 -2
- package/dist-web/assets/{vendor-echarts-wordcloud-B1kQMklB.js → vendor-echarts-wordcloud-k5ih2Jl4.js} +1 -1
- package/dist-web/assets/{vendor-nuxt-ui-Zy8txNdC.js → vendor-nuxt-ui-DaLxYaC4.js} +2 -2
- package/dist-web/assets/{vendor-reka-ui-mMvma1EP.js → vendor-reka-ui-CzM4kDQ7.js} +1 -1
- package/dist-web/assets/{vendor-zrender-DFl4U2Oe.js → vendor-zrender-5DcYjSZk.js} +1 -1
- package/dist-web/assets/{web-H3XLx159.js → web-DPVWfPS-.js} +1 -1
- package/dist-web/assets/webworkerAll-DF5QV_vN.js +83 -0
- package/dist-web/index.html +4 -4
- package/package.json +1 -1
- package/dist/chunk-3QBVO4J7.mjs.map +0 -1
- package/dist/chunk-AB6TAXTQ.mjs.map +0 -1
- package/dist/chunk-GHTGFM6O.mjs +0 -540
- package/dist/chunk-GHTGFM6O.mjs.map +0 -1
- package/dist/chunk-YBS3YQB6.mjs.map +0 -1
- package/dist/chunk-ZVDOFWL7.mjs.map +0 -1
- package/dist-web/assets/DebugToolsPanel-DFw6_G5V.css +0 -1
- package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-Do3x4aL3.js +0 -110
- package/dist-web/assets/MessageExportModal-rijqUusq.js +0 -1
- package/dist-web/assets/electron-DWDCqx_d.js +0 -1
- package/dist-web/assets/fetch-BAxKmbKF.js +0 -1
- package/dist-web/assets/fetch-Da_TBxrX.js +0 -1
- package/dist-web/assets/fetch-DyD8wDhb.js +0 -17
- package/dist-web/assets/index-BzjLqlW_.js +0 -173
- package/dist-web/assets/index-C74qEDFh.js +0 -64
- package/dist-web/assets/index-CM6xNPFp.css +0 -1
- package/dist-web/assets/index-CgK2LLIF.js +0 -12
- package/dist-web/assets/index-CjkOvM3N.js +0 -2
- package/dist-web/assets/index-Dno7CADA.js +0 -1
- package/dist-web/assets/index-I3SWZjnW.css +0 -1
- /package/dist/{chunk-NJB2NNB2.mjs.map → chunk-RDR6PS2R.mjs.map} +0 -0
- /package/dist/{http-MBILV37U.mjs.map → http-UWRA5FLB.mjs.map} +0 -0
- /package/dist/{import-EYV3GXHE.mjs.map → import-745KQL7N.mjs.map} +0 -0
- /package/dist/{src-36ZIR65K.mjs.map → src-Y7MDRXLJ.mjs.map} +0 -0
- /package/dist/{stream-import-YZMZ56B5.mjs.map → stream-import-3JMXQD7P.mjs.map} +0 -0
|
@@ -4,30 +4,31 @@ import { fileURLToPath as __fileURLToPath } from 'url';
|
|
|
4
4
|
const require = __createRequire(import.meta.url);
|
|
5
5
|
const __filename = __fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = __pathDirname(__filename);
|
|
7
|
+
import {
|
|
8
|
+
analyzeIncrementalImport,
|
|
9
|
+
analyzeNewImport,
|
|
10
|
+
incrementalImport,
|
|
11
|
+
streamImport
|
|
12
|
+
} from "./chunk-FCCL5Z4W.mjs";
|
|
7
13
|
import {
|
|
8
14
|
closeServerAiLogger,
|
|
9
15
|
createCliRunAgentStream,
|
|
16
|
+
getServerAiLogger,
|
|
10
17
|
initServerAiLogger
|
|
11
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ZFU2CHW3.mjs";
|
|
12
19
|
import {
|
|
13
20
|
AGENT_TOOL_REGISTRY,
|
|
14
21
|
CoreDataProvider
|
|
15
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-5UBHNQFZ.mjs";
|
|
16
23
|
import {
|
|
17
24
|
assertCliDataDirCompatible
|
|
18
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-RDR6PS2R.mjs";
|
|
19
26
|
import {
|
|
20
27
|
getVersion
|
|
21
28
|
} from "./chunk-5ADKYYNA.mjs";
|
|
22
29
|
import {
|
|
23
30
|
resolveCliPath
|
|
24
31
|
} from "./chunk-FJ35V3TE.mjs";
|
|
25
|
-
import {
|
|
26
|
-
analyzeIncrementalImport,
|
|
27
|
-
analyzeNewImport,
|
|
28
|
-
incrementalImport,
|
|
29
|
-
streamImport
|
|
30
|
-
} from "./chunk-T7IETWHN.mjs";
|
|
31
32
|
import {
|
|
32
33
|
AnalyticsService,
|
|
33
34
|
NodePathProvider,
|
|
@@ -38,59 +39,80 @@ import {
|
|
|
38
39
|
createJiebaNlpProvider,
|
|
39
40
|
createNodeDataDirSwitch,
|
|
40
41
|
createSemanticIndexWorkerRuntimeClient,
|
|
41
|
-
getDbFileVersion,
|
|
42
42
|
getDefaultNodeUserDataDir,
|
|
43
|
-
getOrComputeAnalysisCache,
|
|
44
43
|
getPendingNodeDataDirMigration,
|
|
45
44
|
hasPendingElectronDataWarning,
|
|
46
45
|
serializeChatLabToJsonl
|
|
47
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-P3ZOLTGS.mjs";
|
|
48
47
|
import {
|
|
49
48
|
AIChatManager,
|
|
50
|
-
ALL_MIGRATIONS,
|
|
51
49
|
ArchiveImportError,
|
|
52
50
|
ArchiveImportSourceManager,
|
|
53
51
|
AssistantManager,
|
|
54
|
-
BUILTIN_MODELS,
|
|
55
|
-
BUILTIN_PROVIDERS,
|
|
56
|
-
BUILTIN_TOOL_CATALOG,
|
|
57
52
|
CustomModelStore,
|
|
58
53
|
CustomProviderStore,
|
|
59
|
-
DataDirCompatibilityError,
|
|
60
|
-
DatabaseManager,
|
|
61
54
|
LLMConfigStore,
|
|
62
55
|
MergeSessionCache,
|
|
63
|
-
MigrationRunner,
|
|
64
|
-
POS_TAG_DEFINITIONS,
|
|
65
56
|
SEMANTIC_INDEX_CONFIG_FILE,
|
|
66
57
|
SemanticIndexConfigStore,
|
|
67
58
|
SkillManagerCore,
|
|
68
59
|
TempDbWriter,
|
|
69
|
-
appLogger,
|
|
70
60
|
buildPiModel,
|
|
71
61
|
computeWordFrequency,
|
|
72
62
|
countMessagesTokens,
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
createContactsService,
|
|
64
|
+
createPeopleRelationshipsService,
|
|
75
65
|
deleteDict,
|
|
76
66
|
detectAllFormats,
|
|
77
67
|
detectFormat,
|
|
78
68
|
downloadDict,
|
|
79
69
|
ensureDefaultDict,
|
|
80
|
-
executeReadonlySql,
|
|
81
|
-
executeSql,
|
|
82
70
|
exportSessionToJson,
|
|
83
71
|
export_service_exports,
|
|
84
72
|
fetchRemoteModels,
|
|
85
73
|
findEntryFileInDirectory,
|
|
74
|
+
getDefaultRulesForLocale,
|
|
75
|
+
getDictList,
|
|
76
|
+
getSupportedFormats,
|
|
77
|
+
isDictDownloaded,
|
|
78
|
+
isSemanticIndexConfigured,
|
|
79
|
+
member_service_exports,
|
|
80
|
+
mergeRulesForLocale,
|
|
81
|
+
owner_profile_service_exports,
|
|
82
|
+
persistSemanticIndexConfig,
|
|
83
|
+
pushImport,
|
|
84
|
+
resolveSemanticIndexApiKeySet,
|
|
85
|
+
runSimpleLlmStream,
|
|
86
|
+
scanMultiChatFile,
|
|
87
|
+
segmentText,
|
|
88
|
+
session_index_service_exports,
|
|
89
|
+
session_service_exports,
|
|
90
|
+
streamParseFileInfo,
|
|
91
|
+
summary_service_exports,
|
|
92
|
+
validateApiKey
|
|
93
|
+
} from "./chunk-A2SANKG4.mjs";
|
|
94
|
+
import {
|
|
95
|
+
ALL_MIGRATIONS,
|
|
96
|
+
BUILTIN_MODELS,
|
|
97
|
+
BUILTIN_PROVIDERS,
|
|
98
|
+
BUILTIN_TOOL_CATALOG,
|
|
99
|
+
CONTACTS_TIME_RANGE_PRESETS,
|
|
100
|
+
DataDirCompatibilityError,
|
|
101
|
+
DatabaseManager,
|
|
102
|
+
MigrationRunner,
|
|
103
|
+
POS_TAG_DEFINITIONS,
|
|
104
|
+
appLogger,
|
|
105
|
+
createDatabaseManagerAdapter,
|
|
106
|
+
deleteAuthProfile,
|
|
107
|
+
executeReadonlySql,
|
|
108
|
+
executeSql,
|
|
86
109
|
getAvailableYears,
|
|
87
110
|
getBuiltinModelsByProvider,
|
|
88
111
|
getCatchphraseAnalysis,
|
|
89
112
|
getCheckInAnalysis,
|
|
90
113
|
getClusterGraph,
|
|
91
114
|
getDailyActivity,
|
|
92
|
-
|
|
93
|
-
getDictList,
|
|
115
|
+
getDbFileVersion,
|
|
94
116
|
getDivingAnalysis,
|
|
95
117
|
getDragonKingAnalysis,
|
|
96
118
|
getHourlyActivity,
|
|
@@ -109,13 +131,13 @@ import {
|
|
|
109
131
|
getMessageTypeStats,
|
|
110
132
|
getMonthlyActivity,
|
|
111
133
|
getNightOwlAnalysis,
|
|
134
|
+
getOrComputeAnalysisCache,
|
|
112
135
|
getRelationshipStats,
|
|
113
136
|
getRepeatAnalysis,
|
|
114
137
|
getSchemaDetailed,
|
|
115
138
|
getSessionInfo,
|
|
116
139
|
getSessionMeta,
|
|
117
140
|
getSessionOverview,
|
|
118
|
-
getSupportedFormats,
|
|
119
141
|
getTextLengthPercentiles,
|
|
120
142
|
getTextStats,
|
|
121
143
|
getTimeRange,
|
|
@@ -123,34 +145,18 @@ import {
|
|
|
123
145
|
getYearlyActivity,
|
|
124
146
|
initAppLogger,
|
|
125
147
|
initNlpDir,
|
|
126
|
-
isDictDownloaded,
|
|
127
148
|
isNewerStableVersion,
|
|
128
|
-
isSemanticIndexConfigured,
|
|
129
149
|
loadConfig,
|
|
130
|
-
member_service_exports,
|
|
131
|
-
mergeRulesForLocale,
|
|
132
|
-
owner_profile_service_exports,
|
|
133
|
-
persistSemanticIndexConfig,
|
|
134
|
-
pushImport,
|
|
135
150
|
queryMessages,
|
|
136
151
|
resolveApiKey,
|
|
137
|
-
resolveSemanticIndexApiKeySet,
|
|
138
|
-
runSimpleLlmStream,
|
|
139
|
-
scanMultiChatFile,
|
|
140
|
-
segmentText,
|
|
141
|
-
session_index_service_exports,
|
|
142
|
-
session_service_exports,
|
|
143
|
-
streamParseFileInfo,
|
|
144
152
|
stripAvatarFields,
|
|
145
|
-
summary_service_exports,
|
|
146
|
-
validateApiKey,
|
|
147
153
|
verifyCliDataPath,
|
|
148
154
|
writeAuthProfile,
|
|
149
155
|
writeConfigField
|
|
150
|
-
} from "./chunk-
|
|
156
|
+
} from "./chunk-7BZLS2KW.mjs";
|
|
151
157
|
|
|
152
158
|
// src/http/index.ts
|
|
153
|
-
import * as
|
|
159
|
+
import * as fs12 from "fs";
|
|
154
160
|
import * as crypto4 from "crypto";
|
|
155
161
|
|
|
156
162
|
// src/http/server.ts
|
|
@@ -626,6 +632,121 @@ function registerMemberRoutes(server2, ctx) {
|
|
|
626
632
|
);
|
|
627
633
|
}
|
|
628
634
|
|
|
635
|
+
// ../../packages/http-routes/src/routes/web/contacts.ts
|
|
636
|
+
function registerContactsRoutes(server2, ctx) {
|
|
637
|
+
const service = ctx.contactsService ?? createContactsService({
|
|
638
|
+
adapter: ctx.sessionAdapter,
|
|
639
|
+
pathProvider: ctx.pathProvider,
|
|
640
|
+
runtimeIdentity: ctx.runtimeIdentity,
|
|
641
|
+
nativeBinding: ctx.nativeBinding
|
|
642
|
+
});
|
|
643
|
+
server2.addHook("onClose", async () => {
|
|
644
|
+
await service.close();
|
|
645
|
+
});
|
|
646
|
+
server2.get("/_web/contacts", async (request) => {
|
|
647
|
+
return service.getContactsPage({
|
|
648
|
+
acceptStale: isTruthy(request.query.acceptStale),
|
|
649
|
+
timeRangePreset: parseContactsTimeRangePreset(request.query.timeRange),
|
|
650
|
+
pool: parseContactPool(request.query.pool),
|
|
651
|
+
page: parsePositiveInteger(request.query.page),
|
|
652
|
+
pageSize: parsePositiveInteger(request.query.pageSize),
|
|
653
|
+
query: request.query.q
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
server2.post("/_web/contacts/recompute", async (request) => {
|
|
657
|
+
return service.startRecompute({
|
|
658
|
+
timeRangePreset: parseContactsTimeRangePreset(request.query.timeRange),
|
|
659
|
+
pool: parseContactPool(request.query.pool),
|
|
660
|
+
page: parsePositiveInteger(request.query.page),
|
|
661
|
+
pageSize: parsePositiveInteger(request.query.pageSize),
|
|
662
|
+
query: request.query.q
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
server2.get("/_web/contacts/:key/detail", async (request) => {
|
|
666
|
+
return service.getContactDetail(request.params.key, {
|
|
667
|
+
acceptStale: isTruthy(request.query.acceptStale),
|
|
668
|
+
timeRangePreset: parseContactsTimeRangePreset(request.query.timeRange)
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
server2.put(
|
|
672
|
+
"/_web/contacts/:key/mark-friend",
|
|
673
|
+
async (request) => {
|
|
674
|
+
return service.markContactAsFriend(request.params.key, {
|
|
675
|
+
timeRangePreset: parseContactsTimeRangePreset(request.query.timeRange)
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
server2.delete(
|
|
680
|
+
"/_web/contacts/:key/mark-friend",
|
|
681
|
+
async (request) => {
|
|
682
|
+
return service.unmarkContactAsFriend(request.params.key, {
|
|
683
|
+
timeRangePreset: parseContactsTimeRangePreset(request.query.timeRange)
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
function isTruthy(value) {
|
|
689
|
+
return value === "1" || value === "true" || value === "yes";
|
|
690
|
+
}
|
|
691
|
+
function parseContactsTimeRangePreset(value) {
|
|
692
|
+
return CONTACTS_TIME_RANGE_PRESETS.includes(value) ? value : "1y";
|
|
693
|
+
}
|
|
694
|
+
function parseContactPool(value) {
|
|
695
|
+
return value === "friend" || value === "non_friend" ? value : void 0;
|
|
696
|
+
}
|
|
697
|
+
function parsePositiveInteger(value) {
|
|
698
|
+
if (!value) return void 0;
|
|
699
|
+
const parsed = Number(value);
|
|
700
|
+
if (!Number.isFinite(parsed)) return void 0;
|
|
701
|
+
return Math.max(1, Math.trunc(parsed));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// ../../packages/http-routes/src/routes/web/people-relationships.ts
|
|
705
|
+
function registerPeopleRelationshipsRoutes(server2, ctx) {
|
|
706
|
+
const service = ctx.peopleRelationshipsService ?? createPeopleRelationshipsService({
|
|
707
|
+
adapter: ctx.sessionAdapter,
|
|
708
|
+
pathProvider: ctx.pathProvider,
|
|
709
|
+
runtimeIdentity: ctx.runtimeIdentity,
|
|
710
|
+
nativeBinding: ctx.nativeBinding
|
|
711
|
+
});
|
|
712
|
+
server2.addHook("onClose", async () => {
|
|
713
|
+
await service.close();
|
|
714
|
+
});
|
|
715
|
+
server2.get("/_web/people/relationships", async (request) => {
|
|
716
|
+
return service.getGraph({
|
|
717
|
+
acceptStale: isTruthy2(request.query.acceptStale),
|
|
718
|
+
timeRangePreset: parseContactsTimeRangePreset2(request.query.timeRange),
|
|
719
|
+
graphScope: parsePeopleRelationshipsGraphScope(request.query.scope),
|
|
720
|
+
query: request.query.q
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
server2.post("/_web/people/relationships/recompute", async (request) => {
|
|
724
|
+
return service.startRecompute({
|
|
725
|
+
timeRangePreset: parseContactsTimeRangePreset2(request.query.timeRange),
|
|
726
|
+
graphScope: parsePeopleRelationshipsGraphScope(request.query.scope),
|
|
727
|
+
query: request.query.q
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
server2.get(
|
|
731
|
+
"/_web/people/relationships/:key/neighborhood",
|
|
732
|
+
async (request) => {
|
|
733
|
+
return service.getNeighborhood(request.params.key, {
|
|
734
|
+
acceptStale: isTruthy2(request.query.acceptStale),
|
|
735
|
+
timeRangePreset: parseContactsTimeRangePreset2(request.query.timeRange)
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
function isTruthy2(value) {
|
|
741
|
+
return value === "1" || value === "true" || value === "yes";
|
|
742
|
+
}
|
|
743
|
+
function parseContactsTimeRangePreset2(value) {
|
|
744
|
+
return CONTACTS_TIME_RANGE_PRESETS.includes(value) ? value : "1y";
|
|
745
|
+
}
|
|
746
|
+
function parsePeopleRelationshipsGraphScope(value) {
|
|
747
|
+
return value === "close" || value === "friends" ? value : "panorama";
|
|
748
|
+
}
|
|
749
|
+
|
|
629
750
|
// ../../packages/http-routes/src/routes/web/preferences.ts
|
|
630
751
|
function registerPreferencesRoutes(server2, ctx) {
|
|
631
752
|
const prefManager = ctx.preferencesManager ?? new PreferencesManager(ctx.pathProvider.getSystemDir());
|
|
@@ -977,7 +1098,14 @@ function registerSqlRoutes(server2, ctx) {
|
|
|
977
1098
|
return reply.code(400).send({ error: "Missing sql parameter" });
|
|
978
1099
|
}
|
|
979
1100
|
try {
|
|
980
|
-
|
|
1101
|
+
const result = executeSql(db, sql, { columnar: true, timing: true, maxRows: 0 });
|
|
1102
|
+
return {
|
|
1103
|
+
columns: result.columns,
|
|
1104
|
+
rows: result.rows,
|
|
1105
|
+
rowCount: result.rowCount,
|
|
1106
|
+
duration: result.duration ?? 0,
|
|
1107
|
+
limited: result.truncated
|
|
1108
|
+
};
|
|
981
1109
|
} catch (err) {
|
|
982
1110
|
const message = err instanceof Error ? err.message : "SQL execution error";
|
|
983
1111
|
return reply.code(400).send({ error: message });
|
|
@@ -1674,13 +1802,53 @@ function registerAiSummaryRoutes(server2, ctx) {
|
|
|
1674
1802
|
});
|
|
1675
1803
|
}
|
|
1676
1804
|
|
|
1677
|
-
// ../../packages/http-routes/src/routes/web/ai-
|
|
1805
|
+
// ../../packages/http-routes/src/routes/web/ai-logs.ts
|
|
1806
|
+
import fs3 from "fs";
|
|
1678
1807
|
import path4 from "path";
|
|
1808
|
+
function findLatestAiLogPath(logsDir) {
|
|
1809
|
+
const aiLogDir = path4.join(logsDir, "ai");
|
|
1810
|
+
if (!fs3.existsSync(aiLogDir)) return null;
|
|
1811
|
+
const latest = fs3.readdirSync(aiLogDir).filter((name) => name.startsWith("ai_") && name.endsWith(".log")).map((name) => {
|
|
1812
|
+
const filePath = path4.join(aiLogDir, name);
|
|
1813
|
+
const stat2 = fs3.statSync(filePath);
|
|
1814
|
+
return { path: filePath, mtimeMs: stat2.mtimeMs };
|
|
1815
|
+
}).sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
|
|
1816
|
+
return latest?.path ?? null;
|
|
1817
|
+
}
|
|
1818
|
+
function resolveAiLogPath(ctx) {
|
|
1819
|
+
const currentPath = ctx.getCurrentAiLogPath?.();
|
|
1820
|
+
if (currentPath && fs3.existsSync(currentPath)) return currentPath;
|
|
1821
|
+
return findLatestAiLogPath(ctx.pathProvider.getLogsDir());
|
|
1822
|
+
}
|
|
1823
|
+
function isLoopbackAddress(address) {
|
|
1824
|
+
if (!address) return true;
|
|
1825
|
+
const normalized = address.replace(/^::ffff:/, "");
|
|
1826
|
+
return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || normalized.startsWith("127.");
|
|
1827
|
+
}
|
|
1828
|
+
function registerAiLogRoutes(server2, ctx) {
|
|
1829
|
+
server2.post("/_web/ai/logs/show", async (request, reply) => {
|
|
1830
|
+
if (!ctx.showInFolder) return reply.code(501).send({ success: false, error: "Not supported" });
|
|
1831
|
+
if (!isLoopbackAddress(request.ip)) {
|
|
1832
|
+
return { success: false, error: "Opening AI logs is only supported on this device" };
|
|
1833
|
+
}
|
|
1834
|
+
const logPath = resolveAiLogPath(ctx);
|
|
1835
|
+
if (!logPath) return { success: false, error: "No AI log files found" };
|
|
1836
|
+
try {
|
|
1837
|
+
await ctx.showInFolder(logPath);
|
|
1838
|
+
return { success: true, path: logPath };
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
|
|
1846
|
+
import path5 from "path";
|
|
1679
1847
|
function registerSemanticIndexRoutes(server2, ctx) {
|
|
1680
1848
|
const service = ctx.semanticIndexService;
|
|
1681
1849
|
if (!service) {
|
|
1682
1850
|
if (ctx.aiDataDir) {
|
|
1683
|
-
const configStore = new SemanticIndexConfigStore(
|
|
1851
|
+
const configStore = new SemanticIndexConfigStore(path5.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
|
|
1684
1852
|
const configResponse = (config) => ({
|
|
1685
1853
|
config,
|
|
1686
1854
|
apiKeySet: resolveSemanticIndexApiKeySet(config, ctx.resolveApiKey),
|
|
@@ -1735,8 +1903,8 @@ function registerSemanticIndexRoutes(server2, ctx) {
|
|
|
1735
1903
|
server2.post("/_web/ai/semantic-index/status", async (request) => {
|
|
1736
1904
|
return { sessions: await service.statusForSessions(request.body.sessionIds ?? []) };
|
|
1737
1905
|
});
|
|
1738
|
-
const sessionAction = (
|
|
1739
|
-
server2.post(
|
|
1906
|
+
const sessionAction = (path14, action) => {
|
|
1907
|
+
server2.post(path14, async (request, reply) => {
|
|
1740
1908
|
const { sessionId } = request.body;
|
|
1741
1909
|
if (!sessionId) return reply.code(400).send({ error: "sessionId is required" });
|
|
1742
1910
|
await action(sessionId);
|
|
@@ -1767,9 +1935,9 @@ function registerSemanticIndexRoutes(server2, ctx) {
|
|
|
1767
1935
|
}
|
|
1768
1936
|
|
|
1769
1937
|
// ../../packages/http-routes/src/routes/web/merge.ts
|
|
1770
|
-
import * as
|
|
1938
|
+
import * as fs4 from "fs";
|
|
1771
1939
|
import * as os from "os";
|
|
1772
|
-
import * as
|
|
1940
|
+
import * as path6 from "path";
|
|
1773
1941
|
function ensureDb2(ctx, sessionId) {
|
|
1774
1942
|
const db = ctx.dbManager.open(sessionId);
|
|
1775
1943
|
if (!db) throw sessionNotFound(sessionId);
|
|
@@ -1786,31 +1954,31 @@ function registerMergeRoutes(server2, ctx) {
|
|
|
1786
1954
|
if (contentType.includes("multipart/form-data")) {
|
|
1787
1955
|
const data = await request.file();
|
|
1788
1956
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
1789
|
-
const tmpDir =
|
|
1790
|
-
const tmpPath =
|
|
1957
|
+
const tmpDir = fs4.mkdtempSync(path6.join(os.tmpdir(), "chatlab-merge-"));
|
|
1958
|
+
const tmpPath = path6.join(tmpDir, data.filename || "upload");
|
|
1791
1959
|
const chunks = [];
|
|
1792
1960
|
for await (const chunk of data.file) {
|
|
1793
1961
|
chunks.push(chunk);
|
|
1794
1962
|
}
|
|
1795
|
-
|
|
1963
|
+
fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
1796
1964
|
filePath = tmpPath;
|
|
1797
1965
|
cleanupFile = true;
|
|
1798
1966
|
cleanupDir = tmpDir;
|
|
1799
1967
|
} else {
|
|
1800
1968
|
const body = request.body;
|
|
1801
1969
|
if (!body?.filePath) return reply.code(400).send({ error: "Missing filePath in body" });
|
|
1802
|
-
if (!
|
|
1970
|
+
if (!fs4.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
|
|
1803
1971
|
filePath = body.filePath;
|
|
1804
1972
|
}
|
|
1805
1973
|
try {
|
|
1806
1974
|
const result = await streamParseFileInfo(filePath, {
|
|
1807
1975
|
createTempDatabase(sourceFilePath) {
|
|
1808
|
-
return mergeCache.createTempDatabase(
|
|
1976
|
+
return mergeCache.createTempDatabase(path6.basename(sourceFilePath));
|
|
1809
1977
|
},
|
|
1810
1978
|
onProgress() {
|
|
1811
1979
|
}
|
|
1812
1980
|
});
|
|
1813
|
-
const filename =
|
|
1981
|
+
const filename = path6.basename(filePath);
|
|
1814
1982
|
const handle = mergeCache.store(filename, result.tempDbPath);
|
|
1815
1983
|
return {
|
|
1816
1984
|
handle,
|
|
@@ -1824,13 +1992,13 @@ function registerMergeRoutes(server2, ctx) {
|
|
|
1824
1992
|
} finally {
|
|
1825
1993
|
if (cleanupFile) {
|
|
1826
1994
|
try {
|
|
1827
|
-
|
|
1995
|
+
fs4.unlinkSync(filePath);
|
|
1828
1996
|
} catch {
|
|
1829
1997
|
}
|
|
1830
1998
|
}
|
|
1831
1999
|
if (cleanupDir) {
|
|
1832
2000
|
try {
|
|
1833
|
-
|
|
2001
|
+
fs4.rmdirSync(cleanupDir);
|
|
1834
2002
|
} catch {
|
|
1835
2003
|
}
|
|
1836
2004
|
}
|
|
@@ -1879,20 +2047,20 @@ function registerMergeRoutes(server2, ctx) {
|
|
|
1879
2047
|
return reply.code(501).send({ error: "Import not available on this server" });
|
|
1880
2048
|
}
|
|
1881
2049
|
const jsonData = JSON.stringify(merged.chatLabData);
|
|
1882
|
-
const tmpDir =
|
|
1883
|
-
const safeName =
|
|
1884
|
-
const tmpPath =
|
|
1885
|
-
|
|
2050
|
+
const tmpDir = fs4.mkdtempSync(path6.join(os.tmpdir(), "chatlab-merged-"));
|
|
2051
|
+
const safeName = path6.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
|
|
2052
|
+
const tmpPath = path6.join(tmpDir, `${safeName}.json`);
|
|
2053
|
+
fs4.writeFileSync(tmpPath, jsonData, "utf-8");
|
|
1886
2054
|
try {
|
|
1887
2055
|
const importResult = await streamImport2(dbManager2, tmpPath);
|
|
1888
2056
|
sessionId = importResult.sessionId;
|
|
1889
2057
|
} finally {
|
|
1890
2058
|
try {
|
|
1891
|
-
|
|
2059
|
+
fs4.unlinkSync(tmpPath);
|
|
1892
2060
|
} catch {
|
|
1893
2061
|
}
|
|
1894
2062
|
try {
|
|
1895
|
-
|
|
2063
|
+
fs4.rmdirSync(tmpDir);
|
|
1896
2064
|
} catch {
|
|
1897
2065
|
}
|
|
1898
2066
|
}
|
|
@@ -1966,16 +2134,16 @@ function registerMergeRoutes(server2, ctx) {
|
|
|
1966
2134
|
}
|
|
1967
2135
|
|
|
1968
2136
|
// ../../packages/http-routes/src/routes/web/cache.ts
|
|
1969
|
-
import * as
|
|
2137
|
+
import * as fs5 from "fs";
|
|
1970
2138
|
import * as fsp from "fs/promises";
|
|
1971
|
-
import * as
|
|
2139
|
+
import * as path7 from "path";
|
|
1972
2140
|
async function getDirSize(dirPath) {
|
|
1973
2141
|
let totalSize = 0;
|
|
1974
2142
|
try {
|
|
1975
|
-
if (!
|
|
2143
|
+
if (!fs5.existsSync(dirPath)) return 0;
|
|
1976
2144
|
const files = await fsp.readdir(dirPath, { withFileTypes: true });
|
|
1977
2145
|
for (const file of files) {
|
|
1978
|
-
const filePath =
|
|
2146
|
+
const filePath = path7.join(dirPath, file.name);
|
|
1979
2147
|
if (file.isDirectory()) {
|
|
1980
2148
|
totalSize += await getDirSize(filePath);
|
|
1981
2149
|
} else {
|
|
@@ -1990,10 +2158,10 @@ async function getDirSize(dirPath) {
|
|
|
1990
2158
|
async function getFileCount(dirPath) {
|
|
1991
2159
|
let count = 0;
|
|
1992
2160
|
try {
|
|
1993
|
-
if (!
|
|
2161
|
+
if (!fs5.existsSync(dirPath)) return 0;
|
|
1994
2162
|
const files = await fsp.readdir(dirPath, { withFileTypes: true });
|
|
1995
2163
|
for (const file of files) {
|
|
1996
|
-
const filePath =
|
|
2164
|
+
const filePath = path7.join(dirPath, file.name);
|
|
1997
2165
|
if (file.isDirectory()) {
|
|
1998
2166
|
count += await getFileCount(filePath);
|
|
1999
2167
|
} else {
|
|
@@ -2006,10 +2174,10 @@ async function getFileCount(dirPath) {
|
|
|
2006
2174
|
}
|
|
2007
2175
|
function hasChatLabDatabases(dirPath) {
|
|
2008
2176
|
try {
|
|
2009
|
-
const markerPath =
|
|
2010
|
-
const dbDir =
|
|
2011
|
-
if (!
|
|
2012
|
-
return
|
|
2177
|
+
const markerPath = path7.join(dirPath, ".chatlab");
|
|
2178
|
+
const dbDir = path7.join(dirPath, "databases");
|
|
2179
|
+
if (!fs5.existsSync(markerPath) || !fs5.existsSync(dbDir)) return false;
|
|
2180
|
+
return fs5.readdirSync(dbDir).some((file) => file.endsWith(".db"));
|
|
2013
2181
|
} catch {
|
|
2014
2182
|
return false;
|
|
2015
2183
|
}
|
|
@@ -2055,7 +2223,7 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2055
2223
|
const results = await Promise.all(
|
|
2056
2224
|
directories.map(async (dir) => {
|
|
2057
2225
|
const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
|
|
2058
|
-
return { ...dir, size, fileCount, exists:
|
|
2226
|
+
return { ...dir, size, fileCount, exists: fs5.existsSync(dir.path) };
|
|
2059
2227
|
})
|
|
2060
2228
|
);
|
|
2061
2229
|
return {
|
|
@@ -2072,10 +2240,10 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2072
2240
|
};
|
|
2073
2241
|
const dirPath = allowedDirs[cacheId];
|
|
2074
2242
|
if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
|
|
2075
|
-
if (!
|
|
2243
|
+
if (!fs5.existsSync(dirPath)) return { success: true };
|
|
2076
2244
|
const files = await fsp.readdir(dirPath);
|
|
2077
2245
|
for (const file of files) {
|
|
2078
|
-
const filePath =
|
|
2246
|
+
const filePath = path7.join(dirPath, file);
|
|
2079
2247
|
const stat2 = await fsp.stat(filePath);
|
|
2080
2248
|
if (stat2.isDirectory()) {
|
|
2081
2249
|
await fsp.rm(filePath, { recursive: true });
|
|
@@ -2092,7 +2260,7 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2092
2260
|
defaultPath: ctx.defaultUserDataDir,
|
|
2093
2261
|
isCustom: ctx.isCustomDataDir ?? false,
|
|
2094
2262
|
canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
|
|
2095
|
-
hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) &&
|
|
2263
|
+
hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path7.resolve(pp.getUserDataDir()) !== path7.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
|
|
2096
2264
|
pendingMigration: pending ? {
|
|
2097
2265
|
from: pending.from,
|
|
2098
2266
|
to: pending.to,
|
|
@@ -2109,8 +2277,8 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2109
2277
|
return ctx.setDataDir(targetPath, migrate);
|
|
2110
2278
|
});
|
|
2111
2279
|
server2.get("/_web/cache/latest-import-log", async () => {
|
|
2112
|
-
const importLogDir =
|
|
2113
|
-
if (!
|
|
2280
|
+
const importLogDir = path7.join(pp.getLogsDir(), "import");
|
|
2281
|
+
if (!fs5.existsSync(importLogDir)) {
|
|
2114
2282
|
return { success: false, error: "Log directory not found" };
|
|
2115
2283
|
}
|
|
2116
2284
|
const files = await fsp.readdir(importLogDir);
|
|
@@ -2120,7 +2288,7 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2120
2288
|
}
|
|
2121
2289
|
const fileStats = await Promise.all(
|
|
2122
2290
|
logFiles.map(async (f) => {
|
|
2123
|
-
const filePath =
|
|
2291
|
+
const filePath = path7.join(importLogDir, f);
|
|
2124
2292
|
const stat2 = await fsp.stat(filePath);
|
|
2125
2293
|
return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
|
|
2126
2294
|
})
|
|
@@ -2144,11 +2312,11 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2144
2312
|
const base64Data = dataUrl.replace(/^data:[^,]+,/, "");
|
|
2145
2313
|
buffer = Buffer.from(base64Data, "base64");
|
|
2146
2314
|
}
|
|
2147
|
-
if (!
|
|
2148
|
-
|
|
2315
|
+
if (!fs5.existsSync(downloadsDir)) {
|
|
2316
|
+
fs5.mkdirSync(downloadsDir, { recursive: true });
|
|
2149
2317
|
}
|
|
2150
|
-
const filePath =
|
|
2151
|
-
|
|
2318
|
+
const filePath = path7.join(downloadsDir, filename);
|
|
2319
|
+
fs5.writeFileSync(filePath, buffer);
|
|
2152
2320
|
return { success: true, filePath };
|
|
2153
2321
|
});
|
|
2154
2322
|
server2.post("/_web/cache/open-dir", async (request, reply) => {
|
|
@@ -2164,7 +2332,7 @@ function registerCacheRoutes(server2, ctx) {
|
|
|
2164
2332
|
};
|
|
2165
2333
|
const dirPath = dirPaths[cacheId];
|
|
2166
2334
|
if (!dirPath) return { success: false, error: "Unknown directory" };
|
|
2167
|
-
if (!
|
|
2335
|
+
if (!fs5.existsSync(dirPath)) {
|
|
2168
2336
|
await fsp.mkdir(dirPath, { recursive: true });
|
|
2169
2337
|
}
|
|
2170
2338
|
try {
|
|
@@ -2234,6 +2402,8 @@ function registerSharedRoutes(server2, ctx, options) {
|
|
|
2234
2402
|
registerImportRoutes(server2, resolvedCtx);
|
|
2235
2403
|
registerSessionRoutes(server2, resolvedCtx);
|
|
2236
2404
|
registerMemberRoutes(server2, resolvedCtx);
|
|
2405
|
+
registerContactsRoutes(server2, resolvedCtx);
|
|
2406
|
+
registerPeopleRelationshipsRoutes(server2, resolvedCtx);
|
|
2237
2407
|
registerPreferencesRoutes(server2, resolvedCtx);
|
|
2238
2408
|
registerAnalyticsRoutes(server2, resolvedCtx);
|
|
2239
2409
|
registerSqlRoutes(server2, resolvedCtx);
|
|
@@ -2262,6 +2432,7 @@ function registerSharedRoutes(server2, ctx, options) {
|
|
|
2262
2432
|
registerAiToolRoutes(server2, resolvedCtx);
|
|
2263
2433
|
registerAiChatRoutes(server2, resolvedCtx);
|
|
2264
2434
|
registerAiSummaryRoutes(server2, resolvedCtx);
|
|
2435
|
+
registerAiLogRoutes(server2, resolvedCtx);
|
|
2265
2436
|
registerSemanticIndexRoutes(server2, resolvedCtx);
|
|
2266
2437
|
registerMergeRoutes(server2, resolvedCtx);
|
|
2267
2438
|
registerCacheRoutes(server2, resolvedCtx);
|
|
@@ -2312,6 +2483,150 @@ function requireBearerToken(request, reply) {
|
|
|
2312
2483
|
}
|
|
2313
2484
|
}
|
|
2314
2485
|
|
|
2486
|
+
// ../../packages/http-routes/src/routes/web/automation.ts
|
|
2487
|
+
function requireAutomation(ctx) {
|
|
2488
|
+
return ctx.automation ?? null;
|
|
2489
|
+
}
|
|
2490
|
+
function normalizeBaseUrl(input) {
|
|
2491
|
+
let url = input.trim().replace(/\/+$/, "");
|
|
2492
|
+
if (url && !/^https?:\/\//i.test(url)) url = `http://${url}`;
|
|
2493
|
+
if (url && !url.endsWith("/api/v1")) {
|
|
2494
|
+
url = url.replace(/\/api\/v1$/, "") + "/api/v1";
|
|
2495
|
+
}
|
|
2496
|
+
return url;
|
|
2497
|
+
}
|
|
2498
|
+
function buildRemoteSessionsUrl(baseUrl, query = {}) {
|
|
2499
|
+
const searchParams = new URLSearchParams();
|
|
2500
|
+
searchParams.set("format", "chatlab");
|
|
2501
|
+
if (query.keyword?.trim()) searchParams.set("keyword", query.keyword.trim());
|
|
2502
|
+
if (query.limit && Number(query.limit) > 0) searchParams.set("limit", String(query.limit));
|
|
2503
|
+
if (query.cursor) searchParams.set("cursor", query.cursor);
|
|
2504
|
+
return `${baseUrl}/sessions?${searchParams.toString()}`;
|
|
2505
|
+
}
|
|
2506
|
+
function asRecord(value) {
|
|
2507
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2508
|
+
}
|
|
2509
|
+
function parseRemoteSessionsResponse(body) {
|
|
2510
|
+
const parsed = JSON.parse(body);
|
|
2511
|
+
let sessions;
|
|
2512
|
+
let pageSource;
|
|
2513
|
+
if (Array.isArray(parsed)) {
|
|
2514
|
+
sessions = parsed;
|
|
2515
|
+
} else {
|
|
2516
|
+
const record = asRecord(parsed);
|
|
2517
|
+
const data = asRecord(record?.data);
|
|
2518
|
+
const sessionsSource = record?.sessions ?? data?.sessions ?? record?.data;
|
|
2519
|
+
sessions = Array.isArray(sessionsSource) ? sessionsSource : [];
|
|
2520
|
+
pageSource = asRecord(record?.page) ?? asRecord(data?.page);
|
|
2521
|
+
}
|
|
2522
|
+
return {
|
|
2523
|
+
sessions,
|
|
2524
|
+
page: pageSource && typeof pageSource === "object" ? {
|
|
2525
|
+
hasMore: Boolean(pageSource.hasMore),
|
|
2526
|
+
nextCursor: typeof pageSource.nextCursor === "string" ? pageSource.nextCursor : void 0
|
|
2527
|
+
} : void 0
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
function registerAutomationRoutes(server2, ctx) {
|
|
2531
|
+
server2.get("/_web/automation/config", async (_request, reply) => {
|
|
2532
|
+
const automation = requireAutomation(ctx);
|
|
2533
|
+
if (!automation?.serverInfo) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2534
|
+
return {
|
|
2535
|
+
enabled: true,
|
|
2536
|
+
port: automation.serverInfo.port,
|
|
2537
|
+
token: automation.serverInfo.token,
|
|
2538
|
+
host: automation.serverInfo.host
|
|
2539
|
+
};
|
|
2540
|
+
});
|
|
2541
|
+
server2.get("/_web/automation/data-sources", async (_request, reply) => {
|
|
2542
|
+
const automation = requireAutomation(ctx);
|
|
2543
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2544
|
+
return automation.dsManager.loadAll();
|
|
2545
|
+
});
|
|
2546
|
+
server2.post("/_web/automation/data-sources", async (request, reply) => {
|
|
2547
|
+
const automation = requireAutomation(ctx);
|
|
2548
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2549
|
+
const ds = automation.dsManager.add(request.body);
|
|
2550
|
+
automation.reloadTimer?.(ds.id);
|
|
2551
|
+
return ds;
|
|
2552
|
+
});
|
|
2553
|
+
server2.patch("/_web/automation/data-sources/:id", async (request, reply) => {
|
|
2554
|
+
const automation = requireAutomation(ctx);
|
|
2555
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2556
|
+
const ds = automation.dsManager.update(request.params.id, request.body);
|
|
2557
|
+
if (!ds) return reply.code(404).send({ error: "Data source not found" });
|
|
2558
|
+
automation.reloadTimer?.(ds.id);
|
|
2559
|
+
return ds;
|
|
2560
|
+
});
|
|
2561
|
+
server2.delete("/_web/automation/data-sources/:id", async (request, reply) => {
|
|
2562
|
+
const automation = requireAutomation(ctx);
|
|
2563
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2564
|
+
automation.stopTimer?.(request.params.id);
|
|
2565
|
+
const ok = automation.dsManager.delete(request.params.id);
|
|
2566
|
+
if (!ok) return reply.code(404).send({ error: "Data source not found" });
|
|
2567
|
+
return { success: true };
|
|
2568
|
+
});
|
|
2569
|
+
server2.post("/_web/automation/data-sources/:id/sessions", async (request, reply) => {
|
|
2570
|
+
const automation = requireAutomation(ctx);
|
|
2571
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2572
|
+
const added = automation.dsManager.addSessions(request.params.id, request.body.sessions);
|
|
2573
|
+
if (added.length === 0 && !automation.dsManager.get(request.params.id)) {
|
|
2574
|
+
return reply.code(404).send({ error: "Data source not found" });
|
|
2575
|
+
}
|
|
2576
|
+
automation.reloadTimer?.(request.params.id, true);
|
|
2577
|
+
for (const sess of added) {
|
|
2578
|
+
void automation.pullEngine.triggerPull(request.params.id, sess.id).catch(() => void 0);
|
|
2579
|
+
}
|
|
2580
|
+
return added;
|
|
2581
|
+
});
|
|
2582
|
+
server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
|
|
2583
|
+
const automation = requireAutomation(ctx);
|
|
2584
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2585
|
+
const removed = automation.dsManager.removeSession(request.params.id, request.params.sessId);
|
|
2586
|
+
if (!removed) return reply.code(404).send({ error: "Session not found" });
|
|
2587
|
+
automation.reloadTimer?.(request.params.id);
|
|
2588
|
+
if (request.query.deleteData === "true" && removed.targetSessionId) {
|
|
2589
|
+
automation.deleteSessionData?.(removed.targetSessionId);
|
|
2590
|
+
}
|
|
2591
|
+
return { success: true };
|
|
2592
|
+
});
|
|
2593
|
+
server2.post(
|
|
2594
|
+
"/_web/automation/data-sources/:id/pull",
|
|
2595
|
+
async (request, reply) => {
|
|
2596
|
+
const automation = requireAutomation(ctx);
|
|
2597
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2598
|
+
return automation.pullEngine.triggerPull(request.params.id, request.body?.sessionId);
|
|
2599
|
+
}
|
|
2600
|
+
);
|
|
2601
|
+
server2.post("/_web/automation/data-sources/:id/pull-all", async (request, reply) => {
|
|
2602
|
+
const automation = requireAutomation(ctx);
|
|
2603
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2604
|
+
return automation.pullEngine.triggerPullAll(request.params.id);
|
|
2605
|
+
});
|
|
2606
|
+
server2.get("/_web/automation/sync-progress", async (_request, reply) => {
|
|
2607
|
+
const automation = requireAutomation(ctx);
|
|
2608
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2609
|
+
return automation.pullEngine.getProgress();
|
|
2610
|
+
});
|
|
2611
|
+
server2.get("/_web/automation/remote-sessions", async (request, reply) => {
|
|
2612
|
+
const automation = requireAutomation(ctx);
|
|
2613
|
+
if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
|
|
2614
|
+
const { baseUrl, token, keyword, limit, cursor } = request.query;
|
|
2615
|
+
if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
|
|
2616
|
+
const url = buildRemoteSessionsUrl(normalizeBaseUrl(baseUrl), { keyword, limit, cursor });
|
|
2617
|
+
const headers = { Accept: "application/json" };
|
|
2618
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2619
|
+
try {
|
|
2620
|
+
const resp = await fetch(url, { headers, signal: AbortSignal.timeout(3e4) });
|
|
2621
|
+
if (!resp.ok) throw new Error(`Remote server returned HTTP ${resp.status}`);
|
|
2622
|
+
const body = await resp.text();
|
|
2623
|
+
return parseRemoteSessionsResponse(body);
|
|
2624
|
+
} catch (err) {
|
|
2625
|
+
return reply.code(502).send({ error: err instanceof Error ? err.message : "Failed to fetch remote sessions" });
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2315
2630
|
// src/http/server.ts
|
|
2316
2631
|
var JSON_BODY_LIMIT = 50 * 1024 * 1024;
|
|
2317
2632
|
function createServer() {
|
|
@@ -2345,21 +2660,21 @@ function createServer() {
|
|
|
2345
2660
|
|
|
2346
2661
|
// src/http/routes/web/index.ts
|
|
2347
2662
|
import * as os3 from "os";
|
|
2348
|
-
import * as
|
|
2663
|
+
import * as path10 from "path";
|
|
2349
2664
|
|
|
2350
2665
|
// src/http/routes/web/import.ts
|
|
2351
|
-
import * as
|
|
2666
|
+
import * as fs7 from "fs";
|
|
2352
2667
|
import * as os2 from "os";
|
|
2353
|
-
import * as
|
|
2668
|
+
import * as path8 from "path";
|
|
2354
2669
|
import { randomUUID } from "crypto";
|
|
2355
2670
|
import { pipeline } from "stream/promises";
|
|
2356
2671
|
|
|
2357
2672
|
// src/http/routes/web/helpers.ts
|
|
2358
|
-
import * as
|
|
2673
|
+
import * as fs6 from "fs";
|
|
2359
2674
|
function resolveNativeBinding() {
|
|
2360
2675
|
if (process.versions.electron) return void 0;
|
|
2361
2676
|
const nativePath = resolveCliPath("native/better_sqlite3.node");
|
|
2362
|
-
if (
|
|
2677
|
+
if (fs6.existsSync(nativePath)) return nativePath;
|
|
2363
2678
|
return void 0;
|
|
2364
2679
|
}
|
|
2365
2680
|
|
|
@@ -2369,11 +2684,11 @@ var ARCHIVE_UPLOAD_LIMIT = 50 * 1024 * 1024 * 1024;
|
|
|
2369
2684
|
function cleanupTemp(...paths) {
|
|
2370
2685
|
for (const p of paths) {
|
|
2371
2686
|
try {
|
|
2372
|
-
const stat2 =
|
|
2687
|
+
const stat2 = fs7.statSync(p);
|
|
2373
2688
|
if (stat2.isDirectory()) {
|
|
2374
|
-
|
|
2689
|
+
fs7.rmSync(p, { recursive: true, force: true });
|
|
2375
2690
|
} else {
|
|
2376
|
-
|
|
2691
|
+
fs7.unlinkSync(p);
|
|
2377
2692
|
}
|
|
2378
2693
|
} catch {
|
|
2379
2694
|
}
|
|
@@ -2381,7 +2696,7 @@ function cleanupTemp(...paths) {
|
|
|
2381
2696
|
}
|
|
2382
2697
|
function registerImportRoutes2(server2, dbManager2, options = {}) {
|
|
2383
2698
|
const sourceManager = options.sourceManager ?? new ArchiveImportSourceManager({
|
|
2384
|
-
tempRoot:
|
|
2699
|
+
tempRoot: fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-import-sources-"))
|
|
2385
2700
|
});
|
|
2386
2701
|
const runPreparedImport = options.runPreparedImport ?? (async (manifestPath, onProgress) => {
|
|
2387
2702
|
const result = await streamImport(dbManager2, manifestPath, {
|
|
@@ -2405,9 +2720,9 @@ function registerImportRoutes2(server2, dbManager2, options = {}) {
|
|
|
2405
2720
|
limits: { fileSize: ARCHIVE_UPLOAD_LIMIT }
|
|
2406
2721
|
});
|
|
2407
2722
|
if (!data) return reply.code(400).send({ success: false, error: "error.no_file_selected" });
|
|
2408
|
-
const uploadPath =
|
|
2723
|
+
const uploadPath = path8.join(os2.tmpdir(), `chatlab-archive-${randomUUID()}.zip`);
|
|
2409
2724
|
try {
|
|
2410
|
-
await pipeline(data.file,
|
|
2725
|
+
await pipeline(data.file, fs7.createWriteStream(uploadPath, { flags: "wx" }));
|
|
2411
2726
|
if (data.file.truncated) {
|
|
2412
2727
|
cleanupTemp(uploadPath);
|
|
2413
2728
|
return reply.code(413).send({ success: false, error: "error.archive_limit_exceeded" });
|
|
@@ -2472,14 +2787,14 @@ data: ${JSON.stringify(eventData)}
|
|
|
2472
2787
|
server2.post("/_web/detect-format", async (request, reply) => {
|
|
2473
2788
|
const data = await request.file();
|
|
2474
2789
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
2475
|
-
const tmpDir =
|
|
2476
|
-
const tmpPath =
|
|
2790
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-detect-"));
|
|
2791
|
+
const tmpPath = path8.join(tmpDir, data.filename || "upload");
|
|
2477
2792
|
try {
|
|
2478
2793
|
const chunks = [];
|
|
2479
2794
|
for await (const chunk of data.file) {
|
|
2480
2795
|
chunks.push(chunk);
|
|
2481
2796
|
}
|
|
2482
|
-
|
|
2797
|
+
fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
2483
2798
|
const format = detectFormat(tmpPath);
|
|
2484
2799
|
const allFormats = detectAllFormats(tmpPath);
|
|
2485
2800
|
return { format, allFormats };
|
|
@@ -2490,14 +2805,14 @@ data: ${JSON.stringify(eventData)}
|
|
|
2490
2805
|
server2.post("/_web/scan-multi-chat", async (request, reply) => {
|
|
2491
2806
|
const data = await request.file();
|
|
2492
2807
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
2493
|
-
const tmpDir =
|
|
2494
|
-
const tmpPath =
|
|
2808
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-scan-"));
|
|
2809
|
+
const tmpPath = path8.join(tmpDir, data.filename || "upload");
|
|
2495
2810
|
try {
|
|
2496
2811
|
const chunks = [];
|
|
2497
2812
|
for await (const chunk of data.file) {
|
|
2498
2813
|
chunks.push(chunk);
|
|
2499
2814
|
}
|
|
2500
|
-
|
|
2815
|
+
fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
2501
2816
|
const chats = await scanMultiChatFile(tmpPath);
|
|
2502
2817
|
return { chats };
|
|
2503
2818
|
} finally {
|
|
@@ -2507,13 +2822,13 @@ data: ${JSON.stringify(eventData)}
|
|
|
2507
2822
|
server2.post("/_web/import", async (request, reply) => {
|
|
2508
2823
|
const data = await request.file();
|
|
2509
2824
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
2510
|
-
const tmpDir =
|
|
2511
|
-
const tmpPath =
|
|
2825
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-import-"));
|
|
2826
|
+
const tmpPath = path8.join(tmpDir, data.filename || "upload");
|
|
2512
2827
|
const chunks = [];
|
|
2513
2828
|
for await (const chunk of data.file) {
|
|
2514
2829
|
chunks.push(chunk);
|
|
2515
2830
|
}
|
|
2516
|
-
|
|
2831
|
+
fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
2517
2832
|
const formatId = data.fields?.formatId?.value;
|
|
2518
2833
|
const chatIndexStr = data.fields?.chatIndex?.value;
|
|
2519
2834
|
const chatIndex = chatIndexStr !== void 0 ? parseInt(chatIndexStr, 10) : void 0;
|
|
@@ -2556,7 +2871,7 @@ data: ${JSON.stringify(eventData)}
|
|
|
2556
2871
|
server2.post("/_web/import-directory", async (request, reply) => {
|
|
2557
2872
|
const parts = request.parts();
|
|
2558
2873
|
if (!parts) return reply.code(400).send({ error: "No files uploaded" });
|
|
2559
|
-
const tmpDir =
|
|
2874
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-dir-import-"));
|
|
2560
2875
|
const relativePaths = [];
|
|
2561
2876
|
const fileBuffers = [];
|
|
2562
2877
|
try {
|
|
@@ -2584,10 +2899,10 @@ data: ${JSON.stringify(eventData)}
|
|
|
2584
2899
|
if (segments.length > 1) {
|
|
2585
2900
|
relPath = segments.slice(1).join("/");
|
|
2586
2901
|
}
|
|
2587
|
-
const targetPath =
|
|
2588
|
-
if (!targetPath.startsWith(tmpDir +
|
|
2589
|
-
|
|
2590
|
-
|
|
2902
|
+
const targetPath = path8.resolve(tmpDir, relPath);
|
|
2903
|
+
if (!targetPath.startsWith(tmpDir + path8.sep)) continue;
|
|
2904
|
+
fs7.mkdirSync(path8.dirname(targetPath), { recursive: true });
|
|
2905
|
+
fs7.writeFileSync(targetPath, fileBuffers[i].data);
|
|
2591
2906
|
}
|
|
2592
2907
|
const entryPath = findEntryFileInDirectory(tmpDir);
|
|
2593
2908
|
if (!entryPath) {
|
|
@@ -2632,13 +2947,13 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
|
|
|
2632
2947
|
const sessionId = request.params.id;
|
|
2633
2948
|
const data = await request.file();
|
|
2634
2949
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
2635
|
-
const tmpDir =
|
|
2636
|
-
const tmpPath =
|
|
2950
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-inc-"));
|
|
2951
|
+
const tmpPath = path8.join(tmpDir, data.filename || "upload");
|
|
2637
2952
|
const chunks = [];
|
|
2638
2953
|
for await (const chunk of data.file) {
|
|
2639
2954
|
chunks.push(chunk);
|
|
2640
2955
|
}
|
|
2641
|
-
|
|
2956
|
+
fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
2642
2957
|
reply.raw.writeHead(200, {
|
|
2643
2958
|
"Content-Type": "text/event-stream",
|
|
2644
2959
|
"Cache-Control": "no-cache",
|
|
@@ -2670,13 +2985,13 @@ data: ${JSON.stringify(eventData)}
|
|
|
2670
2985
|
const sessionId = request.params.id;
|
|
2671
2986
|
const data = await request.file();
|
|
2672
2987
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
2673
|
-
const tmpDir =
|
|
2674
|
-
const tmpPath =
|
|
2988
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-analyze-"));
|
|
2989
|
+
const tmpPath = path8.join(tmpDir, data.filename || "upload");
|
|
2675
2990
|
const chunks = [];
|
|
2676
2991
|
for await (const chunk of data.file) {
|
|
2677
2992
|
chunks.push(chunk);
|
|
2678
2993
|
}
|
|
2679
|
-
|
|
2994
|
+
fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
2680
2995
|
try {
|
|
2681
2996
|
return await analyzeIncrementalImport(dbManager2, sessionId, tmpPath);
|
|
2682
2997
|
} catch (err) {
|
|
@@ -2688,13 +3003,13 @@ data: ${JSON.stringify(eventData)}
|
|
|
2688
3003
|
server2.post("/_web/import/analyze", async (request, reply) => {
|
|
2689
3004
|
const data = await request.file();
|
|
2690
3005
|
if (!data) return reply.code(400).send({ error: "No file uploaded" });
|
|
2691
|
-
const tmpDir =
|
|
2692
|
-
const tmpPath =
|
|
3006
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-analyze-"));
|
|
3007
|
+
const tmpPath = path8.join(tmpDir, data.filename || "upload");
|
|
2693
3008
|
const chunks = [];
|
|
2694
3009
|
for await (const chunk of data.file) {
|
|
2695
3010
|
chunks.push(chunk);
|
|
2696
3011
|
}
|
|
2697
|
-
|
|
3012
|
+
fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
|
|
2698
3013
|
try {
|
|
2699
3014
|
return await analyzeNewImport(tmpPath);
|
|
2700
3015
|
} catch (err) {
|
|
@@ -2724,17 +3039,17 @@ data: ${JSON.stringify(eventData)}
|
|
|
2724
3039
|
|
|
2725
3040
|
`);
|
|
2726
3041
|
}
|
|
2727
|
-
const tmpDir =
|
|
3042
|
+
const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-demo-"));
|
|
2728
3043
|
try {
|
|
2729
3044
|
const localPaths = [];
|
|
2730
3045
|
for (let i = 0; i < total; i++) {
|
|
2731
3046
|
sendEvent("progress", { stage: "downloading", current: i + 1, total });
|
|
2732
|
-
const localPath =
|
|
3047
|
+
const localPath = path8.join(tmpDir, DEMO_FILES[i]);
|
|
2733
3048
|
const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
|
|
2734
3049
|
signal: AbortSignal.timeout(6e4)
|
|
2735
3050
|
});
|
|
2736
3051
|
if (!resp.ok) throw new Error(`Download demo failed (${DEMO_FILES[i]}): ${resp.status}`);
|
|
2737
|
-
|
|
3052
|
+
fs7.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
|
|
2738
3053
|
localPaths.push(localPath);
|
|
2739
3054
|
}
|
|
2740
3055
|
sendEvent("progress", { stage: "importing", current: 1, total });
|
|
@@ -2765,7 +3080,7 @@ data: ${JSON.stringify(eventData)}
|
|
|
2765
3080
|
}
|
|
2766
3081
|
|
|
2767
3082
|
// src/http/routes/web/cache.ts
|
|
2768
|
-
import * as
|
|
3083
|
+
import * as path9 from "path";
|
|
2769
3084
|
import { execFile } from "child_process";
|
|
2770
3085
|
function runExecFile(file, args, runner = execFile) {
|
|
2771
3086
|
return new Promise((resolve4, reject) => {
|
|
@@ -2793,7 +3108,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
|
|
|
2793
3108
|
} else if (platform === "win32") {
|
|
2794
3109
|
await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
|
|
2795
3110
|
} else {
|
|
2796
|
-
await runExecFile("xdg-open", [
|
|
3111
|
+
await runExecFile("xdg-open", [path9.dirname(filePath)], runner);
|
|
2797
3112
|
}
|
|
2798
3113
|
}
|
|
2799
3114
|
|
|
@@ -2816,27 +3131,27 @@ function registerWebRoutes(server2, dbManager2, options) {
|
|
|
2816
3131
|
const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
|
|
2817
3132
|
mergeCache?.cleanupOrphans();
|
|
2818
3133
|
const fallbackPathProvider = {
|
|
2819
|
-
getSystemDir: () =>
|
|
2820
|
-
getUserDataDir: () =>
|
|
2821
|
-
getDatabaseDir: () =>
|
|
2822
|
-
getVectorDir: () =>
|
|
2823
|
-
getAiDataDir: () =>
|
|
2824
|
-
getSettingsDir: () =>
|
|
2825
|
-
getCacheDir: () =>
|
|
2826
|
-
getTempDir: () =>
|
|
2827
|
-
getLogsDir: () =>
|
|
2828
|
-
getDownloadsDir: () =>
|
|
3134
|
+
getSystemDir: () => path10.join(os3.homedir(), ".chatlab"),
|
|
3135
|
+
getUserDataDir: () => path10.join(os3.homedir(), ".chatlab", "data"),
|
|
3136
|
+
getDatabaseDir: () => path10.join(os3.homedir(), ".chatlab", "data", "databases"),
|
|
3137
|
+
getVectorDir: () => path10.join(os3.homedir(), ".chatlab", "data", "vector"),
|
|
3138
|
+
getAiDataDir: () => path10.join(os3.homedir(), ".chatlab", "ai"),
|
|
3139
|
+
getSettingsDir: () => path10.join(os3.homedir(), ".chatlab", "settings"),
|
|
3140
|
+
getCacheDir: () => path10.join(os3.homedir(), ".chatlab", "cache"),
|
|
3141
|
+
getTempDir: () => path10.join(os3.homedir(), ".chatlab", "temp"),
|
|
3142
|
+
getLogsDir: () => path10.join(os3.homedir(), ".chatlab", "logs"),
|
|
3143
|
+
getDownloadsDir: () => path10.join(os3.homedir(), "Downloads")
|
|
2829
3144
|
};
|
|
2830
3145
|
const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
|
|
2831
3146
|
const ai = options?.aiContext;
|
|
2832
3147
|
const cliStreamImport = async (dm, filePath) => {
|
|
2833
|
-
const { streamImport: streamImport2 } = await import("./stream-import-
|
|
3148
|
+
const { streamImport: streamImport2 } = await import("./stream-import-3JMXQD7P.mjs");
|
|
2834
3149
|
const result = await streamImport2(dm, filePath);
|
|
2835
3150
|
if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
|
|
2836
3151
|
return { sessionId: result.sessionId };
|
|
2837
3152
|
};
|
|
2838
3153
|
const defaultUserDataDir = getDefaultNodeUserDataDir();
|
|
2839
|
-
const isCustom =
|
|
3154
|
+
const isCustom = path10.resolve(resolvedPathProvider.getUserDataDir()) !== path10.resolve(defaultUserDataDir);
|
|
2840
3155
|
const semanticIndexService = options?.semanticIndexService;
|
|
2841
3156
|
const analyticsService = "A-US-0629338532" ? new AnalyticsService(resolvedPathProvider.getSystemDir(), "A-US-0629338532", getVersion()) : void 0;
|
|
2842
3157
|
registerSharedRoutes(
|
|
@@ -2845,10 +3160,12 @@ function registerWebRoutes(server2, dbManager2, options) {
|
|
|
2845
3160
|
dbManager: dbManager2,
|
|
2846
3161
|
sessionAdapter: adapter,
|
|
2847
3162
|
pathProvider: resolvedPathProvider,
|
|
3163
|
+
runtimeIdentity: options?.runtimeIdentity,
|
|
2848
3164
|
getVersion,
|
|
2849
3165
|
nativeBinding: options?.nativeBinding,
|
|
2850
3166
|
semanticIndexService,
|
|
2851
3167
|
analyticsService,
|
|
3168
|
+
getCurrentAiLogPath: () => getServerAiLogger()?.getExistingLogPath() ?? null,
|
|
2852
3169
|
openDirectory: openDirectoryPath,
|
|
2853
3170
|
showInFolder: showPathInFolder,
|
|
2854
3171
|
downloadsDir: resolvedPathProvider.getDownloadsDir(),
|
|
@@ -2931,31 +3248,31 @@ function registerProxyRoutes(server2) {
|
|
|
2931
3248
|
}
|
|
2932
3249
|
|
|
2933
3250
|
// src/ai/manager-factory.ts
|
|
2934
|
-
import * as
|
|
2935
|
-
import * as
|
|
3251
|
+
import * as fs8 from "fs";
|
|
3252
|
+
import * as path11 from "path";
|
|
2936
3253
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
2937
3254
|
var nodeFs = {
|
|
2938
3255
|
ensureDir(dir) {
|
|
2939
|
-
if (!
|
|
3256
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
2940
3257
|
},
|
|
2941
3258
|
listFiles(dir, ext) {
|
|
2942
|
-
if (!
|
|
2943
|
-
return
|
|
3259
|
+
if (!fs8.existsSync(dir)) return [];
|
|
3260
|
+
return fs8.readdirSync(dir).filter((f) => f.endsWith(ext));
|
|
2944
3261
|
},
|
|
2945
3262
|
readFile(filePath) {
|
|
2946
|
-
return
|
|
3263
|
+
return fs8.readFileSync(filePath, "utf-8");
|
|
2947
3264
|
},
|
|
2948
3265
|
writeFile(filePath, content) {
|
|
2949
|
-
|
|
3266
|
+
fs8.writeFileSync(filePath, content, "utf-8");
|
|
2950
3267
|
},
|
|
2951
3268
|
deleteFile(filePath) {
|
|
2952
|
-
if (
|
|
3269
|
+
if (fs8.existsSync(filePath)) fs8.unlinkSync(filePath);
|
|
2953
3270
|
},
|
|
2954
3271
|
fileExists(filePath) {
|
|
2955
|
-
return
|
|
3272
|
+
return fs8.existsSync(filePath);
|
|
2956
3273
|
},
|
|
2957
3274
|
joinPath(...parts) {
|
|
2958
|
-
return
|
|
3275
|
+
return path11.join(...parts);
|
|
2959
3276
|
}
|
|
2960
3277
|
};
|
|
2961
3278
|
var managerCache = /* @__PURE__ */ new Map();
|
|
@@ -2965,13 +3282,13 @@ function getAssistantManager(aiDataDir) {
|
|
|
2965
3282
|
cached = {
|
|
2966
3283
|
assistant: new AssistantManager({
|
|
2967
3284
|
fs: nodeFs,
|
|
2968
|
-
assistantsDir:
|
|
3285
|
+
assistantsDir: path11.join(aiDataDir, "assistants"),
|
|
2969
3286
|
builtinRawConfigs: [],
|
|
2970
3287
|
generateId: () => `custom_${randomUUID2().replace(/-/g, "").slice(0, 12)}`
|
|
2971
3288
|
}),
|
|
2972
3289
|
skill: new SkillManagerCore({
|
|
2973
3290
|
fs: nodeFs,
|
|
2974
|
-
skillsDir:
|
|
3291
|
+
skillsDir: path11.join(aiDataDir, "skills"),
|
|
2975
3292
|
builtinRawSkills: [],
|
|
2976
3293
|
contentHash: (content) => createHash("md5").update(content).digest("hex")
|
|
2977
3294
|
})
|
|
@@ -2995,14 +3312,14 @@ var noop = () => {
|
|
|
2995
3312
|
var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
|
|
2996
3313
|
|
|
2997
3314
|
// ../../packages/sync/src/data-source-manager.ts
|
|
2998
|
-
import * as
|
|
2999
|
-
import * as
|
|
3315
|
+
import * as fs9 from "fs";
|
|
3316
|
+
import * as path12 from "path";
|
|
3000
3317
|
import * as crypto from "crypto";
|
|
3001
3318
|
var CONFIG_FILE = "data-sources.json";
|
|
3002
3319
|
function generateId(prefix = "ds") {
|
|
3003
3320
|
return `${prefix}_${crypto.randomBytes(6).toString("hex")}`;
|
|
3004
3321
|
}
|
|
3005
|
-
function
|
|
3322
|
+
function normalizeBaseUrl2(input) {
|
|
3006
3323
|
let url = input.trim().replace(/\/+$/, "");
|
|
3007
3324
|
if (url && !/^https?:\/\//i.test(url)) {
|
|
3008
3325
|
url = `http://${url}`;
|
|
@@ -3024,17 +3341,17 @@ var DataSourceManager = class {
|
|
|
3024
3341
|
this.logger = logger ?? NOOP_LOGGER;
|
|
3025
3342
|
}
|
|
3026
3343
|
getConfigPath() {
|
|
3027
|
-
return
|
|
3344
|
+
return path12.join(this.configDir, CONFIG_FILE);
|
|
3028
3345
|
}
|
|
3029
3346
|
ensureDir() {
|
|
3030
|
-
|
|
3347
|
+
fs9.mkdirSync(this.configDir, { recursive: true });
|
|
3031
3348
|
}
|
|
3032
3349
|
// ==================== Load / Save ====================
|
|
3033
3350
|
loadAll() {
|
|
3034
3351
|
try {
|
|
3035
3352
|
const filePath = this.getConfigPath();
|
|
3036
|
-
if (
|
|
3037
|
-
const raw =
|
|
3353
|
+
if (fs9.existsSync(filePath)) {
|
|
3354
|
+
const raw = fs9.readFileSync(filePath, "utf-8");
|
|
3038
3355
|
const parsed = JSON.parse(raw);
|
|
3039
3356
|
if (!isValidDataSourceArray(parsed)) {
|
|
3040
3357
|
this.logger.warn("[DataSource] Incompatible config format detected, returning empty.");
|
|
@@ -3053,7 +3370,7 @@ var DataSourceManager = class {
|
|
|
3053
3370
|
saveAll(sources) {
|
|
3054
3371
|
try {
|
|
3055
3372
|
this.ensureDir();
|
|
3056
|
-
|
|
3373
|
+
fs9.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
|
|
3057
3374
|
} catch (err) {
|
|
3058
3375
|
this.logger.error("[DataSource] Failed to save config", err);
|
|
3059
3376
|
}
|
|
@@ -3067,7 +3384,7 @@ var DataSourceManager = class {
|
|
|
3067
3384
|
const ds = {
|
|
3068
3385
|
id: generateId("src"),
|
|
3069
3386
|
name: partial.name || "",
|
|
3070
|
-
baseUrl:
|
|
3387
|
+
baseUrl: normalizeBaseUrl2(partial.baseUrl),
|
|
3071
3388
|
token: partial.token,
|
|
3072
3389
|
intervalMinutes: partial.intervalMinutes,
|
|
3073
3390
|
pullLimit: partial.pullLimit || 1e3,
|
|
@@ -3085,7 +3402,7 @@ var DataSourceManager = class {
|
|
|
3085
3402
|
if (idx === -1) return null;
|
|
3086
3403
|
const ds = sources[idx];
|
|
3087
3404
|
if (updates.name !== void 0) ds.name = updates.name;
|
|
3088
|
-
if (updates.baseUrl !== void 0) ds.baseUrl =
|
|
3405
|
+
if (updates.baseUrl !== void 0) ds.baseUrl = normalizeBaseUrl2(updates.baseUrl);
|
|
3089
3406
|
if (updates.token !== void 0) ds.token = updates.token;
|
|
3090
3407
|
if (updates.intervalMinutes !== void 0) ds.intervalMinutes = updates.intervalMinutes;
|
|
3091
3408
|
if (updates.pullLimit !== void 0) ds.pullLimit = updates.pullLimit;
|
|
@@ -3146,42 +3463,13 @@ var DataSourceManager = class {
|
|
|
3146
3463
|
}
|
|
3147
3464
|
};
|
|
3148
3465
|
|
|
3149
|
-
// ../../packages/sync/src/discovery.ts
|
|
3150
|
-
function buildRemoteSessionsUrl(baseUrl, query = {}) {
|
|
3151
|
-
const searchParams = new URLSearchParams();
|
|
3152
|
-
searchParams.set("format", "chatlab");
|
|
3153
|
-
if (query.keyword?.trim()) searchParams.set("keyword", query.keyword.trim());
|
|
3154
|
-
if (query.limit && query.limit > 0) searchParams.set("limit", String(query.limit));
|
|
3155
|
-
if (query.cursor) searchParams.set("cursor", query.cursor);
|
|
3156
|
-
return `${baseUrl}/sessions?${searchParams.toString()}`;
|
|
3157
|
-
}
|
|
3158
|
-
function parseRemoteSessionsResponse(body) {
|
|
3159
|
-
const parsed = JSON.parse(body);
|
|
3160
|
-
let sessions;
|
|
3161
|
-
let pageSource;
|
|
3162
|
-
if (Array.isArray(parsed)) {
|
|
3163
|
-
sessions = parsed;
|
|
3164
|
-
} else if (parsed && typeof parsed === "object") {
|
|
3165
|
-
sessions = parsed.sessions ?? parsed.data?.sessions ?? parsed.data ?? [];
|
|
3166
|
-
if (!Array.isArray(sessions)) sessions = [];
|
|
3167
|
-
pageSource = parsed.page ?? parsed.data?.page;
|
|
3168
|
-
} else {
|
|
3169
|
-
sessions = [];
|
|
3170
|
-
}
|
|
3171
|
-
return {
|
|
3172
|
-
sessions,
|
|
3173
|
-
page: pageSource && typeof pageSource === "object" ? {
|
|
3174
|
-
hasMore: Boolean(pageSource.hasMore),
|
|
3175
|
-
nextCursor: typeof pageSource.nextCursor === "string" ? pageSource.nextCursor : void 0
|
|
3176
|
-
} : void 0
|
|
3177
|
-
};
|
|
3178
|
-
}
|
|
3179
|
-
|
|
3180
3466
|
// ../../packages/sync/src/pull-engine.ts
|
|
3181
|
-
import * as
|
|
3467
|
+
import * as fs10 from "fs";
|
|
3182
3468
|
import * as crypto2 from "crypto";
|
|
3183
3469
|
var MAX_PAGES_PER_PULL = 5e3;
|
|
3184
3470
|
var PULL_OVERLAP_SECONDS = 60;
|
|
3471
|
+
var SOURCE_UNAVAILABLE_LOG_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
3472
|
+
var SOURCE_UNAVAILABLE_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOTFOUND", "ECONNRESET", "ETIMEDOUT", "ABORT_ERR"]);
|
|
3185
3473
|
function buildPullUrl(baseUrl, remoteSessionId, params) {
|
|
3186
3474
|
const base = `${baseUrl}/sessions/${remoteSessionId}/messages`;
|
|
3187
3475
|
const qs = ["format=chatlab"];
|
|
@@ -3198,7 +3486,7 @@ function parseSyncFromFile(filePath) {
|
|
|
3198
3486
|
try {
|
|
3199
3487
|
const isJsonl = filePath.endsWith(".jsonl");
|
|
3200
3488
|
if (isJsonl) {
|
|
3201
|
-
const content =
|
|
3489
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3202
3490
|
const lines = content.trimEnd().split("\n");
|
|
3203
3491
|
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
|
|
3204
3492
|
try {
|
|
@@ -3217,7 +3505,7 @@ function parseSyncFromFile(filePath) {
|
|
|
3217
3505
|
}
|
|
3218
3506
|
return null;
|
|
3219
3507
|
}
|
|
3220
|
-
const raw =
|
|
3508
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
3221
3509
|
const parsed = JSON.parse(raw);
|
|
3222
3510
|
if (parsed.sync && typeof parsed.sync === "object") {
|
|
3223
3511
|
const s = parsed.sync;
|
|
@@ -3231,7 +3519,7 @@ function parseSyncFromFile(filePath) {
|
|
|
3231
3519
|
function fileContainsMessages(filePath) {
|
|
3232
3520
|
try {
|
|
3233
3521
|
if (filePath.endsWith(".jsonl")) {
|
|
3234
|
-
const content =
|
|
3522
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3235
3523
|
for (const line of content.split("\n")) {
|
|
3236
3524
|
const trimmed = line.trim();
|
|
3237
3525
|
if (!trimmed) continue;
|
|
@@ -3244,7 +3532,7 @@ function fileContainsMessages(filePath) {
|
|
|
3244
3532
|
}
|
|
3245
3533
|
return false;
|
|
3246
3534
|
}
|
|
3247
|
-
const raw =
|
|
3535
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
3248
3536
|
const parsed = JSON.parse(raw);
|
|
3249
3537
|
return Array.isArray(parsed.messages) && parsed.messages.length > 0;
|
|
3250
3538
|
} catch {
|
|
@@ -3261,7 +3549,7 @@ function getMaxMessageTimestampFromFile(filePath) {
|
|
|
3261
3549
|
}
|
|
3262
3550
|
};
|
|
3263
3551
|
if (filePath.endsWith(".jsonl")) {
|
|
3264
|
-
const content =
|
|
3552
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
3265
3553
|
for (const line of content.split("\n")) {
|
|
3266
3554
|
const trimmed = line.trim();
|
|
3267
3555
|
if (!trimmed) continue;
|
|
@@ -3274,7 +3562,7 @@ function getMaxMessageTimestampFromFile(filePath) {
|
|
|
3274
3562
|
}
|
|
3275
3563
|
return maxTs;
|
|
3276
3564
|
}
|
|
3277
|
-
const raw =
|
|
3565
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
3278
3566
|
const parsed = JSON.parse(raw);
|
|
3279
3567
|
if (Array.isArray(parsed.messages)) {
|
|
3280
3568
|
for (const message of parsed.messages) {
|
|
@@ -3288,10 +3576,35 @@ function getMaxMessageTimestampFromFile(filePath) {
|
|
|
3288
3576
|
}
|
|
3289
3577
|
function cleanupTempFile(filePath) {
|
|
3290
3578
|
try {
|
|
3291
|
-
if (
|
|
3579
|
+
if (fs10.existsSync(filePath)) fs10.unlinkSync(filePath);
|
|
3292
3580
|
} catch {
|
|
3293
3581
|
}
|
|
3294
3582
|
}
|
|
3583
|
+
function getErrorProp(error, prop) {
|
|
3584
|
+
return error && typeof error === "object" ? error[prop] : void 0;
|
|
3585
|
+
}
|
|
3586
|
+
function getErrorMessage(error) {
|
|
3587
|
+
return error instanceof Error ? error.message : String(error || "Pull failed");
|
|
3588
|
+
}
|
|
3589
|
+
function describeSourceUnavailableError(error) {
|
|
3590
|
+
let current = error;
|
|
3591
|
+
while (current && typeof current === "object") {
|
|
3592
|
+
const code = getErrorProp(current, "code");
|
|
3593
|
+
const name = getErrorProp(current, "name");
|
|
3594
|
+
if (typeof code === "string" && SOURCE_UNAVAILABLE_ERROR_CODES.has(code)) {
|
|
3595
|
+
const address = getErrorProp(current, "address");
|
|
3596
|
+
const port = getErrorProp(current, "port");
|
|
3597
|
+
if (typeof address === "string" && (typeof port === "number" || typeof port === "string")) {
|
|
3598
|
+
return `${code} ${address}:${port}`;
|
|
3599
|
+
}
|
|
3600
|
+
const host = getErrorProp(current, "hostname") ?? getErrorProp(current, "host");
|
|
3601
|
+
return typeof host === "string" ? `${code} ${host}` : code;
|
|
3602
|
+
}
|
|
3603
|
+
if (name === "AbortError") return "Request timeout";
|
|
3604
|
+
current = getErrorProp(current, "cause");
|
|
3605
|
+
}
|
|
3606
|
+
return null;
|
|
3607
|
+
}
|
|
3295
3608
|
var PullEngine = class {
|
|
3296
3609
|
fetcher;
|
|
3297
3610
|
importer;
|
|
@@ -3302,6 +3615,7 @@ var PullEngine = class {
|
|
|
3302
3615
|
onSessionImported;
|
|
3303
3616
|
pullingSourceIds = /* @__PURE__ */ new Set();
|
|
3304
3617
|
progressMap = /* @__PURE__ */ new Map();
|
|
3618
|
+
sourceUnavailableLogs = /* @__PURE__ */ new Map();
|
|
3305
3619
|
constructor(options) {
|
|
3306
3620
|
this.fetcher = options.fetcher;
|
|
3307
3621
|
this.importer = options.importer;
|
|
@@ -3337,7 +3651,6 @@ var PullEngine = class {
|
|
|
3337
3651
|
this.logger.info(`[Pull] Skipping "${sess.name}": import in progress`);
|
|
3338
3652
|
return { success: false, newMessageCount: 0, error: "Import in progress" };
|
|
3339
3653
|
}
|
|
3340
|
-
this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
|
|
3341
3654
|
let totalNewMessages = 0;
|
|
3342
3655
|
let since = sess.lastPullAt;
|
|
3343
3656
|
let offset;
|
|
@@ -3354,7 +3667,7 @@ var PullEngine = class {
|
|
|
3354
3667
|
limit: ds.pullLimit
|
|
3355
3668
|
});
|
|
3356
3669
|
try {
|
|
3357
|
-
const stat2 =
|
|
3670
|
+
const stat2 = fs10.statSync(tempFile);
|
|
3358
3671
|
this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
|
|
3359
3672
|
const sync0 = parseSyncFromFile(tempFile);
|
|
3360
3673
|
if (stat2.size < 1024) {
|
|
@@ -3374,7 +3687,7 @@ var PullEngine = class {
|
|
|
3374
3687
|
offset,
|
|
3375
3688
|
limit: ds.pullLimit
|
|
3376
3689
|
});
|
|
3377
|
-
const retryStat =
|
|
3690
|
+
const retryStat = fs10.statSync(retryFile);
|
|
3378
3691
|
this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
|
|
3379
3692
|
const retrySync = parseSyncFromFile(retryFile);
|
|
3380
3693
|
if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
|
|
@@ -3545,8 +3858,11 @@ var PullEngine = class {
|
|
|
3545
3858
|
}
|
|
3546
3859
|
return { success: true, newMessageCount: totalNewMessages };
|
|
3547
3860
|
} catch (error) {
|
|
3548
|
-
const
|
|
3549
|
-
|
|
3861
|
+
const sourceUnavailable = describeSourceUnavailableError(error);
|
|
3862
|
+
const errMsg = sourceUnavailable ?? getErrorMessage(error);
|
|
3863
|
+
if (!sourceUnavailable) {
|
|
3864
|
+
this.logger.error(`[Pull] Pull failed for "${sess.name}": ${errMsg}`);
|
|
3865
|
+
}
|
|
3550
3866
|
this.dsManager.updateSession(sourceId, sess.id, {
|
|
3551
3867
|
lastPullAt: Math.floor(Date.now() / 1e3),
|
|
3552
3868
|
lastStatus: "error",
|
|
@@ -3554,7 +3870,7 @@ var PullEngine = class {
|
|
|
3554
3870
|
});
|
|
3555
3871
|
this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
|
|
3556
3872
|
this.markProgressDone(sess.id);
|
|
3557
|
-
return { success: false, newMessageCount: 0, error: errMsg };
|
|
3873
|
+
return { success: false, newMessageCount: 0, error: errMsg, sourceUnavailable: !!sourceUnavailable };
|
|
3558
3874
|
}
|
|
3559
3875
|
}
|
|
3560
3876
|
markProgressDone(sessionId) {
|
|
@@ -3571,13 +3887,43 @@ var PullEngine = class {
|
|
|
3571
3887
|
}
|
|
3572
3888
|
this.pullingSourceIds.add(ds.id);
|
|
3573
3889
|
try {
|
|
3574
|
-
|
|
3575
|
-
|
|
3890
|
+
let sourceUnavailable = false;
|
|
3891
|
+
for (let index = 0; index < ds.sessions.length; index++) {
|
|
3892
|
+
const sess = ds.sessions[index];
|
|
3893
|
+
const result = await this.executePullSession(ds.id, ds, sess);
|
|
3894
|
+
if (result.sourceUnavailable && result.error) {
|
|
3895
|
+
sourceUnavailable = true;
|
|
3896
|
+
this.markRemainingSessionsSourceUnavailable(ds, index + 1, result.error);
|
|
3897
|
+
this.logSourceUnavailable(ds, result.error, ds.sessions.length - index);
|
|
3898
|
+
break;
|
|
3899
|
+
}
|
|
3576
3900
|
}
|
|
3901
|
+
if (!sourceUnavailable) this.sourceUnavailableLogs.delete(ds.id);
|
|
3577
3902
|
} finally {
|
|
3578
3903
|
this.pullingSourceIds.delete(ds.id);
|
|
3579
3904
|
}
|
|
3580
3905
|
}
|
|
3906
|
+
markRemainingSessionsSourceUnavailable(ds, startIndex, detail) {
|
|
3907
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3908
|
+
for (const sess of ds.sessions.slice(startIndex)) {
|
|
3909
|
+
this.dsManager.updateSession(ds.id, sess.id, {
|
|
3910
|
+
lastPullAt: now,
|
|
3911
|
+
lastStatus: "error",
|
|
3912
|
+
lastError: detail
|
|
3913
|
+
});
|
|
3914
|
+
this.notifier.onPullResult(ds.id, sess.id, "error", detail);
|
|
3915
|
+
this.markProgressDone(sess.id);
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
logSourceUnavailable(ds, detail, affectedSessions) {
|
|
3919
|
+
const now = Date.now();
|
|
3920
|
+
const previous = this.sourceUnavailableLogs.get(ds.id);
|
|
3921
|
+
if (previous && previous.detail === detail && now - previous.lastLoggedAt < SOURCE_UNAVAILABLE_LOG_COOLDOWN_MS) {
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
this.sourceUnavailableLogs.set(ds.id, { detail, lastLoggedAt: now });
|
|
3925
|
+
this.logger.warn(`[Pull] Source ${ds.baseUrl} unavailable: ${detail}, skipped ${affectedSessions} sessions`);
|
|
3926
|
+
}
|
|
3581
3927
|
async triggerPull(sourceId, sessionId) {
|
|
3582
3928
|
const ds = this.dsManager.get(sourceId);
|
|
3583
3929
|
if (!ds) return { success: false, error: "Data source not found" };
|
|
@@ -3585,6 +3931,8 @@ var PullEngine = class {
|
|
|
3585
3931
|
const sess = ds.sessions.find((s) => s.id === sessionId);
|
|
3586
3932
|
if (!sess) return { success: false, error: "Session not found" };
|
|
3587
3933
|
const result = await this.executePullSession(sourceId, ds, sess);
|
|
3934
|
+
if (result.sourceUnavailable && result.error) this.logSourceUnavailable(ds, result.error, 1);
|
|
3935
|
+
if (result.success) this.sourceUnavailableLogs.delete(ds.id);
|
|
3588
3936
|
return { success: result.success, error: result.error };
|
|
3589
3937
|
}
|
|
3590
3938
|
if (this.pullingSourceIds.has(sourceId)) {
|
|
@@ -3594,10 +3942,19 @@ var PullEngine = class {
|
|
|
3594
3942
|
this.pullingSourceIds.add(sourceId);
|
|
3595
3943
|
try {
|
|
3596
3944
|
const errors = [];
|
|
3597
|
-
|
|
3945
|
+
let sourceUnavailable = false;
|
|
3946
|
+
for (let index = 0; index < ds.sessions.length; index++) {
|
|
3947
|
+
const sess = ds.sessions[index];
|
|
3598
3948
|
const result = await this.executePullSession(sourceId, ds, sess);
|
|
3599
3949
|
if (!result.success && result.error) errors.push(`${sess.name}: ${result.error}`);
|
|
3950
|
+
if (result.sourceUnavailable && result.error) {
|
|
3951
|
+
sourceUnavailable = true;
|
|
3952
|
+
this.markRemainingSessionsSourceUnavailable(ds, index + 1, result.error);
|
|
3953
|
+
this.logSourceUnavailable(ds, result.error, ds.sessions.length - index);
|
|
3954
|
+
break;
|
|
3955
|
+
}
|
|
3600
3956
|
}
|
|
3957
|
+
if (!sourceUnavailable) this.sourceUnavailableLogs.delete(ds.id);
|
|
3601
3958
|
if (errors.length > 0) {
|
|
3602
3959
|
return { success: false, error: errors.join("; ") };
|
|
3603
3960
|
}
|
|
@@ -3678,13 +4035,13 @@ function reloadTimer(dsId, skipInitialPull = false) {
|
|
|
3678
4035
|
}
|
|
3679
4036
|
|
|
3680
4037
|
// src/sync/adapters.ts
|
|
3681
|
-
import * as
|
|
4038
|
+
import * as fs11 from "fs";
|
|
3682
4039
|
import * as os4 from "os";
|
|
3683
|
-
import * as
|
|
4040
|
+
import * as path13 from "path";
|
|
3684
4041
|
import * as crypto3 from "crypto";
|
|
3685
4042
|
function getTempFilePath(ext) {
|
|
3686
4043
|
const id = crypto3.randomBytes(8).toString("hex");
|
|
3687
|
-
return
|
|
4044
|
+
return path13.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
|
|
3688
4045
|
}
|
|
3689
4046
|
var NodeFetcher = class {
|
|
3690
4047
|
async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
|
|
@@ -3701,7 +4058,7 @@ var NodeFetcher = class {
|
|
|
3701
4058
|
const isJsonl = contentType.includes("ndjson") || contentType.includes("jsonl");
|
|
3702
4059
|
const tempFile = getTempFilePath(isJsonl ? ".jsonl" : ".json");
|
|
3703
4060
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
3704
|
-
|
|
4061
|
+
fs11.writeFileSync(tempFile, buffer);
|
|
3705
4062
|
return tempFile;
|
|
3706
4063
|
}
|
|
3707
4064
|
};
|
|
@@ -3714,7 +4071,7 @@ var DirectImporter = class {
|
|
|
3714
4071
|
}
|
|
3715
4072
|
sessionExists(sessionId) {
|
|
3716
4073
|
const dbPath = this.dbManager.getDbPath(sessionId);
|
|
3717
|
-
if (!
|
|
4074
|
+
if (!fs11.existsSync(dbPath)) return false;
|
|
3718
4075
|
try {
|
|
3719
4076
|
const db = this.dbManager.openRawSessionDatabase(sessionId, { readonly: true });
|
|
3720
4077
|
const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
|
|
@@ -3722,7 +4079,7 @@ var DirectImporter = class {
|
|
|
3722
4079
|
if (row.cnt === 0) {
|
|
3723
4080
|
this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
|
|
3724
4081
|
try {
|
|
3725
|
-
|
|
4082
|
+
fs11.unlinkSync(dbPath);
|
|
3726
4083
|
} catch {
|
|
3727
4084
|
}
|
|
3728
4085
|
return false;
|
|
@@ -3732,7 +4089,7 @@ var DirectImporter = class {
|
|
|
3732
4089
|
if (error instanceof DataDirCompatibilityError) throw error;
|
|
3733
4090
|
this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
|
|
3734
4091
|
try {
|
|
3735
|
-
|
|
4092
|
+
fs11.unlinkSync(dbPath);
|
|
3736
4093
|
} catch {
|
|
3737
4094
|
}
|
|
3738
4095
|
return false;
|
|
@@ -3791,92 +4148,6 @@ var NoopNotifier = class {
|
|
|
3791
4148
|
onPullResult = noop2;
|
|
3792
4149
|
};
|
|
3793
4150
|
|
|
3794
|
-
// src/sync/routes.ts
|
|
3795
|
-
function registerAutomationRoutes(server2, ctx) {
|
|
3796
|
-
const { dsManager: dsManager2, pullEngine: pullEngine2, dbManager: dbManager2, serverInfo } = ctx;
|
|
3797
|
-
server2.get("/_web/automation/config", async () => {
|
|
3798
|
-
return {
|
|
3799
|
-
enabled: true,
|
|
3800
|
-
port: serverInfo.port,
|
|
3801
|
-
token: serverInfo.token,
|
|
3802
|
-
host: serverInfo.host
|
|
3803
|
-
};
|
|
3804
|
-
});
|
|
3805
|
-
server2.get("/_web/automation/data-sources", async () => {
|
|
3806
|
-
return dsManager2.loadAll();
|
|
3807
|
-
});
|
|
3808
|
-
server2.post("/_web/automation/data-sources", async (request) => {
|
|
3809
|
-
const ds = dsManager2.add(request.body);
|
|
3810
|
-
reloadTimer(ds.id);
|
|
3811
|
-
return ds;
|
|
3812
|
-
});
|
|
3813
|
-
server2.patch("/_web/automation/data-sources/:id", async (request, reply) => {
|
|
3814
|
-
const ds = dsManager2.update(request.params.id, request.body);
|
|
3815
|
-
if (!ds) return reply.code(404).send({ error: "Data source not found" });
|
|
3816
|
-
reloadTimer(ds.id);
|
|
3817
|
-
return ds;
|
|
3818
|
-
});
|
|
3819
|
-
server2.delete("/_web/automation/data-sources/:id", async (request, reply) => {
|
|
3820
|
-
stopTimer(request.params.id);
|
|
3821
|
-
const ok = dsManager2.delete(request.params.id);
|
|
3822
|
-
if (!ok) return reply.code(404).send({ error: "Data source not found" });
|
|
3823
|
-
return { success: true };
|
|
3824
|
-
});
|
|
3825
|
-
server2.post("/_web/automation/data-sources/:id/sessions", async (request, reply) => {
|
|
3826
|
-
const added = dsManager2.addSessions(request.params.id, request.body.sessions);
|
|
3827
|
-
if (added.length === 0 && !dsManager2.get(request.params.id)) {
|
|
3828
|
-
return reply.code(404).send({ error: "Data source not found" });
|
|
3829
|
-
}
|
|
3830
|
-
reloadTimer(request.params.id, true);
|
|
3831
|
-
for (const sess of added) {
|
|
3832
|
-
pullEngine2.triggerPull(request.params.id, sess.id).catch(() => {
|
|
3833
|
-
});
|
|
3834
|
-
}
|
|
3835
|
-
return added;
|
|
3836
|
-
});
|
|
3837
|
-
server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
|
|
3838
|
-
const removed = dsManager2.removeSession(request.params.id, request.params.sessId);
|
|
3839
|
-
if (!removed) return reply.code(404).send({ error: "Session not found" });
|
|
3840
|
-
reloadTimer(request.params.id);
|
|
3841
|
-
if (request.query.deleteData === "true" && removed.targetSessionId) {
|
|
3842
|
-
dbManager2.deleteSessionDatabaseFiles(removed.targetSessionId);
|
|
3843
|
-
}
|
|
3844
|
-
return { success: true };
|
|
3845
|
-
});
|
|
3846
|
-
server2.post(
|
|
3847
|
-
"/_web/automation/data-sources/:id/pull",
|
|
3848
|
-
async (request) => {
|
|
3849
|
-
return pullEngine2.triggerPull(request.params.id, request.body?.sessionId);
|
|
3850
|
-
}
|
|
3851
|
-
);
|
|
3852
|
-
server2.post("/_web/automation/data-sources/:id/pull-all", async (request) => {
|
|
3853
|
-
return pullEngine2.triggerPullAll(request.params.id);
|
|
3854
|
-
});
|
|
3855
|
-
server2.get("/_web/automation/sync-progress", async () => {
|
|
3856
|
-
return pullEngine2.getProgress();
|
|
3857
|
-
});
|
|
3858
|
-
server2.get("/_web/automation/remote-sessions", async (request, reply) => {
|
|
3859
|
-
const { baseUrl, token, keyword, limit, cursor } = request.query;
|
|
3860
|
-
if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
|
|
3861
|
-
const normalizedUrl = normalizeBaseUrl(baseUrl);
|
|
3862
|
-
const url = buildRemoteSessionsUrl(normalizedUrl, {
|
|
3863
|
-
keyword,
|
|
3864
|
-
limit: limit ? parseInt(limit, 10) : void 0,
|
|
3865
|
-
cursor
|
|
3866
|
-
});
|
|
3867
|
-
const headers = { Accept: "application/json" };
|
|
3868
|
-
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
3869
|
-
try {
|
|
3870
|
-
const resp = await fetch(url, { headers, signal: AbortSignal.timeout(3e4) });
|
|
3871
|
-
if (!resp.ok) throw new Error(`Remote server returned HTTP ${resp.status}`);
|
|
3872
|
-
const body = await resp.text();
|
|
3873
|
-
return parseRemoteSessionsResponse(body);
|
|
3874
|
-
} catch (err) {
|
|
3875
|
-
return reply.code(502).send({ error: err.message || "Failed to fetch remote sessions" });
|
|
3876
|
-
}
|
|
3877
|
-
});
|
|
3878
|
-
}
|
|
3879
|
-
|
|
3880
4151
|
// src/sync/index.ts
|
|
3881
4152
|
var syncLogger = {
|
|
3882
4153
|
info: (msg) => console.log(`[Sync] ${msg}`),
|
|
@@ -3907,7 +4178,16 @@ function initSync(server2, dbManager2, pathProvider, serverInfo) {
|
|
|
3907
4178
|
}
|
|
3908
4179
|
}
|
|
3909
4180
|
});
|
|
3910
|
-
registerAutomationRoutes(server2, {
|
|
4181
|
+
registerAutomationRoutes(server2, {
|
|
4182
|
+
automation: {
|
|
4183
|
+
dsManager,
|
|
4184
|
+
pullEngine,
|
|
4185
|
+
serverInfo,
|
|
4186
|
+
deleteSessionData: (sessionId) => dbManager2.deleteSessionDatabaseFiles(sessionId),
|
|
4187
|
+
reloadTimer,
|
|
4188
|
+
stopTimer
|
|
4189
|
+
}
|
|
4190
|
+
});
|
|
3911
4191
|
initScheduler({
|
|
3912
4192
|
dsManager,
|
|
3913
4193
|
pullEngine,
|
|
@@ -3928,21 +4208,21 @@ function createFileConfigStorage(aiDataDir) {
|
|
|
3928
4208
|
return {
|
|
3929
4209
|
readJson(key) {
|
|
3930
4210
|
try {
|
|
3931
|
-
return JSON.parse(
|
|
4211
|
+
return JSON.parse(fs12.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
|
|
3932
4212
|
} catch {
|
|
3933
4213
|
return null;
|
|
3934
4214
|
}
|
|
3935
4215
|
},
|
|
3936
4216
|
writeJson(key, data) {
|
|
3937
|
-
if (!
|
|
3938
|
-
|
|
4217
|
+
if (!fs12.existsSync(aiDataDir)) fs12.mkdirSync(aiDataDir, { recursive: true });
|
|
4218
|
+
fs12.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
|
|
3939
4219
|
}
|
|
3940
4220
|
};
|
|
3941
4221
|
}
|
|
3942
4222
|
function resolveNativeBinding2() {
|
|
3943
4223
|
if (process.versions.electron) return void 0;
|
|
3944
4224
|
const nativePath = resolveCliPath("native/better_sqlite3.node");
|
|
3945
|
-
if (
|
|
4225
|
+
if (fs12.existsSync(nativePath)) return nativePath;
|
|
3946
4226
|
return void 0;
|
|
3947
4227
|
}
|
|
3948
4228
|
function ensureToken(config) {
|
|
@@ -4035,6 +4315,7 @@ async function startHttpServer(options) {
|
|
|
4035
4315
|
registerWebRoutes(server, dbManager, {
|
|
4036
4316
|
pathProvider,
|
|
4037
4317
|
nativeBinding,
|
|
4318
|
+
runtimeIdentity: runtime,
|
|
4038
4319
|
semanticIndexService,
|
|
4039
4320
|
aiContext: {
|
|
4040
4321
|
aiDataDir,
|
|
@@ -4048,7 +4329,7 @@ async function startHttpServer(options) {
|
|
|
4048
4329
|
}
|
|
4049
4330
|
});
|
|
4050
4331
|
initSync(server, dbManager, pathProvider, { port, host, token });
|
|
4051
|
-
if (options?.webRoot &&
|
|
4332
|
+
if (options?.webRoot && fs12.existsSync(options.webRoot)) {
|
|
4052
4333
|
registerProxyRoutes(server);
|
|
4053
4334
|
const fastifyStatic = await import("@fastify/static");
|
|
4054
4335
|
await server.register(fastifyStatic.default, {
|
|
@@ -4089,4 +4370,4 @@ export {
|
|
|
4089
4370
|
startHttpServer,
|
|
4090
4371
|
stopHttpServer
|
|
4091
4372
|
};
|
|
4092
|
-
//# sourceMappingURL=chunk-
|
|
4373
|
+
//# sourceMappingURL=chunk-4IZ3WKYU.mjs.map
|