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.
Files changed (116) hide show
  1. package/dist/{chat-command-ISJ2DC7R.mjs → chat-command-UZ23GKO7.mjs} +8 -5
  2. package/dist/{chat-command-ISJ2DC7R.mjs.map → chat-command-UZ23GKO7.mjs.map} +1 -1
  3. package/dist/{chunk-AB6TAXTQ.mjs → chunk-4IZ3WKYU.mjs} +591 -310
  4. package/dist/chunk-4IZ3WKYU.mjs.map +1 -0
  5. package/dist/{chunk-3QBVO4J7.mjs → chunk-5UBHNQFZ.mjs} +101 -41
  6. package/dist/chunk-5UBHNQFZ.mjs.map +1 -0
  7. package/dist/chunk-7BZLS2KW.mjs +8965 -0
  8. package/dist/chunk-7BZLS2KW.mjs.map +1 -0
  9. package/dist/{chunk-ZVDOFWL7.mjs → chunk-A2SANKG4.mjs} +4043 -10895
  10. package/dist/chunk-A2SANKG4.mjs.map +1 -0
  11. package/dist/chunk-CNSYXRJP.mjs +526 -0
  12. package/dist/chunk-CNSYXRJP.mjs.map +1 -0
  13. package/dist/{chunk-T7IETWHN.mjs → chunk-FCCL5Z4W.mjs} +5 -3
  14. package/dist/{chunk-T7IETWHN.mjs.map → chunk-FCCL5Z4W.mjs.map} +1 -1
  15. package/dist/chunk-M55KW2HA.mjs +2323 -0
  16. package/dist/chunk-M55KW2HA.mjs.map +1 -0
  17. package/dist/chunk-MUPILWAZ.mjs +1339 -0
  18. package/dist/chunk-MUPILWAZ.mjs.map +1 -0
  19. package/dist/{chunk-YBS3YQB6.mjs → chunk-P3ZOLTGS.mjs} +31 -57
  20. package/dist/chunk-P3ZOLTGS.mjs.map +1 -0
  21. package/dist/{chunk-NJB2NNB2.mjs → chunk-RDR6PS2R.mjs} +2 -2
  22. package/dist/{chunk-ERAAWLEF.mjs → chunk-ZFU2CHW3.mjs} +11 -8
  23. package/dist/{chunk-ERAAWLEF.mjs.map → chunk-ZFU2CHW3.mjs.map} +1 -1
  24. package/dist/cli.mjs +7 -4
  25. package/dist/contacts-worker.mjs +48 -0
  26. package/dist/contacts-worker.mjs.map +1 -0
  27. package/dist/{http-MBILV37U.mjs → http-UWRA5FLB.mjs} +11 -8
  28. package/dist/{import-EYV3GXHE.mjs → import-745KQL7N.mjs} +7 -4
  29. package/dist/index.mjs +11 -8
  30. package/dist/{mcp-H55AEXU5.mjs → mcp-UHN5JOHC.mjs} +8 -5
  31. package/dist/{mcp-H55AEXU5.mjs.map → mcp-UHN5JOHC.mjs.map} +1 -1
  32. package/dist/people-relationships-worker.mjs +48 -0
  33. package/dist/people-relationships-worker.mjs.map +1 -0
  34. package/dist/semantic-index-worker.mjs +1 -1
  35. package/dist/{src-36ZIR65K.mjs → src-Y7MDRXLJ.mjs} +68 -44
  36. package/dist/{stream-import-YZMZ56B5.mjs → stream-import-3JMXQD7P.mjs} +7 -4
  37. package/dist/{worker-runtime-YDYS2C6T.mjs → worker-runtime-63RYMGZI.mjs} +7 -3
  38. package/dist/{worker-runtime-YDYS2C6T.mjs.map → worker-runtime-63RYMGZI.mjs.map} +1 -1
  39. package/dist-web/assets/BufferResource-BVdJ_I5X.js +185 -0
  40. package/dist-web/assets/CanvasRenderer-BaRuT54R.js +1 -0
  41. package/dist-web/assets/DebugToolsPanel-D_lFIW6j.css +1 -0
  42. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-N5CoiJzN.js +110 -0
  43. package/dist-web/assets/{EChartWordcloud-B_YisOW5.js → EChartWordcloud-Clg60FMo.js} +1 -1
  44. package/dist-web/assets/Filter-B7cNFtuE.js +1 -0
  45. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js +1 -0
  46. package/dist-web/assets/MessageExportModal-B2Qm6VVj.js +1 -0
  47. package/dist-web/assets/PageHeader-Bbn87QZS.css +1 -0
  48. package/dist-web/assets/PageHeader-PjlWy9fC.js +1 -0
  49. package/dist-web/assets/RelationshipGalaxyCanvas-11MafciW.js +244 -0
  50. package/dist-web/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css +1 -0
  51. package/dist-web/assets/RelationshipGalaxyThreeCanvas-C1I4B8NX.js +4108 -0
  52. package/dist-web/assets/RenderTargetSystem-Chkz1WlI.js +184 -0
  53. package/dist-web/assets/WebGLRenderer-xK19ru58.js +156 -0
  54. package/dist-web/assets/WebGPURenderer-CL5ZaKsn.js +41 -0
  55. package/dist-web/assets/browserAll-CyyNzRD_.js +14 -0
  56. package/dist-web/assets/electron-MXaTKaMu.js +1 -0
  57. package/dist-web/assets/{fetch-29ZBtGV0.js → fetch-D1dDiFO4.js} +1 -1
  58. package/dist-web/assets/{fetch-TCeykOkO.js → fetch-DDnVXGte.js} +1 -1
  59. package/dist-web/assets/{fetch-Bh1fo3Df.js → fetch-DTwdRTXM.js} +1 -1
  60. package/dist-web/assets/{fetch-BcxrU2mr.js → fetch-DaaJwT-5.js} +1 -1
  61. package/dist-web/assets/{fetch-tIIVdXSv.js → fetch-DeA0ztwT.js} +1 -1
  62. package/dist-web/assets/fetch-DpnIGZSN.js +17 -0
  63. package/dist-web/assets/{fetch-C3yhWO2S.js → fetch-DsE-xcP2.js} +1 -1
  64. package/dist-web/assets/{fetch-BYczbT4C.js → fetch-DsTgYu_X.js} +1 -1
  65. package/dist-web/assets/fetch-hKE53UO4.js +1 -0
  66. package/dist-web/assets/fetch-u9GTMhMF.js +1 -0
  67. package/dist-web/assets/index---rXG1hZ.css +1 -0
  68. package/dist-web/assets/index-BVkE4qis.js +22 -0
  69. package/dist-web/assets/index-BcXLcFnV.css +1 -0
  70. package/dist-web/assets/index-BnHKyESZ.js +1 -0
  71. package/dist-web/assets/index-Btr5H7d-.js +2 -0
  72. package/dist-web/assets/{index-BX3tqfLz.css → index-C1p9CbmN.css} +1 -1
  73. package/dist-web/assets/index-COpza1A4.css +1 -0
  74. package/dist-web/assets/index-D04LWJop.js +173 -0
  75. package/dist-web/assets/index-DFCYmzGE.js +2 -0
  76. package/dist-web/assets/index-DosG7LmJ.css +1 -0
  77. package/dist-web/assets/index-DqXHRIhI.js +1 -0
  78. package/dist-web/assets/{index-fbWrQpgH.js → index-DqtXt3wl.js} +1 -1
  79. package/dist-web/assets/index-Dszv-QFp.js +64 -0
  80. package/dist-web/assets/index-DzLrWGXu.js +1 -0
  81. package/dist-web/assets/people-page-header-Mupn-U_b.js +1 -0
  82. package/dist-web/assets/relationship-galaxy-viewport-Cwfa51ER.js +1 -0
  83. package/dist-web/assets/{vendor-echarts-CITQeskI.js → vendor-echarts-hJVnODyU.js} +2 -2
  84. package/dist-web/assets/{vendor-echarts-wordcloud-B1kQMklB.js → vendor-echarts-wordcloud-k5ih2Jl4.js} +1 -1
  85. package/dist-web/assets/{vendor-nuxt-ui-Zy8txNdC.js → vendor-nuxt-ui-DaLxYaC4.js} +2 -2
  86. package/dist-web/assets/{vendor-reka-ui-mMvma1EP.js → vendor-reka-ui-CzM4kDQ7.js} +1 -1
  87. package/dist-web/assets/{vendor-zrender-DFl4U2Oe.js → vendor-zrender-5DcYjSZk.js} +1 -1
  88. package/dist-web/assets/{web-H3XLx159.js → web-DPVWfPS-.js} +1 -1
  89. package/dist-web/assets/webworkerAll-DF5QV_vN.js +83 -0
  90. package/dist-web/index.html +4 -4
  91. package/package.json +1 -1
  92. package/dist/chunk-3QBVO4J7.mjs.map +0 -1
  93. package/dist/chunk-AB6TAXTQ.mjs.map +0 -1
  94. package/dist/chunk-GHTGFM6O.mjs +0 -540
  95. package/dist/chunk-GHTGFM6O.mjs.map +0 -1
  96. package/dist/chunk-YBS3YQB6.mjs.map +0 -1
  97. package/dist/chunk-ZVDOFWL7.mjs.map +0 -1
  98. package/dist-web/assets/DebugToolsPanel-DFw6_G5V.css +0 -1
  99. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-Do3x4aL3.js +0 -110
  100. package/dist-web/assets/MessageExportModal-rijqUusq.js +0 -1
  101. package/dist-web/assets/electron-DWDCqx_d.js +0 -1
  102. package/dist-web/assets/fetch-BAxKmbKF.js +0 -1
  103. package/dist-web/assets/fetch-Da_TBxrX.js +0 -1
  104. package/dist-web/assets/fetch-DyD8wDhb.js +0 -17
  105. package/dist-web/assets/index-BzjLqlW_.js +0 -173
  106. package/dist-web/assets/index-C74qEDFh.js +0 -64
  107. package/dist-web/assets/index-CM6xNPFp.css +0 -1
  108. package/dist-web/assets/index-CgK2LLIF.js +0 -12
  109. package/dist-web/assets/index-CjkOvM3N.js +0 -2
  110. package/dist-web/assets/index-Dno7CADA.js +0 -1
  111. package/dist-web/assets/index-I3SWZjnW.css +0 -1
  112. /package/dist/{chunk-NJB2NNB2.mjs.map → chunk-RDR6PS2R.mjs.map} +0 -0
  113. /package/dist/{http-MBILV37U.mjs.map → http-UWRA5FLB.mjs.map} +0 -0
  114. /package/dist/{import-EYV3GXHE.mjs.map → import-745KQL7N.mjs.map} +0 -0
  115. /package/dist/{src-36ZIR65K.mjs.map → src-Y7MDRXLJ.mjs.map} +0 -0
  116. /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-ERAAWLEF.mjs";
18
+ } from "./chunk-ZFU2CHW3.mjs";
12
19
  import {
13
20
  AGENT_TOOL_REGISTRY,
14
21
  CoreDataProvider
15
- } from "./chunk-3QBVO4J7.mjs";
22
+ } from "./chunk-5UBHNQFZ.mjs";
16
23
  import {
17
24
  assertCliDataDirCompatible
18
- } from "./chunk-NJB2NNB2.mjs";
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-YBS3YQB6.mjs";
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
- createDatabaseManagerAdapter,
74
- deleteAuthProfile,
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
- getDefaultRulesForLocale,
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-ZVDOFWL7.mjs";
156
+ } from "./chunk-7BZLS2KW.mjs";
151
157
 
152
158
  // src/http/index.ts
153
- import * as fs11 from "fs";
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
- return executeSql(db, sql, { timing: true });
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-semantic-index.ts
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(path4.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
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 = (path13, action) => {
1739
- server2.post(path13, async (request, reply) => {
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 fs3 from "fs";
1938
+ import * as fs4 from "fs";
1771
1939
  import * as os from "os";
1772
- import * as path5 from "path";
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 = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merge-"));
1790
- const tmpPath = path5.join(tmpDir, data.filename || "upload");
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
- fs3.writeFileSync(tmpPath, Buffer.concat(chunks));
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 (!fs3.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
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(path5.basename(sourceFilePath));
1976
+ return mergeCache.createTempDatabase(path6.basename(sourceFilePath));
1809
1977
  },
1810
1978
  onProgress() {
1811
1979
  }
1812
1980
  });
1813
- const filename = path5.basename(filePath);
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
- fs3.unlinkSync(filePath);
1995
+ fs4.unlinkSync(filePath);
1828
1996
  } catch {
1829
1997
  }
1830
1998
  }
1831
1999
  if (cleanupDir) {
1832
2000
  try {
1833
- fs3.rmdirSync(cleanupDir);
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 = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merged-"));
1883
- const safeName = path5.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1884
- const tmpPath = path5.join(tmpDir, `${safeName}.json`);
1885
- fs3.writeFileSync(tmpPath, jsonData, "utf-8");
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
- fs3.unlinkSync(tmpPath);
2059
+ fs4.unlinkSync(tmpPath);
1892
2060
  } catch {
1893
2061
  }
1894
2062
  try {
1895
- fs3.rmdirSync(tmpDir);
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 fs4 from "fs";
2137
+ import * as fs5 from "fs";
1970
2138
  import * as fsp from "fs/promises";
1971
- import * as path6 from "path";
2139
+ import * as path7 from "path";
1972
2140
  async function getDirSize(dirPath) {
1973
2141
  let totalSize = 0;
1974
2142
  try {
1975
- if (!fs4.existsSync(dirPath)) return 0;
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 = path6.join(dirPath, file.name);
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 (!fs4.existsSync(dirPath)) return 0;
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 = path6.join(dirPath, file.name);
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 = path6.join(dirPath, ".chatlab");
2010
- const dbDir = path6.join(dirPath, "databases");
2011
- if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
2012
- return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
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: fs4.existsSync(dir.path) };
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 (!fs4.existsSync(dirPath)) return { success: true };
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 = path6.join(dirPath, file);
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) && path6.resolve(pp.getUserDataDir()) !== path6.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(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 = path6.join(pp.getLogsDir(), "import");
2113
- if (!fs4.existsSync(importLogDir)) {
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 = path6.join(importLogDir, f);
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 (!fs4.existsSync(downloadsDir)) {
2148
- fs4.mkdirSync(downloadsDir, { recursive: true });
2315
+ if (!fs5.existsSync(downloadsDir)) {
2316
+ fs5.mkdirSync(downloadsDir, { recursive: true });
2149
2317
  }
2150
- const filePath = path6.join(downloadsDir, filename);
2151
- fs4.writeFileSync(filePath, buffer);
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 (!fs4.existsSync(dirPath)) {
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 path9 from "path";
2663
+ import * as path10 from "path";
2349
2664
 
2350
2665
  // src/http/routes/web/import.ts
2351
- import * as fs6 from "fs";
2666
+ import * as fs7 from "fs";
2352
2667
  import * as os2 from "os";
2353
- import * as path7 from "path";
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 fs5 from "fs";
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 (fs5.existsSync(nativePath)) return nativePath;
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 = fs6.statSync(p);
2687
+ const stat2 = fs7.statSync(p);
2373
2688
  if (stat2.isDirectory()) {
2374
- fs6.rmSync(p, { recursive: true, force: true });
2689
+ fs7.rmSync(p, { recursive: true, force: true });
2375
2690
  } else {
2376
- fs6.unlinkSync(p);
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: fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-sources-"))
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 = path7.join(os2.tmpdir(), `chatlab-archive-${randomUUID()}.zip`);
2723
+ const uploadPath = path8.join(os2.tmpdir(), `chatlab-archive-${randomUUID()}.zip`);
2409
2724
  try {
2410
- await pipeline(data.file, fs6.createWriteStream(uploadPath, { flags: "wx" }));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-detect-"));
2476
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
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
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-scan-"));
2494
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
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
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-"));
2511
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
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
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-dir-import-"));
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 = path7.resolve(tmpDir, relPath);
2588
- if (!targetPath.startsWith(tmpDir + path7.sep)) continue;
2589
- fs6.mkdirSync(path7.dirname(targetPath), { recursive: true });
2590
- fs6.writeFileSync(targetPath, fileBuffers[i].data);
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-inc-"));
2636
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
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
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2674
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
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
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2692
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
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
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
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 = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-demo-"));
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 = path7.join(tmpDir, DEMO_FILES[i]);
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
- fs6.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
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 path8 from "path";
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", [path8.dirname(filePath)], runner);
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: () => path9.join(os3.homedir(), ".chatlab"),
2820
- getUserDataDir: () => path9.join(os3.homedir(), ".chatlab", "data"),
2821
- getDatabaseDir: () => path9.join(os3.homedir(), ".chatlab", "data", "databases"),
2822
- getVectorDir: () => path9.join(os3.homedir(), ".chatlab", "data", "vector"),
2823
- getAiDataDir: () => path9.join(os3.homedir(), ".chatlab", "ai"),
2824
- getSettingsDir: () => path9.join(os3.homedir(), ".chatlab", "settings"),
2825
- getCacheDir: () => path9.join(os3.homedir(), ".chatlab", "cache"),
2826
- getTempDir: () => path9.join(os3.homedir(), ".chatlab", "temp"),
2827
- getLogsDir: () => path9.join(os3.homedir(), ".chatlab", "logs"),
2828
- getDownloadsDir: () => path9.join(os3.homedir(), "Downloads")
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-YZMZ56B5.mjs");
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 = path9.resolve(resolvedPathProvider.getUserDataDir()) !== path9.resolve(defaultUserDataDir);
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 fs7 from "fs";
2935
- import * as path10 from "path";
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 (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
3256
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
2940
3257
  },
2941
3258
  listFiles(dir, ext) {
2942
- if (!fs7.existsSync(dir)) return [];
2943
- return fs7.readdirSync(dir).filter((f) => f.endsWith(ext));
3259
+ if (!fs8.existsSync(dir)) return [];
3260
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(ext));
2944
3261
  },
2945
3262
  readFile(filePath) {
2946
- return fs7.readFileSync(filePath, "utf-8");
3263
+ return fs8.readFileSync(filePath, "utf-8");
2947
3264
  },
2948
3265
  writeFile(filePath, content) {
2949
- fs7.writeFileSync(filePath, content, "utf-8");
3266
+ fs8.writeFileSync(filePath, content, "utf-8");
2950
3267
  },
2951
3268
  deleteFile(filePath) {
2952
- if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
3269
+ if (fs8.existsSync(filePath)) fs8.unlinkSync(filePath);
2953
3270
  },
2954
3271
  fileExists(filePath) {
2955
- return fs7.existsSync(filePath);
3272
+ return fs8.existsSync(filePath);
2956
3273
  },
2957
3274
  joinPath(...parts) {
2958
- return path10.join(...parts);
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: path10.join(aiDataDir, "assistants"),
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: path10.join(aiDataDir, "skills"),
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 fs8 from "fs";
2999
- import * as path11 from "path";
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 normalizeBaseUrl(input) {
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 path11.join(this.configDir, CONFIG_FILE);
3344
+ return path12.join(this.configDir, CONFIG_FILE);
3028
3345
  }
3029
3346
  ensureDir() {
3030
- fs8.mkdirSync(this.configDir, { recursive: true });
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 (fs8.existsSync(filePath)) {
3037
- const raw = fs8.readFileSync(filePath, "utf-8");
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
- fs8.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
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: normalizeBaseUrl(partial.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 = normalizeBaseUrl(updates.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 fs9 from "fs";
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 = fs9.readFileSync(filePath, "utf-8");
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 = fs9.readFileSync(filePath, "utf-8");
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 = fs9.readFileSync(filePath, "utf-8");
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 = fs9.readFileSync(filePath, "utf-8");
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 = fs9.readFileSync(filePath, "utf-8");
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 = fs9.readFileSync(filePath, "utf-8");
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 (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
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 = fs9.statSync(tempFile);
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 = fs9.statSync(retryFile);
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 errMsg = error.message || "Pull failed";
3549
- this.logger.error(`[Pull] Pull failed for "${sess.name}"`, error);
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
- for (const sess of ds.sessions) {
3575
- await this.executePullSession(ds.id, ds, sess);
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
- for (const sess of ds.sessions) {
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 fs10 from "fs";
4038
+ import * as fs11 from "fs";
3682
4039
  import * as os4 from "os";
3683
- import * as path12 from "path";
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 path12.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
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
- fs10.writeFileSync(tempFile, buffer);
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 (!fs10.existsSync(dbPath)) return false;
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
- fs10.unlinkSync(dbPath);
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
- fs10.unlinkSync(dbPath);
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, { dsManager, pullEngine, dbManager: dbManager2, serverInfo });
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(fs11.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
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 (!fs11.existsSync(aiDataDir)) fs11.mkdirSync(aiDataDir, { recursive: true });
3938
- fs11.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
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 (fs11.existsSync(nativePath)) return nativePath;
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 && fs11.existsSync(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-AB6TAXTQ.mjs.map
4373
+ //# sourceMappingURL=chunk-4IZ3WKYU.mjs.map