codesesh 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{chunk-SQYHWMQV.js → chunk-7GQEIPVK.js} +1567 -605
- package/dist/chunk-7GQEIPVK.js.map +1 -0
- package/dist/{dist-NT4CH6KD.js → dist-L4XKQDGX.js} +18 -2
- package/dist/index.js +882 -219
- package/dist/index.js.map +1 -1
- package/dist/scan-refresh-worker.js +50 -0
- package/dist/scan-refresh-worker.js.map +1 -0
- package/dist/search-index-worker.js +46 -16
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +33 -0
- package/dist/smart-tag-worker.js.map +1 -0
- package/dist/web/assets/index-LM0Z2sjk.js +108 -0
- package/dist/web/assets/index-xfiMbcgm.css +2 -0
- package/dist/web/index.html +189 -3
- package/package.json +1 -1
- package/dist/chunk-SQYHWMQV.js.map +0 -1
- package/dist/web/assets/index-D9lwrpQG.css +0 -2
- package/dist/web/assets/index-DZaQy6OQ.js +0 -106
- /package/dist/{dist-NT4CH6KD.js.map → dist-L4XKQDGX.js.map} +0 -0
|
@@ -31,13 +31,13 @@ import {
|
|
|
31
31
|
import { join as join7, basename as basename5 } from "path";
|
|
32
32
|
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
|
|
33
33
|
import { join as join8, normalize } from "path";
|
|
34
|
-
import { resolve, sep } from "path";
|
|
35
34
|
import { availableParallelism } from "os";
|
|
36
35
|
import { Worker } from "worker_threads";
|
|
37
36
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
38
37
|
import { spawnSync } from "child_process";
|
|
39
|
-
import
|
|
38
|
+
import * as os from "os";
|
|
40
39
|
import * as path from "path";
|
|
40
|
+
import { resolve, sep } from "path";
|
|
41
41
|
import { existsSync as existsSync10, rmSync, unlinkSync } from "fs";
|
|
42
42
|
import { join as join9 } from "path";
|
|
43
43
|
import { homedir as homedir4 } from "os";
|
|
@@ -701,7 +701,7 @@ function withEstimatedSessionCost(stats, model) {
|
|
|
701
701
|
function estimateTokenCost(model, tokens) {
|
|
702
702
|
return estimateCostForTokens(model, tokens)?.cost ?? null;
|
|
703
703
|
}
|
|
704
|
-
var
|
|
704
|
+
var HEAD_INDEX_VERSION = "claudecode-head-v1";
|
|
705
705
|
function parseTimestampMs(data) {
|
|
706
706
|
const raw = String(data["timestamp"] ?? "").trim();
|
|
707
707
|
if (!raw) return 0;
|
|
@@ -743,36 +743,64 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
743
743
|
const listMarker = perf.start("listProjectDirs");
|
|
744
744
|
const projectDirs = this.listProjectDirs();
|
|
745
745
|
perf.end(listMarker);
|
|
746
|
-
|
|
746
|
+
const filesByProject = projectDirs.map((projectDir) => {
|
|
747
747
|
const fileMarker = perf.start(`listJsonlFiles:${basename2(projectDir)}`);
|
|
748
|
-
const files = this.listJsonlFiles(projectDir)
|
|
748
|
+
const files = this.listJsonlFiles(projectDir).filter((file) => {
|
|
749
|
+
try {
|
|
750
|
+
return matchesScanWindow(statSync(file).mtimeMs, options);
|
|
751
|
+
} catch {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
});
|
|
749
755
|
perf.end(fileMarker);
|
|
756
|
+
return { projectDir, files };
|
|
757
|
+
});
|
|
758
|
+
const totalFiles = filesByProject.reduce((total, item) => total + item.files.length, 0);
|
|
759
|
+
options?.onProgress?.({ total: totalFiles, processed: 0, sessions: 0 });
|
|
760
|
+
let processed = 0;
|
|
761
|
+
for (const { projectDir, files } of filesByProject) {
|
|
750
762
|
for (const file of files) {
|
|
751
763
|
try {
|
|
752
|
-
if (!matchesScanWindow(statSync(file).mtimeMs, options)) continue;
|
|
753
764
|
const parseMarker = perf.start(`parseSessionHead:${basename2(file)}`);
|
|
754
765
|
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
755
766
|
perf.end(parseMarker);
|
|
756
767
|
if (head) {
|
|
757
768
|
heads.push(head);
|
|
758
|
-
this.sessionMetaMap.set(head.id,
|
|
759
|
-
id: head.id,
|
|
760
|
-
title: head.title,
|
|
761
|
-
sourcePath: file,
|
|
762
|
-
directory: head.directory,
|
|
763
|
-
model: head.stats.total_tokens ? "unknown" : void 0,
|
|
764
|
-
messageCount: head.stats.message_count,
|
|
765
|
-
createdAt: head.time_created,
|
|
766
|
-
updatedAt: head.time_updated ?? head.time_created
|
|
767
|
-
});
|
|
769
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file, projectDir));
|
|
768
770
|
}
|
|
769
771
|
} catch {
|
|
772
|
+
} finally {
|
|
773
|
+
processed += 1;
|
|
774
|
+
options?.onProgress?.({ total: totalFiles, processed, sessions: heads.length });
|
|
770
775
|
}
|
|
771
776
|
}
|
|
772
777
|
}
|
|
773
778
|
perf.end(scanMarker);
|
|
774
779
|
return heads;
|
|
775
780
|
}
|
|
781
|
+
listSessionSources() {
|
|
782
|
+
if (!this.basePath) return [];
|
|
783
|
+
const refs = [];
|
|
784
|
+
for (const projectDir of this.listProjectDirs()) {
|
|
785
|
+
for (const file of this.listJsonlFiles(projectDir)) {
|
|
786
|
+
const sessionId = basename2(file, ".jsonl");
|
|
787
|
+
refs.push({
|
|
788
|
+
sessionId,
|
|
789
|
+
sourcePath: file,
|
|
790
|
+
fingerprint: this.sourceFingerprint(file, projectDir)
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return refs;
|
|
795
|
+
}
|
|
796
|
+
scanSessionSource(sourcePath) {
|
|
797
|
+
const projectDir = dirname(sourcePath);
|
|
798
|
+
const head = getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir));
|
|
799
|
+
if (head) {
|
|
800
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, sourcePath, projectDir));
|
|
801
|
+
}
|
|
802
|
+
return head;
|
|
803
|
+
}
|
|
776
804
|
getSessionMetaMap() {
|
|
777
805
|
return this.sessionMetaMap;
|
|
778
806
|
}
|
|
@@ -844,23 +872,14 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
844
872
|
}
|
|
845
873
|
/**
|
|
846
874
|
* 检测文件系统变更
|
|
847
|
-
*
|
|
875
|
+
* - 已有 session:statSync 检测文件修改(APFS 写文件不更新 dir mtime,必须 file-level)
|
|
876
|
+
* - 新 session 检测:readdirSync 文件列表比对,比 dir statSync 快 ~10x
|
|
848
877
|
*/
|
|
849
|
-
checkForChanges(
|
|
878
|
+
checkForChanges(_sinceTimestamp, cachedSessions) {
|
|
850
879
|
if (!this.basePath) {
|
|
851
880
|
return { hasChanges: false, timestamp: Date.now() };
|
|
852
881
|
}
|
|
853
|
-
const now = Date.now();
|
|
854
882
|
const changedIds = /* @__PURE__ */ new Set();
|
|
855
|
-
const recentSessions = cachedSessions.filter(
|
|
856
|
-
(session) => now - session.time_created <= RECENT_SESSION_REVALIDATION_WINDOW_MS
|
|
857
|
-
);
|
|
858
|
-
for (const session of recentSessions) {
|
|
859
|
-
changedIds.add(session.id);
|
|
860
|
-
const meta = this.sessionMetaMap.get(session.id);
|
|
861
|
-
if (!meta) continue;
|
|
862
|
-
delete this.sessionsIndexCache[basename2(dirname(meta.sourcePath))];
|
|
863
|
-
}
|
|
864
883
|
for (const session of cachedSessions) {
|
|
865
884
|
const meta = this.sessionMetaMap.get(session.id);
|
|
866
885
|
if (!meta) {
|
|
@@ -868,32 +887,36 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
868
887
|
continue;
|
|
869
888
|
}
|
|
870
889
|
try {
|
|
871
|
-
|
|
872
|
-
if (stat.mtimeMs > sinceTimestamp) {
|
|
890
|
+
if (this.hasMetaChanged(meta)) {
|
|
873
891
|
changedIds.add(session.id);
|
|
892
|
+
delete this.sessionsIndexCache[basename2(dirname(meta.sourcePath))];
|
|
874
893
|
}
|
|
875
894
|
} catch {
|
|
876
895
|
changedIds.add(session.id);
|
|
877
896
|
}
|
|
878
897
|
}
|
|
898
|
+
const cachedIdSet = new Set(cachedSessions.map((s) => s.id));
|
|
899
|
+
let hasNewFiles = false;
|
|
879
900
|
try {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
901
|
+
outer: for (const dir of this.listProjectDirs()) {
|
|
902
|
+
try {
|
|
903
|
+
for (const file of this.listJsonlFiles(dir)) {
|
|
904
|
+
if (!cachedIdSet.has(basename2(file, ".jsonl"))) {
|
|
905
|
+
hasNewFiles = true;
|
|
906
|
+
delete this.sessionsIndexCache[basename2(dir)];
|
|
907
|
+
break outer;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
883
912
|
}
|
|
884
|
-
const hasNewFiles = totalFiles > cachedSessions.length;
|
|
885
|
-
return {
|
|
886
|
-
hasChanges: changedIds.size > 0 || hasNewFiles,
|
|
887
|
-
changedIds: Array.from(changedIds),
|
|
888
|
-
timestamp: Date.now()
|
|
889
|
-
};
|
|
890
913
|
} catch {
|
|
891
|
-
return {
|
|
892
|
-
hasChanges: changedIds.size > 0,
|
|
893
|
-
changedIds: Array.from(changedIds),
|
|
894
|
-
timestamp: Date.now()
|
|
895
|
-
};
|
|
896
914
|
}
|
|
915
|
+
return {
|
|
916
|
+
hasChanges: changedIds.size > 0 || hasNewFiles,
|
|
917
|
+
changedIds: Array.from(changedIds),
|
|
918
|
+
timestamp: Date.now()
|
|
919
|
+
};
|
|
897
920
|
}
|
|
898
921
|
/**
|
|
899
922
|
* 增量扫描 - 只扫描变更的会话
|
|
@@ -901,48 +924,16 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
901
924
|
incrementalScan(cachedSessions, changedIds) {
|
|
902
925
|
if (!this.basePath) return cachedSessions;
|
|
903
926
|
const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
|
|
927
|
+
const changedSet = new Set(changedIds);
|
|
904
928
|
for (const projectDir of this.listProjectDirs()) {
|
|
905
929
|
for (const file of this.listJsonlFiles(projectDir)) {
|
|
906
930
|
try {
|
|
907
931
|
const sessionId = basename2(file, ".jsonl");
|
|
908
|
-
if (
|
|
909
|
-
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
910
|
-
if (head) {
|
|
911
|
-
sessionMap.set(head.id, head);
|
|
912
|
-
this.sessionMetaMap.set(head.id, {
|
|
913
|
-
id: head.id,
|
|
914
|
-
title: head.title,
|
|
915
|
-
sourcePath: file,
|
|
916
|
-
directory: head.directory,
|
|
917
|
-
model: head.stats.total_tokens ? "unknown" : void 0,
|
|
918
|
-
messageCount: head.stats.message_count,
|
|
919
|
-
createdAt: head.time_created,
|
|
920
|
-
updatedAt: head.time_updated ?? head.time_created
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
} catch {
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
for (const projectDir of this.listProjectDirs()) {
|
|
929
|
-
for (const file of this.listJsonlFiles(projectDir)) {
|
|
930
|
-
try {
|
|
931
|
-
const sessionId = basename2(file, ".jsonl");
|
|
932
|
-
if (!sessionMap.has(sessionId)) {
|
|
932
|
+
if (changedSet.has(sessionId) || !sessionMap.has(sessionId)) {
|
|
933
933
|
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
934
934
|
if (head) {
|
|
935
935
|
sessionMap.set(head.id, head);
|
|
936
|
-
this.sessionMetaMap.set(head.id,
|
|
937
|
-
id: head.id,
|
|
938
|
-
title: head.title,
|
|
939
|
-
sourcePath: file,
|
|
940
|
-
directory: head.directory,
|
|
941
|
-
model: head.stats.total_tokens ? "unknown" : void 0,
|
|
942
|
-
messageCount: head.stats.message_count,
|
|
943
|
-
createdAt: head.time_created,
|
|
944
|
-
updatedAt: head.time_updated ?? head.time_created
|
|
945
|
-
});
|
|
936
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file, projectDir));
|
|
946
937
|
}
|
|
947
938
|
}
|
|
948
939
|
} catch {
|
|
@@ -967,12 +958,57 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
967
958
|
return [];
|
|
968
959
|
}
|
|
969
960
|
}
|
|
961
|
+
buildSessionMeta(head, file, projectDir) {
|
|
962
|
+
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
963
|
+
return {
|
|
964
|
+
id: head.id,
|
|
965
|
+
title: head.title,
|
|
966
|
+
sourcePath: file,
|
|
967
|
+
sourceFingerprint: this.sourceFingerprint(file, projectDir),
|
|
968
|
+
sourceMtimeMs: statSync(file).mtimeMs,
|
|
969
|
+
indexPath: existsSync3(indexPath) ? indexPath : null,
|
|
970
|
+
indexMtimeMs: this.getFileMtimeMs(indexPath),
|
|
971
|
+
headIndexVersion: HEAD_INDEX_VERSION,
|
|
972
|
+
directory: head.directory,
|
|
973
|
+
model: head.stats.total_tokens ? "unknown" : void 0,
|
|
974
|
+
messageCount: head.stats.message_count,
|
|
975
|
+
createdAt: head.time_created,
|
|
976
|
+
updatedAt: head.time_updated ?? head.time_created
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
hasMetaChanged(meta) {
|
|
980
|
+
if (meta.headIndexVersion !== HEAD_INDEX_VERSION) return true;
|
|
981
|
+
if (typeof meta.sourceMtimeMs !== "number") return true;
|
|
982
|
+
if (statSync(meta.sourcePath).mtimeMs !== meta.sourceMtimeMs) return true;
|
|
983
|
+
const indexPath = meta.indexPath ?? this.getSessionsIndexPath(dirname(meta.sourcePath));
|
|
984
|
+
return this.getFileMtimeMs(indexPath) !== (meta.indexMtimeMs ?? null);
|
|
985
|
+
}
|
|
986
|
+
sourceFingerprint(file, projectDir) {
|
|
987
|
+
const stat = statSync(file);
|
|
988
|
+
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
989
|
+
return JSON.stringify([
|
|
990
|
+
HEAD_INDEX_VERSION,
|
|
991
|
+
stat.mtimeMs,
|
|
992
|
+
stat.size,
|
|
993
|
+
this.getFileMtimeMs(indexPath)
|
|
994
|
+
]);
|
|
995
|
+
}
|
|
996
|
+
getSessionsIndexPath(projectDir) {
|
|
997
|
+
return join3(projectDir, "sessions-index.json");
|
|
998
|
+
}
|
|
999
|
+
getFileMtimeMs(filePath) {
|
|
1000
|
+
try {
|
|
1001
|
+
return statSync(filePath).mtimeMs;
|
|
1002
|
+
} catch {
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
970
1006
|
loadSessionsIndex(projectDir) {
|
|
971
1007
|
const cacheKey = basename2(projectDir);
|
|
972
1008
|
if (cacheKey in this.sessionsIndexCache) {
|
|
973
1009
|
return this.sessionsIndexCache[cacheKey];
|
|
974
1010
|
}
|
|
975
|
-
const indexPath =
|
|
1011
|
+
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
976
1012
|
const map = /* @__PURE__ */ new Map();
|
|
977
1013
|
if (existsSync3(indexPath)) {
|
|
978
1014
|
try {
|
|
@@ -1675,11 +1711,7 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1675
1711
|
rows = db.prepare(`
|
|
1676
1712
|
SELECT
|
|
1677
1713
|
s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
|
|
1678
|
-
s.version, s.summary_files
|
|
1679
|
-
(SELECT COUNT(*) FROM message m WHERE m.session_id = s.id) AS message_count,
|
|
1680
|
-
(SELECT m.data FROM message m
|
|
1681
|
-
WHERE m.session_id = s.id AND m.data LIKE '%"modelID"%'
|
|
1682
|
-
ORDER BY m.time_created DESC LIMIT 1) AS model_message_data
|
|
1714
|
+
s.version, s.summary_files
|
|
1683
1715
|
FROM session s
|
|
1684
1716
|
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
1685
1717
|
ORDER BY s.time_created DESC
|
|
@@ -1693,17 +1725,28 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1693
1725
|
ORDER BY s.time_created DESC
|
|
1694
1726
|
`).all(cutoffTime);
|
|
1695
1727
|
}
|
|
1728
|
+
const headContexts = hasMessageTable ? this.buildHeadContexts(
|
|
1729
|
+
this.readHeadMessageRows(db, cutoffTime),
|
|
1730
|
+
this.readHeadPartRows(db, cutoffTime)
|
|
1731
|
+
) : /* @__PURE__ */ new Map();
|
|
1696
1732
|
const heads = [];
|
|
1733
|
+
options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
|
|
1734
|
+
let processed = 0;
|
|
1697
1735
|
for (const row of rows) {
|
|
1698
|
-
const head = getParsedSession(
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
if (
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1736
|
+
const head = getParsedSession(
|
|
1737
|
+
this.parseSessionHeadRow(row, hasMessageTable, headContexts.get(String(row.id ?? "")))
|
|
1738
|
+
);
|
|
1739
|
+
if (head) {
|
|
1740
|
+
heads.push(head);
|
|
1741
|
+
if (this.dbPath) {
|
|
1742
|
+
this.sessionMetaMap.set(head.id, {
|
|
1743
|
+
id: head.id,
|
|
1744
|
+
sourcePath: this.dbPath
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1706
1747
|
}
|
|
1748
|
+
processed += 1;
|
|
1749
|
+
options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
|
|
1707
1750
|
}
|
|
1708
1751
|
return heads;
|
|
1709
1752
|
} catch {
|
|
@@ -1712,15 +1755,15 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1712
1755
|
db.close();
|
|
1713
1756
|
}
|
|
1714
1757
|
}
|
|
1715
|
-
parseSessionHeadRow(
|
|
1758
|
+
parseSessionHeadRow(row, hasMessageTable, context) {
|
|
1716
1759
|
const id = String(row.id ?? "");
|
|
1717
1760
|
if (!id) return skippedSession("missing session id");
|
|
1718
1761
|
const timeCreated = Number(row.time_created ?? 0);
|
|
1719
1762
|
const timeUpdated = Number(row.time_updated ?? timeCreated);
|
|
1720
|
-
const stats =
|
|
1721
|
-
const messageCount = stats?.message_count ??
|
|
1763
|
+
const stats = context?.stats ?? null;
|
|
1764
|
+
const messageCount = stats?.message_count ?? 0;
|
|
1722
1765
|
if (hasMessageTable && messageCount === 0) return filteredSession("no visible messages");
|
|
1723
|
-
const messageTitle =
|
|
1766
|
+
const messageTitle = context?.messageTitle ?? null;
|
|
1724
1767
|
return parsedSession({
|
|
1725
1768
|
id,
|
|
1726
1769
|
slug: `opencode/${id}`,
|
|
@@ -1737,6 +1780,29 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1737
1780
|
}
|
|
1738
1781
|
});
|
|
1739
1782
|
}
|
|
1783
|
+
readHeadMessageRows(db, cutoffTime) {
|
|
1784
|
+
return db.prepare(
|
|
1785
|
+
`
|
|
1786
|
+
SELECT m.id, m.session_id, m.data, m.time_created
|
|
1787
|
+
FROM message m
|
|
1788
|
+
JOIN session s ON s.id = m.session_id
|
|
1789
|
+
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
1790
|
+
ORDER BY m.session_id, m.time_created ASC
|
|
1791
|
+
`
|
|
1792
|
+
).all(cutoffTime);
|
|
1793
|
+
}
|
|
1794
|
+
readHeadPartRows(db, cutoffTime) {
|
|
1795
|
+
return db.prepare(
|
|
1796
|
+
`
|
|
1797
|
+
SELECT p.message_id, p.data, p.time_created
|
|
1798
|
+
FROM part p
|
|
1799
|
+
JOIN message m ON m.id = p.message_id
|
|
1800
|
+
JOIN session s ON s.id = m.session_id
|
|
1801
|
+
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
1802
|
+
ORDER BY p.message_id, p.time_created ASC
|
|
1803
|
+
`
|
|
1804
|
+
).all(cutoffTime);
|
|
1805
|
+
}
|
|
1740
1806
|
getSessionMetaMap() {
|
|
1741
1807
|
return this.sessionMetaMap;
|
|
1742
1808
|
}
|
|
@@ -1773,92 +1839,103 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1773
1839
|
incrementalScan(_cachedSessions, _changedIds) {
|
|
1774
1840
|
return this.scan();
|
|
1775
1841
|
}
|
|
1842
|
+
parsePartRow(partRow) {
|
|
1843
|
+
const partData = JSON.parse(String(partRow.data ?? "{}"));
|
|
1844
|
+
const partType = String(partData.type ?? "");
|
|
1845
|
+
if (isInternalEventType(partType)) return null;
|
|
1846
|
+
if (partType === "text" || partType === "reasoning") {
|
|
1847
|
+
const text = cleanInternalText(String(partData.text ?? ""));
|
|
1848
|
+
if (!text) return null;
|
|
1849
|
+
return {
|
|
1850
|
+
type: partType,
|
|
1851
|
+
text,
|
|
1852
|
+
time_created: Number(partRow.time_created ?? 0)
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
if (partType === "tool") {
|
|
1856
|
+
return {
|
|
1857
|
+
type: "tool",
|
|
1858
|
+
tool: String(partData.tool ?? ""),
|
|
1859
|
+
callID: String(partData.callID ?? ""),
|
|
1860
|
+
title: cleanInternalText(String(partData.title ?? "")),
|
|
1861
|
+
state: partData.state ?? {},
|
|
1862
|
+
time_created: Number(partRow.time_created ?? 0)
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1776
1867
|
readMessageParts(db, messageId) {
|
|
1777
|
-
const partRows = db.prepare("SELECT
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
} else if (partType === "tool") {
|
|
1793
|
-
parts.push({
|
|
1794
|
-
type: "tool",
|
|
1795
|
-
tool: String(partData.tool ?? ""),
|
|
1796
|
-
callID: String(partData.callID ?? ""),
|
|
1797
|
-
title: cleanInternalText(String(partData.title ?? "")),
|
|
1798
|
-
state: partData.state ?? {},
|
|
1799
|
-
time_created: Number(partRow.time_created ?? 0)
|
|
1800
|
-
});
|
|
1868
|
+
const partRows = db.prepare("SELECT data, time_created FROM part WHERE message_id = ? ORDER BY time_created ASC").all(messageId);
|
|
1869
|
+
return partRows.map((partRow) => this.parsePartRow(partRow)).filter((part) => part !== null);
|
|
1870
|
+
}
|
|
1871
|
+
buildPartsByMessage(partRows) {
|
|
1872
|
+
const partsByMessage = /* @__PURE__ */ new Map();
|
|
1873
|
+
for (const row of partRows) {
|
|
1874
|
+
const messageId = String(row.message_id ?? "");
|
|
1875
|
+
if (!messageId) continue;
|
|
1876
|
+
const part = this.parsePartRow(row);
|
|
1877
|
+
if (!part) continue;
|
|
1878
|
+
const parts = partsByMessage.get(messageId);
|
|
1879
|
+
if (parts) {
|
|
1880
|
+
parts.push(part);
|
|
1881
|
+
} else {
|
|
1882
|
+
partsByMessage.set(messageId, [part]);
|
|
1801
1883
|
}
|
|
1802
1884
|
}
|
|
1803
|
-
return
|
|
1885
|
+
return partsByMessage;
|
|
1804
1886
|
}
|
|
1805
|
-
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1887
|
+
buildHeadContexts(messageRows, partRows) {
|
|
1888
|
+
const partsByMessage = this.buildPartsByMessage(partRows);
|
|
1889
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
1890
|
+
for (const row of messageRows) {
|
|
1891
|
+
const sessionId = String(row.session_id ?? "");
|
|
1892
|
+
if (!sessionId) continue;
|
|
1810
1893
|
const msgData = JSON.parse(String(row.data ?? "{}"));
|
|
1811
1894
|
if (isInternalEventType(msgData.type)) continue;
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1895
|
+
const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
|
|
1896
|
+
if (parts.length === 0) continue;
|
|
1897
|
+
let context = contexts.get(sessionId);
|
|
1898
|
+
if (!context) {
|
|
1899
|
+
context = {
|
|
1900
|
+
stats: {
|
|
1901
|
+
message_count: 0,
|
|
1902
|
+
total_input_tokens: 0,
|
|
1903
|
+
total_output_tokens: 0,
|
|
1904
|
+
total_cost: 0
|
|
1905
|
+
},
|
|
1906
|
+
messageTitle: null
|
|
1907
|
+
};
|
|
1908
|
+
contexts.set(sessionId, context);
|
|
1909
|
+
}
|
|
1910
|
+
const cost = Number(msgData.cost ?? 0);
|
|
1911
|
+
const tokens = msgData.tokens;
|
|
1912
|
+
const inputTokens = Number(tokens?.input ?? 0);
|
|
1913
|
+
const outputTokens = Number(tokens?.output ?? 0);
|
|
1914
|
+
const model = msgData.modelID ?? null;
|
|
1915
|
+
const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
|
|
1916
|
+
if (estimatedCost !== null) context.stats.cost_source = "estimated";
|
|
1917
|
+
context.stats.total_cost += cost || estimatedCost || 0;
|
|
1918
|
+
context.stats.total_input_tokens += inputTokens;
|
|
1919
|
+
context.stats.total_output_tokens += outputTokens;
|
|
1920
|
+
context.stats.message_count += 1;
|
|
1921
|
+
if (!context.messageTitle && String(msgData.role ?? "") === "user") {
|
|
1922
|
+
context.messageTitle = firstUserMessageTitle([
|
|
1923
|
+
{
|
|
1924
|
+
id: String(row.id ?? ""),
|
|
1925
|
+
role: "user",
|
|
1926
|
+
agent: null,
|
|
1927
|
+
time_created: Number(row.time_created ?? 0),
|
|
1928
|
+
parts
|
|
1929
|
+
}
|
|
1930
|
+
]);
|
|
1931
|
+
}
|
|
1824
1932
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
try {
|
|
1829
|
-
const rows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
|
|
1830
|
-
let totalCost = 0;
|
|
1831
|
-
let totalInputTokens = 0;
|
|
1832
|
-
let totalOutputTokens = 0;
|
|
1833
|
-
let hasEstimatedCost = false;
|
|
1834
|
-
let messageCount = 0;
|
|
1835
|
-
for (const row of rows) {
|
|
1836
|
-
const msgData = JSON.parse(String(row.data ?? "{}"));
|
|
1837
|
-
if (isInternalEventType(msgData.type)) continue;
|
|
1838
|
-
const parts = this.readMessageParts(db, row.id);
|
|
1839
|
-
if (parts.length === 0) continue;
|
|
1840
|
-
const cost = Number(msgData.cost ?? 0);
|
|
1841
|
-
const tokens = msgData.tokens;
|
|
1842
|
-
const inputTokens = Number(tokens?.input ?? 0);
|
|
1843
|
-
const outputTokens = Number(tokens?.output ?? 0);
|
|
1844
|
-
const model = msgData.modelID ?? null;
|
|
1845
|
-
const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
|
|
1846
|
-
if (estimatedCost !== null) hasEstimatedCost = true;
|
|
1847
|
-
totalCost += cost || estimatedCost || 0;
|
|
1848
|
-
totalInputTokens += inputTokens;
|
|
1849
|
-
totalOutputTokens += outputTokens;
|
|
1850
|
-
messageCount++;
|
|
1933
|
+
for (const context of contexts.values()) {
|
|
1934
|
+
if (context.stats.total_cost > 0 && context.stats.cost_source !== "estimated") {
|
|
1935
|
+
context.stats.cost_source = "recorded";
|
|
1851
1936
|
}
|
|
1852
|
-
return {
|
|
1853
|
-
message_count: messageCount,
|
|
1854
|
-
total_input_tokens: totalInputTokens,
|
|
1855
|
-
total_output_tokens: totalOutputTokens,
|
|
1856
|
-
total_cost: totalCost,
|
|
1857
|
-
cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
|
|
1858
|
-
};
|
|
1859
|
-
} catch {
|
|
1860
|
-
return null;
|
|
1861
1937
|
}
|
|
1938
|
+
return contexts;
|
|
1862
1939
|
}
|
|
1863
1940
|
getSessionData(sessionId) {
|
|
1864
1941
|
if (!this.dbPath) {
|
|
@@ -2162,14 +2239,24 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2162
2239
|
const listMarker = perf.start("listSessionDirs");
|
|
2163
2240
|
const sessionDirs = this.listSessionDirs();
|
|
2164
2241
|
perf.end(listMarker);
|
|
2165
|
-
const
|
|
2242
|
+
const metas = [];
|
|
2166
2243
|
for (const dir of sessionDirs) {
|
|
2167
2244
|
try {
|
|
2168
2245
|
const parseMarker = perf.start(`parseSessionDir:${basename4(dir)}`);
|
|
2169
2246
|
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
2170
2247
|
perf.end(parseMarker);
|
|
2171
|
-
if (
|
|
2172
|
-
|
|
2248
|
+
if (meta && matchesScanWindow(meta.createdAt, options)) {
|
|
2249
|
+
metas.push(meta);
|
|
2250
|
+
}
|
|
2251
|
+
} catch {
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
options?.onProgress?.({ total: metas.length, processed: 0, sessions: 0 });
|
|
2255
|
+
const heads = [];
|
|
2256
|
+
let processed = 0;
|
|
2257
|
+
for (const meta of metas) {
|
|
2258
|
+
try {
|
|
2259
|
+
meta.sourceFingerprint = this.sourceFingerprint(meta);
|
|
2173
2260
|
this.sessionMetaMap.set(meta.id, meta);
|
|
2174
2261
|
const stats = this.extractStats(meta.sourcePath);
|
|
2175
2262
|
heads.push({
|
|
@@ -2182,11 +2269,46 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2182
2269
|
stats
|
|
2183
2270
|
});
|
|
2184
2271
|
} catch {
|
|
2272
|
+
} finally {
|
|
2273
|
+
processed += 1;
|
|
2274
|
+
options?.onProgress?.({ total: metas.length, processed, sessions: heads.length });
|
|
2185
2275
|
}
|
|
2186
2276
|
}
|
|
2187
2277
|
perf.end(scanMarker);
|
|
2188
2278
|
return heads;
|
|
2189
2279
|
}
|
|
2280
|
+
listSessionSources() {
|
|
2281
|
+
if (!this.basePath) return [];
|
|
2282
|
+
const refs = [];
|
|
2283
|
+
for (const dir of this.listSessionDirs()) {
|
|
2284
|
+
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
2285
|
+
if (!meta) continue;
|
|
2286
|
+
meta.sourceFingerprint = this.sourceFingerprint(meta);
|
|
2287
|
+
this.sessionMetaMap.set(meta.id, meta);
|
|
2288
|
+
refs.push({
|
|
2289
|
+
sessionId: meta.id,
|
|
2290
|
+
sourcePath: meta.sourcePath,
|
|
2291
|
+
fingerprint: this.sourceFingerprint(meta)
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
return refs;
|
|
2295
|
+
}
|
|
2296
|
+
scanSessionSource(sourcePath) {
|
|
2297
|
+
const meta = getParsedSession(this.parseSessionDirResult(sourcePath));
|
|
2298
|
+
if (!meta) return null;
|
|
2299
|
+
meta.sourceFingerprint = this.sourceFingerprint(meta);
|
|
2300
|
+
this.sessionMetaMap.set(meta.id, meta);
|
|
2301
|
+
const stats = this.extractStats(meta.sourcePath);
|
|
2302
|
+
return {
|
|
2303
|
+
id: meta.id,
|
|
2304
|
+
slug: `kimi/${meta.id}`,
|
|
2305
|
+
title: meta.title,
|
|
2306
|
+
directory: meta.cwd,
|
|
2307
|
+
time_created: meta.createdAt,
|
|
2308
|
+
time_updated: meta.createdAt,
|
|
2309
|
+
stats
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2190
2312
|
getSessionMetaMap() {
|
|
2191
2313
|
return this.sessionMetaMap;
|
|
2192
2314
|
}
|
|
@@ -2498,6 +2620,21 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2498
2620
|
return this.buildSessionData(meta, filteredMessages, stats);
|
|
2499
2621
|
}
|
|
2500
2622
|
// --- Helpers ---
|
|
2623
|
+
sourceFingerprint(meta) {
|
|
2624
|
+
const fileMtime = (path2) => {
|
|
2625
|
+
if (!path2) return null;
|
|
2626
|
+
try {
|
|
2627
|
+
return statSync3(path2).mtimeMs;
|
|
2628
|
+
} catch {
|
|
2629
|
+
return null;
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
return JSON.stringify([
|
|
2633
|
+
fileMtime(meta.metaFile),
|
|
2634
|
+
fileMtime(meta.contextFile),
|
|
2635
|
+
fileMtime(meta.wireFile)
|
|
2636
|
+
]);
|
|
2637
|
+
}
|
|
2501
2638
|
buildMessage(opts) {
|
|
2502
2639
|
return {
|
|
2503
2640
|
id: opts.messageId,
|
|
@@ -2695,6 +2832,8 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2695
2832
|
var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
|
|
2696
2833
|
var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
|
|
2697
2834
|
var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
|
|
2835
|
+
var HEAD_INDEX_VERSION2 = "codex-head-v1";
|
|
2836
|
+
var PARSER_VERSION = "codex-parser-v3";
|
|
2698
2837
|
var DEVELOPER_LIKE_USER_MARKERS = [
|
|
2699
2838
|
"agents.md instructions for",
|
|
2700
2839
|
"<instructions>",
|
|
@@ -2713,7 +2852,6 @@ var CODEX_TOOL_TITLE_MAP = {
|
|
|
2713
2852
|
spawn_agent: "subagent",
|
|
2714
2853
|
subagent: "subagent"
|
|
2715
2854
|
};
|
|
2716
|
-
var RECENT_SESSION_REVALIDATION_WINDOW_MS2 = 24 * 60 * 60 * 1e3;
|
|
2717
2855
|
function extractSessionId(filename) {
|
|
2718
2856
|
const stem = basename5(filename, ".jsonl");
|
|
2719
2857
|
const parts = stem.split("-");
|
|
@@ -2738,8 +2876,23 @@ function extractCachedInputTokens(usage) {
|
|
|
2738
2876
|
if (!usage) return 0;
|
|
2739
2877
|
return Number(usage["cached_input_tokens"] ?? usage["cache_read_input_tokens"] ?? 0);
|
|
2740
2878
|
}
|
|
2741
|
-
function
|
|
2742
|
-
|
|
2879
|
+
function resolveToolIdentity(name, namespace) {
|
|
2880
|
+
const mappedName = CODEX_TOOL_TITLE_MAP[name];
|
|
2881
|
+
if (mappedName) return { tool: mappedName };
|
|
2882
|
+
const namespaceText = typeof namespace === "string" ? namespace.trim() : "";
|
|
2883
|
+
if (!namespaceText) return { tool: name };
|
|
2884
|
+
const namespaceName = namespaceText.split("__").at(-1) ?? namespaceText;
|
|
2885
|
+
const toolName = name.replace(/^[_.]+/, "");
|
|
2886
|
+
if (!namespaceName) {
|
|
2887
|
+
return {
|
|
2888
|
+
tool: toolName || name,
|
|
2889
|
+
metadata: { name, namespace: namespaceText }
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
return {
|
|
2893
|
+
tool: `${namespaceName}.${toolName || name}`,
|
|
2894
|
+
metadata: { name, namespace: namespaceText }
|
|
2895
|
+
};
|
|
2743
2896
|
}
|
|
2744
2897
|
function normalizeToolArguments2(raw) {
|
|
2745
2898
|
if (typeof raw === "string") {
|
|
@@ -2870,6 +3023,8 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2870
3023
|
const listMarker = perf.start("listRolloutFiles");
|
|
2871
3024
|
const files = this.listRolloutFiles(options);
|
|
2872
3025
|
perf.end(listMarker);
|
|
3026
|
+
options?.onProgress?.({ total: files.length, processed: 0, sessions: 0 });
|
|
3027
|
+
let processed = 0;
|
|
2873
3028
|
for (const file of files) {
|
|
2874
3029
|
try {
|
|
2875
3030
|
const parseMarker = perf.start(`parseSessionHead:${basename5(file)}`);
|
|
@@ -2877,23 +3032,34 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2877
3032
|
perf.end(parseMarker);
|
|
2878
3033
|
if (head) {
|
|
2879
3034
|
heads.push(head);
|
|
2880
|
-
this.sessionMetaMap.set(head.id,
|
|
2881
|
-
id: head.id,
|
|
2882
|
-
title: head.title,
|
|
2883
|
-
sourcePath: file,
|
|
2884
|
-
directory: head.directory,
|
|
2885
|
-
model: null,
|
|
2886
|
-
messageCount: head.stats.message_count,
|
|
2887
|
-
createdAt: head.time_created,
|
|
2888
|
-
updatedAt: head.time_updated ?? head.time_created
|
|
2889
|
-
});
|
|
3035
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file));
|
|
2890
3036
|
}
|
|
2891
3037
|
} catch {
|
|
3038
|
+
} finally {
|
|
3039
|
+
processed += 1;
|
|
3040
|
+
options?.onProgress?.({ total: files.length, processed, sessions: heads.length });
|
|
2892
3041
|
}
|
|
2893
3042
|
}
|
|
2894
3043
|
perf.end(scanMarker);
|
|
2895
3044
|
return heads;
|
|
2896
3045
|
}
|
|
3046
|
+
listSessionSources() {
|
|
3047
|
+
if (!this.basePath) return [];
|
|
3048
|
+
this.loadSessionIndex();
|
|
3049
|
+
return this.listRolloutFiles().map((file) => ({
|
|
3050
|
+
sessionId: extractSessionId(file),
|
|
3051
|
+
sourcePath: file,
|
|
3052
|
+
fingerprint: this.sourceFingerprint(file)
|
|
3053
|
+
}));
|
|
3054
|
+
}
|
|
3055
|
+
scanSessionSource(sourcePath) {
|
|
3056
|
+
this.loadSessionIndex();
|
|
3057
|
+
const head = getParsedSession(this.parseSessionHeadResult(sourcePath));
|
|
3058
|
+
if (head) {
|
|
3059
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, sourcePath));
|
|
3060
|
+
}
|
|
3061
|
+
return head;
|
|
3062
|
+
}
|
|
2897
3063
|
getSessionMetaMap() {
|
|
2898
3064
|
return this.sessionMetaMap;
|
|
2899
3065
|
}
|
|
@@ -2903,32 +3069,26 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2903
3069
|
/**
|
|
2904
3070
|
* 检测文件系统变更
|
|
2905
3071
|
*/
|
|
2906
|
-
checkForChanges(
|
|
3072
|
+
checkForChanges(_sinceTimestamp, cachedSessions) {
|
|
2907
3073
|
if (!this.basePath) {
|
|
2908
3074
|
return { hasChanges: false, timestamp: Date.now() };
|
|
2909
3075
|
}
|
|
2910
|
-
const now = Date.now();
|
|
2911
|
-
const changedIds = /* @__PURE__ */ new Set();
|
|
2912
3076
|
const currentFiles = this.listRolloutFiles();
|
|
2913
3077
|
const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
|
|
2914
3078
|
const cachedIds = new Set(cachedSessions.map((session) => session.id));
|
|
2915
|
-
const
|
|
2916
|
-
for (const sessionId of recentIds) {
|
|
2917
|
-
changedIds.add(sessionId);
|
|
2918
|
-
}
|
|
3079
|
+
const changedIds = /* @__PURE__ */ new Set();
|
|
2919
3080
|
for (const session of cachedSessions) {
|
|
2920
|
-
const meta = this.sessionMetaMap.get(session.id);
|
|
2921
3081
|
if (!currentIds.has(session.id)) {
|
|
2922
3082
|
changedIds.add(session.id);
|
|
2923
3083
|
continue;
|
|
2924
3084
|
}
|
|
3085
|
+
const meta = this.sessionMetaMap.get(session.id);
|
|
2925
3086
|
if (!meta) {
|
|
2926
3087
|
changedIds.add(session.id);
|
|
2927
3088
|
continue;
|
|
2928
3089
|
}
|
|
2929
3090
|
try {
|
|
2930
|
-
|
|
2931
|
-
if (stat.mtimeMs > sinceTimestamp) {
|
|
3091
|
+
if (this.hasMetaChanged(meta)) {
|
|
2932
3092
|
changedIds.add(session.id);
|
|
2933
3093
|
}
|
|
2934
3094
|
} catch {
|
|
@@ -2936,7 +3096,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2936
3096
|
}
|
|
2937
3097
|
}
|
|
2938
3098
|
const hasAddedSessions = currentFiles.some((file) => !cachedIds.has(extractSessionId(file)));
|
|
2939
|
-
if (
|
|
3099
|
+
if (hasAddedSessions || changedIds.size > 0) {
|
|
2940
3100
|
this.sessionIndexCache.clear();
|
|
2941
3101
|
}
|
|
2942
3102
|
return {
|
|
@@ -2967,16 +3127,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2967
3127
|
const head = this.parseSessionHead(file);
|
|
2968
3128
|
if (head) {
|
|
2969
3129
|
sessionMap.set(head.id, head);
|
|
2970
|
-
this.sessionMetaMap.set(head.id,
|
|
2971
|
-
id: head.id,
|
|
2972
|
-
title: head.title,
|
|
2973
|
-
sourcePath: file,
|
|
2974
|
-
directory: head.directory,
|
|
2975
|
-
model: null,
|
|
2976
|
-
messageCount: head.stats.message_count,
|
|
2977
|
-
createdAt: head.time_created,
|
|
2978
|
-
updatedAt: head.time_updated ?? head.time_created
|
|
2979
|
-
});
|
|
3130
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file));
|
|
2980
3131
|
}
|
|
2981
3132
|
}
|
|
2982
3133
|
} catch {
|
|
@@ -2989,16 +3140,7 @@ var CodexAgent = class extends BaseAgent {
|
|
|
2989
3140
|
const head = this.parseSessionHead(file);
|
|
2990
3141
|
if (head) {
|
|
2991
3142
|
sessionMap.set(head.id, head);
|
|
2992
|
-
this.sessionMetaMap.set(head.id,
|
|
2993
|
-
id: head.id,
|
|
2994
|
-
title: head.title,
|
|
2995
|
-
sourcePath: file,
|
|
2996
|
-
directory: head.directory,
|
|
2997
|
-
model: null,
|
|
2998
|
-
messageCount: head.stats.message_count,
|
|
2999
|
-
createdAt: head.time_created,
|
|
3000
|
-
updatedAt: head.time_updated ?? head.time_created
|
|
3001
|
-
});
|
|
3143
|
+
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file));
|
|
3002
3144
|
}
|
|
3003
3145
|
}
|
|
3004
3146
|
} catch {
|
|
@@ -3145,13 +3287,18 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3145
3287
|
walkDirForRolloutFiles(dir, options) {
|
|
3146
3288
|
const files = [];
|
|
3147
3289
|
try {
|
|
3148
|
-
for (const entry of readdirSync3(dir)) {
|
|
3149
|
-
const fullPath = join7(dir, entry);
|
|
3150
|
-
|
|
3151
|
-
if (stat.isDirectory()) {
|
|
3290
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
3291
|
+
const fullPath = join7(dir, entry.name);
|
|
3292
|
+
if (entry.isDirectory()) {
|
|
3152
3293
|
files.push(...this.walkDirForRolloutFiles(fullPath, options));
|
|
3153
|
-
} else if (entry.endsWith(".jsonl") && entry.startsWith("rollout-")) {
|
|
3154
|
-
if (
|
|
3294
|
+
} else if (entry.name.endsWith(".jsonl") && entry.name.startsWith("rollout-")) {
|
|
3295
|
+
if (options?.from != null || options?.to != null) {
|
|
3296
|
+
try {
|
|
3297
|
+
if (!matchesScanWindow(statSync4(fullPath).mtimeMs, options)) continue;
|
|
3298
|
+
} catch {
|
|
3299
|
+
continue;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3155
3302
|
files.push(fullPath);
|
|
3156
3303
|
}
|
|
3157
3304
|
}
|
|
@@ -3159,11 +3306,59 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3159
3306
|
}
|
|
3160
3307
|
return files;
|
|
3161
3308
|
}
|
|
3309
|
+
buildSessionMeta(head, file) {
|
|
3310
|
+
const indexPath = this.getSessionIndexPath();
|
|
3311
|
+
return {
|
|
3312
|
+
id: head.id,
|
|
3313
|
+
title: head.title,
|
|
3314
|
+
sourcePath: file,
|
|
3315
|
+
sourceFingerprint: this.sourceFingerprint(file),
|
|
3316
|
+
sourceMtimeMs: statSync4(file).mtimeMs,
|
|
3317
|
+
indexPath: existsSync7(indexPath) ? indexPath : null,
|
|
3318
|
+
indexMtimeMs: this.getFileMtimeMs(indexPath),
|
|
3319
|
+
headIndexVersion: HEAD_INDEX_VERSION2,
|
|
3320
|
+
parserVersion: PARSER_VERSION,
|
|
3321
|
+
directory: head.directory,
|
|
3322
|
+
model: null,
|
|
3323
|
+
messageCount: head.stats.message_count,
|
|
3324
|
+
createdAt: head.time_created,
|
|
3325
|
+
updatedAt: head.time_updated ?? head.time_created
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
hasMetaChanged(meta) {
|
|
3329
|
+
if (meta.headIndexVersion !== HEAD_INDEX_VERSION2) return true;
|
|
3330
|
+
if (meta.parserVersion !== PARSER_VERSION) return true;
|
|
3331
|
+
if (typeof meta.sourceMtimeMs !== "number") return true;
|
|
3332
|
+
if (statSync4(meta.sourcePath).mtimeMs !== meta.sourceMtimeMs) return true;
|
|
3333
|
+
const indexPath = meta.indexPath ?? this.getSessionIndexPath();
|
|
3334
|
+
return this.getFileMtimeMs(indexPath) !== (meta.indexMtimeMs ?? null);
|
|
3335
|
+
}
|
|
3336
|
+
sourceFingerprint(file) {
|
|
3337
|
+
const stat = statSync4(file);
|
|
3338
|
+
const indexPath = this.getSessionIndexPath();
|
|
3339
|
+
return JSON.stringify([
|
|
3340
|
+
HEAD_INDEX_VERSION2,
|
|
3341
|
+
PARSER_VERSION,
|
|
3342
|
+
stat.mtimeMs,
|
|
3343
|
+
stat.size,
|
|
3344
|
+
this.getFileMtimeMs(indexPath)
|
|
3345
|
+
]);
|
|
3346
|
+
}
|
|
3347
|
+
getSessionIndexPath() {
|
|
3348
|
+
const roots = resolveProviderRoots();
|
|
3349
|
+
return join7(roots.codexRoot, "session_index.jsonl");
|
|
3350
|
+
}
|
|
3351
|
+
getFileMtimeMs(filePath) {
|
|
3352
|
+
try {
|
|
3353
|
+
return statSync4(filePath).mtimeMs;
|
|
3354
|
+
} catch {
|
|
3355
|
+
return null;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3162
3358
|
// ---- Session index ----
|
|
3163
3359
|
loadSessionIndex() {
|
|
3164
3360
|
if (this.sessionIndexCache.size > 0) return;
|
|
3165
|
-
const
|
|
3166
|
-
const indexPath = join7(roots.codexRoot, "session_index.jsonl");
|
|
3361
|
+
const indexPath = this.getSessionIndexPath();
|
|
3167
3362
|
if (!existsSync7(indexPath)) return;
|
|
3168
3363
|
try {
|
|
3169
3364
|
const content = readFileSync5(indexPath, "utf-8");
|
|
@@ -3646,16 +3841,17 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3646
3841
|
if (!name) {
|
|
3647
3842
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan: null };
|
|
3648
3843
|
}
|
|
3649
|
-
const
|
|
3844
|
+
const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
|
|
3650
3845
|
const arguments_ = normalizeToolArguments2(payload["arguments"]);
|
|
3651
3846
|
const toolPart = {
|
|
3652
3847
|
type: "tool",
|
|
3653
|
-
tool:
|
|
3848
|
+
tool: toolIdentity.tool,
|
|
3654
3849
|
callID: callId,
|
|
3655
|
-
title: `Tool: ${
|
|
3850
|
+
title: `Tool: ${toolIdentity.tool}`,
|
|
3656
3851
|
state: {
|
|
3657
3852
|
arguments: arguments_,
|
|
3658
|
-
output: null
|
|
3853
|
+
output: null,
|
|
3854
|
+
metadata: toolIdentity.metadata
|
|
3659
3855
|
},
|
|
3660
3856
|
time_created: timestampMs
|
|
3661
3857
|
};
|
|
@@ -3712,17 +3908,18 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3712
3908
|
if (!name) {
|
|
3713
3909
|
return { currentAssistantIndex, latestAssistantTextIndex, pendingPlan: null };
|
|
3714
3910
|
}
|
|
3715
|
-
const
|
|
3911
|
+
const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
|
|
3716
3912
|
const rawInput = payload["input"];
|
|
3717
3913
|
const normalizedInput = normalizeCustomToolArguments(name, rawInput);
|
|
3718
3914
|
const toolPart = {
|
|
3719
3915
|
type: "tool",
|
|
3720
|
-
tool:
|
|
3916
|
+
tool: toolIdentity.tool,
|
|
3721
3917
|
callID: callId,
|
|
3722
|
-
title: `Tool: ${
|
|
3918
|
+
title: `Tool: ${toolIdentity.tool}`,
|
|
3723
3919
|
state: {
|
|
3724
3920
|
arguments: normalizedInput,
|
|
3725
|
-
output: null
|
|
3921
|
+
output: null,
|
|
3922
|
+
metadata: toolIdentity.metadata
|
|
3726
3923
|
},
|
|
3727
3924
|
time_created: timestampMs
|
|
3728
3925
|
};
|
|
@@ -3797,7 +3994,7 @@ var CURSOR_TOOL_TITLE_MAP = {
|
|
|
3797
3994
|
ripgrep_raw_search: "grep",
|
|
3798
3995
|
glob_file_search: "glob"
|
|
3799
3996
|
};
|
|
3800
|
-
function
|
|
3997
|
+
function mapToolTitle2(toolName) {
|
|
3801
3998
|
return CURSOR_TOOL_TITLE_MAP[toolName] ?? toolName;
|
|
3802
3999
|
}
|
|
3803
4000
|
function normalizeToolOutputParts2(output, timestampMs) {
|
|
@@ -3866,9 +4063,9 @@ function buildToolPart(action, timestampMs) {
|
|
|
3866
4063
|
const toolName = action.tool ?? "unknown";
|
|
3867
4064
|
return {
|
|
3868
4065
|
type: "tool",
|
|
3869
|
-
tool:
|
|
4066
|
+
tool: mapToolTitle2(toolName),
|
|
3870
4067
|
callID: action.type ? `${action.type}:${String(action.input?.id ?? "")}` : "",
|
|
3871
|
-
title: `Tool: ${
|
|
4068
|
+
title: `Tool: ${mapToolTitle2(toolName)}`,
|
|
3872
4069
|
state: buildToolState(action),
|
|
3873
4070
|
time_created: timestampMs
|
|
3874
4071
|
};
|
|
@@ -3990,6 +4187,8 @@ var CursorAgent = class extends BaseAgent {
|
|
|
3990
4187
|
try {
|
|
3991
4188
|
const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'").all();
|
|
3992
4189
|
const heads = [];
|
|
4190
|
+
options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
|
|
4191
|
+
let processed = 0;
|
|
3993
4192
|
for (const row of rows) {
|
|
3994
4193
|
try {
|
|
3995
4194
|
const composer = JSON.parse(row.value);
|
|
@@ -4093,6 +4292,9 @@ var CursorAgent = class extends BaseAgent {
|
|
|
4093
4292
|
sourcePath: this.dbPath || ""
|
|
4094
4293
|
});
|
|
4095
4294
|
} catch {
|
|
4295
|
+
} finally {
|
|
4296
|
+
processed += 1;
|
|
4297
|
+
options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
|
|
4096
4298
|
}
|
|
4097
4299
|
}
|
|
4098
4300
|
perf.end(scanMarker);
|
|
@@ -4350,7 +4552,7 @@ var CursorAgent = class extends BaseAgent {
|
|
|
4350
4552
|
convertToolFormerData(toolData, timestampMs) {
|
|
4351
4553
|
if (!toolData || !toolData.name) return null;
|
|
4352
4554
|
const toolName = toolData.name;
|
|
4353
|
-
const normalizedName = toolName === "create_plan" ? "plan" :
|
|
4555
|
+
const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle2(toolName);
|
|
4354
4556
|
const state = {
|
|
4355
4557
|
status: toolData.status === "completed" ? "completed" : "running"
|
|
4356
4558
|
};
|
|
@@ -4579,7 +4781,7 @@ function computeIdentity(cwd, fs) {
|
|
|
4579
4781
|
if (!cwd) return loose();
|
|
4580
4782
|
const pathOps = getPathOps(cwd);
|
|
4581
4783
|
const absoluteCwd = pathOps.resolve(cwd);
|
|
4582
|
-
const homeDir =
|
|
4784
|
+
const homeDir = os.homedir();
|
|
4583
4785
|
const homePathOps = getPathOps(homeDir);
|
|
4584
4786
|
const home = homePathOps === pathOps ? pathOps.resolve(homeDir) : homeDir;
|
|
4585
4787
|
if (absoluteCwd === home || LOOSE_DIRS.has(absoluteCwd)) return loose();
|
|
@@ -4620,6 +4822,10 @@ function computeIdentity(cwd, fs) {
|
|
|
4620
4822
|
displayName: deriveDisplayName({ kind: "manifest_path", key: manifestDir, fs })
|
|
4621
4823
|
};
|
|
4622
4824
|
}
|
|
4825
|
+
if (homePathOps === pathOps) {
|
|
4826
|
+
const synthetic = synthesizeCodexScratchIdentity(absoluteCwd, home, pathOps);
|
|
4827
|
+
if (synthetic) return synthetic;
|
|
4828
|
+
}
|
|
4623
4829
|
return {
|
|
4624
4830
|
kind: "path",
|
|
4625
4831
|
key: absoluteCwd,
|
|
@@ -4629,6 +4835,14 @@ function computeIdentity(cwd, fs) {
|
|
|
4629
4835
|
function loose() {
|
|
4630
4836
|
return { kind: "loose", key: "loose", displayName: "Loose" };
|
|
4631
4837
|
}
|
|
4838
|
+
function synthesizeCodexScratchIdentity(absoluteCwd, home, pathOps) {
|
|
4839
|
+
const root = pathOps.resolve(pathOps.join(home, "Documents", "Codex"));
|
|
4840
|
+
const child = pathOps.relative(root, absoluteCwd);
|
|
4841
|
+
if (!child || child === ".." || child.startsWith(`..${pathOps.sep}`) || pathOps.isAbsolute(child)) {
|
|
4842
|
+
return null;
|
|
4843
|
+
}
|
|
4844
|
+
return { kind: "synthetic", key: "codex:scratch", displayName: "Chats" };
|
|
4845
|
+
}
|
|
4632
4846
|
function getPathOps(input) {
|
|
4633
4847
|
if (/^[a-zA-Z]:[\\/]/.test(input) || input.startsWith("\\\\") || input.includes("\\")) {
|
|
4634
4848
|
return path.win32;
|
|
@@ -4684,6 +4898,28 @@ function parseManifestName(file, text) {
|
|
|
4684
4898
|
}
|
|
4685
4899
|
return null;
|
|
4686
4900
|
}
|
|
4901
|
+
function createProjectScopeMatcher(queryPath, fs = realFs) {
|
|
4902
|
+
return {
|
|
4903
|
+
identityKey: computeIdentity(queryPath, fs).key,
|
|
4904
|
+
path: normalizeScopePath(queryPath)
|
|
4905
|
+
};
|
|
4906
|
+
}
|
|
4907
|
+
function matchesProjectScope(session, scope) {
|
|
4908
|
+
if (!session.directory) return false;
|
|
4909
|
+
if (session.project_identity?.key === scope.identityKey) return true;
|
|
4910
|
+
return isPathScopeMatch(scope.path, session.directory);
|
|
4911
|
+
}
|
|
4912
|
+
function filterSessionsByProjectScope(sessions, queryPath, fs) {
|
|
4913
|
+
const scope = createProjectScopeMatcher(queryPath, fs);
|
|
4914
|
+
return sessions.filter((session) => matchesProjectScope(session, scope));
|
|
4915
|
+
}
|
|
4916
|
+
function isPathScopeMatch(queryPath, sessionPath) {
|
|
4917
|
+
const session = normalizeScopePath(sessionPath);
|
|
4918
|
+
return session === queryPath || session.startsWith(queryPath + "/") || queryPath.startsWith(session + "/");
|
|
4919
|
+
}
|
|
4920
|
+
function normalizeScopePath(path2) {
|
|
4921
|
+
return resolve(path2).replaceAll(sep, "/");
|
|
4922
|
+
}
|
|
4687
4923
|
var TAG_ORDER = [
|
|
4688
4924
|
"bugfix",
|
|
4689
4925
|
"refactoring",
|
|
@@ -4953,7 +5189,8 @@ function extractSessionFileActivity(agentName, sessionId, projectIdentityKey, me
|
|
|
4953
5189
|
extractFileActivityOccurrences(messages)
|
|
4954
5190
|
);
|
|
4955
5191
|
}
|
|
4956
|
-
var CACHE_SCHEMA_VERSION =
|
|
5192
|
+
var CACHE_SCHEMA_VERSION = 13;
|
|
5193
|
+
var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
|
|
4957
5194
|
var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
|
|
4958
5195
|
var CACHE_FILENAME = "codesesh.db";
|
|
4959
5196
|
var LEGACY_CACHE_FILENAME = "scan-cache.json";
|
|
@@ -4984,6 +5221,17 @@ function withCacheDb(fn) {
|
|
|
4984
5221
|
db.close();
|
|
4985
5222
|
}
|
|
4986
5223
|
}
|
|
5224
|
+
function withCacheDbReadOnly(fn) {
|
|
5225
|
+
const db = openDbReadOnly(getCachePath2());
|
|
5226
|
+
if (!db) return null;
|
|
5227
|
+
try {
|
|
5228
|
+
return fn(db);
|
|
5229
|
+
} catch {
|
|
5230
|
+
return null;
|
|
5231
|
+
} finally {
|
|
5232
|
+
db.close();
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
4987
5235
|
function createCacheTables(db) {
|
|
4988
5236
|
db.exec(`
|
|
4989
5237
|
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
@@ -5003,6 +5251,13 @@ function createCacheTables(db) {
|
|
|
5003
5251
|
meta_json TEXT,
|
|
5004
5252
|
PRIMARY KEY (agent_name, session_id)
|
|
5005
5253
|
);
|
|
5254
|
+
|
|
5255
|
+
CREATE TABLE IF NOT EXISTS cache_initialization (
|
|
5256
|
+
agent_name TEXT PRIMARY KEY,
|
|
5257
|
+
initialized_at INTEGER NOT NULL,
|
|
5258
|
+
index_version TEXT NOT NULL,
|
|
5259
|
+
last_sync_at INTEGER NOT NULL
|
|
5260
|
+
);
|
|
5006
5261
|
`);
|
|
5007
5262
|
}
|
|
5008
5263
|
function createSessionTables(db) {
|
|
@@ -5071,38 +5326,148 @@ function createSessionTables(db) {
|
|
|
5071
5326
|
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
5072
5327
|
ON messages(agent_name, session_id, message_index);
|
|
5073
5328
|
`);
|
|
5329
|
+
createMessageToolTables(db);
|
|
5074
5330
|
}
|
|
5075
|
-
function
|
|
5331
|
+
function createMessageToolTables(db) {
|
|
5076
5332
|
db.exec(`
|
|
5077
|
-
CREATE TABLE IF NOT EXISTS
|
|
5333
|
+
CREATE TABLE IF NOT EXISTS message_tools (
|
|
5078
5334
|
agent_name TEXT NOT NULL,
|
|
5079
5335
|
session_id TEXT NOT NULL,
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
PRIMARY KEY (agent_name, session_id, project_identity_key, path, kind),
|
|
5086
|
-
FOREIGN KEY (agent_name, session_id)
|
|
5087
|
-
REFERENCES sessions(agent_name, session_id)
|
|
5336
|
+
message_index INTEGER NOT NULL,
|
|
5337
|
+
tool_name TEXT NOT NULL,
|
|
5338
|
+
PRIMARY KEY (agent_name, session_id, message_index, tool_name),
|
|
5339
|
+
FOREIGN KEY (agent_name, session_id, message_index)
|
|
5340
|
+
REFERENCES messages(agent_name, session_id, message_index)
|
|
5088
5341
|
ON DELETE CASCADE
|
|
5089
5342
|
);
|
|
5090
5343
|
|
|
5091
|
-
CREATE INDEX IF NOT EXISTS
|
|
5092
|
-
ON
|
|
5093
|
-
|
|
5094
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_path
|
|
5095
|
-
ON session_file_activity(path);
|
|
5096
|
-
|
|
5097
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_kind
|
|
5098
|
-
ON session_file_activity(kind);
|
|
5344
|
+
CREATE INDEX IF NOT EXISTS idx_message_tools_filter
|
|
5345
|
+
ON message_tools(tool_name, agent_name, session_id);
|
|
5099
5346
|
`);
|
|
5100
5347
|
}
|
|
5101
|
-
function
|
|
5348
|
+
function createMessageSearchTables(db) {
|
|
5349
|
+
if (!tableExists(db, "messages")) {
|
|
5350
|
+
createSessionTables(db);
|
|
5351
|
+
}
|
|
5102
5352
|
db.exec(`
|
|
5103
|
-
CREATE TABLE IF NOT EXISTS
|
|
5104
|
-
|
|
5105
|
-
|
|
5353
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
5354
|
+
content_text,
|
|
5355
|
+
content='messages',
|
|
5356
|
+
content_rowid='rowid'
|
|
5357
|
+
);
|
|
5358
|
+
`);
|
|
5359
|
+
createMessageSearchTriggers(db);
|
|
5360
|
+
}
|
|
5361
|
+
function createMessageSearchTriggers(db) {
|
|
5362
|
+
db.exec(`
|
|
5363
|
+
CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
|
|
5364
|
+
INSERT INTO messages_fts(rowid, content_text)
|
|
5365
|
+
VALUES (new.rowid, new.content_text);
|
|
5366
|
+
END;
|
|
5367
|
+
|
|
5368
|
+
CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
|
|
5369
|
+
INSERT INTO messages_fts(messages_fts, rowid, content_text)
|
|
5370
|
+
VALUES ('delete', old.rowid, old.content_text);
|
|
5371
|
+
END;
|
|
5372
|
+
|
|
5373
|
+
CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
|
|
5374
|
+
INSERT INTO messages_fts(messages_fts, rowid, content_text)
|
|
5375
|
+
VALUES ('delete', old.rowid, old.content_text);
|
|
5376
|
+
INSERT INTO messages_fts(rowid, content_text)
|
|
5377
|
+
VALUES (new.rowid, new.content_text);
|
|
5378
|
+
END;
|
|
5379
|
+
`);
|
|
5380
|
+
}
|
|
5381
|
+
function dropMessageSearchTriggers(db) {
|
|
5382
|
+
db.exec(`
|
|
5383
|
+
DROP TRIGGER IF EXISTS messages_ai;
|
|
5384
|
+
DROP TRIGGER IF EXISTS messages_ad;
|
|
5385
|
+
DROP TRIGGER IF EXISTS messages_au;
|
|
5386
|
+
`);
|
|
5387
|
+
}
|
|
5388
|
+
function createFileActivityTables(db) {
|
|
5389
|
+
db.exec(`
|
|
5390
|
+
CREATE TABLE IF NOT EXISTS session_file_activity (
|
|
5391
|
+
agent_name TEXT NOT NULL,
|
|
5392
|
+
session_id TEXT NOT NULL,
|
|
5393
|
+
project_identity_key TEXT NOT NULL,
|
|
5394
|
+
path TEXT NOT NULL,
|
|
5395
|
+
kind TEXT NOT NULL,
|
|
5396
|
+
count INTEGER NOT NULL,
|
|
5397
|
+
latest_time INTEGER NOT NULL,
|
|
5398
|
+
PRIMARY KEY (agent_name, session_id, project_identity_key, path, kind),
|
|
5399
|
+
FOREIGN KEY (agent_name, session_id)
|
|
5400
|
+
REFERENCES sessions(agent_name, session_id)
|
|
5401
|
+
ON DELETE CASCADE
|
|
5402
|
+
);
|
|
5403
|
+
|
|
5404
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest
|
|
5405
|
+
ON session_file_activity(project_identity_key, latest_time);
|
|
5406
|
+
|
|
5407
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_latest
|
|
5408
|
+
ON session_file_activity(latest_time DESC, count DESC, path);
|
|
5409
|
+
|
|
5410
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_agent_latest
|
|
5411
|
+
ON session_file_activity(agent_name, latest_time DESC, count DESC, path);
|
|
5412
|
+
|
|
5413
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest_ordered
|
|
5414
|
+
ON session_file_activity(project_identity_key, latest_time DESC, count DESC, path);
|
|
5415
|
+
|
|
5416
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_path
|
|
5417
|
+
ON session_file_activity(path);
|
|
5418
|
+
|
|
5419
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_kind
|
|
5420
|
+
ON session_file_activity(kind);
|
|
5421
|
+
`);
|
|
5422
|
+
createFileActivityPathSearchTables(db);
|
|
5423
|
+
}
|
|
5424
|
+
function createFileActivityPathSearchTables(db) {
|
|
5425
|
+
db.exec(`
|
|
5426
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS session_file_activity_path_fts USING fts5(
|
|
5427
|
+
path,
|
|
5428
|
+
content='session_file_activity',
|
|
5429
|
+
content_rowid='rowid',
|
|
5430
|
+
tokenize='trigram'
|
|
5431
|
+
);
|
|
5432
|
+
`);
|
|
5433
|
+
createFileActivityPathSearchTriggers(db);
|
|
5434
|
+
}
|
|
5435
|
+
function createFileActivityPathSearchTriggers(db) {
|
|
5436
|
+
db.exec(`
|
|
5437
|
+
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_ai
|
|
5438
|
+
AFTER INSERT ON session_file_activity BEGIN
|
|
5439
|
+
INSERT INTO session_file_activity_path_fts(rowid, path)
|
|
5440
|
+
VALUES (new.rowid, new.path);
|
|
5441
|
+
END;
|
|
5442
|
+
|
|
5443
|
+
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_ad
|
|
5444
|
+
AFTER DELETE ON session_file_activity BEGIN
|
|
5445
|
+
INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts, rowid, path)
|
|
5446
|
+
VALUES ('delete', old.rowid, old.path);
|
|
5447
|
+
END;
|
|
5448
|
+
|
|
5449
|
+
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_au
|
|
5450
|
+
AFTER UPDATE ON session_file_activity BEGIN
|
|
5451
|
+
INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts, rowid, path)
|
|
5452
|
+
VALUES ('delete', old.rowid, old.path);
|
|
5453
|
+
INSERT INTO session_file_activity_path_fts(rowid, path)
|
|
5454
|
+
VALUES (new.rowid, new.path);
|
|
5455
|
+
END;
|
|
5456
|
+
`);
|
|
5457
|
+
}
|
|
5458
|
+
function rebuildFileActivityPathIndex(db) {
|
|
5459
|
+
if (!tableExists(db, "session_file_activity_path_fts")) {
|
|
5460
|
+
return;
|
|
5461
|
+
}
|
|
5462
|
+
db.exec(
|
|
5463
|
+
"INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts) VALUES ('rebuild')"
|
|
5464
|
+
);
|
|
5465
|
+
}
|
|
5466
|
+
function createSearchTables(db) {
|
|
5467
|
+
db.exec(`
|
|
5468
|
+
CREATE TABLE IF NOT EXISTS session_documents (
|
|
5469
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5470
|
+
agent_name TEXT NOT NULL,
|
|
5106
5471
|
session_id TEXT NOT NULL,
|
|
5107
5472
|
slug TEXT NOT NULL,
|
|
5108
5473
|
title TEXT NOT NULL,
|
|
@@ -5227,6 +5592,7 @@ function recreateProjectGroupsView(db) {
|
|
|
5227
5592
|
function createLatestCacheSchema(db) {
|
|
5228
5593
|
createCacheTables(db);
|
|
5229
5594
|
createSessionTables(db);
|
|
5595
|
+
createMessageSearchTables(db);
|
|
5230
5596
|
createFileActivityTables(db);
|
|
5231
5597
|
createSearchTables(db);
|
|
5232
5598
|
createProjectTables(db);
|
|
@@ -5245,6 +5611,34 @@ function sourcePathFromMetaJson(metaJson) {
|
|
|
5245
5611
|
const meta = JSON.parse(metaJson);
|
|
5246
5612
|
return sourcePathFromMeta(meta);
|
|
5247
5613
|
}
|
|
5614
|
+
function prepareUpsertCachedSession(db) {
|
|
5615
|
+
return db.prepare(`
|
|
5616
|
+
INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
|
|
5617
|
+
VALUES (?, ?, ?, ?)
|
|
5618
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
5619
|
+
session_json = excluded.session_json,
|
|
5620
|
+
meta_json = excluded.meta_json
|
|
5621
|
+
`);
|
|
5622
|
+
}
|
|
5623
|
+
function prepareUpsertProjectSession(db) {
|
|
5624
|
+
return db.prepare(`
|
|
5625
|
+
INSERT INTO project_sessions(
|
|
5626
|
+
agent_name,
|
|
5627
|
+
session_id,
|
|
5628
|
+
identity_kind,
|
|
5629
|
+
identity_key,
|
|
5630
|
+
display_name,
|
|
5631
|
+
directory,
|
|
5632
|
+
activity_time
|
|
5633
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
5634
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
5635
|
+
identity_kind = excluded.identity_kind,
|
|
5636
|
+
identity_key = excluded.identity_key,
|
|
5637
|
+
display_name = excluded.display_name,
|
|
5638
|
+
directory = excluded.directory,
|
|
5639
|
+
activity_time = excluded.activity_time
|
|
5640
|
+
`);
|
|
5641
|
+
}
|
|
5248
5642
|
function prepareUpsertSession(db) {
|
|
5249
5643
|
return db.prepare(`
|
|
5250
5644
|
INSERT INTO sessions(
|
|
@@ -5396,6 +5790,16 @@ function prepareInsertFileActivity(db) {
|
|
|
5396
5790
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
5397
5791
|
`);
|
|
5398
5792
|
}
|
|
5793
|
+
function prepareInsertMessageTool(db) {
|
|
5794
|
+
return db.prepare(`
|
|
5795
|
+
INSERT OR IGNORE INTO message_tools(
|
|
5796
|
+
agent_name,
|
|
5797
|
+
session_id,
|
|
5798
|
+
message_index,
|
|
5799
|
+
tool_name
|
|
5800
|
+
) VALUES (?, ?, ?, ?)
|
|
5801
|
+
`);
|
|
5802
|
+
}
|
|
5399
5803
|
function writeFileActivityRows(statement, activities) {
|
|
5400
5804
|
for (const activity of activities) {
|
|
5401
5805
|
statement.run(
|
|
@@ -5409,6 +5813,17 @@ function writeFileActivityRows(statement, activities) {
|
|
|
5409
5813
|
);
|
|
5410
5814
|
}
|
|
5411
5815
|
}
|
|
5816
|
+
function writeProjectSessionRow(statement, agentName, session, identity) {
|
|
5817
|
+
statement.run(
|
|
5818
|
+
agentName,
|
|
5819
|
+
session.id,
|
|
5820
|
+
identity.kind,
|
|
5821
|
+
identity.key,
|
|
5822
|
+
identity.displayName,
|
|
5823
|
+
session.directory,
|
|
5824
|
+
session.time_updated ?? session.time_created
|
|
5825
|
+
);
|
|
5826
|
+
}
|
|
5412
5827
|
function sessionFromRow(row) {
|
|
5413
5828
|
const session = {
|
|
5414
5829
|
id: String(row.session_id),
|
|
@@ -5476,6 +5891,15 @@ function readLegacyCacheVersion(db) {
|
|
|
5476
5891
|
return Number(versionRow?.value ?? 0);
|
|
5477
5892
|
}
|
|
5478
5893
|
function inferCacheSchemaVersion(db) {
|
|
5894
|
+
if (tableExists(db, "message_tools")) {
|
|
5895
|
+
return 11;
|
|
5896
|
+
}
|
|
5897
|
+
if (tableExists(db, "session_file_activity_path_fts")) {
|
|
5898
|
+
return 10;
|
|
5899
|
+
}
|
|
5900
|
+
if (tableExists(db, "messages_fts")) {
|
|
5901
|
+
return 9;
|
|
5902
|
+
}
|
|
5479
5903
|
if (tableExists(db, "session_file_activity")) {
|
|
5480
5904
|
return 8;
|
|
5481
5905
|
}
|
|
@@ -5508,7 +5932,9 @@ function hasAnyCacheSchema(db) {
|
|
|
5508
5932
|
"cached_sessions",
|
|
5509
5933
|
"sessions",
|
|
5510
5934
|
"messages",
|
|
5935
|
+
"message_tools",
|
|
5511
5936
|
"session_file_activity",
|
|
5937
|
+
"session_file_activity_path_fts",
|
|
5512
5938
|
"session_documents",
|
|
5513
5939
|
"session_documents_fts",
|
|
5514
5940
|
"project_sessions"
|
|
@@ -5580,6 +6006,46 @@ function migrateProjectIdentity(db) {
|
|
|
5580
6006
|
backfillProjectSessions(db);
|
|
5581
6007
|
backfillSessionDocumentProjects(db);
|
|
5582
6008
|
}
|
|
6009
|
+
function refreshProjectIdentities(db) {
|
|
6010
|
+
if (tableExists(db, "sessions") && columnExists(db, "sessions", "project_identity_key") && columnExists(db, "sessions", "directory")) {
|
|
6011
|
+
const rows = db.prepare("SELECT agent_name, session_id, directory FROM sessions").all();
|
|
6012
|
+
const update = db.prepare(`
|
|
6013
|
+
UPDATE sessions
|
|
6014
|
+
SET
|
|
6015
|
+
project_identity_kind = ?,
|
|
6016
|
+
project_identity_key = ?,
|
|
6017
|
+
project_display_name = ?
|
|
6018
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6019
|
+
`);
|
|
6020
|
+
const updateFileActivity = tableExists(db, "session_file_activity") && columnExists(db, "session_file_activity", "project_identity_key") ? db.prepare(`
|
|
6021
|
+
UPDATE session_file_activity
|
|
6022
|
+
SET project_identity_key = ?
|
|
6023
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6024
|
+
`) : null;
|
|
6025
|
+
for (const row of rows) {
|
|
6026
|
+
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6027
|
+
update.run(identity.kind, identity.key, identity.displayName, row.agent_name, row.session_id);
|
|
6028
|
+
updateFileActivity?.run(identity.key, row.agent_name, row.session_id);
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
if (tableExists(db, "project_sessions") && columnExists(db, "project_sessions", "identity_key") && columnExists(db, "project_sessions", "directory")) {
|
|
6032
|
+
const rows = db.prepare("SELECT agent_name, session_id, directory FROM project_sessions").all();
|
|
6033
|
+
const update = db.prepare(`
|
|
6034
|
+
UPDATE project_sessions
|
|
6035
|
+
SET
|
|
6036
|
+
identity_kind = ?,
|
|
6037
|
+
identity_key = ?,
|
|
6038
|
+
display_name = ?
|
|
6039
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6040
|
+
`);
|
|
6041
|
+
for (const row of rows) {
|
|
6042
|
+
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6043
|
+
update.run(identity.kind, identity.key, identity.displayName, row.agent_name, row.session_id);
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
backfillSessionDocumentProjects(db);
|
|
6047
|
+
recreateProjectGroupsView(db);
|
|
6048
|
+
}
|
|
5583
6049
|
function backfillStructuredSessions(db) {
|
|
5584
6050
|
createSessionTables(db);
|
|
5585
6051
|
recreateProjectGroupsView(db);
|
|
@@ -5678,6 +6144,43 @@ function messageFromBackfillRow(row) {
|
|
|
5678
6144
|
nickname: row.nickname ?? void 0
|
|
5679
6145
|
};
|
|
5680
6146
|
}
|
|
6147
|
+
function messageFromCachedRow(row) {
|
|
6148
|
+
const message = messageFromBackfillRow(row);
|
|
6149
|
+
const tokens = parseOptionalJson(row.tokens_json);
|
|
6150
|
+
if (tokens) {
|
|
6151
|
+
message.tokens = tokens;
|
|
6152
|
+
}
|
|
6153
|
+
if (row.cost != null) {
|
|
6154
|
+
message.cost = Number(row.cost);
|
|
6155
|
+
}
|
|
6156
|
+
if (row.cost_source) {
|
|
6157
|
+
message.cost_source = row.cost_source;
|
|
6158
|
+
}
|
|
6159
|
+
return message;
|
|
6160
|
+
}
|
|
6161
|
+
function backfillMessageTools(db) {
|
|
6162
|
+
createMessageToolTables(db);
|
|
6163
|
+
if (!tableExists(db, "messages")) {
|
|
6164
|
+
return;
|
|
6165
|
+
}
|
|
6166
|
+
db.exec("DELETE FROM message_tools");
|
|
6167
|
+
const rows = db.prepare(
|
|
6168
|
+
`
|
|
6169
|
+
SELECT agent_name, session_id, message_index, tool_metadata_json
|
|
6170
|
+
FROM messages
|
|
6171
|
+
WHERE tool_metadata_json IS NOT NULL
|
|
6172
|
+
`
|
|
6173
|
+
).all();
|
|
6174
|
+
const insertTool = prepareInsertMessageTool(db);
|
|
6175
|
+
for (const row of rows) {
|
|
6176
|
+
if (!row.agent_name || !row.session_id || row.message_index == null) {
|
|
6177
|
+
continue;
|
|
6178
|
+
}
|
|
6179
|
+
for (const toolName of toolNamesFromMetadataJson(row.tool_metadata_json)) {
|
|
6180
|
+
insertTool.run(row.agent_name, row.session_id, row.message_index, toolName);
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
5681
6184
|
function backfillFileActivity(db) {
|
|
5682
6185
|
createFileActivityTables(db);
|
|
5683
6186
|
if (!tableExists(db, "sessions") || !tableExists(db, "messages")) {
|
|
@@ -5742,6 +6245,12 @@ function rebuildSearchIndex(db) {
|
|
|
5742
6245
|
}
|
|
5743
6246
|
db.exec("INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild')");
|
|
5744
6247
|
}
|
|
6248
|
+
function rebuildMessageSearchIndex(db) {
|
|
6249
|
+
if (!tableExists(db, "messages_fts")) {
|
|
6250
|
+
return;
|
|
6251
|
+
}
|
|
6252
|
+
db.exec("INSERT INTO messages_fts(messages_fts) VALUES ('rebuild')");
|
|
6253
|
+
}
|
|
5745
6254
|
function shouldBulkSyncSearchIndex(options, changedCount) {
|
|
5746
6255
|
if (options.isBulk != null) {
|
|
5747
6256
|
return options.isBulk;
|
|
@@ -5754,6 +6263,11 @@ function ensureFtsReady(db) {
|
|
|
5754
6263
|
createSearchTables(db);
|
|
5755
6264
|
}
|
|
5756
6265
|
createSearchTriggers(db);
|
|
6266
|
+
const needsMessageSearchRebuild = !tableExists(db, "messages_fts");
|
|
6267
|
+
createMessageSearchTables(db);
|
|
6268
|
+
if (needsMessageSearchRebuild) {
|
|
6269
|
+
rebuildMessageSearchIndex(db);
|
|
6270
|
+
}
|
|
5757
6271
|
}
|
|
5758
6272
|
function ensureFtsConsistency(db) {
|
|
5759
6273
|
ensureFtsReady(db);
|
|
@@ -5765,9 +6279,11 @@ function ensureFtsConsistency(db) {
|
|
|
5765
6279
|
db.exec(
|
|
5766
6280
|
"INSERT INTO session_documents_fts(session_documents_fts, rank) VALUES ('integrity-check', 1)"
|
|
5767
6281
|
);
|
|
6282
|
+
db.exec("INSERT INTO messages_fts(messages_fts, rank) VALUES ('integrity-check', 1)");
|
|
5768
6283
|
ftsIntegrityCheckedPath = cachePath;
|
|
5769
6284
|
} catch {
|
|
5770
6285
|
rebuildSearchIndex(db);
|
|
6286
|
+
rebuildMessageSearchIndex(db);
|
|
5771
6287
|
ftsIntegrityCheckedPath = cachePath;
|
|
5772
6288
|
}
|
|
5773
6289
|
}
|
|
@@ -5796,9 +6312,11 @@ function ensureSchema(db, dbPath) {
|
|
|
5796
6312
|
backupLabel: "cache-migration",
|
|
5797
6313
|
backupTables: [
|
|
5798
6314
|
"agent_cache",
|
|
6315
|
+
"cache_initialization",
|
|
5799
6316
|
"cached_sessions",
|
|
5800
6317
|
"sessions",
|
|
5801
6318
|
"messages",
|
|
6319
|
+
"message_tools",
|
|
5802
6320
|
"session_file_activity",
|
|
5803
6321
|
"session_documents",
|
|
5804
6322
|
"project_sessions"
|
|
@@ -5817,7 +6335,34 @@ function ensureSchema(db, dbPath) {
|
|
|
5817
6335
|
}
|
|
5818
6336
|
},
|
|
5819
6337
|
{ version: 7, migrate: backfillStructuredSessions },
|
|
5820
|
-
{ version: 8, migrate: backfillFileActivity }
|
|
6338
|
+
{ version: 8, migrate: backfillFileActivity },
|
|
6339
|
+
{
|
|
6340
|
+
version: 9,
|
|
6341
|
+
migrate(db2) {
|
|
6342
|
+
createMessageSearchTables(db2);
|
|
6343
|
+
rebuildMessageSearchIndex(db2);
|
|
6344
|
+
}
|
|
6345
|
+
},
|
|
6346
|
+
{
|
|
6347
|
+
version: 10,
|
|
6348
|
+
migrate(db2) {
|
|
6349
|
+
createFileActivityPathSearchTables(db2);
|
|
6350
|
+
rebuildFileActivityPathIndex(db2);
|
|
6351
|
+
}
|
|
6352
|
+
},
|
|
6353
|
+
{
|
|
6354
|
+
version: 11,
|
|
6355
|
+
migrate(db2) {
|
|
6356
|
+
backfillMessageTools(db2);
|
|
6357
|
+
}
|
|
6358
|
+
},
|
|
6359
|
+
{
|
|
6360
|
+
version: 12,
|
|
6361
|
+
migrate(db2) {
|
|
6362
|
+
refreshProjectIdentities(db2);
|
|
6363
|
+
}
|
|
6364
|
+
},
|
|
6365
|
+
{ version: 13, migrate: createCacheTables }
|
|
5821
6366
|
]
|
|
5822
6367
|
});
|
|
5823
6368
|
createLatestCacheSchema(db);
|
|
@@ -6002,6 +6547,36 @@ function appendPlainText(value, chunks) {
|
|
|
6002
6547
|
function compactRecord(record) {
|
|
6003
6548
|
return Object.fromEntries(Object.entries(record).filter(([, value]) => value != null));
|
|
6004
6549
|
}
|
|
6550
|
+
function normalizeToolName2(value) {
|
|
6551
|
+
if (typeof value !== "string") return null;
|
|
6552
|
+
const name = value.trim().toLowerCase();
|
|
6553
|
+
return name || null;
|
|
6554
|
+
}
|
|
6555
|
+
function toolNamesFromMetadataJson(value) {
|
|
6556
|
+
if (!value) return [];
|
|
6557
|
+
try {
|
|
6558
|
+
const metadata = JSON.parse(String(value));
|
|
6559
|
+
if (!Array.isArray(metadata)) return [];
|
|
6560
|
+
const tools = /* @__PURE__ */ new Set();
|
|
6561
|
+
for (const item of metadata) {
|
|
6562
|
+
if (item == null || typeof item !== "object") continue;
|
|
6563
|
+
const toolName = normalizeToolName2(item.tool);
|
|
6564
|
+
if (toolName) tools.add(toolName);
|
|
6565
|
+
}
|
|
6566
|
+
return [...tools];
|
|
6567
|
+
} catch {
|
|
6568
|
+
return [];
|
|
6569
|
+
}
|
|
6570
|
+
}
|
|
6571
|
+
function toolNamesFromMessage(message) {
|
|
6572
|
+
const tools = /* @__PURE__ */ new Set();
|
|
6573
|
+
for (const part of message.parts) {
|
|
6574
|
+
if (part.type !== "tool") continue;
|
|
6575
|
+
const toolName = normalizeToolName2(part.tool);
|
|
6576
|
+
if (toolName) tools.add(toolName);
|
|
6577
|
+
}
|
|
6578
|
+
return [...tools];
|
|
6579
|
+
}
|
|
6005
6580
|
function summarizeToolPart(part) {
|
|
6006
6581
|
const state = part.state == null ? void 0 : compactRecord({
|
|
6007
6582
|
status: part.state.status,
|
|
@@ -6055,7 +6630,8 @@ function normalizeMessages(session) {
|
|
|
6055
6630
|
subagentId: message.subagent_id ?? null,
|
|
6056
6631
|
nickname: message.nickname ?? null,
|
|
6057
6632
|
contentText: buildMessageText(message),
|
|
6058
|
-
toolMetadataJson: toolMetadata.length > 0 ? JSON.stringify(toolMetadata) : null
|
|
6633
|
+
toolMetadataJson: toolMetadata.length > 0 ? JSON.stringify(toolMetadata) : null,
|
|
6634
|
+
toolNames: toolNamesFromMessage(message)
|
|
6059
6635
|
};
|
|
6060
6636
|
});
|
|
6061
6637
|
}
|
|
@@ -6077,14 +6653,14 @@ function deleteLegacyCacheFile() {
|
|
|
6077
6653
|
} catch {
|
|
6078
6654
|
}
|
|
6079
6655
|
}
|
|
6080
|
-
function loadCachedSessions(agentName) {
|
|
6656
|
+
function loadCachedSessions(agentName, options = {}) {
|
|
6081
6657
|
if (!hasCacheStorage()) {
|
|
6082
6658
|
return null;
|
|
6083
6659
|
}
|
|
6084
6660
|
return withCacheDb((db) => {
|
|
6085
6661
|
const timestampRow = db.prepare("SELECT timestamp AS value FROM agent_cache WHERE agent_name = ?").get(agentName);
|
|
6086
6662
|
const timestamp = Number(timestampRow?.value ?? 0);
|
|
6087
|
-
if (!timestamp || Date.now() - timestamp > CACHE_TTL) {
|
|
6663
|
+
if (!timestamp || !options.ignoreTtl && Date.now() - timestamp > CACHE_TTL) {
|
|
6088
6664
|
return null;
|
|
6089
6665
|
}
|
|
6090
6666
|
const rows = db.prepare(
|
|
@@ -6130,6 +6706,113 @@ function loadCachedSessions(agentName) {
|
|
|
6130
6706
|
return { sessions, meta, timestamp };
|
|
6131
6707
|
});
|
|
6132
6708
|
}
|
|
6709
|
+
function isAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATION_VERSION) {
|
|
6710
|
+
if (!hasCacheStorage()) {
|
|
6711
|
+
return false;
|
|
6712
|
+
}
|
|
6713
|
+
return withCacheDbReadOnly((db) => {
|
|
6714
|
+
if (!tableExists(db, "cache_initialization")) return false;
|
|
6715
|
+
const row = db.prepare(
|
|
6716
|
+
`
|
|
6717
|
+
SELECT index_version
|
|
6718
|
+
FROM cache_initialization
|
|
6719
|
+
WHERE agent_name = ?
|
|
6720
|
+
`
|
|
6721
|
+
).get(agentName);
|
|
6722
|
+
return row?.index_version === indexVersion;
|
|
6723
|
+
}) ?? false;
|
|
6724
|
+
}
|
|
6725
|
+
function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATION_VERSION) {
|
|
6726
|
+
withCacheDb((db) => {
|
|
6727
|
+
const now = Date.now();
|
|
6728
|
+
db.prepare(
|
|
6729
|
+
`
|
|
6730
|
+
INSERT INTO cache_initialization(agent_name, initialized_at, index_version, last_sync_at)
|
|
6731
|
+
VALUES (?, ?, ?, ?)
|
|
6732
|
+
ON CONFLICT(agent_name) DO UPDATE SET
|
|
6733
|
+
index_version = excluded.index_version,
|
|
6734
|
+
last_sync_at = excluded.last_sync_at
|
|
6735
|
+
`
|
|
6736
|
+
).run(agentName, now, indexVersion, now);
|
|
6737
|
+
});
|
|
6738
|
+
}
|
|
6739
|
+
function loadCachedSessionData(agentName, sessionId) {
|
|
6740
|
+
if (!hasCacheStorage()) {
|
|
6741
|
+
return null;
|
|
6742
|
+
}
|
|
6743
|
+
return withCacheDbReadOnly((db) => {
|
|
6744
|
+
const row = db.prepare(
|
|
6745
|
+
`
|
|
6746
|
+
SELECT
|
|
6747
|
+
session_id,
|
|
6748
|
+
sort_index,
|
|
6749
|
+
slug,
|
|
6750
|
+
title,
|
|
6751
|
+
source_path,
|
|
6752
|
+
directory,
|
|
6753
|
+
project_identity_kind,
|
|
6754
|
+
project_identity_key,
|
|
6755
|
+
project_display_name,
|
|
6756
|
+
time_created,
|
|
6757
|
+
time_updated,
|
|
6758
|
+
message_count,
|
|
6759
|
+
total_input_tokens,
|
|
6760
|
+
total_output_tokens,
|
|
6761
|
+
total_cache_read_tokens,
|
|
6762
|
+
total_cache_create_tokens,
|
|
6763
|
+
total_cost,
|
|
6764
|
+
cost_source,
|
|
6765
|
+
total_tokens,
|
|
6766
|
+
model_usage_json,
|
|
6767
|
+
smart_tags_json,
|
|
6768
|
+
smart_tags_source_updated_at,
|
|
6769
|
+
meta_json
|
|
6770
|
+
FROM sessions
|
|
6771
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6772
|
+
`
|
|
6773
|
+
).get(agentName, sessionId);
|
|
6774
|
+
if (!row) {
|
|
6775
|
+
return null;
|
|
6776
|
+
}
|
|
6777
|
+
const messageRows = db.prepare(
|
|
6778
|
+
`
|
|
6779
|
+
SELECT
|
|
6780
|
+
message_id,
|
|
6781
|
+
role,
|
|
6782
|
+
time_created,
|
|
6783
|
+
time_completed,
|
|
6784
|
+
agent,
|
|
6785
|
+
mode,
|
|
6786
|
+
model,
|
|
6787
|
+
provider,
|
|
6788
|
+
tokens_json,
|
|
6789
|
+
cost,
|
|
6790
|
+
cost_source,
|
|
6791
|
+
parts_json,
|
|
6792
|
+
subagent_id,
|
|
6793
|
+
nickname
|
|
6794
|
+
FROM messages
|
|
6795
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6796
|
+
ORDER BY message_index
|
|
6797
|
+
`
|
|
6798
|
+
).all(agentName, sessionId);
|
|
6799
|
+
const head = sessionFromRow(row);
|
|
6800
|
+
const fileActivityRows = db.prepare(
|
|
6801
|
+
`
|
|
6802
|
+
SELECT agent_name, session_id, project_identity_key, path, kind, count, latest_time
|
|
6803
|
+
FROM session_file_activity
|
|
6804
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6805
|
+
ORDER BY latest_time DESC, count DESC, path
|
|
6806
|
+
LIMIT 500
|
|
6807
|
+
`
|
|
6808
|
+
).all(agentName, sessionId);
|
|
6809
|
+
return {
|
|
6810
|
+
...head,
|
|
6811
|
+
messages: messageRows.map((messageRow) => messageFromCachedRow(messageRow)),
|
|
6812
|
+
file_activity: fileActivityRows.map((activityRow) => fileActivityFromRow(activityRow))
|
|
6813
|
+
};
|
|
6814
|
+
});
|
|
6815
|
+
}
|
|
6133
6816
|
function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
6134
6817
|
withCacheDb((db) => {
|
|
6135
6818
|
const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
|
|
@@ -6140,31 +6823,27 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
6140
6823
|
const deleteSearchDocument = db.prepare(
|
|
6141
6824
|
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
6142
6825
|
);
|
|
6826
|
+
const deleteMessages = db.prepare(
|
|
6827
|
+
"DELETE FROM messages WHERE agent_name = ? AND session_id = ?"
|
|
6828
|
+
);
|
|
6829
|
+
const deleteMessageTools = db.prepare(
|
|
6830
|
+
"DELETE FROM message_tools WHERE agent_name = ? AND session_id = ?"
|
|
6831
|
+
);
|
|
6143
6832
|
const deleteFileActivity = db.prepare(
|
|
6144
6833
|
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6145
6834
|
);
|
|
6835
|
+
const deleteProjectSession = db.prepare(
|
|
6836
|
+
"DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
|
|
6837
|
+
);
|
|
6146
6838
|
const deleteProjectSessions = db.prepare("DELETE FROM project_sessions WHERE agent_name = ?");
|
|
6147
6839
|
const upsertAgent = db.prepare(`
|
|
6148
6840
|
INSERT INTO agent_cache(agent_name, timestamp)
|
|
6149
6841
|
VALUES (?, ?)
|
|
6150
6842
|
ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
|
|
6151
6843
|
`);
|
|
6152
|
-
const
|
|
6153
|
-
INSERT INTO cached_sessions(agent_name, session_id, session_json, meta_json)
|
|
6154
|
-
VALUES (?, ?, ?, ?)
|
|
6155
|
-
`);
|
|
6844
|
+
const upsertCachedSession = prepareUpsertCachedSession(db);
|
|
6156
6845
|
const upsertSession = prepareUpsertSession(db);
|
|
6157
|
-
const
|
|
6158
|
-
INSERT INTO project_sessions(
|
|
6159
|
-
agent_name,
|
|
6160
|
-
session_id,
|
|
6161
|
-
identity_kind,
|
|
6162
|
-
identity_key,
|
|
6163
|
-
display_name,
|
|
6164
|
-
directory,
|
|
6165
|
-
activity_time
|
|
6166
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6167
|
-
`);
|
|
6846
|
+
const upsertProjectSession = prepareUpsertProjectSession(db);
|
|
6168
6847
|
const write = db.transaction(() => {
|
|
6169
6848
|
const timestamp = Date.now();
|
|
6170
6849
|
const sessionIds = new Set(sessions.map((session) => session.id));
|
|
@@ -6177,7 +6856,10 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
6177
6856
|
const sessionId = String(row.session_id);
|
|
6178
6857
|
if (!sessionIds.has(sessionId)) {
|
|
6179
6858
|
deleteSearchDocument.run(agentName, sessionId);
|
|
6859
|
+
deleteMessageTools.run(agentName, sessionId);
|
|
6860
|
+
deleteMessages.run(agentName, sessionId);
|
|
6180
6861
|
deleteFileActivity.run(agentName, sessionId);
|
|
6862
|
+
deleteProjectSession.run(agentName, sessionId);
|
|
6181
6863
|
deleteSession.run(agentName, sessionId);
|
|
6182
6864
|
}
|
|
6183
6865
|
}
|
|
@@ -6185,7 +6867,7 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
6185
6867
|
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
6186
6868
|
const sessionMeta = meta[session.id];
|
|
6187
6869
|
const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
|
|
6188
|
-
|
|
6870
|
+
upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
|
|
6189
6871
|
upsertSessionRow(
|
|
6190
6872
|
upsertSession,
|
|
6191
6873
|
agentName,
|
|
@@ -6194,16 +6876,73 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
6194
6876
|
index,
|
|
6195
6877
|
sourcePathFromMeta(sessionMeta)
|
|
6196
6878
|
);
|
|
6197
|
-
|
|
6879
|
+
writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
|
|
6880
|
+
});
|
|
6881
|
+
});
|
|
6882
|
+
write();
|
|
6883
|
+
deleteLegacyCacheFile();
|
|
6884
|
+
});
|
|
6885
|
+
}
|
|
6886
|
+
function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
|
|
6887
|
+
if (changes.length === 0 && removedSessionIds.length === 0) {
|
|
6888
|
+
return;
|
|
6889
|
+
}
|
|
6890
|
+
withCacheDb((db) => {
|
|
6891
|
+
const deleteLegacySession = db.prepare(
|
|
6892
|
+
"DELETE FROM cached_sessions WHERE agent_name = ? AND session_id = ?"
|
|
6893
|
+
);
|
|
6894
|
+
const deleteSession = db.prepare(
|
|
6895
|
+
"DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
|
|
6896
|
+
);
|
|
6897
|
+
const deleteSearchDocument = db.prepare(
|
|
6898
|
+
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
6899
|
+
);
|
|
6900
|
+
const deleteMessages = db.prepare(
|
|
6901
|
+
"DELETE FROM messages WHERE agent_name = ? AND session_id = ?"
|
|
6902
|
+
);
|
|
6903
|
+
const deleteMessageTools = db.prepare(
|
|
6904
|
+
"DELETE FROM message_tools WHERE agent_name = ? AND session_id = ?"
|
|
6905
|
+
);
|
|
6906
|
+
const deleteFileActivity = db.prepare(
|
|
6907
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6908
|
+
);
|
|
6909
|
+
const deleteProjectSession = db.prepare(
|
|
6910
|
+
"DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
|
|
6911
|
+
);
|
|
6912
|
+
const upsertAgent = db.prepare(`
|
|
6913
|
+
INSERT INTO agent_cache(agent_name, timestamp)
|
|
6914
|
+
VALUES (?, ?)
|
|
6915
|
+
ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
|
|
6916
|
+
`);
|
|
6917
|
+
const upsertCachedSession = prepareUpsertCachedSession(db);
|
|
6918
|
+
const upsertSession = prepareUpsertSession(db);
|
|
6919
|
+
const upsertProjectSession = prepareUpsertProjectSession(db);
|
|
6920
|
+
const write = db.transaction(() => {
|
|
6921
|
+
upsertAgent.run(agentName, Date.now());
|
|
6922
|
+
for (const sessionId of new Set(removedSessionIds)) {
|
|
6923
|
+
deleteLegacySession.run(agentName, sessionId);
|
|
6924
|
+
deleteSearchDocument.run(agentName, sessionId);
|
|
6925
|
+
deleteMessageTools.run(agentName, sessionId);
|
|
6926
|
+
deleteMessages.run(agentName, sessionId);
|
|
6927
|
+
deleteFileActivity.run(agentName, sessionId);
|
|
6928
|
+
deleteProjectSession.run(agentName, sessionId);
|
|
6929
|
+
deleteSession.run(agentName, sessionId);
|
|
6930
|
+
}
|
|
6931
|
+
for (const { session, sortIndex } of changes) {
|
|
6932
|
+
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
6933
|
+
const sessionMeta = meta[session.id];
|
|
6934
|
+
const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
|
|
6935
|
+
upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
|
|
6936
|
+
upsertSessionRow(
|
|
6937
|
+
upsertSession,
|
|
6198
6938
|
agentName,
|
|
6199
|
-
session
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
session.directory,
|
|
6204
|
-
session.time_updated ?? session.time_created
|
|
6939
|
+
session,
|
|
6940
|
+
metaJson,
|
|
6941
|
+
sortIndex,
|
|
6942
|
+
sourcePathFromMeta(sessionMeta)
|
|
6205
6943
|
);
|
|
6206
|
-
|
|
6944
|
+
writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
|
|
6945
|
+
}
|
|
6207
6946
|
});
|
|
6208
6947
|
write();
|
|
6209
6948
|
deleteLegacyCacheFile();
|
|
@@ -6218,9 +6957,11 @@ function clearCache() {
|
|
|
6218
6957
|
withCacheDb((db) => {
|
|
6219
6958
|
db.exec(`
|
|
6220
6959
|
DELETE FROM agent_cache;
|
|
6960
|
+
DELETE FROM cache_initialization;
|
|
6221
6961
|
DELETE FROM cached_sessions;
|
|
6222
6962
|
DELETE FROM session_documents;
|
|
6223
6963
|
DELETE FROM session_file_activity;
|
|
6964
|
+
DELETE FROM message_tools;
|
|
6224
6965
|
DELETE FROM messages;
|
|
6225
6966
|
DELETE FROM sessions;
|
|
6226
6967
|
DELETE FROM project_sessions;
|
|
@@ -6253,6 +6994,173 @@ function getCacheInfo() {
|
|
|
6253
6994
|
});
|
|
6254
6995
|
return info ?? { lastScanTime: null, size: 0 };
|
|
6255
6996
|
}
|
|
6997
|
+
function loadSearchIndexEntry(agentName, change, loadSessionData) {
|
|
6998
|
+
try {
|
|
6999
|
+
const data = loadSessionData(change.session.id);
|
|
7000
|
+
const messages = normalizeMessages(data);
|
|
7001
|
+
const identity = change.session.project_identity ?? data.project_identity ?? computeIdentity(change.session.directory, realFs);
|
|
7002
|
+
return {
|
|
7003
|
+
session: change.session,
|
|
7004
|
+
identity,
|
|
7005
|
+
messages,
|
|
7006
|
+
contentText: buildSessionContentFromMessages(data.title ?? change.session.title, messages),
|
|
7007
|
+
contentHash: sessionContentHash(change.session),
|
|
7008
|
+
fileActivity: extractSessionFileActivity(
|
|
7009
|
+
agentName,
|
|
7010
|
+
change.session.id,
|
|
7011
|
+
identity.key,
|
|
7012
|
+
data.messages
|
|
7013
|
+
),
|
|
7014
|
+
sortIndex: change.sortIndex
|
|
7015
|
+
};
|
|
7016
|
+
} catch {
|
|
7017
|
+
return null;
|
|
7018
|
+
}
|
|
7019
|
+
}
|
|
7020
|
+
function writeSearchIndexRows(db, agentName, removedSessionIds, entries) {
|
|
7021
|
+
const deleteRow = db.prepare(
|
|
7022
|
+
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
7023
|
+
);
|
|
7024
|
+
const deleteMessages = db.prepare(
|
|
7025
|
+
"DELETE FROM messages WHERE agent_name = ? AND session_id = ? AND message_index >= ?"
|
|
7026
|
+
);
|
|
7027
|
+
const deleteMessageTools = db.prepare(
|
|
7028
|
+
"DELETE FROM message_tools WHERE agent_name = ? AND session_id = ? AND message_index >= ?"
|
|
7029
|
+
);
|
|
7030
|
+
const deleteFileActivity = db.prepare(
|
|
7031
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
7032
|
+
);
|
|
7033
|
+
const upsertIndexedSession = prepareUpsertIndexedSession(db);
|
|
7034
|
+
const insertFileActivity = prepareInsertFileActivity(db);
|
|
7035
|
+
const insertMessageTool = prepareInsertMessageTool(db);
|
|
7036
|
+
const upsertMessage = db.prepare(`
|
|
7037
|
+
INSERT INTO messages(
|
|
7038
|
+
agent_name,
|
|
7039
|
+
session_id,
|
|
7040
|
+
message_index,
|
|
7041
|
+
message_id,
|
|
7042
|
+
role,
|
|
7043
|
+
time_created,
|
|
7044
|
+
time_completed,
|
|
7045
|
+
agent,
|
|
7046
|
+
mode,
|
|
7047
|
+
model,
|
|
7048
|
+
provider,
|
|
7049
|
+
tokens_json,
|
|
7050
|
+
cost,
|
|
7051
|
+
cost_source,
|
|
7052
|
+
parts_json,
|
|
7053
|
+
subagent_id,
|
|
7054
|
+
nickname,
|
|
7055
|
+
content_text,
|
|
7056
|
+
tool_metadata_json
|
|
7057
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7058
|
+
ON CONFLICT(agent_name, session_id, message_index) DO UPDATE SET
|
|
7059
|
+
message_id = excluded.message_id,
|
|
7060
|
+
role = excluded.role,
|
|
7061
|
+
time_created = excluded.time_created,
|
|
7062
|
+
time_completed = excluded.time_completed,
|
|
7063
|
+
agent = excluded.agent,
|
|
7064
|
+
mode = excluded.mode,
|
|
7065
|
+
model = excluded.model,
|
|
7066
|
+
provider = excluded.provider,
|
|
7067
|
+
tokens_json = excluded.tokens_json,
|
|
7068
|
+
cost = excluded.cost,
|
|
7069
|
+
cost_source = excluded.cost_source,
|
|
7070
|
+
parts_json = excluded.parts_json,
|
|
7071
|
+
subagent_id = excluded.subagent_id,
|
|
7072
|
+
nickname = excluded.nickname,
|
|
7073
|
+
content_text = excluded.content_text,
|
|
7074
|
+
tool_metadata_json = excluded.tool_metadata_json
|
|
7075
|
+
`);
|
|
7076
|
+
const upsertRow = db.prepare(`
|
|
7077
|
+
INSERT INTO session_documents(
|
|
7078
|
+
agent_name,
|
|
7079
|
+
session_id,
|
|
7080
|
+
slug,
|
|
7081
|
+
title,
|
|
7082
|
+
directory,
|
|
7083
|
+
project_identity_kind,
|
|
7084
|
+
project_identity_key,
|
|
7085
|
+
project_display_name,
|
|
7086
|
+
time_created,
|
|
7087
|
+
time_updated,
|
|
7088
|
+
activity_time,
|
|
7089
|
+
content_text,
|
|
7090
|
+
content_hash,
|
|
7091
|
+
indexed_at
|
|
7092
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7093
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
7094
|
+
slug = excluded.slug,
|
|
7095
|
+
title = excluded.title,
|
|
7096
|
+
directory = excluded.directory,
|
|
7097
|
+
project_identity_kind = excluded.project_identity_kind,
|
|
7098
|
+
project_identity_key = excluded.project_identity_key,
|
|
7099
|
+
project_display_name = excluded.project_display_name,
|
|
7100
|
+
time_created = excluded.time_created,
|
|
7101
|
+
time_updated = excluded.time_updated,
|
|
7102
|
+
activity_time = excluded.activity_time,
|
|
7103
|
+
content_text = excluded.content_text,
|
|
7104
|
+
content_hash = excluded.content_hash,
|
|
7105
|
+
indexed_at = excluded.indexed_at
|
|
7106
|
+
`);
|
|
7107
|
+
for (const sessionId of new Set(removedSessionIds)) {
|
|
7108
|
+
deleteRow.run(agentName, sessionId);
|
|
7109
|
+
deleteFileActivity.run(agentName, sessionId);
|
|
7110
|
+
deleteMessageTools.run(agentName, sessionId, 0);
|
|
7111
|
+
deleteMessages.run(agentName, sessionId, 0);
|
|
7112
|
+
}
|
|
7113
|
+
for (const entry of entries) {
|
|
7114
|
+
const activityTime = entry.session.time_updated ?? entry.session.time_created;
|
|
7115
|
+
upsertSessionRow(upsertIndexedSession, agentName, entry.session, null, entry.sortIndex, null);
|
|
7116
|
+
deleteFileActivity.run(agentName, entry.session.id);
|
|
7117
|
+
deleteMessageTools.run(agentName, entry.session.id, 0);
|
|
7118
|
+
writeFileActivityRows(insertFileActivity, entry.fileActivity);
|
|
7119
|
+
for (const message of entry.messages) {
|
|
7120
|
+
upsertMessage.run(
|
|
7121
|
+
agentName,
|
|
7122
|
+
entry.session.id,
|
|
7123
|
+
message.index,
|
|
7124
|
+
message.id,
|
|
7125
|
+
message.role,
|
|
7126
|
+
message.timeCreated,
|
|
7127
|
+
message.timeCompleted ?? null,
|
|
7128
|
+
message.agent ?? null,
|
|
7129
|
+
message.mode ?? null,
|
|
7130
|
+
message.model ?? null,
|
|
7131
|
+
message.provider ?? null,
|
|
7132
|
+
message.tokensJson ?? null,
|
|
7133
|
+
message.cost ?? null,
|
|
7134
|
+
message.costSource ?? null,
|
|
7135
|
+
message.partsJson,
|
|
7136
|
+
message.subagentId ?? null,
|
|
7137
|
+
message.nickname ?? null,
|
|
7138
|
+
message.contentText,
|
|
7139
|
+
message.toolMetadataJson ?? null
|
|
7140
|
+
);
|
|
7141
|
+
for (const toolName of message.toolNames) {
|
|
7142
|
+
insertMessageTool.run(agentName, entry.session.id, message.index, toolName);
|
|
7143
|
+
}
|
|
7144
|
+
}
|
|
7145
|
+
deleteMessages.run(agentName, entry.session.id, entry.messages.length);
|
|
7146
|
+
upsertRow.run(
|
|
7147
|
+
agentName,
|
|
7148
|
+
entry.session.id,
|
|
7149
|
+
entry.session.slug,
|
|
7150
|
+
entry.session.title,
|
|
7151
|
+
entry.session.directory,
|
|
7152
|
+
entry.identity.kind,
|
|
7153
|
+
entry.identity.key,
|
|
7154
|
+
entry.identity.displayName,
|
|
7155
|
+
entry.session.time_created,
|
|
7156
|
+
entry.session.time_updated ?? null,
|
|
7157
|
+
activityTime,
|
|
7158
|
+
entry.contentText,
|
|
7159
|
+
entry.contentHash,
|
|
7160
|
+
Date.now()
|
|
7161
|
+
);
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
6256
7164
|
function syncSessionSearchIndex(agentName, sessions, loadSessionData, options = {}) {
|
|
6257
7165
|
return withCacheDb((db) => {
|
|
6258
7166
|
ensureFtsConsistency(db);
|
|
@@ -6277,180 +7185,27 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
|
|
|
6277
7185
|
);
|
|
6278
7186
|
const changedCount = toDelete.length + toUpsert.length;
|
|
6279
7187
|
const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
|
|
6280
|
-
const loaded = toUpsert.map(
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
messages,
|
|
6289
|
-
contentText: buildSessionContentFromMessages(data.title ?? session.title, messages),
|
|
6290
|
-
contentHash: sessionContentHash(session),
|
|
6291
|
-
fileActivity: extractSessionFileActivity(
|
|
6292
|
-
agentName,
|
|
6293
|
-
session.id,
|
|
6294
|
-
identity.key,
|
|
6295
|
-
data.messages
|
|
6296
|
-
)
|
|
6297
|
-
};
|
|
6298
|
-
} catch {
|
|
6299
|
-
return null;
|
|
6300
|
-
}
|
|
6301
|
-
}).filter((entry) => entry !== null);
|
|
6302
|
-
const deleteRow = db.prepare(
|
|
6303
|
-
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
6304
|
-
);
|
|
6305
|
-
const deleteMessages = db.prepare(
|
|
6306
|
-
"DELETE FROM messages WHERE agent_name = ? AND session_id = ? AND message_index >= ?"
|
|
6307
|
-
);
|
|
6308
|
-
const deleteFileActivity = db.prepare(
|
|
6309
|
-
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6310
|
-
);
|
|
6311
|
-
const upsertIndexedSession = prepareUpsertIndexedSession(db);
|
|
6312
|
-
const insertFileActivity = prepareInsertFileActivity(db);
|
|
6313
|
-
const upsertMessage = db.prepare(`
|
|
6314
|
-
INSERT INTO messages(
|
|
6315
|
-
agent_name,
|
|
6316
|
-
session_id,
|
|
6317
|
-
message_index,
|
|
6318
|
-
message_id,
|
|
6319
|
-
role,
|
|
6320
|
-
time_created,
|
|
6321
|
-
time_completed,
|
|
6322
|
-
agent,
|
|
6323
|
-
mode,
|
|
6324
|
-
model,
|
|
6325
|
-
provider,
|
|
6326
|
-
tokens_json,
|
|
6327
|
-
cost,
|
|
6328
|
-
cost_source,
|
|
6329
|
-
parts_json,
|
|
6330
|
-
subagent_id,
|
|
6331
|
-
nickname,
|
|
6332
|
-
content_text,
|
|
6333
|
-
tool_metadata_json
|
|
6334
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6335
|
-
ON CONFLICT(agent_name, session_id, message_index) DO UPDATE SET
|
|
6336
|
-
message_id = excluded.message_id,
|
|
6337
|
-
role = excluded.role,
|
|
6338
|
-
time_created = excluded.time_created,
|
|
6339
|
-
time_completed = excluded.time_completed,
|
|
6340
|
-
agent = excluded.agent,
|
|
6341
|
-
mode = excluded.mode,
|
|
6342
|
-
model = excluded.model,
|
|
6343
|
-
provider = excluded.provider,
|
|
6344
|
-
tokens_json = excluded.tokens_json,
|
|
6345
|
-
cost = excluded.cost,
|
|
6346
|
-
cost_source = excluded.cost_source,
|
|
6347
|
-
parts_json = excluded.parts_json,
|
|
6348
|
-
subagent_id = excluded.subagent_id,
|
|
6349
|
-
nickname = excluded.nickname,
|
|
6350
|
-
content_text = excluded.content_text,
|
|
6351
|
-
tool_metadata_json = excluded.tool_metadata_json
|
|
6352
|
-
`);
|
|
6353
|
-
const upsertRow = db.prepare(`
|
|
6354
|
-
INSERT INTO session_documents(
|
|
6355
|
-
agent_name,
|
|
6356
|
-
session_id,
|
|
6357
|
-
slug,
|
|
6358
|
-
title,
|
|
6359
|
-
directory,
|
|
6360
|
-
project_identity_kind,
|
|
6361
|
-
project_identity_key,
|
|
6362
|
-
project_display_name,
|
|
6363
|
-
time_created,
|
|
6364
|
-
time_updated,
|
|
6365
|
-
activity_time,
|
|
6366
|
-
content_text,
|
|
6367
|
-
content_hash,
|
|
6368
|
-
indexed_at
|
|
6369
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6370
|
-
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
6371
|
-
slug = excluded.slug,
|
|
6372
|
-
title = excluded.title,
|
|
6373
|
-
directory = excluded.directory,
|
|
6374
|
-
project_identity_kind = excluded.project_identity_kind,
|
|
6375
|
-
project_identity_key = excluded.project_identity_key,
|
|
6376
|
-
project_display_name = excluded.project_display_name,
|
|
6377
|
-
time_created = excluded.time_created,
|
|
6378
|
-
time_updated = excluded.time_updated,
|
|
6379
|
-
activity_time = excluded.activity_time,
|
|
6380
|
-
content_text = excluded.content_text,
|
|
6381
|
-
content_hash = excluded.content_hash,
|
|
6382
|
-
indexed_at = excluded.indexed_at
|
|
6383
|
-
`);
|
|
6384
|
-
const writeRows = () => {
|
|
6385
|
-
for (const sessionId of toDelete) {
|
|
6386
|
-
deleteRow.run(agentName, sessionId);
|
|
6387
|
-
deleteFileActivity.run(agentName, sessionId);
|
|
6388
|
-
deleteMessages.run(agentName, sessionId, 0);
|
|
6389
|
-
}
|
|
6390
|
-
for (const entry of loaded) {
|
|
6391
|
-
const activityTime = entry.session.time_updated ?? entry.session.time_created;
|
|
6392
|
-
upsertSessionRow(
|
|
6393
|
-
upsertIndexedSession,
|
|
6394
|
-
agentName,
|
|
6395
|
-
entry.session,
|
|
6396
|
-
null,
|
|
6397
|
-
sessionSortIndexMap.get(entry.session.id) ?? 0,
|
|
6398
|
-
null
|
|
6399
|
-
);
|
|
6400
|
-
deleteFileActivity.run(agentName, entry.session.id);
|
|
6401
|
-
writeFileActivityRows(insertFileActivity, entry.fileActivity);
|
|
6402
|
-
for (const message of entry.messages) {
|
|
6403
|
-
upsertMessage.run(
|
|
6404
|
-
agentName,
|
|
6405
|
-
entry.session.id,
|
|
6406
|
-
message.index,
|
|
6407
|
-
message.id,
|
|
6408
|
-
message.role,
|
|
6409
|
-
message.timeCreated,
|
|
6410
|
-
message.timeCompleted ?? null,
|
|
6411
|
-
message.agent ?? null,
|
|
6412
|
-
message.mode ?? null,
|
|
6413
|
-
message.model ?? null,
|
|
6414
|
-
message.provider ?? null,
|
|
6415
|
-
message.tokensJson ?? null,
|
|
6416
|
-
message.cost ?? null,
|
|
6417
|
-
message.costSource ?? null,
|
|
6418
|
-
message.partsJson,
|
|
6419
|
-
message.subagentId ?? null,
|
|
6420
|
-
message.nickname ?? null,
|
|
6421
|
-
message.contentText,
|
|
6422
|
-
message.toolMetadataJson ?? null
|
|
6423
|
-
);
|
|
6424
|
-
}
|
|
6425
|
-
deleteMessages.run(agentName, entry.session.id, entry.messages.length);
|
|
6426
|
-
upsertRow.run(
|
|
6427
|
-
agentName,
|
|
6428
|
-
entry.session.id,
|
|
6429
|
-
entry.session.slug,
|
|
6430
|
-
entry.session.title,
|
|
6431
|
-
entry.session.directory,
|
|
6432
|
-
entry.identity.kind,
|
|
6433
|
-
entry.identity.key,
|
|
6434
|
-
entry.identity.displayName,
|
|
6435
|
-
entry.session.time_created,
|
|
6436
|
-
entry.session.time_updated ?? null,
|
|
6437
|
-
activityTime,
|
|
6438
|
-
entry.contentText,
|
|
6439
|
-
entry.contentHash,
|
|
6440
|
-
Date.now()
|
|
6441
|
-
);
|
|
6442
|
-
}
|
|
6443
|
-
};
|
|
7188
|
+
const loaded = toUpsert.map(
|
|
7189
|
+
(session) => loadSearchIndexEntry(
|
|
7190
|
+
agentName,
|
|
7191
|
+
{ session, sortIndex: sessionSortIndexMap.get(session.id) ?? 0 },
|
|
7192
|
+
loadSessionData
|
|
7193
|
+
)
|
|
7194
|
+
).filter((entry) => entry !== null);
|
|
7195
|
+
const writeRows = () => writeSearchIndexRows(db, agentName, toDelete, loaded);
|
|
6444
7196
|
let rebuildDurationMs;
|
|
6445
7197
|
const needsRebuild = isBulk && (toDelete.length > 0 || loaded.length > 0);
|
|
6446
7198
|
if (needsRebuild) {
|
|
6447
7199
|
db.transaction(() => {
|
|
6448
7200
|
dropSearchTriggers(db);
|
|
7201
|
+
dropMessageSearchTriggers(db);
|
|
6449
7202
|
writeRows();
|
|
6450
7203
|
const rebuildStartedAt = performance.now();
|
|
6451
7204
|
rebuildSearchIndex(db);
|
|
7205
|
+
rebuildMessageSearchIndex(db);
|
|
6452
7206
|
rebuildDurationMs = performance.now() - rebuildStartedAt;
|
|
6453
7207
|
createSearchTriggers(db);
|
|
7208
|
+
createMessageSearchTriggers(db);
|
|
6454
7209
|
})();
|
|
6455
7210
|
} else {
|
|
6456
7211
|
db.transaction(writeRows)();
|
|
@@ -6468,6 +7223,68 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
|
|
|
6468
7223
|
};
|
|
6469
7224
|
});
|
|
6470
7225
|
}
|
|
7226
|
+
function syncSessionSearchIndexChanges(agentName, changes, removedSessionIds, loadSessionData, options = {}) {
|
|
7227
|
+
if (changes.length === 0 && removedSessionIds.length === 0) {
|
|
7228
|
+
return {
|
|
7229
|
+
agentName,
|
|
7230
|
+
mode: "incremental",
|
|
7231
|
+
sessions: 0,
|
|
7232
|
+
changed: 0,
|
|
7233
|
+
deleted: 0,
|
|
7234
|
+
indexed: 0,
|
|
7235
|
+
skipped: 0,
|
|
7236
|
+
durationMs: 0
|
|
7237
|
+
};
|
|
7238
|
+
}
|
|
7239
|
+
return withCacheDb((db) => {
|
|
7240
|
+
ensureFtsConsistency(db);
|
|
7241
|
+
const startedAt = performance.now();
|
|
7242
|
+
const getIndexedRow = db.prepare(
|
|
7243
|
+
"SELECT content_hash FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
7244
|
+
);
|
|
7245
|
+
const getMessageCount = db.prepare(
|
|
7246
|
+
"SELECT COUNT(*) AS value FROM messages WHERE agent_name = ? AND session_id = ?"
|
|
7247
|
+
);
|
|
7248
|
+
const toUpsert = changes.filter(({ session }) => {
|
|
7249
|
+
const indexed = getIndexedRow.get(agentName, session.id);
|
|
7250
|
+
const messageCount = getMessageCount.get(agentName, session.id);
|
|
7251
|
+
return String(indexed?.content_hash ?? "") !== sessionContentHash(session) || Number(messageCount?.value ?? 0) !== session.stats.message_count;
|
|
7252
|
+
});
|
|
7253
|
+
const uniqueRemovedSessionIds = Array.from(new Set(removedSessionIds));
|
|
7254
|
+
const changedCount = uniqueRemovedSessionIds.length + toUpsert.length;
|
|
7255
|
+
const isBulk = shouldBulkSyncSearchIndex(options, changedCount);
|
|
7256
|
+
const loaded = toUpsert.map((change) => loadSearchIndexEntry(agentName, change, loadSessionData)).filter((entry) => entry !== null);
|
|
7257
|
+
const writeRows = () => writeSearchIndexRows(db, agentName, uniqueRemovedSessionIds, loaded);
|
|
7258
|
+
let rebuildDurationMs;
|
|
7259
|
+
const needsRebuild = isBulk && (uniqueRemovedSessionIds.length > 0 || loaded.length > 0);
|
|
7260
|
+
if (needsRebuild) {
|
|
7261
|
+
db.transaction(() => {
|
|
7262
|
+
dropSearchTriggers(db);
|
|
7263
|
+
dropMessageSearchTriggers(db);
|
|
7264
|
+
writeRows();
|
|
7265
|
+
const rebuildStartedAt = performance.now();
|
|
7266
|
+
rebuildSearchIndex(db);
|
|
7267
|
+
rebuildMessageSearchIndex(db);
|
|
7268
|
+
rebuildDurationMs = performance.now() - rebuildStartedAt;
|
|
7269
|
+
createSearchTriggers(db);
|
|
7270
|
+
createMessageSearchTriggers(db);
|
|
7271
|
+
})();
|
|
7272
|
+
} else {
|
|
7273
|
+
db.transaction(writeRows)();
|
|
7274
|
+
}
|
|
7275
|
+
return {
|
|
7276
|
+
agentName,
|
|
7277
|
+
mode: isBulk ? "bulk" : "incremental",
|
|
7278
|
+
sessions: changes.length,
|
|
7279
|
+
changed: toUpsert.length,
|
|
7280
|
+
deleted: uniqueRemovedSessionIds.length,
|
|
7281
|
+
indexed: loaded.length,
|
|
7282
|
+
skipped: toUpsert.length - loaded.length,
|
|
7283
|
+
durationMs: performance.now() - startedAt,
|
|
7284
|
+
rebuildDurationMs
|
|
7285
|
+
};
|
|
7286
|
+
});
|
|
7287
|
+
}
|
|
6471
7288
|
function sessionHeadFromSearchRow(row) {
|
|
6472
7289
|
return sessionFromRow(row);
|
|
6473
7290
|
}
|
|
@@ -6514,6 +7331,11 @@ function sessionMatchesSearchCost(session, options) {
|
|
|
6514
7331
|
function likePattern(value) {
|
|
6515
7332
|
return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
|
|
6516
7333
|
}
|
|
7334
|
+
function filePathFtsQuery(value) {
|
|
7335
|
+
const path2 = normalizeFilePathSearch(value);
|
|
7336
|
+
if (path2.length < 3) return null;
|
|
7337
|
+
return `"${path2.replaceAll('"', '""')}"`;
|
|
7338
|
+
}
|
|
6517
7339
|
function escapeRegExp(value) {
|
|
6518
7340
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6519
7341
|
}
|
|
@@ -6544,16 +7366,26 @@ function buildSessionSearchFilters(options) {
|
|
|
6544
7366
|
params.push(`%"${tag}"%`);
|
|
6545
7367
|
}
|
|
6546
7368
|
for (const tool of options.tools ?? []) {
|
|
7369
|
+
const toolName = normalizeToolName2(tool);
|
|
7370
|
+
if (!toolName) continue;
|
|
6547
7371
|
clauses.push(
|
|
6548
|
-
"EXISTS (SELECT 1 FROM
|
|
7372
|
+
"EXISTS (SELECT 1 FROM message_tools mt WHERE mt.tool_name = ? AND mt.agent_name = s.agent_name AND mt.session_id = s.session_id)"
|
|
6549
7373
|
);
|
|
6550
|
-
params.push(
|
|
7374
|
+
params.push(toolName);
|
|
6551
7375
|
}
|
|
6552
7376
|
if (options.file || options.fileKind) {
|
|
6553
7377
|
const fileClauses = ["fa.agent_name = s.agent_name", "fa.session_id = s.session_id"];
|
|
6554
7378
|
if (options.file) {
|
|
6555
|
-
|
|
6556
|
-
|
|
7379
|
+
const pathQuery = filePathFtsQuery(options.file);
|
|
7380
|
+
if (pathQuery) {
|
|
7381
|
+
fileClauses.push(
|
|
7382
|
+
"fa.rowid IN (SELECT rowid FROM session_file_activity_path_fts WHERE path MATCH ?)"
|
|
7383
|
+
);
|
|
7384
|
+
params.push(pathQuery);
|
|
7385
|
+
} else {
|
|
7386
|
+
fileClauses.push("LOWER(fa.path) LIKE ? ESCAPE '\\'");
|
|
7387
|
+
params.push(likePattern(options.file));
|
|
7388
|
+
}
|
|
6557
7389
|
}
|
|
6558
7390
|
if (options.fileKind) {
|
|
6559
7391
|
fileClauses.push("fa.kind = ?");
|
|
@@ -6639,8 +7471,51 @@ function messageMatchType(row) {
|
|
|
6639
7471
|
if (row.role === "tool" || row.mode === "tool" || row.tool_metadata_json) return "tool_output";
|
|
6640
7472
|
return "assistant_reply";
|
|
6641
7473
|
}
|
|
6642
|
-
function
|
|
6643
|
-
|
|
7474
|
+
function searchResultRowKey(row) {
|
|
7475
|
+
return `${String(row.agent_name)}\0${String(row.session_id)}`;
|
|
7476
|
+
}
|
|
7477
|
+
function fetchMessageSearchMatches(db, rows, ftsQuery, terms) {
|
|
7478
|
+
const candidates = rows.filter((row) => !textMatchesTerms(String(row.title ?? ""), terms));
|
|
7479
|
+
if (candidates.length === 0) {
|
|
7480
|
+
return /* @__PURE__ */ new Map();
|
|
7481
|
+
}
|
|
7482
|
+
const clauses = [];
|
|
7483
|
+
const params = [ftsQuery];
|
|
7484
|
+
for (const row of candidates) {
|
|
7485
|
+
clauses.push("(m.agent_name = ? AND m.session_id = ?)");
|
|
7486
|
+
params.push(String(row.agent_name), String(row.session_id));
|
|
7487
|
+
}
|
|
7488
|
+
const messageRows = db.prepare(
|
|
7489
|
+
`
|
|
7490
|
+
SELECT
|
|
7491
|
+
m.agent_name,
|
|
7492
|
+
m.session_id,
|
|
7493
|
+
m.message_index,
|
|
7494
|
+
m.role,
|
|
7495
|
+
m.mode,
|
|
7496
|
+
m.content_text,
|
|
7497
|
+
m.tool_metadata_json
|
|
7498
|
+
FROM messages_fts
|
|
7499
|
+
JOIN messages m ON m.rowid = messages_fts.rowid
|
|
7500
|
+
WHERE messages_fts MATCH ?
|
|
7501
|
+
AND (${clauses.join(" OR ")})
|
|
7502
|
+
ORDER BY m.message_index
|
|
7503
|
+
`
|
|
7504
|
+
).all(...params);
|
|
7505
|
+
const matches = /* @__PURE__ */ new Map();
|
|
7506
|
+
for (const message of messageRows) {
|
|
7507
|
+
const key = searchResultRowKey(message);
|
|
7508
|
+
if (matches.has(key)) continue;
|
|
7509
|
+
const text = String(message.content_text ?? "");
|
|
7510
|
+
if (!textMatchesTerms(text, terms)) continue;
|
|
7511
|
+
matches.set(key, {
|
|
7512
|
+
snippet: buildTermSnippet(text, terms),
|
|
7513
|
+
matchType: messageMatchType(message)
|
|
7514
|
+
});
|
|
7515
|
+
}
|
|
7516
|
+
return matches;
|
|
7517
|
+
}
|
|
7518
|
+
function resolveSearchMatch(row, terms, messageMatches) {
|
|
6644
7519
|
const title = String(row.title ?? "");
|
|
6645
7520
|
if (terms.terms.length === 0) {
|
|
6646
7521
|
return {
|
|
@@ -6651,30 +7526,20 @@ function resolveSearchMatch(db, row, textQuery) {
|
|
|
6651
7526
|
if (textMatchesTerms(title, terms)) {
|
|
6652
7527
|
return { snippet: buildTermSnippet(title, terms), matchType: "title" };
|
|
6653
7528
|
}
|
|
6654
|
-
const
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
FROM messages
|
|
6658
|
-
WHERE agent_name = ? AND session_id = ?
|
|
6659
|
-
ORDER BY message_index
|
|
6660
|
-
`
|
|
6661
|
-
).all(row.agent_name, row.session_id);
|
|
6662
|
-
for (const message of messages) {
|
|
6663
|
-
const text = String(message.content_text ?? "");
|
|
6664
|
-
if (!textMatchesTerms(text, terms)) continue;
|
|
6665
|
-
return {
|
|
6666
|
-
snippet: buildTermSnippet(text, terms),
|
|
6667
|
-
matchType: messageMatchType(message)
|
|
6668
|
-
};
|
|
7529
|
+
const messageMatch = messageMatches.get(searchResultRowKey(row));
|
|
7530
|
+
if (messageMatch) {
|
|
7531
|
+
return messageMatch;
|
|
6669
7532
|
}
|
|
6670
7533
|
return {
|
|
6671
7534
|
snippet: String(row.snippet ?? ""),
|
|
6672
7535
|
matchType: "assistant_reply"
|
|
6673
7536
|
};
|
|
6674
7537
|
}
|
|
6675
|
-
function rowsToSearchResults(db, rows, textQuery) {
|
|
7538
|
+
function rowsToSearchResults(db, rows, textQuery, ftsQuery = toFtsQuery(textQuery)) {
|
|
7539
|
+
const terms = parseTextTerms(textQuery);
|
|
7540
|
+
const messageMatches = terms.terms.length > 0 && ftsQuery ? fetchMessageSearchMatches(db, rows, ftsQuery, terms) : /* @__PURE__ */ new Map();
|
|
6676
7541
|
return rows.map((row) => {
|
|
6677
|
-
const match = resolveSearchMatch(
|
|
7542
|
+
const match = resolveSearchMatch(row, terms, messageMatches);
|
|
6678
7543
|
return {
|
|
6679
7544
|
agentName: String(row.agent_name),
|
|
6680
7545
|
session: sessionHeadFromSearchRow(row),
|
|
@@ -6726,7 +7591,7 @@ function searchSessions(query, options = {}) {
|
|
|
6726
7591
|
LIMIT ?
|
|
6727
7592
|
`
|
|
6728
7593
|
).all(ftsQuery, ...filters.params, search.options.limit ?? 50);
|
|
6729
|
-
return rowsToSearchResults(db, rows, normalizedQuery);
|
|
7594
|
+
return rowsToSearchResults(db, rows, normalizedQuery, ftsQuery);
|
|
6730
7595
|
});
|
|
6731
7596
|
return results ?? [];
|
|
6732
7597
|
}
|
|
@@ -6740,6 +7605,7 @@ function fileActivityFilters(options) {
|
|
|
6740
7605
|
projectLike: options.project ? likePattern(options.project) : null,
|
|
6741
7606
|
cwdKey: options.cwd ? computeIdentity(options.cwd, realFs).key : null,
|
|
6742
7607
|
cwdLike: options.cwd ? likePattern(options.cwd) : null,
|
|
7608
|
+
path: path2,
|
|
6743
7609
|
pathLike: path2 ? likePattern(path2) : null
|
|
6744
7610
|
};
|
|
6745
7611
|
}
|
|
@@ -6754,14 +7620,68 @@ function fileActivityFromRow(row) {
|
|
|
6754
7620
|
latest_time: Number(row.latest_time ?? 0)
|
|
6755
7621
|
};
|
|
6756
7622
|
}
|
|
7623
|
+
function buildFileActivityWhere(options) {
|
|
7624
|
+
const filters = fileActivityFilters(options);
|
|
7625
|
+
const clauses = [];
|
|
7626
|
+
const params = [];
|
|
7627
|
+
if (options.agent != null) {
|
|
7628
|
+
clauses.push("fa.agent_name = ?");
|
|
7629
|
+
params.push(options.agent);
|
|
7630
|
+
}
|
|
7631
|
+
if (options.sessionId != null) {
|
|
7632
|
+
clauses.push("fa.session_id = ?");
|
|
7633
|
+
params.push(options.sessionId);
|
|
7634
|
+
}
|
|
7635
|
+
if (filters.projectKey != null) {
|
|
7636
|
+
clauses.push("fa.project_identity_key = ?");
|
|
7637
|
+
params.push(filters.projectKey);
|
|
7638
|
+
}
|
|
7639
|
+
if (filters.projectLike != null) {
|
|
7640
|
+
clauses.push(
|
|
7641
|
+
"(LOWER(fa.project_identity_key) LIKE ? ESCAPE '\\' OR LOWER(s.project_display_name) LIKE ? ESCAPE '\\' OR LOWER(s.directory) LIKE ? ESCAPE '\\')"
|
|
7642
|
+
);
|
|
7643
|
+
params.push(filters.projectLike, filters.projectLike, filters.projectLike);
|
|
7644
|
+
}
|
|
7645
|
+
if (filters.cwdKey != null) {
|
|
7646
|
+
clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
|
|
7647
|
+
params.push(filters.cwdKey, filters.cwdLike);
|
|
7648
|
+
}
|
|
7649
|
+
if (filters.pathLike != null) {
|
|
7650
|
+
const pathQuery = filePathFtsQuery(filters.path);
|
|
7651
|
+
if (pathQuery) {
|
|
7652
|
+
clauses.push(
|
|
7653
|
+
"fa.rowid IN (SELECT rowid FROM session_file_activity_path_fts WHERE path MATCH ?)"
|
|
7654
|
+
);
|
|
7655
|
+
params.push(pathQuery);
|
|
7656
|
+
} else {
|
|
7657
|
+
clauses.push("LOWER(fa.path) LIKE ? ESCAPE '\\'");
|
|
7658
|
+
params.push(filters.pathLike);
|
|
7659
|
+
}
|
|
7660
|
+
}
|
|
7661
|
+
if (options.kind != null) {
|
|
7662
|
+
clauses.push("fa.kind = ?");
|
|
7663
|
+
params.push(options.kind);
|
|
7664
|
+
}
|
|
7665
|
+
if (options.from != null) {
|
|
7666
|
+
clauses.push("fa.latest_time >= ?");
|
|
7667
|
+
params.push(options.from);
|
|
7668
|
+
}
|
|
7669
|
+
if (options.to != null) {
|
|
7670
|
+
clauses.push("fa.latest_time <= ?");
|
|
7671
|
+
params.push(options.to);
|
|
7672
|
+
}
|
|
7673
|
+
return {
|
|
7674
|
+
where: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
7675
|
+
params
|
|
7676
|
+
};
|
|
7677
|
+
}
|
|
6757
7678
|
function listFileActivity(options = {}) {
|
|
6758
7679
|
if (!hasCacheStorage()) {
|
|
6759
7680
|
return [];
|
|
6760
7681
|
}
|
|
6761
|
-
const filters =
|
|
6762
|
-
const
|
|
6763
|
-
|
|
6764
|
-
`
|
|
7682
|
+
const filters = buildFileActivityWhere(options);
|
|
7683
|
+
const queryRows = (db) => db.prepare(
|
|
7684
|
+
`
|
|
6765
7685
|
SELECT
|
|
6766
7686
|
fa.agent_name,
|
|
6767
7687
|
fa.session_id,
|
|
@@ -6787,43 +7707,15 @@ function listFileActivity(options = {}) {
|
|
|
6787
7707
|
s.total_tokens
|
|
6788
7708
|
FROM session_file_activity fa
|
|
6789
7709
|
JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
|
|
6790
|
-
|
|
6791
|
-
AND (? IS NULL OR fa.session_id = ?)
|
|
6792
|
-
AND (? IS NULL OR fa.project_identity_key = ?)
|
|
6793
|
-
AND (? IS NULL OR LOWER(fa.project_identity_key) LIKE ? ESCAPE '\\' OR LOWER(s.project_display_name) LIKE ? ESCAPE '\\' OR LOWER(s.directory) LIKE ? ESCAPE '\\')
|
|
6794
|
-
AND (? IS NULL OR s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')
|
|
6795
|
-
AND (? IS NULL OR LOWER(fa.path) LIKE ? ESCAPE '\\')
|
|
6796
|
-
AND (? IS NULL OR fa.kind = ?)
|
|
6797
|
-
AND (? IS NULL OR fa.latest_time >= ?)
|
|
6798
|
-
AND (? IS NULL OR fa.latest_time <= ?)
|
|
7710
|
+
${filters.where}
|
|
6799
7711
|
ORDER BY fa.latest_time DESC, fa.count DESC, fa.path
|
|
6800
7712
|
LIMIT ?
|
|
6801
7713
|
`
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
filters.projectKey,
|
|
6808
|
-
filters.projectKey,
|
|
6809
|
-
filters.projectLike,
|
|
6810
|
-
filters.projectLike,
|
|
6811
|
-
filters.projectLike,
|
|
6812
|
-
filters.projectLike,
|
|
6813
|
-
filters.cwdKey,
|
|
6814
|
-
filters.cwdKey,
|
|
6815
|
-
filters.cwdLike,
|
|
6816
|
-
filters.pathLike,
|
|
6817
|
-
filters.pathLike,
|
|
6818
|
-
options.kind ?? null,
|
|
6819
|
-
options.kind ?? null,
|
|
6820
|
-
options.from ?? null,
|
|
6821
|
-
options.from ?? null,
|
|
6822
|
-
options.to ?? null,
|
|
6823
|
-
options.to ?? null,
|
|
6824
|
-
options.limit ?? 50
|
|
6825
|
-
)
|
|
6826
|
-
);
|
|
7714
|
+
).all(...filters.params, options.limit ?? 50);
|
|
7715
|
+
let rows = withCacheDbReadOnly(queryRows);
|
|
7716
|
+
if (rows == null && options.path) {
|
|
7717
|
+
rows = withCacheDb(queryRows);
|
|
7718
|
+
}
|
|
6827
7719
|
return (rows ?? []).map((row) => ({
|
|
6828
7720
|
...fileActivityFromRow(row),
|
|
6829
7721
|
session: sessionHeadFromSearchRow(row)
|
|
@@ -6905,15 +7797,6 @@ function listCachedProjectGroups(sessions) {
|
|
|
6905
7797
|
});
|
|
6906
7798
|
return groups ?? [];
|
|
6907
7799
|
}
|
|
6908
|
-
function isPathScopeMatch(queryPath, sessionPath) {
|
|
6909
|
-
if (!sessionPath) return false;
|
|
6910
|
-
const q = resolve(queryPath);
|
|
6911
|
-
const s = resolve(sessionPath);
|
|
6912
|
-
const sepNorm = (p) => p.replaceAll(sep, "/");
|
|
6913
|
-
const sn = sepNorm(s);
|
|
6914
|
-
const qn = sepNorm(q);
|
|
6915
|
-
return sn === qn || sn.startsWith(qn + "/") || qn.startsWith(sn + "/");
|
|
6916
|
-
}
|
|
6917
7800
|
function createIdentityResolver() {
|
|
6918
7801
|
const cache = /* @__PURE__ */ new Map();
|
|
6919
7802
|
return (directory) => {
|
|
@@ -6935,17 +7818,10 @@ function attachProjectIdentities(sessions) {
|
|
|
6935
7818
|
};
|
|
6936
7819
|
});
|
|
6937
7820
|
}
|
|
6938
|
-
function isProjectScopeMatch(queryPath, session) {
|
|
6939
|
-
if (!session.directory) return false;
|
|
6940
|
-
const queryIdentity = computeIdentity(queryPath, realFs);
|
|
6941
|
-
if (session.project_identity?.key === queryIdentity.key) return true;
|
|
6942
|
-
return isPathScopeMatch(queryPath, session.directory);
|
|
6943
|
-
}
|
|
6944
7821
|
function filterSessions(sessions, options) {
|
|
6945
7822
|
let result = sessions;
|
|
6946
7823
|
if (options.cwd) {
|
|
6947
|
-
|
|
6948
|
-
result = result.filter((s) => isProjectScopeMatch(cwd, s));
|
|
7824
|
+
result = filterSessionsByProjectScope(result, options.cwd);
|
|
6949
7825
|
}
|
|
6950
7826
|
if (options.from != null) {
|
|
6951
7827
|
result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
|
|
@@ -6964,8 +7840,34 @@ function buildAgentCacheMeta(agent) {
|
|
|
6964
7840
|
}
|
|
6965
7841
|
return meta;
|
|
6966
7842
|
}
|
|
7843
|
+
function sessionCacheValue(session) {
|
|
7844
|
+
return JSON.stringify(session);
|
|
7845
|
+
}
|
|
7846
|
+
function buildCacheChanges(cachedSessions, updatedSessions, changedIds = []) {
|
|
7847
|
+
const cachedMap = new Map(cachedSessions.map((session) => [session.id, session]));
|
|
7848
|
+
const updatedIds = new Set(updatedSessions.map((session) => session.id));
|
|
7849
|
+
const changedIdSet = new Set(changedIds);
|
|
7850
|
+
const removedSessionIds = cachedSessions.filter((session) => !updatedIds.has(session.id)).map((session) => session.id);
|
|
7851
|
+
const changes = [];
|
|
7852
|
+
updatedSessions.forEach((session, sortIndex) => {
|
|
7853
|
+
const cached = cachedMap.get(session.id);
|
|
7854
|
+
if (!cached || changedIdSet.has(session.id) || cached !== session && sessionCacheValue(cached) !== sessionCacheValue(session)) {
|
|
7855
|
+
changes.push({ session, sortIndex });
|
|
7856
|
+
}
|
|
7857
|
+
});
|
|
7858
|
+
return { changes, removedSessionIds };
|
|
7859
|
+
}
|
|
7860
|
+
function saveCachedSessionDiff(agent, cachedSessions, updatedSessions, changedIds = []) {
|
|
7861
|
+
const diff = buildCacheChanges(cachedSessions, updatedSessions, changedIds);
|
|
7862
|
+
saveCachedSessionChanges(
|
|
7863
|
+
agent.name,
|
|
7864
|
+
diff.changes,
|
|
7865
|
+
diff.removedSessionIds,
|
|
7866
|
+
buildAgentCacheMeta(agent)
|
|
7867
|
+
);
|
|
7868
|
+
}
|
|
6967
7869
|
function getSmartTagWorkerCount(sessionCount) {
|
|
6968
|
-
if (sessionCount <
|
|
7870
|
+
if (sessionCount < 50) return 1;
|
|
6969
7871
|
return Math.min(sessionCount, Math.max(1, Math.min(4, availableParallelism() - 1)));
|
|
6970
7872
|
}
|
|
6971
7873
|
function chunkSessions(items, chunkCount) {
|
|
@@ -6998,58 +7900,12 @@ function ensureSessionTagsSync(agent, sessions) {
|
|
|
6998
7900
|
});
|
|
6999
7901
|
return { sessions: tagged, changed };
|
|
7000
7902
|
}
|
|
7001
|
-
async function classifySessionTagsInWorker(agentName, sessionIds) {
|
|
7903
|
+
async function classifySessionTagsInWorker(workerUrl, agentName, sessionIds, meta) {
|
|
7002
7904
|
return new Promise((resolveWorker, rejectWorker) => {
|
|
7003
|
-
const worker = new Worker(
|
|
7004
|
-
|
|
7005
|
-
const { parentPort, workerData } = require("node:worker_threads");
|
|
7006
|
-
|
|
7007
|
-
(async () => {
|
|
7008
|
-
const {
|
|
7009
|
-
createRegisteredAgents,
|
|
7010
|
-
classifySessionTags,
|
|
7011
|
-
getSmartTagSourceTimestamp,
|
|
7012
|
-
} = await import("@codesesh/core");
|
|
7013
|
-
|
|
7014
|
-
const agent = createRegisteredAgents().find((item) => item.name === workerData.agentName);
|
|
7015
|
-
const results = [];
|
|
7016
|
-
|
|
7017
|
-
if (agent) {
|
|
7018
|
-
for (const sessionId of workerData.sessionIds) {
|
|
7019
|
-
try {
|
|
7020
|
-
const data = agent.getSessionData(sessionId);
|
|
7021
|
-
results.push({
|
|
7022
|
-
id: sessionId,
|
|
7023
|
-
tags: classifySessionTags(data),
|
|
7024
|
-
sourceUpdatedAt: getSmartTagSourceTimestamp(data),
|
|
7025
|
-
});
|
|
7026
|
-
} catch (error) {
|
|
7027
|
-
results.push({
|
|
7028
|
-
id: sessionId,
|
|
7029
|
-
error: error instanceof Error ? error.message : String(error),
|
|
7030
|
-
});
|
|
7031
|
-
}
|
|
7032
|
-
}
|
|
7033
|
-
}
|
|
7034
|
-
|
|
7035
|
-
parentPort?.postMessage(results);
|
|
7036
|
-
})().catch((error) => {
|
|
7037
|
-
parentPort?.postMessage([
|
|
7038
|
-
{
|
|
7039
|
-
id: "",
|
|
7040
|
-
error: error instanceof Error ? error.message : String(error),
|
|
7041
|
-
},
|
|
7042
|
-
]);
|
|
7043
|
-
});
|
|
7044
|
-
`,
|
|
7045
|
-
{
|
|
7046
|
-
eval: true,
|
|
7047
|
-
workerData: { agentName, sessionIds }
|
|
7048
|
-
}
|
|
7049
|
-
);
|
|
7050
|
-
worker.once("message", (results) => {
|
|
7051
|
-
resolveWorker(results);
|
|
7905
|
+
const worker = new Worker(workerUrl, {
|
|
7906
|
+
workerData: { agentName, sessionIds, meta }
|
|
7052
7907
|
});
|
|
7908
|
+
worker.once("message", (results) => resolveWorker(results));
|
|
7053
7909
|
worker.once("error", rejectWorker);
|
|
7054
7910
|
worker.once("exit", (code) => {
|
|
7055
7911
|
if (code !== 0) {
|
|
@@ -7058,7 +7914,7 @@ async function classifySessionTagsInWorker(agentName, sessionIds) {
|
|
|
7058
7914
|
});
|
|
7059
7915
|
});
|
|
7060
7916
|
}
|
|
7061
|
-
async function ensureSessionTags(agent, sessions) {
|
|
7917
|
+
async function ensureSessionTags(agent, sessions, workerUrl) {
|
|
7062
7918
|
const staleSessions = sessions.filter((session) => {
|
|
7063
7919
|
const sourceUpdatedAt = session.time_updated ?? session.time_created;
|
|
7064
7920
|
const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
|
|
@@ -7067,16 +7923,19 @@ async function ensureSessionTags(agent, sessions) {
|
|
|
7067
7923
|
if (staleSessions.length === 0) {
|
|
7068
7924
|
return { sessions, changed: false };
|
|
7069
7925
|
}
|
|
7070
|
-
const workerCount = getSmartTagWorkerCount(staleSessions.length);
|
|
7926
|
+
const workerCount = workerUrl ? getSmartTagWorkerCount(staleSessions.length) : 1;
|
|
7071
7927
|
if (workerCount <= 1) {
|
|
7072
7928
|
return ensureSessionTagsSync(agent, sessions);
|
|
7073
7929
|
}
|
|
7930
|
+
const meta = buildAgentCacheMeta(agent);
|
|
7074
7931
|
try {
|
|
7075
7932
|
const results = (await Promise.all(
|
|
7076
7933
|
chunkSessions(
|
|
7077
7934
|
staleSessions.map((session) => session.id),
|
|
7078
7935
|
workerCount
|
|
7079
|
-
).map(
|
|
7936
|
+
).map(
|
|
7937
|
+
(sessionIds) => classifySessionTagsInWorker(workerUrl, agent.name, sessionIds, meta)
|
|
7938
|
+
)
|
|
7080
7939
|
)).flat();
|
|
7081
7940
|
const resultMap = new Map(results.filter((item) => item.tags).map((item) => [item.id, item]));
|
|
7082
7941
|
return {
|
|
@@ -7096,10 +7955,14 @@ async function ensureSessionTags(agent, sessions) {
|
|
|
7096
7955
|
}
|
|
7097
7956
|
}
|
|
7098
7957
|
async function scanAgentSmart(agent, options, onProgress) {
|
|
7958
|
+
const agentStart = performance.now();
|
|
7959
|
+
const timing = { total: 0 };
|
|
7099
7960
|
const useCache = options.useCache ?? true;
|
|
7100
7961
|
const canValidateCache = Boolean(agent.checkForChanges && agent.incrementalScan);
|
|
7101
7962
|
if (useCache) {
|
|
7963
|
+
const t0 = performance.now();
|
|
7102
7964
|
const cached = loadCachedSessions(agent.name);
|
|
7965
|
+
timing.cacheLoad = performance.now() - t0;
|
|
7103
7966
|
if (cached !== null) {
|
|
7104
7967
|
if (agent.setSessionMetaMap) {
|
|
7105
7968
|
const metaMap = /* @__PURE__ */ new Map();
|
|
@@ -7108,6 +7971,26 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
7108
7971
|
}
|
|
7109
7972
|
agent.setSessionMetaMap(metaMap);
|
|
7110
7973
|
}
|
|
7974
|
+
if (options.cacheOnly) {
|
|
7975
|
+
onProgress?.({
|
|
7976
|
+
agent: agent.name,
|
|
7977
|
+
phase: "cache",
|
|
7978
|
+
cachedCount: cached.sessions.length
|
|
7979
|
+
});
|
|
7980
|
+
onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
|
|
7981
|
+
const t32 = performance.now();
|
|
7982
|
+
const cachedWithIdentity2 = attachProjectIdentities(cached.sessions);
|
|
7983
|
+
timing.identity = performance.now() - t32;
|
|
7984
|
+
const filtered3 = filterSessions(cachedWithIdentity2, options);
|
|
7985
|
+
timing.total = performance.now() - agentStart;
|
|
7986
|
+
return {
|
|
7987
|
+
agent,
|
|
7988
|
+
heads: filtered3,
|
|
7989
|
+
fromCache: true,
|
|
7990
|
+
timing,
|
|
7991
|
+
cacheTimestamp: cached.timestamp
|
|
7992
|
+
};
|
|
7993
|
+
}
|
|
7111
7994
|
const isAvail = agent.isAvailable();
|
|
7112
7995
|
if (!isAvail) {
|
|
7113
7996
|
return null;
|
|
@@ -7119,22 +8002,35 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
7119
8002
|
});
|
|
7120
8003
|
if (canValidateCache) {
|
|
7121
8004
|
onProgress?.({ agent: agent.name, phase: "checking" });
|
|
8005
|
+
const t1 = performance.now();
|
|
7122
8006
|
const checkResult = await Promise.resolve(
|
|
7123
8007
|
agent.checkForChanges(cached.timestamp, cached.sessions)
|
|
7124
8008
|
);
|
|
8009
|
+
timing.checkChanges = performance.now() - t1;
|
|
7125
8010
|
if (checkResult.hasChanges) {
|
|
7126
8011
|
onProgress?.({
|
|
7127
8012
|
agent: agent.name,
|
|
7128
8013
|
phase: "incremental",
|
|
7129
8014
|
changedCount: checkResult.changedIds?.length
|
|
7130
8015
|
});
|
|
8016
|
+
const t2 = performance.now();
|
|
7131
8017
|
const updatedSessions = await Promise.resolve(
|
|
7132
8018
|
agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
|
|
7133
8019
|
);
|
|
8020
|
+
timing.scan = performance.now() - t2;
|
|
8021
|
+
const t32 = performance.now();
|
|
7134
8022
|
const sessionsWithIdentity = attachProjectIdentities(updatedSessions);
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
8023
|
+
timing.identity = performance.now() - t32;
|
|
8024
|
+
const t42 = performance.now();
|
|
8025
|
+
const tagged2 = options.includeSmartTags === false ? { sessions: sessionsWithIdentity, changed: false } : await ensureSessionTags(agent, sessionsWithIdentity, options.smartTagWorkerUrl);
|
|
8026
|
+
timing.tags = performance.now() - t42;
|
|
8027
|
+
if (options.writeCache !== false) {
|
|
8028
|
+
saveCachedSessionDiff(
|
|
8029
|
+
agent,
|
|
8030
|
+
cached.sessions,
|
|
8031
|
+
tagged2.sessions,
|
|
8032
|
+
checkResult.changedIds ?? []
|
|
8033
|
+
);
|
|
7138
8034
|
}
|
|
7139
8035
|
onProgress?.({
|
|
7140
8036
|
agent: agent.name,
|
|
@@ -7142,22 +8038,45 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
7142
8038
|
newCount: tagged2.sessions.length
|
|
7143
8039
|
});
|
|
7144
8040
|
const filtered3 = filterSessions(tagged2.sessions, options);
|
|
7145
|
-
|
|
8041
|
+
timing.total = performance.now() - agentStart;
|
|
8042
|
+
return {
|
|
8043
|
+
agent,
|
|
8044
|
+
heads: filtered3,
|
|
8045
|
+
fromCache: true,
|
|
8046
|
+
refreshed: true,
|
|
8047
|
+
timing,
|
|
8048
|
+
cacheTimestamp: checkResult.timestamp
|
|
8049
|
+
};
|
|
7146
8050
|
}
|
|
7147
8051
|
onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
|
|
7148
8052
|
}
|
|
8053
|
+
const t3 = performance.now();
|
|
7149
8054
|
const cachedWithIdentity = attachProjectIdentities(cached.sessions);
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
8055
|
+
timing.identity = performance.now() - t3;
|
|
8056
|
+
const t4 = performance.now();
|
|
8057
|
+
const tagged = options.includeSmartTags === false ? { sessions: cachedWithIdentity, changed: false } : await ensureSessionTags(agent, cachedWithIdentity, options.smartTagWorkerUrl);
|
|
8058
|
+
timing.tags = performance.now() - t4;
|
|
8059
|
+
if (tagged.changed && options.writeCache !== false) {
|
|
8060
|
+
saveCachedSessionDiff(agent, cached.sessions, tagged.sessions);
|
|
7153
8061
|
}
|
|
7154
8062
|
const filtered2 = filterSessions(tagged.sessions, options);
|
|
7155
|
-
|
|
8063
|
+
timing.total = performance.now() - agentStart;
|
|
8064
|
+
return {
|
|
8065
|
+
agent,
|
|
8066
|
+
heads: filtered2,
|
|
8067
|
+
fromCache: true,
|
|
8068
|
+
timing,
|
|
8069
|
+
cacheTimestamp: cached.timestamp
|
|
8070
|
+
};
|
|
7156
8071
|
}
|
|
7157
8072
|
}
|
|
7158
|
-
|
|
8073
|
+
if (options.cacheOnly) {
|
|
8074
|
+
timing.total = performance.now() - agentStart;
|
|
8075
|
+
return null;
|
|
8076
|
+
}
|
|
8077
|
+
return scanAgentFull(agent, options, onProgress, timing, agentStart);
|
|
7159
8078
|
}
|
|
7160
|
-
async function scanAgentFull(agent, options, onProgress) {
|
|
8079
|
+
async function scanAgentFull(agent, options, onProgress, timing = { total: 0 }, agentStart = performance.now()) {
|
|
7161
8080
|
const availMarker = perf.start(`agent:${agent.name}:isAvailable`);
|
|
7162
8081
|
const isAvail = agent.isAvailable();
|
|
7163
8082
|
perf.end(availMarker);
|
|
@@ -7166,17 +8085,38 @@ async function scanAgentFull(agent, options, onProgress) {
|
|
|
7166
8085
|
}
|
|
7167
8086
|
try {
|
|
7168
8087
|
const scanMarker = perf.start(`agent:${agent.name}:scan`);
|
|
7169
|
-
const
|
|
8088
|
+
const t0 = performance.now();
|
|
8089
|
+
const heads = agent.scan({
|
|
8090
|
+
from: options.from,
|
|
8091
|
+
to: options.to,
|
|
8092
|
+
fast: options.fast,
|
|
8093
|
+
onProgress: (progress) => {
|
|
8094
|
+
onProgress?.({
|
|
8095
|
+
agent: agent.name,
|
|
8096
|
+
phase: "incremental",
|
|
8097
|
+
cachedCount: progress.total,
|
|
8098
|
+
newCount: progress.sessions,
|
|
8099
|
+
changedCount: progress.processed
|
|
8100
|
+
});
|
|
8101
|
+
}
|
|
8102
|
+
});
|
|
7170
8103
|
perf.end(scanMarker);
|
|
8104
|
+
timing.scan = performance.now() - t0;
|
|
8105
|
+
const t1 = performance.now();
|
|
7171
8106
|
const headsWithIdentity = attachProjectIdentities(heads);
|
|
7172
|
-
|
|
8107
|
+
timing.identity = performance.now() - t1;
|
|
8108
|
+
const t2 = performance.now();
|
|
8109
|
+
const tagged = options.includeSmartTags === false ? { sessions: headsWithIdentity, changed: false } : await ensureSessionTags(agent, headsWithIdentity, options.smartTagWorkerUrl);
|
|
8110
|
+
timing.tags = performance.now() - t2;
|
|
7173
8111
|
const meta = buildAgentCacheMeta(agent);
|
|
7174
8112
|
if (options.writeCache !== false && options.from == null && options.to == null) {
|
|
7175
8113
|
saveCachedSessions(agent.name, tagged.sessions, meta);
|
|
8114
|
+
markAgentCacheInitialized(agent.name);
|
|
7176
8115
|
}
|
|
7177
8116
|
onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
|
|
7178
8117
|
const filtered2 = filterSessions(tagged.sessions, options);
|
|
7179
|
-
|
|
8118
|
+
timing.total = performance.now() - agentStart;
|
|
8119
|
+
return { agent, heads: filtered2, fromCache: false, timing };
|
|
7180
8120
|
} catch (err) {
|
|
7181
8121
|
console.error(`Error scanning ${agent.name}:`, err);
|
|
7182
8122
|
return { agent, heads: [], fromCache: false };
|
|
@@ -7188,6 +8128,7 @@ async function scanSessions(options = {}, onProgress) {
|
|
|
7188
8128
|
const byAgent = {};
|
|
7189
8129
|
const allSessions = [];
|
|
7190
8130
|
const availableAgents = [];
|
|
8131
|
+
const cacheTimestamps = {};
|
|
7191
8132
|
const agentFilter = options.agents?.length ? new Set(options.agents.map((a) => a.toLowerCase())) : null;
|
|
7192
8133
|
const agentsToScan = agents.filter((agent) => {
|
|
7193
8134
|
if (agentFilter && !agentFilter.has(agent.name.toLowerCase())) {
|
|
@@ -7197,15 +8138,28 @@ async function scanSessions(options = {}, onProgress) {
|
|
|
7197
8138
|
});
|
|
7198
8139
|
const scanPromises = agentsToScan.map((agent) => scanAgentSmart(agent, options, onProgress));
|
|
7199
8140
|
const results = await Promise.all(scanPromises);
|
|
8141
|
+
const timings = {};
|
|
7200
8142
|
for (const result of results) {
|
|
7201
8143
|
if (result) {
|
|
7202
8144
|
availableAgents.push(result.agent);
|
|
7203
8145
|
byAgent[result.agent.name] = result.heads;
|
|
7204
8146
|
allSessions.push(...result.heads);
|
|
8147
|
+
if (result.timing) {
|
|
8148
|
+
timings[result.agent.name] = result.timing;
|
|
8149
|
+
}
|
|
8150
|
+
if (result.cacheTimestamp != null) {
|
|
8151
|
+
cacheTimestamps[result.agent.name] = result.cacheTimestamp;
|
|
8152
|
+
}
|
|
7205
8153
|
}
|
|
7206
8154
|
}
|
|
7207
8155
|
perf.end(scanMarker);
|
|
7208
|
-
return {
|
|
8156
|
+
return {
|
|
8157
|
+
sessions: allSessions,
|
|
8158
|
+
byAgent,
|
|
8159
|
+
agents: availableAgents,
|
|
8160
|
+
timings,
|
|
8161
|
+
cacheTimestamps: Object.keys(cacheTimestamps).length > 0 ? cacheTimestamps : void 0
|
|
8162
|
+
};
|
|
7209
8163
|
}
|
|
7210
8164
|
async function scanSessionsAsync(options = {}, onProgress) {
|
|
7211
8165
|
return scanSessions(options, onProgress);
|
|
@@ -7570,6 +8524,9 @@ export {
|
|
|
7570
8524
|
buildProjectGroups,
|
|
7571
8525
|
normalizeGitRemote,
|
|
7572
8526
|
computeIdentity,
|
|
8527
|
+
createProjectScopeMatcher,
|
|
8528
|
+
matchesProjectScope,
|
|
8529
|
+
filterSessionsByProjectScope,
|
|
7573
8530
|
getSmartTagSourceTimestamp,
|
|
7574
8531
|
classifySessionTags,
|
|
7575
8532
|
extractFileActivityOccurrences,
|
|
@@ -7577,10 +8534,15 @@ export {
|
|
|
7577
8534
|
extractSessionFileActivity,
|
|
7578
8535
|
parseSearchQuery,
|
|
7579
8536
|
loadCachedSessions,
|
|
8537
|
+
isAgentCacheInitialized,
|
|
8538
|
+
markAgentCacheInitialized,
|
|
8539
|
+
loadCachedSessionData,
|
|
7580
8540
|
saveCachedSessions,
|
|
8541
|
+
saveCachedSessionChanges,
|
|
7581
8542
|
clearCache,
|
|
7582
8543
|
getCacheInfo,
|
|
7583
8544
|
syncSessionSearchIndex,
|
|
8545
|
+
syncSessionSearchIndexChanges,
|
|
7584
8546
|
searchSessions,
|
|
7585
8547
|
listFileActivity,
|
|
7586
8548
|
listSessionFileActivity,
|
|
@@ -7595,4 +8557,4 @@ export {
|
|
|
7595
8557
|
importBookmarks,
|
|
7596
8558
|
deleteBookmark
|
|
7597
8559
|
};
|
|
7598
|
-
//# sourceMappingURL=chunk-
|
|
8560
|
+
//# sourceMappingURL=chunk-7GQEIPVK.js.map
|