chattercatcher 0.1.6 → 0.1.7
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/cli.js +452 -277
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.js +367 -270
- package/dist/index.js.map +1 -1
- package/docs/superpowers/plans/2026-04-28-gateway-background-start.md +86 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,155 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/config/paths.ts
|
|
13
|
+
import os2 from "os";
|
|
14
|
+
import path2 from "path";
|
|
15
|
+
function getChatterCatcherHome() {
|
|
16
|
+
return process.env.CHATTERCATCHER_HOME || path2.join(os2.homedir(), ".chattercatcher");
|
|
17
|
+
}
|
|
18
|
+
function resolveHomePath(value) {
|
|
19
|
+
if (value === "~") {
|
|
20
|
+
return os2.homedir();
|
|
21
|
+
}
|
|
22
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
23
|
+
return path2.join(os2.homedir(), value.slice(2));
|
|
24
|
+
}
|
|
25
|
+
return path2.resolve(value);
|
|
26
|
+
}
|
|
27
|
+
function getConfigPath() {
|
|
28
|
+
return path2.join(getChatterCatcherHome(), "config.json");
|
|
29
|
+
}
|
|
30
|
+
function getSecretsPath() {
|
|
31
|
+
return path2.join(getChatterCatcherHome(), "secrets.json");
|
|
32
|
+
}
|
|
33
|
+
var init_paths = __esm({
|
|
34
|
+
"src/config/paths.ts"() {
|
|
35
|
+
"use strict";
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// src/rag/lancedb-store.ts
|
|
40
|
+
var lancedb_store_exports = {};
|
|
41
|
+
__export(lancedb_store_exports, {
|
|
42
|
+
LanceDbVectorStore: () => LanceDbVectorStore,
|
|
43
|
+
getLanceDbPath: () => getLanceDbPath
|
|
44
|
+
});
|
|
45
|
+
import fs6 from "fs/promises";
|
|
46
|
+
import path9 from "path";
|
|
47
|
+
function getLanceDbPath(config) {
|
|
48
|
+
return path9.join(resolveHomePath(config.storage.dataDir), "vector", "lancedb");
|
|
49
|
+
}
|
|
50
|
+
function toRow(record) {
|
|
51
|
+
return {
|
|
52
|
+
id: record.id,
|
|
53
|
+
vector: record.vector,
|
|
54
|
+
text: record.evidence.text,
|
|
55
|
+
source_json: JSON.stringify(record.evidence.source)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function toLanceData(rows) {
|
|
59
|
+
return rows.map((row) => ({
|
|
60
|
+
id: row.id,
|
|
61
|
+
vector: row.vector,
|
|
62
|
+
text: row.text,
|
|
63
|
+
source_json: row.source_json
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
function escapeSqlString(value) {
|
|
67
|
+
return value.replace(/'/g, "''");
|
|
68
|
+
}
|
|
69
|
+
function toEvidence(row) {
|
|
70
|
+
const distance = row._distance ?? 0;
|
|
71
|
+
const vectorScore = 1 / (1 + Math.max(0, distance));
|
|
72
|
+
return {
|
|
73
|
+
id: row.id,
|
|
74
|
+
text: row.text,
|
|
75
|
+
score: vectorScore,
|
|
76
|
+
vectorScore,
|
|
77
|
+
source: JSON.parse(row.source_json)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
var DEFAULT_TABLE_NAME, LanceDbVectorStore;
|
|
81
|
+
var init_lancedb_store = __esm({
|
|
82
|
+
"src/rag/lancedb-store.ts"() {
|
|
83
|
+
"use strict";
|
|
84
|
+
init_paths();
|
|
85
|
+
DEFAULT_TABLE_NAME = "message_chunks";
|
|
86
|
+
LanceDbVectorStore = class _LanceDbVectorStore {
|
|
87
|
+
constructor(connection, tableName) {
|
|
88
|
+
this.connection = connection;
|
|
89
|
+
this.tableName = tableName;
|
|
90
|
+
}
|
|
91
|
+
connection;
|
|
92
|
+
tableName;
|
|
93
|
+
static async connect(uri, tableName = DEFAULT_TABLE_NAME) {
|
|
94
|
+
await fs6.mkdir(uri, { recursive: true });
|
|
95
|
+
const lancedb = await import("@lancedb/lancedb");
|
|
96
|
+
const connection = await lancedb.connect(uri);
|
|
97
|
+
return new _LanceDbVectorStore(connection, tableName);
|
|
98
|
+
}
|
|
99
|
+
static async connectFromConfig(config, tableName = DEFAULT_TABLE_NAME) {
|
|
100
|
+
return _LanceDbVectorStore.connect(getLanceDbPath(config), tableName);
|
|
101
|
+
}
|
|
102
|
+
close() {
|
|
103
|
+
this.connection.close();
|
|
104
|
+
}
|
|
105
|
+
async upsert(records) {
|
|
106
|
+
if (records.length === 0) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const rows = records.map(toRow);
|
|
110
|
+
const data2 = toLanceData(rows);
|
|
111
|
+
const table = await this.ensureTable(data2);
|
|
112
|
+
const ids = rows.map((row) => `'${escapeSqlString(row.id)}'`).join(", ");
|
|
113
|
+
await table.delete(`id IN (${ids})`);
|
|
114
|
+
await table.add(data2);
|
|
115
|
+
}
|
|
116
|
+
async search(vector, limit) {
|
|
117
|
+
const table = await this.openTableIfExists();
|
|
118
|
+
if (!table) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
const rows = await table.vectorSearch(vector).limit(limit).toArray();
|
|
122
|
+
return rows.map(toEvidence);
|
|
123
|
+
}
|
|
124
|
+
async count() {
|
|
125
|
+
const table = await this.openTableIfExists();
|
|
126
|
+
if (!table) {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
return table.countRows();
|
|
130
|
+
}
|
|
131
|
+
async ensureTable(initialRows) {
|
|
132
|
+
const table = await this.openTableIfExists();
|
|
133
|
+
if (table) {
|
|
134
|
+
return table;
|
|
135
|
+
}
|
|
136
|
+
return this.connection.createTable(this.tableName, initialRows);
|
|
137
|
+
}
|
|
138
|
+
async openTableIfExists() {
|
|
139
|
+
const tableNames = await this.connection.tableNames();
|
|
140
|
+
if (!tableNames.includes(this.tableName)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return this.connection.openTable(this.tableName);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
2
148
|
|
|
3
149
|
// src/cli.ts
|
|
4
150
|
import { input, password, select, confirm, number } from "@inquirer/prompts";
|
|
5
151
|
import { Command } from "commander";
|
|
6
|
-
import
|
|
152
|
+
import fs14 from "fs/promises";
|
|
7
153
|
|
|
8
154
|
// src/config/store.ts
|
|
9
155
|
import fs from "fs/promises";
|
|
@@ -72,29 +218,8 @@ function createDefaultSecrets() {
|
|
|
72
218
|
});
|
|
73
219
|
}
|
|
74
220
|
|
|
75
|
-
// src/config/paths.ts
|
|
76
|
-
import os2 from "os";
|
|
77
|
-
import path2 from "path";
|
|
78
|
-
function getChatterCatcherHome() {
|
|
79
|
-
return process.env.CHATTERCATCHER_HOME || path2.join(os2.homedir(), ".chattercatcher");
|
|
80
|
-
}
|
|
81
|
-
function resolveHomePath(value) {
|
|
82
|
-
if (value === "~") {
|
|
83
|
-
return os2.homedir();
|
|
84
|
-
}
|
|
85
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
86
|
-
return path2.join(os2.homedir(), value.slice(2));
|
|
87
|
-
}
|
|
88
|
-
return path2.resolve(value);
|
|
89
|
-
}
|
|
90
|
-
function getConfigPath() {
|
|
91
|
-
return path2.join(getChatterCatcherHome(), "config.json");
|
|
92
|
-
}
|
|
93
|
-
function getSecretsPath() {
|
|
94
|
-
return path2.join(getChatterCatcherHome(), "secrets.json");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
221
|
// src/config/store.ts
|
|
222
|
+
init_paths();
|
|
98
223
|
async function readJsonFile(filePath, fallback) {
|
|
99
224
|
try {
|
|
100
225
|
const raw = await fs.readFile(filePath, "utf8");
|
|
@@ -157,7 +282,11 @@ function resolveEmbeddingApiKey(input2) {
|
|
|
157
282
|
return explicit || input2.llmApiKey;
|
|
158
283
|
}
|
|
159
284
|
|
|
285
|
+
// src/cli.ts
|
|
286
|
+
init_paths();
|
|
287
|
+
|
|
160
288
|
// src/data/deletion.ts
|
|
289
|
+
init_paths();
|
|
161
290
|
import fs2 from "fs/promises";
|
|
162
291
|
import path4 from "path";
|
|
163
292
|
function emptyResult(targetType, targetId) {
|
|
@@ -283,6 +412,7 @@ async function deleteLocalData(input2) {
|
|
|
283
412
|
}
|
|
284
413
|
|
|
285
414
|
// src/db/database.ts
|
|
415
|
+
init_paths();
|
|
286
416
|
import Database from "better-sqlite3";
|
|
287
417
|
import fs3 from "fs";
|
|
288
418
|
import path5 from "path";
|
|
@@ -343,13 +473,6 @@ function migrateDatabase(database) {
|
|
|
343
473
|
tokenize = 'unicode61'
|
|
344
474
|
);
|
|
345
475
|
|
|
346
|
-
CREATE TABLE IF NOT EXISTS message_chunk_vectors (
|
|
347
|
-
chunk_id TEXT PRIMARY KEY REFERENCES message_chunks(id) ON DELETE CASCADE,
|
|
348
|
-
vector_json TEXT NOT NULL,
|
|
349
|
-
evidence_json TEXT NOT NULL,
|
|
350
|
-
updated_at TEXT NOT NULL
|
|
351
|
-
);
|
|
352
|
-
|
|
353
476
|
CREATE TABLE IF NOT EXISTS file_jobs (
|
|
354
477
|
id TEXT PRIMARY KEY,
|
|
355
478
|
source_path TEXT NOT NULL,
|
|
@@ -369,7 +492,8 @@ function migrateDatabase(database) {
|
|
|
369
492
|
}
|
|
370
493
|
|
|
371
494
|
// src/doctor/checks.ts
|
|
372
|
-
|
|
495
|
+
init_paths();
|
|
496
|
+
import fs7 from "fs/promises";
|
|
373
497
|
|
|
374
498
|
// src/files/jobs.ts
|
|
375
499
|
import crypto from "crypto";
|
|
@@ -511,10 +635,120 @@ var FileJobRepository = class {
|
|
|
511
635
|
};
|
|
512
636
|
|
|
513
637
|
// src/gateway/runtime.ts
|
|
514
|
-
|
|
638
|
+
init_paths();
|
|
639
|
+
import fs5 from "fs";
|
|
640
|
+
import path8 from "path";
|
|
641
|
+
|
|
642
|
+
// src/logs/reader.ts
|
|
643
|
+
init_paths();
|
|
644
|
+
import fs4 from "fs/promises";
|
|
645
|
+
import { watch } from "fs";
|
|
515
646
|
import path7 from "path";
|
|
647
|
+
function getLogsDirectory() {
|
|
648
|
+
return path7.join(getChatterCatcherHome(), "logs");
|
|
649
|
+
}
|
|
650
|
+
function resolveLogPath(fileName, logsDir = getLogsDirectory()) {
|
|
651
|
+
return path7.isAbsolute(fileName) ? fileName : path7.join(logsDir, fileName);
|
|
652
|
+
}
|
|
653
|
+
function normalizeLineCount(value, fallback = 200) {
|
|
654
|
+
const parsed = Number(value ?? fallback);
|
|
655
|
+
return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 1e4) : fallback;
|
|
656
|
+
}
|
|
657
|
+
async function listLogFiles(logsDir = getLogsDirectory()) {
|
|
658
|
+
let entries;
|
|
659
|
+
try {
|
|
660
|
+
entries = await fs4.readdir(logsDir, { withFileTypes: true });
|
|
661
|
+
} catch (error) {
|
|
662
|
+
if (error.code === "ENOENT") {
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
throw error;
|
|
666
|
+
}
|
|
667
|
+
const files2 = await Promise.all(
|
|
668
|
+
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
|
|
669
|
+
const filePath = path7.join(logsDir, entry.name);
|
|
670
|
+
const stats = await fs4.stat(filePath);
|
|
671
|
+
return {
|
|
672
|
+
name: entry.name,
|
|
673
|
+
path: filePath,
|
|
674
|
+
updatedAt: stats.mtime,
|
|
675
|
+
bytes: stats.size
|
|
676
|
+
};
|
|
677
|
+
})
|
|
678
|
+
);
|
|
679
|
+
return files2.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
|
|
680
|
+
}
|
|
681
|
+
function tailLines(content, lines) {
|
|
682
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
683
|
+
const parts = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
|
|
684
|
+
return parts.slice(-lines).join("\n");
|
|
685
|
+
}
|
|
686
|
+
async function readLogTail(input2) {
|
|
687
|
+
const stats = await fs4.stat(input2.filePath);
|
|
688
|
+
const content = await fs4.readFile(input2.filePath, "utf8");
|
|
689
|
+
return {
|
|
690
|
+
file: {
|
|
691
|
+
name: path7.basename(input2.filePath),
|
|
692
|
+
path: input2.filePath,
|
|
693
|
+
updatedAt: stats.mtime,
|
|
694
|
+
bytes: stats.size
|
|
695
|
+
},
|
|
696
|
+
content: tailLines(content, normalizeLineCount(input2.lines))
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
async function readLatestLogTail(input2 = {}) {
|
|
700
|
+
if (input2.fileName) {
|
|
701
|
+
return readLogTail({
|
|
702
|
+
filePath: resolveLogPath(input2.fileName, input2.logsDir),
|
|
703
|
+
lines: input2.lines
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
const [latest] = await listLogFiles(input2.logsDir);
|
|
707
|
+
if (!latest) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
return readLogTail({ filePath: latest.path, lines: input2.lines });
|
|
711
|
+
}
|
|
712
|
+
async function followLogFile(input2) {
|
|
713
|
+
let offset = (await fs4.stat(input2.filePath)).size;
|
|
714
|
+
const directory = path7.dirname(input2.filePath);
|
|
715
|
+
const fileName = path7.basename(input2.filePath);
|
|
716
|
+
async function readAppended() {
|
|
717
|
+
const stats = await fs4.stat(input2.filePath);
|
|
718
|
+
if (stats.size < offset) {
|
|
719
|
+
offset = 0;
|
|
720
|
+
}
|
|
721
|
+
if (stats.size === offset) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const handle = await fs4.open(input2.filePath, "r");
|
|
725
|
+
try {
|
|
726
|
+
const length = stats.size - offset;
|
|
727
|
+
const buffer = Buffer.alloc(length);
|
|
728
|
+
await handle.read(buffer, 0, length, offset);
|
|
729
|
+
offset = stats.size;
|
|
730
|
+
input2.onChunk(buffer.toString("utf8"));
|
|
731
|
+
} finally {
|
|
732
|
+
await handle.close();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
const watcher = watch(directory, (eventType, changedFileName) => {
|
|
736
|
+
if (eventType !== "change" || changedFileName?.toString() !== fileName) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
void readAppended().catch((error) => {
|
|
740
|
+
input2.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
return () => watcher.close();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/gateway/runtime.ts
|
|
516
747
|
function getGatewayPidPath() {
|
|
517
|
-
return
|
|
748
|
+
return path8.join(getChatterCatcherHome(), "gateway.pid");
|
|
749
|
+
}
|
|
750
|
+
function getGatewayLogPath() {
|
|
751
|
+
return path8.join(getLogsDirectory(), "gateway.log");
|
|
518
752
|
}
|
|
519
753
|
function isProcessRunning(pid) {
|
|
520
754
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
@@ -529,7 +763,7 @@ function isProcessRunning(pid) {
|
|
|
529
763
|
}
|
|
530
764
|
function readGatewayPidRecord(pidFile = getGatewayPidPath()) {
|
|
531
765
|
try {
|
|
532
|
-
const raw =
|
|
766
|
+
const raw = fs5.readFileSync(pidFile, "utf8");
|
|
533
767
|
const parsed = JSON.parse(raw);
|
|
534
768
|
if (!Number.isInteger(parsed.pid) || typeof parsed.startedAt !== "string" || typeof parsed.command !== "string") {
|
|
535
769
|
return null;
|
|
@@ -541,7 +775,9 @@ function readGatewayPidRecord(pidFile = getGatewayPidPath()) {
|
|
|
541
775
|
return {
|
|
542
776
|
pid,
|
|
543
777
|
startedAt: parsed.startedAt,
|
|
544
|
-
command: parsed.command
|
|
778
|
+
command: parsed.command,
|
|
779
|
+
...typeof parsed.logFile === "string" ? { logFile: parsed.logFile } : {},
|
|
780
|
+
...parsed.mode === "gateway" || parsed.mode === "web" ? { mode: parsed.mode } : {}
|
|
545
781
|
};
|
|
546
782
|
} catch (error) {
|
|
547
783
|
if (error.code === "ENOENT") {
|
|
@@ -555,13 +791,13 @@ function writeGatewayPidRecord(pidFile = getGatewayPidPath(), record = {
|
|
|
555
791
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
556
792
|
command: process.argv.join(" ")
|
|
557
793
|
}) {
|
|
558
|
-
|
|
559
|
-
|
|
794
|
+
fs5.mkdirSync(path8.dirname(pidFile), { recursive: true });
|
|
795
|
+
fs5.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}
|
|
560
796
|
`, "utf8");
|
|
561
797
|
}
|
|
562
798
|
function removeGatewayPidRecord(pidFile = getGatewayPidPath()) {
|
|
563
799
|
try {
|
|
564
|
-
|
|
800
|
+
fs5.rmSync(pidFile, { force: true });
|
|
565
801
|
} catch {
|
|
566
802
|
}
|
|
567
803
|
}
|
|
@@ -613,6 +849,28 @@ function stopGatewayProcess(pidFile = getGatewayPidPath()) {
|
|
|
613
849
|
|
|
614
850
|
// src/gateway/index.ts
|
|
615
851
|
function getGatewayStatus(config, secrets) {
|
|
852
|
+
const runtime = getGatewayRuntimeState();
|
|
853
|
+
const configured = Boolean(config.feishu.appId && (!secrets || secrets.feishu.appSecret));
|
|
854
|
+
if (runtime.running && runtime.record) {
|
|
855
|
+
if (runtime.record.mode === "web" && !configured) {
|
|
856
|
+
return {
|
|
857
|
+
configured,
|
|
858
|
+
connection: "running",
|
|
859
|
+
message: `\u672C\u5730 Web UI \u8FDB\u7A0B\u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}\uFF1B\u98DE\u4E66\u914D\u7F6E\u5C1A\u672A\u5B8C\u6210\u3002`,
|
|
860
|
+
pid: runtime.record.pid,
|
|
861
|
+
pidFile: runtime.pidFile,
|
|
862
|
+
logFile: runtime.record.logFile
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
configured: true,
|
|
867
|
+
connection: "running",
|
|
868
|
+
message: `\u98DE\u4E66 Gateway \u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}`,
|
|
869
|
+
pid: runtime.record.pid,
|
|
870
|
+
pidFile: runtime.pidFile,
|
|
871
|
+
logFile: runtime.record.logFile
|
|
872
|
+
};
|
|
873
|
+
}
|
|
616
874
|
if (!config.feishu.appId) {
|
|
617
875
|
return {
|
|
618
876
|
configured: false,
|
|
@@ -627,23 +885,14 @@ function getGatewayStatus(config, secrets) {
|
|
|
627
885
|
message: "\u5C1A\u672A\u914D\u7F6E\u98DE\u4E66 App Secret\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002"
|
|
628
886
|
};
|
|
629
887
|
}
|
|
630
|
-
const runtime = getGatewayRuntimeState();
|
|
631
|
-
if (runtime.running && runtime.record) {
|
|
632
|
-
return {
|
|
633
|
-
configured: true,
|
|
634
|
-
connection: "running",
|
|
635
|
-
message: `\u98DE\u4E66 Gateway \u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}`,
|
|
636
|
-
pid: runtime.record.pid,
|
|
637
|
-
pidFile: runtime.pidFile
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
888
|
if (runtime.stale && runtime.record) {
|
|
641
889
|
return {
|
|
642
890
|
configured: true,
|
|
643
891
|
connection: "ready_for_start",
|
|
644
892
|
message: `\u98DE\u4E66\u957F\u8FDE\u63A5\u914D\u7F6E\u5DF2\u5C31\u7EEA\uFF1B\u53D1\u73B0\u8FC7\u671F PID \u6587\u4EF6\uFF1Apid=${runtime.record.pid}\u3002\u8FD0\u884C chattercatcher gateway start \u4F1A\u8986\u76D6\u8FD0\u884C\u8BB0\u5F55\u3002`,
|
|
645
893
|
pid: runtime.record.pid,
|
|
646
|
-
pidFile: runtime.pidFile
|
|
894
|
+
pidFile: runtime.pidFile,
|
|
895
|
+
logFile: runtime.record.logFile
|
|
647
896
|
};
|
|
648
897
|
}
|
|
649
898
|
return {
|
|
@@ -1140,82 +1389,6 @@ var MessageFtsRetriever = class {
|
|
|
1140
1389
|
}
|
|
1141
1390
|
};
|
|
1142
1391
|
|
|
1143
|
-
// src/rag/embedding.ts
|
|
1144
|
-
function cosineSimilarity(left, right) {
|
|
1145
|
-
if (left.length === 0 || right.length === 0 || left.length !== right.length) {
|
|
1146
|
-
return 0;
|
|
1147
|
-
}
|
|
1148
|
-
let dot = 0;
|
|
1149
|
-
let leftNorm = 0;
|
|
1150
|
-
let rightNorm = 0;
|
|
1151
|
-
for (let index2 = 0; index2 < left.length; index2 += 1) {
|
|
1152
|
-
const leftValue = left[index2] ?? 0;
|
|
1153
|
-
const rightValue = right[index2] ?? 0;
|
|
1154
|
-
dot += leftValue * rightValue;
|
|
1155
|
-
leftNorm += leftValue * leftValue;
|
|
1156
|
-
rightNorm += rightValue * rightValue;
|
|
1157
|
-
}
|
|
1158
|
-
if (leftNorm === 0 || rightNorm === 0) {
|
|
1159
|
-
return 0;
|
|
1160
|
-
}
|
|
1161
|
-
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// src/rag/sqlite-vector-store.ts
|
|
1165
|
-
var SQLiteVectorStore = class {
|
|
1166
|
-
constructor(database) {
|
|
1167
|
-
this.database = database;
|
|
1168
|
-
}
|
|
1169
|
-
database;
|
|
1170
|
-
async upsert(records) {
|
|
1171
|
-
if (records.length === 0) {
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
const statement = this.database.prepare(`
|
|
1175
|
-
INSERT INTO message_chunk_vectors (chunk_id, vector_json, evidence_json, updated_at)
|
|
1176
|
-
VALUES (@chunkId, @vectorJson, @evidenceJson, @updatedAt)
|
|
1177
|
-
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
1178
|
-
vector_json = excluded.vector_json,
|
|
1179
|
-
evidence_json = excluded.evidence_json,
|
|
1180
|
-
updated_at = excluded.updated_at
|
|
1181
|
-
`);
|
|
1182
|
-
const upsertMany = this.database.transaction((items) => {
|
|
1183
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1184
|
-
for (const item of items) {
|
|
1185
|
-
statement.run({
|
|
1186
|
-
chunkId: item.id,
|
|
1187
|
-
vectorJson: JSON.stringify(item.vector),
|
|
1188
|
-
evidenceJson: JSON.stringify(item.evidence),
|
|
1189
|
-
updatedAt
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
});
|
|
1193
|
-
upsertMany(records);
|
|
1194
|
-
}
|
|
1195
|
-
async search(vector, limit) {
|
|
1196
|
-
if (limit <= 0) {
|
|
1197
|
-
return [];
|
|
1198
|
-
}
|
|
1199
|
-
const rows = this.database.prepare("SELECT chunk_id, vector_json, evidence_json FROM message_chunk_vectors").all();
|
|
1200
|
-
return rows.map((row) => {
|
|
1201
|
-
const storedVector = JSON.parse(row.vector_json);
|
|
1202
|
-
const evidence = JSON.parse(row.evidence_json);
|
|
1203
|
-
const vectorScore = cosineSimilarity(vector, storedVector);
|
|
1204
|
-
return {
|
|
1205
|
-
...evidence,
|
|
1206
|
-
score: vectorScore,
|
|
1207
|
-
vectorScore
|
|
1208
|
-
};
|
|
1209
|
-
}).sort((left, right) => right.vectorScore - left.vectorScore).slice(0, limit);
|
|
1210
|
-
}
|
|
1211
|
-
async count() {
|
|
1212
|
-
const row = this.database.prepare("SELECT COUNT(*) AS count FROM message_chunk_vectors").get();
|
|
1213
|
-
return row.count;
|
|
1214
|
-
}
|
|
1215
|
-
close() {
|
|
1216
|
-
}
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
1392
|
// src/rag/vector-retriever.ts
|
|
1220
1393
|
var VectorRetriever = class {
|
|
1221
1394
|
constructor(embedding, store, limit = 8) {
|
|
@@ -1240,7 +1413,8 @@ async function createHybridRetriever(input2) {
|
|
|
1240
1413
|
const retrievers = [new MessageFtsRetriever(input2.messages, { excludeMessageIds: input2.excludeMessageIds })];
|
|
1241
1414
|
const closers = [];
|
|
1242
1415
|
if (hasEmbeddingConfig(input2.config, input2.secrets)) {
|
|
1243
|
-
const
|
|
1416
|
+
const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
1417
|
+
const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
|
|
1244
1418
|
retrievers.push(new VectorRetriever(createEmbeddingModel(input2.config, input2.secrets), vectorStore));
|
|
1245
1419
|
closers.push(() => vectorStore.close());
|
|
1246
1420
|
}
|
|
@@ -1272,7 +1446,7 @@ async function runDoctor(config, secrets, options = {}) {
|
|
|
1272
1446
|
checks.push(checkEmbeddingConfig(config, secrets));
|
|
1273
1447
|
checks.push(await checkSqlite(config));
|
|
1274
1448
|
checks.push(await checkFilePipeline(config));
|
|
1275
|
-
checks.push(await
|
|
1449
|
+
checks.push(await checkLanceDb(config));
|
|
1276
1450
|
checks.push(checkRagPolicy());
|
|
1277
1451
|
if (options.online) {
|
|
1278
1452
|
checks.push(await checkChatModel(config, secrets));
|
|
@@ -1283,8 +1457,8 @@ async function runDoctor(config, secrets, options = {}) {
|
|
|
1283
1457
|
async function checkHomeDirectory() {
|
|
1284
1458
|
const home = getChatterCatcherHome();
|
|
1285
1459
|
try {
|
|
1286
|
-
await
|
|
1287
|
-
await
|
|
1460
|
+
await fs7.mkdir(home, { recursive: true });
|
|
1461
|
+
await fs7.access(home);
|
|
1288
1462
|
return pass("\u914D\u7F6E\u76EE\u5F55", home);
|
|
1289
1463
|
} catch (error) {
|
|
1290
1464
|
return fail("\u914D\u7F6E\u76EE\u5F55", error instanceof Error ? error.message : String(error));
|
|
@@ -1305,7 +1479,7 @@ function checkLlmConfig(config, secrets) {
|
|
|
1305
1479
|
}
|
|
1306
1480
|
function checkEmbeddingConfig(config, secrets) {
|
|
1307
1481
|
if (!hasEmbeddingConfig(config, secrets)) {
|
|
1308
|
-
return warn("Embedding \u914D\u7F6E", "\u672A\u914D\u7F6E\u5B8C\u6574\uFF1BRAG \u4F1A\u4F7F\u7528 SQLite FTS\uFF0C\u65E0\u6CD5\u4F7F\u7528
|
|
1482
|
+
return warn("Embedding \u914D\u7F6E", "\u672A\u914D\u7F6E\u5B8C\u6574\uFF1BRAG \u4F1A\u4F7F\u7528 SQLite FTS\uFF0C\u65E0\u6CD5\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\u3002");
|
|
1309
1483
|
}
|
|
1310
1484
|
return pass("Embedding \u914D\u7F6E", `${config.embedding.model} @ ${config.embedding.baseUrl || config.llm.baseUrl}`);
|
|
1311
1485
|
}
|
|
@@ -1339,17 +1513,17 @@ async function checkFilePipeline(config) {
|
|
|
1339
1513
|
database?.close();
|
|
1340
1514
|
}
|
|
1341
1515
|
}
|
|
1342
|
-
async function
|
|
1343
|
-
let
|
|
1516
|
+
async function checkLanceDb(config) {
|
|
1517
|
+
let store = null;
|
|
1344
1518
|
try {
|
|
1345
|
-
|
|
1346
|
-
|
|
1519
|
+
const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
1520
|
+
store = await LanceDbVectorStore2.connectFromConfig(config);
|
|
1347
1521
|
const count = await store.count();
|
|
1348
|
-
return pass("
|
|
1522
|
+
return pass("LanceDB", `${getLanceDbPath2(config)}\uFF1Bvectors=${count}`);
|
|
1349
1523
|
} catch (error) {
|
|
1350
|
-
return fail("
|
|
1524
|
+
return fail("LanceDB", error instanceof Error ? error.message : String(error));
|
|
1351
1525
|
} finally {
|
|
1352
|
-
|
|
1526
|
+
store?.close();
|
|
1353
1527
|
}
|
|
1354
1528
|
}
|
|
1355
1529
|
function checkRagPolicy() {
|
|
@@ -1390,8 +1564,9 @@ function formatDoctorChecks(checks) {
|
|
|
1390
1564
|
}
|
|
1391
1565
|
|
|
1392
1566
|
// src/export/data-export.ts
|
|
1393
|
-
|
|
1394
|
-
import
|
|
1567
|
+
init_paths();
|
|
1568
|
+
import fs8 from "fs/promises";
|
|
1569
|
+
import path10 from "path";
|
|
1395
1570
|
function parseJsonObject(value) {
|
|
1396
1571
|
try {
|
|
1397
1572
|
const parsed = JSON.parse(value);
|
|
@@ -1410,11 +1585,11 @@ function parseJsonArray(value) {
|
|
|
1410
1585
|
}
|
|
1411
1586
|
function defaultExportPath(config, exportedAt) {
|
|
1412
1587
|
const fileName = `chattercatcher-export-${exportedAt.replace(/[:.]/g, "-")}.json`;
|
|
1413
|
-
return
|
|
1588
|
+
return path10.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
|
|
1414
1589
|
}
|
|
1415
1590
|
async function exportLocalData(input2) {
|
|
1416
1591
|
const exportedAt = input2.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1417
|
-
const outputPath =
|
|
1592
|
+
const outputPath = path10.resolve(input2.outputPath ?? defaultExportPath(input2.config, exportedAt));
|
|
1418
1593
|
const chats = input2.database.prepare(
|
|
1419
1594
|
`
|
|
1420
1595
|
SELECT
|
|
@@ -1501,8 +1676,8 @@ async function exportLocalData(input2) {
|
|
|
1501
1676
|
fileJobs
|
|
1502
1677
|
}
|
|
1503
1678
|
};
|
|
1504
|
-
await
|
|
1505
|
-
await
|
|
1679
|
+
await fs8.mkdir(path10.dirname(outputPath), { recursive: true });
|
|
1680
|
+
await fs8.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
|
|
1506
1681
|
`, "utf8");
|
|
1507
1682
|
return {
|
|
1508
1683
|
outputPath,
|
|
@@ -1514,8 +1689,8 @@ async function exportLocalData(input2) {
|
|
|
1514
1689
|
}
|
|
1515
1690
|
|
|
1516
1691
|
// src/export/data-restore.ts
|
|
1517
|
-
import
|
|
1518
|
-
import
|
|
1692
|
+
import fs9 from "fs/promises";
|
|
1693
|
+
import path11 from "path";
|
|
1519
1694
|
function asObject(value) {
|
|
1520
1695
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1521
1696
|
}
|
|
@@ -1563,8 +1738,8 @@ function clearDatabase(database) {
|
|
|
1563
1738
|
database.prepare("DELETE FROM chats").run();
|
|
1564
1739
|
}
|
|
1565
1740
|
async function restoreLocalData(input2) {
|
|
1566
|
-
const inputPath =
|
|
1567
|
-
const payload = parsePayload(await
|
|
1741
|
+
const inputPath = path11.resolve(input2.inputPath);
|
|
1742
|
+
const payload = parsePayload(await fs9.readFile(inputPath, "utf8"));
|
|
1568
1743
|
const mode = input2.replace ? "replace" : "merge";
|
|
1569
1744
|
const restore = input2.database.transaction(() => {
|
|
1570
1745
|
if (input2.replace) {
|
|
@@ -2156,9 +2331,10 @@ function createFeishuGateway(options) {
|
|
|
2156
2331
|
}
|
|
2157
2332
|
|
|
2158
2333
|
// src/feishu/resource-downloader.ts
|
|
2334
|
+
init_paths();
|
|
2159
2335
|
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
2160
|
-
import
|
|
2161
|
-
import
|
|
2336
|
+
import fs10 from "fs/promises";
|
|
2337
|
+
import path12 from "path";
|
|
2162
2338
|
var RESOURCE_TYPE_BY_KIND = {
|
|
2163
2339
|
file: "file",
|
|
2164
2340
|
image: "image",
|
|
@@ -2196,10 +2372,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
2196
2372
|
}
|
|
2197
2373
|
async download(input2) {
|
|
2198
2374
|
const resourceType = RESOURCE_TYPE_BY_KIND[input2.attachment.kind];
|
|
2199
|
-
const targetDir =
|
|
2200
|
-
await
|
|
2375
|
+
const targetDir = path12.join(this.dataDir, "files", "feishu");
|
|
2376
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
2201
2377
|
const fileName = buildStoredFileName(input2);
|
|
2202
|
-
const storedPath =
|
|
2378
|
+
const storedPath = path12.join(targetDir, fileName);
|
|
2203
2379
|
const payload = {
|
|
2204
2380
|
params: { type: resourceType },
|
|
2205
2381
|
path: { message_id: input2.messageId, file_key: input2.attachment.fileKey }
|
|
@@ -2221,30 +2397,31 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
2221
2397
|
};
|
|
2222
2398
|
|
|
2223
2399
|
// src/files/ingest.ts
|
|
2400
|
+
init_paths();
|
|
2224
2401
|
import crypto3 from "crypto";
|
|
2225
|
-
import
|
|
2226
|
-
import
|
|
2402
|
+
import fs12 from "fs/promises";
|
|
2403
|
+
import path14 from "path";
|
|
2227
2404
|
|
|
2228
2405
|
// src/files/parser.ts
|
|
2229
|
-
import
|
|
2230
|
-
import
|
|
2406
|
+
import fs11 from "fs/promises";
|
|
2407
|
+
import path13 from "path";
|
|
2231
2408
|
import mammoth from "mammoth";
|
|
2232
2409
|
import { PDFParse } from "pdf-parse";
|
|
2233
2410
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
|
|
2234
2411
|
var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
|
|
2235
2412
|
var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
|
|
2236
2413
|
function isSupportedParseFile(filePath) {
|
|
2237
|
-
const extension =
|
|
2414
|
+
const extension = path13.extname(filePath).toLowerCase();
|
|
2238
2415
|
return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
|
|
2239
2416
|
}
|
|
2240
2417
|
function describeSupportedParseTypes() {
|
|
2241
2418
|
return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
|
|
2242
2419
|
}
|
|
2243
2420
|
async function parseFileToText(filePath) {
|
|
2244
|
-
const extension =
|
|
2421
|
+
const extension = path13.extname(filePath).toLowerCase();
|
|
2245
2422
|
if (TEXT_EXTENSIONS.has(extension)) {
|
|
2246
2423
|
return {
|
|
2247
|
-
text: await
|
|
2424
|
+
text: await fs11.readFile(filePath, "utf8"),
|
|
2248
2425
|
parser: "text",
|
|
2249
2426
|
warnings: []
|
|
2250
2427
|
};
|
|
@@ -2258,7 +2435,7 @@ async function parseFileToText(filePath) {
|
|
|
2258
2435
|
};
|
|
2259
2436
|
}
|
|
2260
2437
|
if (PDF_EXTENSIONS.has(extension)) {
|
|
2261
|
-
const buffer = await
|
|
2438
|
+
const buffer = await fs11.readFile(filePath);
|
|
2262
2439
|
const parser = new PDFParse({ data: buffer });
|
|
2263
2440
|
try {
|
|
2264
2441
|
const result = await parser.getText();
|
|
@@ -2280,7 +2457,7 @@ function isSupportedTextFile(filePath) {
|
|
|
2280
2457
|
}
|
|
2281
2458
|
function ensureSupportedTextFile(filePath) {
|
|
2282
2459
|
if (!isSupportedTextFile(filePath)) {
|
|
2283
|
-
const extension =
|
|
2460
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
2284
2461
|
throw new Error(`\u6682\u4E0D\u652F\u6301\u8BE5\u6587\u4EF6\u7C7B\u578B\uFF1A${extension || "\u65E0\u6269\u5C55\u540D"}\u3002\u5F53\u524D\u652F\u6301 ${describeSupportedParseTypes()}\u3002`);
|
|
2285
2462
|
}
|
|
2286
2463
|
}
|
|
@@ -2289,12 +2466,12 @@ function stableStoredName(sourcePath, fileName) {
|
|
|
2289
2466
|
return `${digest}-${fileName}`;
|
|
2290
2467
|
}
|
|
2291
2468
|
async function ingestLocalFile(input2) {
|
|
2292
|
-
const sourcePath =
|
|
2293
|
-
const fileName =
|
|
2469
|
+
const sourcePath = path14.resolve(input2.filePath);
|
|
2470
|
+
const fileName = path14.basename(sourcePath);
|
|
2294
2471
|
const jobId = input2.jobs?.start({ sourcePath, fileName });
|
|
2295
2472
|
try {
|
|
2296
2473
|
ensureSupportedTextFile(sourcePath);
|
|
2297
|
-
const stat = await
|
|
2474
|
+
const stat = await fs12.stat(sourcePath);
|
|
2298
2475
|
if (!stat.isFile()) {
|
|
2299
2476
|
throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
|
|
2300
2477
|
}
|
|
@@ -2303,10 +2480,10 @@ async function ingestLocalFile(input2) {
|
|
|
2303
2480
|
if (!text) {
|
|
2304
2481
|
throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
|
|
2305
2482
|
}
|
|
2306
|
-
const fileDir =
|
|
2307
|
-
await
|
|
2308
|
-
const storedPath =
|
|
2309
|
-
await
|
|
2483
|
+
const fileDir = path14.join(resolveHomePath(input2.config.storage.dataDir), "files");
|
|
2484
|
+
await fs12.mkdir(fileDir, { recursive: true });
|
|
2485
|
+
const storedPath = path14.join(fileDir, stableStoredName(sourcePath, fileName));
|
|
2486
|
+
await fs12.copyFile(sourcePath, storedPath);
|
|
2310
2487
|
const messageId = input2.messages.ingest({
|
|
2311
2488
|
platform: "local-file",
|
|
2312
2489
|
platformChatId: "local-files",
|
|
@@ -2587,107 +2764,72 @@ var GatewayIngestor = class {
|
|
|
2587
2764
|
}
|
|
2588
2765
|
};
|
|
2589
2766
|
|
|
2590
|
-
// src/
|
|
2591
|
-
import
|
|
2592
|
-
import
|
|
2593
|
-
import
|
|
2594
|
-
function
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 1e4) : fallback;
|
|
2603
|
-
}
|
|
2604
|
-
async function listLogFiles(logsDir = getLogsDirectory()) {
|
|
2605
|
-
let entries;
|
|
2606
|
-
try {
|
|
2607
|
-
entries = await fs11.readdir(logsDir, { withFileTypes: true });
|
|
2608
|
-
} catch (error) {
|
|
2609
|
-
if (error.code === "ENOENT") {
|
|
2610
|
-
return [];
|
|
2611
|
-
}
|
|
2612
|
-
throw error;
|
|
2767
|
+
// src/gateway/detached.ts
|
|
2768
|
+
import { spawn } from "child_process";
|
|
2769
|
+
import fs13 from "fs";
|
|
2770
|
+
import path15 from "path";
|
|
2771
|
+
function buildGatewayForegroundSpawnCommand(argv = process.argv) {
|
|
2772
|
+
const [command = process.execPath, ...rawArgs] = argv;
|
|
2773
|
+
const args = [...rawArgs];
|
|
2774
|
+
while (args.at(-1) === "--foreground") {
|
|
2775
|
+
args.pop();
|
|
2776
|
+
}
|
|
2777
|
+
if (args.at(-1) === "start" && args.at(-2) === "gateway") {
|
|
2778
|
+
args.splice(-2, 2);
|
|
2613
2779
|
}
|
|
2614
|
-
const files2 = await Promise.all(
|
|
2615
|
-
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
|
|
2616
|
-
const filePath = path13.join(logsDir, entry.name);
|
|
2617
|
-
const stats = await fs11.stat(filePath);
|
|
2618
|
-
return {
|
|
2619
|
-
name: entry.name,
|
|
2620
|
-
path: filePath,
|
|
2621
|
-
updatedAt: stats.mtime,
|
|
2622
|
-
bytes: stats.size
|
|
2623
|
-
};
|
|
2624
|
-
})
|
|
2625
|
-
);
|
|
2626
|
-
return files2.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
|
|
2627
|
-
}
|
|
2628
|
-
function tailLines(content, lines) {
|
|
2629
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
2630
|
-
const parts = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
|
|
2631
|
-
return parts.slice(-lines).join("\n");
|
|
2632
|
-
}
|
|
2633
|
-
async function readLogTail(input2) {
|
|
2634
|
-
const stats = await fs11.stat(input2.filePath);
|
|
2635
|
-
const content = await fs11.readFile(input2.filePath, "utf8");
|
|
2636
2780
|
return {
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
path: input2.filePath,
|
|
2640
|
-
updatedAt: stats.mtime,
|
|
2641
|
-
bytes: stats.size
|
|
2642
|
-
},
|
|
2643
|
-
content: tailLines(content, normalizeLineCount(input2.lines))
|
|
2781
|
+
command,
|
|
2782
|
+
args: [...args, "gateway", "start", "--foreground"]
|
|
2644
2783
|
};
|
|
2645
2784
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2785
|
+
function startDetachedGateway(input2) {
|
|
2786
|
+
const status = getGatewayStatus(input2.config, input2.secrets);
|
|
2787
|
+
const logFile = getGatewayLogPath();
|
|
2788
|
+
if (status.connection === "running") {
|
|
2789
|
+
return {
|
|
2790
|
+
started: false,
|
|
2791
|
+
message: `\u98DE\u4E66 Gateway \u5DF2\u7ECF\u6B63\u5728\u8FD0\u884C\uFF1Apid=${status.pid ?? "unknown"}`,
|
|
2792
|
+
logFile,
|
|
2793
|
+
...status.pid ? { pid: status.pid } : {}
|
|
2794
|
+
};
|
|
2656
2795
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
let
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2663
|
-
async function readAppended() {
|
|
2664
|
-
const stats = await fs11.stat(input2.filePath);
|
|
2665
|
-
if (stats.size < offset) {
|
|
2666
|
-
offset = 0;
|
|
2667
|
-
}
|
|
2668
|
-
if (stats.size === offset) {
|
|
2796
|
+
fs13.mkdirSync(path15.dirname(logFile), { recursive: true });
|
|
2797
|
+
let out;
|
|
2798
|
+
let err;
|
|
2799
|
+
let stdioClosed = false;
|
|
2800
|
+
const closeStdio = () => {
|
|
2801
|
+
if (stdioClosed) {
|
|
2669
2802
|
return;
|
|
2670
2803
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
const buffer = Buffer.alloc(length);
|
|
2675
|
-
await handle.read(buffer, 0, length, offset);
|
|
2676
|
-
offset = stats.size;
|
|
2677
|
-
input2.onChunk(buffer.toString("utf8"));
|
|
2678
|
-
} finally {
|
|
2679
|
-
await handle.close();
|
|
2804
|
+
stdioClosed = true;
|
|
2805
|
+
if (typeof out === "number") {
|
|
2806
|
+
fs13.closeSync(out);
|
|
2680
2807
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
if (eventType !== "change" || changedFileName?.toString() !== fileName) {
|
|
2684
|
-
return;
|
|
2808
|
+
if (typeof err === "number") {
|
|
2809
|
+
fs13.closeSync(err);
|
|
2685
2810
|
}
|
|
2686
|
-
|
|
2687
|
-
|
|
2811
|
+
};
|
|
2812
|
+
try {
|
|
2813
|
+
out = fs13.openSync(logFile, "a");
|
|
2814
|
+
err = fs13.openSync(logFile, "a");
|
|
2815
|
+
const foreground = buildGatewayForegroundSpawnCommand(input2.argv);
|
|
2816
|
+
const child = spawn(foreground.command, foreground.args, {
|
|
2817
|
+
detached: true,
|
|
2818
|
+
stdio: ["ignore", out, err],
|
|
2819
|
+
windowsHide: true
|
|
2688
2820
|
});
|
|
2689
|
-
|
|
2690
|
-
|
|
2821
|
+
closeStdio();
|
|
2822
|
+
child.unref();
|
|
2823
|
+
return {
|
|
2824
|
+
started: true,
|
|
2825
|
+
message: `\u5DF2\u5728\u540E\u53F0\u542F\u52A8\u98DE\u4E66 Gateway\uFF1Apid=${child.pid}`,
|
|
2826
|
+
pid: child.pid,
|
|
2827
|
+
logFile
|
|
2828
|
+
};
|
|
2829
|
+
} catch (error) {
|
|
2830
|
+
closeStdio();
|
|
2831
|
+
throw error;
|
|
2832
|
+
}
|
|
2691
2833
|
}
|
|
2692
2834
|
|
|
2693
2835
|
// src/rag/indexer.ts
|
|
@@ -2749,7 +2891,8 @@ async function processMessagesNow(input2) {
|
|
|
2749
2891
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2750
2892
|
};
|
|
2751
2893
|
}
|
|
2752
|
-
const
|
|
2894
|
+
const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
2895
|
+
const vectorStore = await LanceDbVectorStore2.connectFromConfig(input2.config);
|
|
2753
2896
|
try {
|
|
2754
2897
|
const stats = await indexMessageChunks({
|
|
2755
2898
|
messages: new MessageRepository(input2.database),
|
|
@@ -3166,7 +3309,7 @@ function createWebApp(config) {
|
|
|
3166
3309
|
note: "\u95EE\u7B54\u5FC5\u987B\u5148\u68C0\u7D22\u8BC1\u636E\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0\u3002",
|
|
3167
3310
|
retrieval: {
|
|
3168
3311
|
keyword: "SQLite FTS5",
|
|
3169
|
-
vector: "
|
|
3312
|
+
vector: "LanceDB",
|
|
3170
3313
|
hybrid: true
|
|
3171
3314
|
}
|
|
3172
3315
|
},
|
|
@@ -3286,7 +3429,7 @@ function printSettings(config, secrets) {
|
|
|
3286
3429
|
2
|
|
3287
3430
|
));
|
|
3288
3431
|
}
|
|
3289
|
-
program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.
|
|
3432
|
+
program.name("chattercatcher").description("\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u673A\u5668\u4EBA").version("0.1.5");
|
|
3290
3433
|
program.command("setup").description("\u4EA4\u4E92\u5F0F\u521D\u59CB\u5316\u914D\u7F6E").action(async () => {
|
|
3291
3434
|
const { config, secrets } = await ensureConfigFiles();
|
|
3292
3435
|
await promptForConfiguration(config, secrets);
|
|
@@ -3323,18 +3466,33 @@ program.command("doctor").description("\u68C0\u67E5\u672C\u5730\u914D\u7F6E\u300
|
|
|
3323
3466
|
console.log(formatDoctorChecks(checks));
|
|
3324
3467
|
});
|
|
3325
3468
|
var gateway = program.command("gateway").description("\u7BA1\u7406\u672C\u5730\u98DE\u4E66 Gateway");
|
|
3326
|
-
async function
|
|
3469
|
+
async function startGatewayForegroundCommand() {
|
|
3327
3470
|
const config = await loadConfig();
|
|
3328
3471
|
const secrets = await loadSecrets();
|
|
3329
3472
|
const status = getGatewayStatus(config, secrets);
|
|
3473
|
+
const pidRecordBase = {
|
|
3474
|
+
pid: process.pid,
|
|
3475
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3476
|
+
command: process.argv.join(" "),
|
|
3477
|
+
logFile: getGatewayLogPath()
|
|
3478
|
+
};
|
|
3330
3479
|
if (!status.configured) {
|
|
3480
|
+
writeGatewayPidRecord(void 0, {
|
|
3481
|
+
...pidRecordBase,
|
|
3482
|
+
mode: "web"
|
|
3483
|
+
});
|
|
3331
3484
|
console.log(status.message);
|
|
3332
3485
|
console.log("\u672C\u5730 Web UI \u4ECD\u4F1A\u542F\u52A8\uFF0C\u65B9\u4FBF\u7EE7\u7EED\u914D\u7F6E\u3002");
|
|
3333
3486
|
await startWebServer(config);
|
|
3334
3487
|
return;
|
|
3335
3488
|
}
|
|
3489
|
+
writeGatewayPidRecord(void 0, {
|
|
3490
|
+
...pidRecordBase,
|
|
3491
|
+
mode: "gateway"
|
|
3492
|
+
});
|
|
3336
3493
|
const database = openDatabase(config);
|
|
3337
|
-
const
|
|
3494
|
+
const { LanceDbVectorStore: LanceDbVectorStore2 } = hasEmbeddingConfig(config, secrets) ? await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports)) : { LanceDbVectorStore: null };
|
|
3495
|
+
const vectorStore = LanceDbVectorStore2 ? await LanceDbVectorStore2.connectFromConfig(config) : null;
|
|
3338
3496
|
const gatewayRuntime = createFeishuGateway({
|
|
3339
3497
|
config,
|
|
3340
3498
|
secrets,
|
|
@@ -3369,7 +3527,6 @@ async function startGatewayCommand() {
|
|
|
3369
3527
|
process.exit(0);
|
|
3370
3528
|
});
|
|
3371
3529
|
console.log(status.message);
|
|
3372
|
-
writeGatewayPidRecord();
|
|
3373
3530
|
try {
|
|
3374
3531
|
await gatewayRuntime.start();
|
|
3375
3532
|
await startWebServer(config);
|
|
@@ -3378,12 +3535,28 @@ async function startGatewayCommand() {
|
|
|
3378
3535
|
throw error;
|
|
3379
3536
|
}
|
|
3380
3537
|
}
|
|
3538
|
+
async function startGatewayCommand(options = {}) {
|
|
3539
|
+
if (options.foreground) {
|
|
3540
|
+
await startGatewayForegroundCommand();
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
const config = await loadConfig();
|
|
3544
|
+
const secrets = await loadSecrets();
|
|
3545
|
+
const result = startDetachedGateway({ config, secrets });
|
|
3546
|
+
console.log(result.message);
|
|
3547
|
+
if (result.pid) {
|
|
3548
|
+
console.log(`PID\uFF1A${result.pid}`);
|
|
3549
|
+
}
|
|
3550
|
+
console.log(`\u65E5\u5FD7\u6587\u4EF6\uFF1A${result.logFile}`);
|
|
3551
|
+
console.log("\u67E5\u770B\u65E5\u5FD7\uFF1Achattercatcher logs --follow --file gateway.log");
|
|
3552
|
+
console.log("\u505C\u6B62 Gateway\uFF1Achattercatcher gateway stop");
|
|
3553
|
+
}
|
|
3381
3554
|
gateway.command("status").description("\u67E5\u770B Gateway \u72B6\u6001").action(async () => {
|
|
3382
3555
|
const config = await loadConfig();
|
|
3383
3556
|
const secrets = await loadSecrets();
|
|
3384
3557
|
console.log(JSON.stringify(getGatewayStatus(config, secrets), null, 2));
|
|
3385
3558
|
});
|
|
3386
|
-
gateway.command("start").description("\u542F\u52A8\u98DE\u4E66\u957F\u8FDE\u63A5 Gateway \u548C\u672C\u5730 Web UI").action(startGatewayCommand);
|
|
3559
|
+
gateway.command("start").description("\u542F\u52A8\u98DE\u4E66\u957F\u8FDE\u63A5 Gateway \u548C\u672C\u5730 Web UI").option("--foreground", "\u5728\u5F53\u524D\u7EC8\u7AEF\u4EE5\u524D\u53F0\u6A21\u5F0F\u8FD0\u884C").action(startGatewayCommand);
|
|
3387
3560
|
gateway.command("stop").description("\u505C\u6B62 Gateway").action(() => {
|
|
3388
3561
|
console.log(stopGatewayProcess().message);
|
|
3389
3562
|
});
|
|
@@ -3419,7 +3592,7 @@ async function deleteDataCommand(targetType, targetId, options) {
|
|
|
3419
3592
|
if (result.skippedStoredFiles.length > 0) {
|
|
3420
3593
|
console.log(`\u8DF3\u8FC7\u975E\u6570\u636E\u76EE\u5F55\u6587\u4EF6\uFF1A${result.skippedStoredFiles.join("\uFF1B")}`);
|
|
3421
3594
|
}
|
|
3422
|
-
console.log("SQLite FTS \u5DF2\u540C\u6B65\u5220\u9664\uFF1B\u5982\u4F7F\u7528\u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild
|
|
3595
|
+
console.log("SQLite FTS \u5DF2\u540C\u6B65\u5220\u9664\uFF1B\u5982\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild\u3002");
|
|
3423
3596
|
} finally {
|
|
3424
3597
|
database.close();
|
|
3425
3598
|
}
|
|
@@ -3434,19 +3607,20 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
|
|
|
3434
3607
|
const secrets = await loadSecrets();
|
|
3435
3608
|
const database = openDatabase(config);
|
|
3436
3609
|
const messages = new MessageRepository(database);
|
|
3437
|
-
const
|
|
3610
|
+
const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
3611
|
+
const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
|
|
3438
3612
|
const vectors = await vectorStore.count();
|
|
3439
3613
|
console.log(JSON.stringify(
|
|
3440
3614
|
{
|
|
3441
3615
|
database: getDatabasePath(config),
|
|
3442
|
-
vectorDatabase:
|
|
3616
|
+
vectorDatabase: getLanceDbPath2(config),
|
|
3443
3617
|
chats: messages.getChatCount(),
|
|
3444
3618
|
messages: messages.getMessageCount(),
|
|
3445
3619
|
vectors,
|
|
3446
3620
|
retrieval: {
|
|
3447
3621
|
keyword: "SQLite FTS5",
|
|
3448
|
-
vector: hasEmbeddingConfig(config, secrets) ? "
|
|
3449
|
-
hybrid: "\u542F\u7528\uFF1ASQLite FTS +
|
|
3622
|
+
vector: hasEmbeddingConfig(config, secrets) ? "LanceDB \u5DF2\u53EF\u7528\u4E8E\u8BED\u4E49\u68C0\u7D22" : "LanceDB \u5DF2\u63A5\u5165\uFF1B\u9700\u914D\u7F6E embedding \u540E\u542F\u7528\u8BED\u4E49\u68C0\u7D22",
|
|
3623
|
+
hybrid: "\u542F\u7528\uFF1ASQLite FTS + LanceDB Vector",
|
|
3450
3624
|
rag: "\u5F3A\u5236\u5148\u68C0\u7D22\u8BC1\u636E\u518D\u56DE\u7B54\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0"
|
|
3451
3625
|
}
|
|
3452
3626
|
},
|
|
@@ -3456,7 +3630,7 @@ index.command("status").description("\u67E5\u770B\u7D22\u5F15\u72B6\u6001").acti
|
|
|
3456
3630
|
vectorStore.close();
|
|
3457
3631
|
database.close();
|
|
3458
3632
|
});
|
|
3459
|
-
index.command("rebuild").description("\u91CD\u5EFA
|
|
3633
|
+
index.command("rebuild").description("\u91CD\u5EFA LanceDB \u5411\u91CF\u7D22\u5F15").option("--limit <number>", "\u6700\u591A\u7D22\u5F15\u7684 chunk \u6570", "10000").action(async (options) => {
|
|
3460
3634
|
const config = await loadConfig();
|
|
3461
3635
|
const secrets = await loadSecrets();
|
|
3462
3636
|
if (!hasEmbeddingConfig(config, secrets)) {
|
|
@@ -3464,7 +3638,8 @@ index.command("rebuild").description("\u91CD\u5EFA SQLite \u5411\u91CF\u7D22\u5F
|
|
|
3464
3638
|
return;
|
|
3465
3639
|
}
|
|
3466
3640
|
const database = openDatabase(config);
|
|
3467
|
-
const
|
|
3641
|
+
const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
3642
|
+
const vectorStore = await LanceDbVectorStore2.connectFromConfig(config);
|
|
3468
3643
|
try {
|
|
3469
3644
|
const stats = await indexMessageChunks({
|
|
3470
3645
|
messages: new MessageRepository(database),
|
|
@@ -3479,7 +3654,7 @@ index.command("rebuild").description("\u91CD\u5EFA SQLite \u5411\u91CF\u7D22\u5F
|
|
|
3479
3654
|
}
|
|
3480
3655
|
});
|
|
3481
3656
|
var processCommand = program.command("process").description("\u7ACB\u5373\u5904\u7406\u540E\u53F0\u4EFB\u52A1");
|
|
3482
|
-
processCommand.command("messages").description("\u7ACB\u5373\u5904\u7406\u6D88\u606F\u7D22\u5F15\u4EFB\u52A1\uFF0C\u628A\u6D88\u606F chunks \u5199\u5165
|
|
3657
|
+
processCommand.command("messages").description("\u7ACB\u5373\u5904\u7406\u6D88\u606F\u7D22\u5F15\u4EFB\u52A1\uFF0C\u628A\u6D88\u606F chunks \u5199\u5165 LanceDB \u5411\u91CF\u7D22\u5F15").option("--limit <number>", "\u6700\u591A\u5904\u7406\u7684 chunk \u6570", "10000").action(async (options) => {
|
|
3483
3658
|
const config = await loadConfig();
|
|
3484
3659
|
const secrets = await loadSecrets();
|
|
3485
3660
|
const database = openDatabase(config);
|
|
@@ -3513,7 +3688,7 @@ files.command("add").description("\u628A\u672C\u5730\u6587\u4EF6\u89E3\u6790\u30
|
|
|
3513
3688
|
`\u5DF2\u5BFC\u5165\u6587\u4EF6\uFF1A${result.fileName}\uFF0C\u89E3\u6790\u5668=${result.parser}\uFF0C\u5B57\u7B26\u6570=${result.characters}\uFF0C\u6D88\u606FID=${result.messageId}`
|
|
3514
3689
|
);
|
|
3515
3690
|
}
|
|
3516
|
-
console.log("\u6587\u4EF6\u5DF2\u8FDB\u5165 SQLite FTS \u68C0\u7D22\uFF1B\u5982\u5DF2\u914D\u7F6E embedding\uFF0C\u53EF\u8FD0\u884C chattercatcher index rebuild \u66F4\u65B0
|
|
3691
|
+
console.log("\u6587\u4EF6\u5DF2\u8FDB\u5165 SQLite FTS \u68C0\u7D22\uFF1B\u5982\u5DF2\u914D\u7F6E embedding\uFF0C\u53EF\u8FD0\u884C chattercatcher index rebuild \u66F4\u65B0 LanceDB \u5411\u91CF\u7D22\u5F15\u3002");
|
|
3517
3692
|
} finally {
|
|
3518
3693
|
database.close();
|
|
3519
3694
|
}
|
|
@@ -3642,7 +3817,7 @@ program.command("restore").description("\u4ECE ChatterCatcher \u5BFC\u51FA\u6587
|
|
|
3642
3817
|
console.log(`\u6062\u590D\u5B8C\u6210\uFF1A${result.inputPath}`);
|
|
3643
3818
|
console.log(`\u6A21\u5F0F\uFF1A${result.mode === "replace" ? "\u66FF\u6362" : "\u5408\u5E76"}`);
|
|
3644
3819
|
console.log(`\u5305\u542B\uFF1A\u7FA4\u804A=${result.chats}\uFF0C\u6D88\u606F=${result.messages}\uFF0Cchunks=${result.chunks}\uFF0C\u6587\u4EF6\u4EFB\u52A1=${result.fileJobs}`);
|
|
3645
|
-
console.log("SQLite FTS \u5DF2\u91CD\u5EFA\uFF1B\u5982\u4F7F\u7528\u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild
|
|
3820
|
+
console.log("SQLite FTS \u5DF2\u91CD\u5EFA\uFF1B\u5982\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\uFF0C\u8BF7\u8FD0\u884C chattercatcher index rebuild\u3002");
|
|
3646
3821
|
} finally {
|
|
3647
3822
|
database.close();
|
|
3648
3823
|
}
|
|
@@ -3672,7 +3847,7 @@ dev.command("ingest-feishu-event").description("\u4ECE JSON \u6587\u4EF6\u6A21\u
|
|
|
3672
3847
|
const config = await loadConfig();
|
|
3673
3848
|
const database = openDatabase(config);
|
|
3674
3849
|
try {
|
|
3675
|
-
const raw = await
|
|
3850
|
+
const raw = await fs14.readFile(options.file, "utf8");
|
|
3676
3851
|
const payload = JSON.parse(raw);
|
|
3677
3852
|
const result = new GatewayIngestor(database).ingestFeishuEvent(payload);
|
|
3678
3853
|
if (!result.accepted) {
|