codesesh 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5UOLRSFH.js → chunk-ZQMIB7QO.js} +2137 -2228
- package/dist/chunk-ZQMIB7QO.js.map +1 -0
- package/dist/{dist-VEIV33FR.js → dist-GZSUB4NY.js} +30 -2
- package/dist/index.js +467 -636
- package/dist/index.js.map +1 -1
- package/dist/scan-refresh-worker.js +7 -13
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +1 -1
- package/dist/smart-tag-worker.js +1 -1
- package/dist/web/assets/index-CxJTOv8L.js +111 -0
- package/dist/web/assets/vendor-CQhWyNUf.js +40 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-5UOLRSFH.js.map +0 -1
- package/dist/web/assets/index-CT4gLIAq.js +0 -111
- package/dist/web/assets/vendor-Da5am_Td.js +0 -40
- /package/dist/{dist-VEIV33FR.js.map → dist-GZSUB4NY.js.map} +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../core/dist/index.mjs
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
5
5
|
import { join as join3, basename as basename2, dirname } from "path";
|
|
6
|
-
import { existsSync } from "fs";
|
|
6
|
+
import { existsSync, statSync } from "fs";
|
|
7
|
+
import { existsSync as existsSync2 } from "fs";
|
|
7
8
|
import { homedir, platform } from "os";
|
|
8
9
|
import { join } from "path";
|
|
9
10
|
import { readFileSync } from "fs";
|
|
10
11
|
import { basename } from "path";
|
|
11
|
-
import { existsSync as
|
|
12
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
12
13
|
import { homedir as homedir2 } from "os";
|
|
13
14
|
import { join as join2 } from "path";
|
|
14
|
-
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
15
15
|
import { join as join5 } from "path";
|
|
16
|
-
import { existsSync as
|
|
16
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
17
17
|
import { basename as basename3, dirname as dirname2, join as join4 } from "path";
|
|
18
18
|
import { createRequire } from "module";
|
|
19
19
|
import { createHash } from "crypto";
|
|
@@ -40,9 +40,10 @@ import { spawnSync } from "child_process";
|
|
|
40
40
|
import * as os from "os";
|
|
41
41
|
import * as path from "path";
|
|
42
42
|
import { resolve, sep } from "path";
|
|
43
|
-
import { existsSync as existsSync11
|
|
43
|
+
import { existsSync as existsSync11 } from "fs";
|
|
44
44
|
import { join as join10 } from "path";
|
|
45
45
|
import { homedir as homedir4 } from "os";
|
|
46
|
+
import { existsSync as existsSync12, rmSync, unlinkSync } from "fs";
|
|
46
47
|
import { homedir as homedir5, platform as platform2 } from "os";
|
|
47
48
|
import { join as join11 } from "path";
|
|
48
49
|
var registrations = [];
|
|
@@ -88,6 +89,105 @@ var BaseAgent = class {
|
|
|
88
89
|
return `${this.name}://${sessionId}`;
|
|
89
90
|
}
|
|
90
91
|
};
|
|
92
|
+
var FileSystemSessionSource = class extends BaseAgent {
|
|
93
|
+
sessionMetaMap = /* @__PURE__ */ new Map();
|
|
94
|
+
getSessionMetaMap() {
|
|
95
|
+
return this.sessionMetaMap;
|
|
96
|
+
}
|
|
97
|
+
setSessionMetaMap(meta) {
|
|
98
|
+
this.sessionMetaMap = meta;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 变更检测:枚举当前源 → 与缓存 metaMap 的指纹/路径比对。
|
|
102
|
+
* 新增、变更、删除三类统一产出 changedIds。
|
|
103
|
+
*/
|
|
104
|
+
checkForChanges(_sinceTimestamp, cachedSessions) {
|
|
105
|
+
const currentRefs = this.listSessionSources();
|
|
106
|
+
const currentIds = new Set(currentRefs.map((ref) => ref.sessionId));
|
|
107
|
+
const changedIds = /* @__PURE__ */ new Set();
|
|
108
|
+
for (const ref of currentRefs) {
|
|
109
|
+
const meta = this.sessionMetaMap.get(ref.sessionId);
|
|
110
|
+
const samePath = meta?.sourcePath === ref.sourcePath;
|
|
111
|
+
const sameFingerprint = typeof meta?.sourceFingerprint === "string" && meta.sourceFingerprint === ref.fingerprint;
|
|
112
|
+
if (!samePath || !sameFingerprint) changedIds.add(ref.sessionId);
|
|
113
|
+
}
|
|
114
|
+
for (const session of cachedSessions) {
|
|
115
|
+
if (!currentIds.has(session.id)) changedIds.add(session.id);
|
|
116
|
+
}
|
|
117
|
+
const changedIdList = [...changedIds];
|
|
118
|
+
return {
|
|
119
|
+
hasChanges: changedIdList.length > 0,
|
|
120
|
+
changedIds: changedIdList,
|
|
121
|
+
timestamp: Date.now()
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 增量扫描:对变更/新增源调用 scanSessionSource 重解析,
|
|
126
|
+
* 删除已消失的源,合并回 cachedSessions。
|
|
127
|
+
*/
|
|
128
|
+
incrementalScan(cachedSessions, changedIds) {
|
|
129
|
+
const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
|
|
130
|
+
const changedSet = new Set(changedIds);
|
|
131
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
132
|
+
for (const ref of this.listSessionSources()) {
|
|
133
|
+
currentIds.add(ref.sessionId);
|
|
134
|
+
if (!changedSet.has(ref.sessionId)) continue;
|
|
135
|
+
const head = this.scanSessionSource(ref.sourcePath);
|
|
136
|
+
if (head) {
|
|
137
|
+
sessionMap.set(head.id, head);
|
|
138
|
+
} else {
|
|
139
|
+
sessionMap.delete(ref.sessionId);
|
|
140
|
+
this.sessionMetaMap.delete(ref.sessionId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const id of changedSet) {
|
|
144
|
+
if (!currentIds.has(id)) {
|
|
145
|
+
sessionMap.delete(id);
|
|
146
|
+
this.sessionMetaMap.delete(id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return [...sessionMap.values()];
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var DatabaseSessionSource = class extends BaseAgent {
|
|
153
|
+
sessionMetaMap = /* @__PURE__ */ new Map();
|
|
154
|
+
/** 记录单个会话的缓存 meta(sourcePath = dbPath)。 */
|
|
155
|
+
rememberSession(sessionId) {
|
|
156
|
+
const dbPath = this.getDatabasePath();
|
|
157
|
+
if (!dbPath) return;
|
|
158
|
+
this.sessionMetaMap.set(sessionId, { id: sessionId, sourcePath: dbPath });
|
|
159
|
+
}
|
|
160
|
+
getSessionMetaMap() {
|
|
161
|
+
return this.sessionMetaMap;
|
|
162
|
+
}
|
|
163
|
+
setSessionMetaMap(meta) {
|
|
164
|
+
this.sessionMetaMap = meta;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 变更检测:数据库内部变更难以按行定位,简单起见按库文件 mtime 判定。
|
|
168
|
+
* 库有变更则标记全部缓存会话刷新。
|
|
169
|
+
*/
|
|
170
|
+
checkForChanges(sinceTimestamp, cachedSessions) {
|
|
171
|
+
const dbPath = this.getDatabasePath();
|
|
172
|
+
if (!dbPath || !existsSync(dbPath)) {
|
|
173
|
+
return { hasChanges: false, timestamp: Date.now() };
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const hasChanges = statSync(dbPath).mtimeMs > sinceTimestamp;
|
|
177
|
+
return {
|
|
178
|
+
hasChanges,
|
|
179
|
+
changedIds: hasChanges ? cachedSessions.map((session) => session.id) : [],
|
|
180
|
+
timestamp: Date.now()
|
|
181
|
+
};
|
|
182
|
+
} catch {
|
|
183
|
+
return { hasChanges: false, timestamp: Date.now() };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** 增量扫描:数据库型无法增量,直接全量重扫。 */
|
|
187
|
+
incrementalScan(_cachedSessions, _changedIds) {
|
|
188
|
+
return this.scan();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
91
191
|
function envPath(name) {
|
|
92
192
|
const value = process.env[name];
|
|
93
193
|
if (!value) return null;
|
|
@@ -95,7 +195,7 @@ function envPath(name) {
|
|
|
95
195
|
}
|
|
96
196
|
function firstExisting(...paths) {
|
|
97
197
|
for (const p of paths) {
|
|
98
|
-
if (
|
|
198
|
+
if (existsSync2(p)) return p;
|
|
99
199
|
}
|
|
100
200
|
return null;
|
|
101
201
|
}
|
|
@@ -527,7 +627,7 @@ function parseLiteLLMData(data) {
|
|
|
527
627
|
}
|
|
528
628
|
function loadDiskCache() {
|
|
529
629
|
const path2 = getCachePath();
|
|
530
|
-
if (!
|
|
630
|
+
if (!existsSync3(path2)) return;
|
|
531
631
|
try {
|
|
532
632
|
const cached = JSON.parse(readFileSync2(path2, "utf-8"));
|
|
533
633
|
if (Date.now() - cached.timestamp <= CACHE_TTL_MS) {
|
|
@@ -550,7 +650,7 @@ function hasBillablePricing(pricing) {
|
|
|
550
650
|
}
|
|
551
651
|
async function refreshPricingCache() {
|
|
552
652
|
const path2 = getCachePath();
|
|
553
|
-
if (
|
|
653
|
+
if (existsSync3(path2)) {
|
|
554
654
|
try {
|
|
555
655
|
const cached = JSON.parse(readFileSync2(path2, "utf-8"));
|
|
556
656
|
if (typeof cached.timestamp === "number" && Date.now() - cached.timestamp <= CACHE_TTL_MS) {
|
|
@@ -733,13 +833,13 @@ function extractClaudeUsage(data, msg) {
|
|
|
733
833
|
cacheCreate: numericUsage(u["cache_creation_input_tokens"])
|
|
734
834
|
};
|
|
735
835
|
}
|
|
736
|
-
var ClaudeCodeAgent = class extends
|
|
836
|
+
var ClaudeCodeAgent = class extends FileSystemSessionSource {
|
|
737
837
|
name = "claudecode";
|
|
738
838
|
displayName = "Claude Code";
|
|
739
839
|
basePath = null;
|
|
740
840
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
741
841
|
sessionsIndexCache = {};
|
|
742
|
-
|
|
842
|
+
sessionsIndexMtime = {};
|
|
743
843
|
findBasePath() {
|
|
744
844
|
const roots = resolveProviderRoots();
|
|
745
845
|
return firstExisting(join3(roots.claudeRoot, "projects"), "data/claudecode");
|
|
@@ -750,7 +850,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
750
850
|
try {
|
|
751
851
|
for (const entry of readdirSync(this.basePath)) {
|
|
752
852
|
const dir = join3(this.basePath, entry);
|
|
753
|
-
if (
|
|
853
|
+
if (existsSync4(dir) && readdirSync(dir).some((f) => f.endsWith(".jsonl"))) {
|
|
754
854
|
return true;
|
|
755
855
|
}
|
|
756
856
|
}
|
|
@@ -769,7 +869,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
769
869
|
const fileMarker = perf.start(`listJsonlFiles:${basename2(projectDir)}`);
|
|
770
870
|
const files = this.listJsonlFiles(projectDir).filter((file) => {
|
|
771
871
|
try {
|
|
772
|
-
return matchesScanWindow(
|
|
872
|
+
return matchesScanWindow(statSync2(file).mtimeMs, options);
|
|
773
873
|
} catch {
|
|
774
874
|
return false;
|
|
775
875
|
}
|
|
@@ -823,18 +923,12 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
823
923
|
}
|
|
824
924
|
return head;
|
|
825
925
|
}
|
|
826
|
-
getSessionMetaMap() {
|
|
827
|
-
return this.sessionMetaMap;
|
|
828
|
-
}
|
|
829
|
-
setSessionMetaMap(meta) {
|
|
830
|
-
this.sessionMetaMap = meta;
|
|
831
|
-
}
|
|
832
926
|
getSessionData(sessionId) {
|
|
833
927
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
834
928
|
if (!meta) {
|
|
835
929
|
throw new Error(`Session not found: ${sessionId}`);
|
|
836
930
|
}
|
|
837
|
-
if (!
|
|
931
|
+
if (!existsSync4(meta.sourcePath)) {
|
|
838
932
|
throw new Error(`Session file missing: ${meta.sourcePath}`);
|
|
839
933
|
}
|
|
840
934
|
const content = readFileSync3(meta.sourcePath, "utf-8");
|
|
@@ -894,83 +988,11 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
894
988
|
messages: cleanedMessages
|
|
895
989
|
};
|
|
896
990
|
}
|
|
897
|
-
/**
|
|
898
|
-
* 检测文件系统变更
|
|
899
|
-
* - 已有 session:statSync 检测文件修改(APFS 写文件不更新 dir mtime,必须 file-level)
|
|
900
|
-
* - 新 session 检测:readdirSync 文件列表比对,比 dir statSync 快 ~10x
|
|
901
|
-
*/
|
|
902
|
-
checkForChanges(_sinceTimestamp, cachedSessions) {
|
|
903
|
-
if (!this.basePath) {
|
|
904
|
-
return { hasChanges: false, timestamp: Date.now() };
|
|
905
|
-
}
|
|
906
|
-
const changedIds = /* @__PURE__ */ new Set();
|
|
907
|
-
for (const session of cachedSessions) {
|
|
908
|
-
const meta = this.sessionMetaMap.get(session.id);
|
|
909
|
-
if (!meta) {
|
|
910
|
-
changedIds.add(session.id);
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
try {
|
|
914
|
-
if (this.hasMetaChanged(meta)) {
|
|
915
|
-
changedIds.add(session.id);
|
|
916
|
-
delete this.sessionsIndexCache[basename2(dirname(meta.sourcePath))];
|
|
917
|
-
}
|
|
918
|
-
} catch {
|
|
919
|
-
changedIds.add(session.id);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
const cachedIdSet = new Set(cachedSessions.map((s) => s.id));
|
|
923
|
-
let hasNewFiles = false;
|
|
924
|
-
try {
|
|
925
|
-
outer: for (const dir of this.listProjectDirs()) {
|
|
926
|
-
try {
|
|
927
|
-
for (const file of this.listJsonlFiles(dir)) {
|
|
928
|
-
if (!cachedIdSet.has(basename2(file, ".jsonl"))) {
|
|
929
|
-
hasNewFiles = true;
|
|
930
|
-
delete this.sessionsIndexCache[basename2(dir)];
|
|
931
|
-
break outer;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
} catch {
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
} catch {
|
|
938
|
-
}
|
|
939
|
-
return {
|
|
940
|
-
hasChanges: changedIds.size > 0 || hasNewFiles,
|
|
941
|
-
changedIds: Array.from(changedIds),
|
|
942
|
-
timestamp: Date.now()
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
/**
|
|
946
|
-
* 增量扫描 - 只扫描变更的会话
|
|
947
|
-
*/
|
|
948
|
-
incrementalScan(cachedSessions, changedIds) {
|
|
949
|
-
if (!this.basePath) return cachedSessions;
|
|
950
|
-
const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
|
|
951
|
-
const changedSet = new Set(changedIds);
|
|
952
|
-
for (const projectDir of this.listProjectDirs()) {
|
|
953
|
-
for (const file of this.listJsonlFiles(projectDir)) {
|
|
954
|
-
try {
|
|
955
|
-
const sessionId = basename2(file, ".jsonl");
|
|
956
|
-
if (changedSet.has(sessionId) || !sessionMap.has(sessionId)) {
|
|
957
|
-
const head = getParsedSession(this.parseSessionHeadResult(file, projectDir));
|
|
958
|
-
if (head) {
|
|
959
|
-
sessionMap.set(head.id, head);
|
|
960
|
-
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file, projectDir));
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
} catch {
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
return Array.from(sessionMap.values());
|
|
968
|
-
}
|
|
969
991
|
// --- Private helpers ---
|
|
970
992
|
listProjectDirs() {
|
|
971
993
|
if (!this.basePath) return [];
|
|
972
994
|
try {
|
|
973
|
-
return readdirSync(this.basePath).map((e) => join3(this.basePath, e)).filter((p) =>
|
|
995
|
+
return readdirSync(this.basePath).map((e) => join3(this.basePath, e)).filter((p) => existsSync4(p));
|
|
974
996
|
} catch {
|
|
975
997
|
return [];
|
|
976
998
|
}
|
|
@@ -989,8 +1011,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
989
1011
|
title: head.title,
|
|
990
1012
|
sourcePath: file,
|
|
991
1013
|
sourceFingerprint: this.sourceFingerprint(file, projectDir),
|
|
992
|
-
sourceMtimeMs:
|
|
993
|
-
indexPath:
|
|
1014
|
+
sourceMtimeMs: statSync2(file).mtimeMs,
|
|
1015
|
+
indexPath: existsSync4(indexPath) ? indexPath : null,
|
|
994
1016
|
indexMtimeMs: this.getFileMtimeMs(indexPath),
|
|
995
1017
|
headIndexVersion: HEAD_INDEX_VERSION,
|
|
996
1018
|
directory: head.directory,
|
|
@@ -1000,15 +1022,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1000
1022
|
updatedAt: head.time_updated ?? head.time_created
|
|
1001
1023
|
};
|
|
1002
1024
|
}
|
|
1003
|
-
hasMetaChanged(meta) {
|
|
1004
|
-
if (meta.headIndexVersion !== HEAD_INDEX_VERSION) return true;
|
|
1005
|
-
if (typeof meta.sourceMtimeMs !== "number") return true;
|
|
1006
|
-
if (statSync(meta.sourcePath).mtimeMs !== meta.sourceMtimeMs) return true;
|
|
1007
|
-
const indexPath = meta.indexPath ?? this.getSessionsIndexPath(dirname(meta.sourcePath));
|
|
1008
|
-
return this.getFileMtimeMs(indexPath) !== (meta.indexMtimeMs ?? null);
|
|
1009
|
-
}
|
|
1010
1025
|
sourceFingerprint(file, projectDir) {
|
|
1011
|
-
const stat =
|
|
1026
|
+
const stat = statSync2(file);
|
|
1012
1027
|
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
1013
1028
|
return JSON.stringify([
|
|
1014
1029
|
HEAD_INDEX_VERSION,
|
|
@@ -1022,19 +1037,20 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1022
1037
|
}
|
|
1023
1038
|
getFileMtimeMs(filePath) {
|
|
1024
1039
|
try {
|
|
1025
|
-
return
|
|
1040
|
+
return statSync2(filePath).mtimeMs;
|
|
1026
1041
|
} catch {
|
|
1027
1042
|
return null;
|
|
1028
1043
|
}
|
|
1029
1044
|
}
|
|
1030
1045
|
loadSessionsIndex(projectDir) {
|
|
1031
1046
|
const cacheKey = basename2(projectDir);
|
|
1032
|
-
|
|
1047
|
+
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
1048
|
+
const mtime = this.getFileMtimeMs(indexPath);
|
|
1049
|
+
if (cacheKey in this.sessionsIndexCache && this.sessionsIndexMtime[cacheKey] === mtime) {
|
|
1033
1050
|
return this.sessionsIndexCache[cacheKey];
|
|
1034
1051
|
}
|
|
1035
|
-
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
1036
1052
|
const map = /* @__PURE__ */ new Map();
|
|
1037
|
-
if (
|
|
1053
|
+
if (existsSync4(indexPath)) {
|
|
1038
1054
|
try {
|
|
1039
1055
|
const data = JSON.parse(readFileSync3(indexPath, "utf-8"));
|
|
1040
1056
|
const entries = data?.entries ?? [];
|
|
@@ -1048,6 +1064,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1048
1064
|
}
|
|
1049
1065
|
}
|
|
1050
1066
|
this.sessionsIndexCache[cacheKey] = map;
|
|
1067
|
+
this.sessionsIndexMtime[cacheKey] = mtime;
|
|
1051
1068
|
return map;
|
|
1052
1069
|
}
|
|
1053
1070
|
parseSessionHead(filePath, projectDir) {
|
|
@@ -1064,7 +1081,7 @@ var ClaudeCodeAgent = class extends BaseAgent {
|
|
|
1064
1081
|
} catch {
|
|
1065
1082
|
return skippedSession("malformed first record");
|
|
1066
1083
|
}
|
|
1067
|
-
const createdAt = parseTimestampMs(firstRecord) ||
|
|
1084
|
+
const createdAt = parseTimestampMs(firstRecord) || statSync2(filePath).mtimeMs;
|
|
1068
1085
|
const index = this.loadSessionsIndex(projectDir);
|
|
1069
1086
|
const indexEntry = index.get(sessionId);
|
|
1070
1087
|
const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
|
|
@@ -1636,7 +1653,7 @@ function backupDatabase(db, dbPath, label) {
|
|
|
1636
1653
|
}
|
|
1637
1654
|
const timestamp = new Date(Date.now()).toISOString().replaceAll(":", "").replaceAll(".", "-");
|
|
1638
1655
|
let backupPath = join4(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.bak`);
|
|
1639
|
-
for (let counter = 1;
|
|
1656
|
+
for (let counter = 1; existsSync5(backupPath); counter += 1) {
|
|
1640
1657
|
backupPath = join4(dirname2(dbPath), `${basename3(dbPath)}.${timestamp}.${label}.${counter}.bak`);
|
|
1641
1658
|
}
|
|
1642
1659
|
db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
|
|
@@ -1709,12 +1726,16 @@ function openDb(dbPath) {
|
|
|
1709
1726
|
function isSqliteAvailable() {
|
|
1710
1727
|
return DatabaseConstructor !== null;
|
|
1711
1728
|
}
|
|
1712
|
-
var OpenCodeAgent = class extends
|
|
1729
|
+
var OpenCodeAgent = class extends DatabaseSessionSource {
|
|
1713
1730
|
name = "opencode";
|
|
1714
1731
|
displayName = "OpenCode";
|
|
1715
1732
|
dbPath = null;
|
|
1716
|
-
|
|
1717
|
-
|
|
1733
|
+
getDatabasePath() {
|
|
1734
|
+
if (!this.dbPath) {
|
|
1735
|
+
this.dbPath = this.findDbPath();
|
|
1736
|
+
}
|
|
1737
|
+
return this.dbPath;
|
|
1738
|
+
}
|
|
1718
1739
|
findDbPath() {
|
|
1719
1740
|
if (!isSqliteAvailable()) return null;
|
|
1720
1741
|
const roots = resolveProviderRoots();
|
|
@@ -1830,42 +1851,6 @@ var OpenCodeAgent = class extends BaseAgent {
|
|
|
1830
1851
|
`
|
|
1831
1852
|
).all(cutoffTime);
|
|
1832
1853
|
}
|
|
1833
|
-
getSessionMetaMap() {
|
|
1834
|
-
return this.sessionMetaMap;
|
|
1835
|
-
}
|
|
1836
|
-
setSessionMetaMap(meta) {
|
|
1837
|
-
this.sessionMetaMap = meta;
|
|
1838
|
-
}
|
|
1839
|
-
/**
|
|
1840
|
-
* 检测数据库变更
|
|
1841
|
-
* 对于 SQLite,检测数据库文件修改时间
|
|
1842
|
-
*/
|
|
1843
|
-
checkForChanges(sinceTimestamp, cachedSessions) {
|
|
1844
|
-
if (!this.dbPath) {
|
|
1845
|
-
this.dbPath = this.findDbPath();
|
|
1846
|
-
}
|
|
1847
|
-
if (!this.dbPath || !existsSync5(this.dbPath)) {
|
|
1848
|
-
return { hasChanges: false, timestamp: Date.now() };
|
|
1849
|
-
}
|
|
1850
|
-
try {
|
|
1851
|
-
const stat = statSync2(this.dbPath);
|
|
1852
|
-
const hasChanges = stat.mtimeMs > sinceTimestamp;
|
|
1853
|
-
const changedIds = hasChanges ? cachedSessions.map((s) => s.id) : [];
|
|
1854
|
-
return {
|
|
1855
|
-
hasChanges,
|
|
1856
|
-
changedIds,
|
|
1857
|
-
timestamp: Date.now()
|
|
1858
|
-
};
|
|
1859
|
-
} catch {
|
|
1860
|
-
return { hasChanges: false, timestamp: Date.now() };
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
/**
|
|
1864
|
-
* 增量扫描 - 重新查询数据库
|
|
1865
|
-
*/
|
|
1866
|
-
incrementalScan(_cachedSessions, _changedIds) {
|
|
1867
|
-
return this.scan();
|
|
1868
|
-
}
|
|
1869
1854
|
parsePartRow(partRow) {
|
|
1870
1855
|
const partData = JSON.parse(String(partRow.data ?? "{}"));
|
|
1871
1856
|
const partType = String(partData.type ?? "");
|
|
@@ -2145,11 +2130,10 @@ function extractFirstUserTitle(contextFile, wireFile) {
|
|
|
2145
2130
|
}
|
|
2146
2131
|
return null;
|
|
2147
2132
|
}
|
|
2148
|
-
var KimiAgent = class extends
|
|
2133
|
+
var KimiAgent = class extends FileSystemSessionSource {
|
|
2149
2134
|
name = "kimi";
|
|
2150
2135
|
displayName = "Kimi-Cli";
|
|
2151
2136
|
basePath = null;
|
|
2152
|
-
sessionMetaMap = /* @__PURE__ */ new Map();
|
|
2153
2137
|
projectMap = /* @__PURE__ */ new Map();
|
|
2154
2138
|
defaultModel = null;
|
|
2155
2139
|
findBasePath() {
|
|
@@ -2310,8 +2294,6 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2310
2294
|
for (const dir of this.listSessionDirs()) {
|
|
2311
2295
|
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
2312
2296
|
if (!meta) continue;
|
|
2313
|
-
meta.sourceFingerprint = this.sourceFingerprint(meta);
|
|
2314
|
-
this.sessionMetaMap.set(meta.id, meta);
|
|
2315
2297
|
refs.push({
|
|
2316
2298
|
sessionId: meta.id,
|
|
2317
2299
|
sourcePath: meta.sourcePath,
|
|
@@ -2336,87 +2318,6 @@ var KimiAgent = class extends BaseAgent {
|
|
|
2336
2318
|
stats
|
|
2337
2319
|
};
|
|
2338
2320
|
}
|
|
2339
|
-
getSessionMetaMap() {
|
|
2340
|
-
return this.sessionMetaMap;
|
|
2341
|
-
}
|
|
2342
|
-
setSessionMetaMap(meta) {
|
|
2343
|
-
this.sessionMetaMap = meta;
|
|
2344
|
-
}
|
|
2345
|
-
/**
|
|
2346
|
-
* 检测文件系统变更
|
|
2347
|
-
*/
|
|
2348
|
-
checkForChanges(sinceTimestamp, cachedSessions) {
|
|
2349
|
-
const changedIds = /* @__PURE__ */ new Set();
|
|
2350
|
-
const cachedIds = new Set(cachedSessions.map((session) => session.id));
|
|
2351
|
-
const currentIds = /* @__PURE__ */ new Set();
|
|
2352
|
-
for (const dir of this.listSessionDirs()) {
|
|
2353
|
-
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
2354
|
-
if (!meta) continue;
|
|
2355
|
-
currentIds.add(meta.id);
|
|
2356
|
-
this.sessionMetaMap.set(meta.id, meta);
|
|
2357
|
-
if (!cachedIds.has(meta.id)) {
|
|
2358
|
-
changedIds.add(meta.id);
|
|
2359
|
-
continue;
|
|
2360
|
-
}
|
|
2361
|
-
const dataFile = meta.wireFile || meta.contextFile;
|
|
2362
|
-
try {
|
|
2363
|
-
const metaStat = statSync3(meta.metaFile);
|
|
2364
|
-
if (metaStat.mtimeMs > sinceTimestamp) {
|
|
2365
|
-
changedIds.add(meta.id);
|
|
2366
|
-
continue;
|
|
2367
|
-
}
|
|
2368
|
-
if (dataFile) {
|
|
2369
|
-
const dataStat = statSync3(dataFile);
|
|
2370
|
-
if (dataStat.mtimeMs > sinceTimestamp) {
|
|
2371
|
-
changedIds.add(meta.id);
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
} catch {
|
|
2375
|
-
changedIds.add(meta.id);
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
for (const session of cachedSessions) {
|
|
2379
|
-
if (!currentIds.has(session.id)) changedIds.add(session.id);
|
|
2380
|
-
}
|
|
2381
|
-
const changedIdList = Array.from(changedIds);
|
|
2382
|
-
return {
|
|
2383
|
-
hasChanges: changedIdList.length > 0,
|
|
2384
|
-
changedIds: changedIdList,
|
|
2385
|
-
timestamp: Date.now()
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
|
-
/**
|
|
2389
|
-
* 增量扫描
|
|
2390
|
-
*/
|
|
2391
|
-
incrementalScan(cachedSessions, changedIds) {
|
|
2392
|
-
const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
|
|
2393
|
-
const changedIdSet = new Set(changedIds);
|
|
2394
|
-
for (const id of changedIdSet) {
|
|
2395
|
-
sessionMap.delete(id);
|
|
2396
|
-
this.sessionMetaMap.delete(id);
|
|
2397
|
-
}
|
|
2398
|
-
for (const dir of this.listSessionDirs()) {
|
|
2399
|
-
try {
|
|
2400
|
-
const meta = getParsedSession(this.parseSessionDirResult(dir));
|
|
2401
|
-
if (!meta) continue;
|
|
2402
|
-
if (changedIdSet.has(meta.id)) {
|
|
2403
|
-
this.sessionMetaMap.set(meta.id, meta);
|
|
2404
|
-
const stats = this.extractStats(meta.sourcePath);
|
|
2405
|
-
sessionMap.set(meta.id, {
|
|
2406
|
-
id: meta.id,
|
|
2407
|
-
slug: `kimi/${meta.id}`,
|
|
2408
|
-
title: meta.title,
|
|
2409
|
-
directory: meta.cwd,
|
|
2410
|
-
time_created: meta.createdAt,
|
|
2411
|
-
time_updated: meta.createdAt,
|
|
2412
|
-
stats
|
|
2413
|
-
});
|
|
2414
|
-
}
|
|
2415
|
-
} catch {
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
return Array.from(sessionMap.values());
|
|
2419
|
-
}
|
|
2420
2321
|
getSessionData(sessionId) {
|
|
2421
2322
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
2422
2323
|
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
@@ -3019,12 +2920,12 @@ function extractPatchContent(lines, startIndex) {
|
|
|
3019
2920
|
}
|
|
3020
2921
|
return { text: contentLines.join("\n"), nextLineIndex: i };
|
|
3021
2922
|
}
|
|
3022
|
-
var CodexAgent = class extends
|
|
2923
|
+
var CodexAgent = class extends FileSystemSessionSource {
|
|
3023
2924
|
name = "codex";
|
|
3024
2925
|
displayName = "Codex";
|
|
3025
2926
|
basePath = null;
|
|
3026
2927
|
sessionIndexCache = /* @__PURE__ */ new Map();
|
|
3027
|
-
|
|
2928
|
+
sessionIndexMtime = null;
|
|
3028
2929
|
// ---- BaseAgent implementation ----
|
|
3029
2930
|
findBasePath() {
|
|
3030
2931
|
const roots = resolveProviderRoots();
|
|
@@ -3087,94 +2988,6 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3087
2988
|
}
|
|
3088
2989
|
return head;
|
|
3089
2990
|
}
|
|
3090
|
-
getSessionMetaMap() {
|
|
3091
|
-
return this.sessionMetaMap;
|
|
3092
|
-
}
|
|
3093
|
-
setSessionMetaMap(meta) {
|
|
3094
|
-
this.sessionMetaMap = meta;
|
|
3095
|
-
}
|
|
3096
|
-
/**
|
|
3097
|
-
* 检测文件系统变更
|
|
3098
|
-
*/
|
|
3099
|
-
checkForChanges(_sinceTimestamp, cachedSessions) {
|
|
3100
|
-
if (!this.basePath) {
|
|
3101
|
-
return { hasChanges: false, timestamp: Date.now() };
|
|
3102
|
-
}
|
|
3103
|
-
const currentFiles = this.listRolloutFiles();
|
|
3104
|
-
const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
|
|
3105
|
-
const cachedIds = new Set(cachedSessions.map((session) => session.id));
|
|
3106
|
-
const changedIds = /* @__PURE__ */ new Set();
|
|
3107
|
-
for (const session of cachedSessions) {
|
|
3108
|
-
if (!currentIds.has(session.id)) {
|
|
3109
|
-
changedIds.add(session.id);
|
|
3110
|
-
continue;
|
|
3111
|
-
}
|
|
3112
|
-
const meta = this.sessionMetaMap.get(session.id);
|
|
3113
|
-
if (!meta) {
|
|
3114
|
-
changedIds.add(session.id);
|
|
3115
|
-
continue;
|
|
3116
|
-
}
|
|
3117
|
-
try {
|
|
3118
|
-
if (this.hasMetaChanged(meta)) {
|
|
3119
|
-
changedIds.add(session.id);
|
|
3120
|
-
}
|
|
3121
|
-
} catch {
|
|
3122
|
-
changedIds.add(session.id);
|
|
3123
|
-
}
|
|
3124
|
-
}
|
|
3125
|
-
const hasAddedSessions = currentFiles.some((file) => !cachedIds.has(extractSessionId(file)));
|
|
3126
|
-
if (hasAddedSessions || changedIds.size > 0) {
|
|
3127
|
-
this.sessionIndexCache.clear();
|
|
3128
|
-
}
|
|
3129
|
-
return {
|
|
3130
|
-
hasChanges: changedIds.size > 0 || hasAddedSessions,
|
|
3131
|
-
changedIds: Array.from(changedIds),
|
|
3132
|
-
timestamp: Date.now()
|
|
3133
|
-
};
|
|
3134
|
-
}
|
|
3135
|
-
/**
|
|
3136
|
-
* 增量扫描
|
|
3137
|
-
*/
|
|
3138
|
-
incrementalScan(cachedSessions, changedIds) {
|
|
3139
|
-
if (!this.basePath) return cachedSessions;
|
|
3140
|
-
const sessionMap = new Map(cachedSessions.map((s) => [s.id, s]));
|
|
3141
|
-
const changedSet = new Set(changedIds);
|
|
3142
|
-
const currentFiles = this.listRolloutFiles();
|
|
3143
|
-
const currentIds = new Set(currentFiles.map((file) => extractSessionId(file)));
|
|
3144
|
-
for (const session of cachedSessions) {
|
|
3145
|
-
if (!currentIds.has(session.id)) {
|
|
3146
|
-
sessionMap.delete(session.id);
|
|
3147
|
-
this.sessionMetaMap.delete(session.id);
|
|
3148
|
-
}
|
|
3149
|
-
}
|
|
3150
|
-
for (const file of currentFiles) {
|
|
3151
|
-
try {
|
|
3152
|
-
const sessionId = extractSessionId(file);
|
|
3153
|
-
if (changedSet.has(sessionId)) {
|
|
3154
|
-
const head = this.parseSessionHead(file);
|
|
3155
|
-
if (head) {
|
|
3156
|
-
sessionMap.set(head.id, head);
|
|
3157
|
-
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file));
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
} catch {
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
for (const file of currentFiles) {
|
|
3164
|
-
try {
|
|
3165
|
-
const sessionId = extractSessionId(file);
|
|
3166
|
-
if (!sessionMap.has(sessionId)) {
|
|
3167
|
-
const head = this.parseSessionHead(file);
|
|
3168
|
-
if (head) {
|
|
3169
|
-
sessionMap.set(head.id, head);
|
|
3170
|
-
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file));
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
|
-
} catch {
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
return Array.from(sessionMap.values());
|
|
3177
|
-
}
|
|
3178
2991
|
getSessionData(sessionId) {
|
|
3179
2992
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
3180
2993
|
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
@@ -3352,14 +3165,6 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3352
3165
|
updatedAt: head.time_updated ?? head.time_created
|
|
3353
3166
|
};
|
|
3354
3167
|
}
|
|
3355
|
-
hasMetaChanged(meta) {
|
|
3356
|
-
if (meta.headIndexVersion !== HEAD_INDEX_VERSION2) return true;
|
|
3357
|
-
if (meta.parserVersion !== PARSER_VERSION) return true;
|
|
3358
|
-
if (typeof meta.sourceMtimeMs !== "number") return true;
|
|
3359
|
-
if (statSync4(meta.sourcePath).mtimeMs !== meta.sourceMtimeMs) return true;
|
|
3360
|
-
const indexTitle = this.getTitleForSession(meta.id);
|
|
3361
|
-
return indexTitle !== null && indexTitle !== meta.title;
|
|
3362
|
-
}
|
|
3363
3168
|
sourceFingerprint(file) {
|
|
3364
3169
|
const stat = statSync4(file);
|
|
3365
3170
|
const sessionId = extractSessionId(file);
|
|
@@ -3384,9 +3189,12 @@ var CodexAgent = class extends BaseAgent {
|
|
|
3384
3189
|
}
|
|
3385
3190
|
// ---- Session index ----
|
|
3386
3191
|
loadSessionIndex() {
|
|
3387
|
-
if (this.sessionIndexCache.size > 0) return;
|
|
3388
3192
|
const indexPath = this.getSessionIndexPath();
|
|
3389
|
-
|
|
3193
|
+
const mtime = this.getFileMtimeMs(indexPath);
|
|
3194
|
+
if (this.sessionIndexCache.size > 0 && this.sessionIndexMtime === mtime) return;
|
|
3195
|
+
this.sessionIndexCache.clear();
|
|
3196
|
+
this.sessionIndexMtime = mtime;
|
|
3197
|
+
if (mtime === null) return;
|
|
3390
3198
|
try {
|
|
3391
3199
|
const content = readFileSync5(indexPath, "utf-8");
|
|
3392
3200
|
for (const record of parseJsonlLines(content)) {
|
|
@@ -4122,14 +3930,18 @@ function convertActionToPart(action, timestampMs) {
|
|
|
4122
3930
|
}
|
|
4123
3931
|
return null;
|
|
4124
3932
|
}
|
|
4125
|
-
var CursorAgent = class extends
|
|
3933
|
+
var CursorAgent = class extends DatabaseSessionSource {
|
|
4126
3934
|
name = "cursor";
|
|
4127
3935
|
displayName = "Cursor";
|
|
4128
3936
|
dbPath = null;
|
|
4129
3937
|
// Cache composer data from scan so getSessionData can reuse it
|
|
4130
3938
|
composerCache = /* @__PURE__ */ new Map();
|
|
4131
|
-
|
|
4132
|
-
|
|
3939
|
+
getDatabasePath() {
|
|
3940
|
+
if (!this.dbPath) {
|
|
3941
|
+
this.dbPath = this.findDbPath();
|
|
3942
|
+
}
|
|
3943
|
+
return this.dbPath;
|
|
3944
|
+
}
|
|
4133
3945
|
findDbPath() {
|
|
4134
3946
|
if (!isSqliteAvailable()) return null;
|
|
4135
3947
|
const dataPath = getCursorDataPath();
|
|
@@ -4332,42 +4144,6 @@ var CursorAgent = class extends BaseAgent {
|
|
|
4332
4144
|
db.close();
|
|
4333
4145
|
}
|
|
4334
4146
|
}
|
|
4335
|
-
getSessionMetaMap() {
|
|
4336
|
-
return this.sessionMetaMap;
|
|
4337
|
-
}
|
|
4338
|
-
setSessionMetaMap(meta) {
|
|
4339
|
-
this.sessionMetaMap = meta;
|
|
4340
|
-
}
|
|
4341
|
-
/**
|
|
4342
|
-
* 检测数据库变更
|
|
4343
|
-
* 对于 SQLite,检测数据库文件修改时间
|
|
4344
|
-
*/
|
|
4345
|
-
checkForChanges(sinceTimestamp, cachedSessions) {
|
|
4346
|
-
if (!this.dbPath) {
|
|
4347
|
-
this.dbPath = this.findDbPath();
|
|
4348
|
-
}
|
|
4349
|
-
if (!this.dbPath || !existsSync8(this.dbPath)) {
|
|
4350
|
-
return { hasChanges: false, timestamp: Date.now() };
|
|
4351
|
-
}
|
|
4352
|
-
try {
|
|
4353
|
-
const stat = statSync5(this.dbPath);
|
|
4354
|
-
const hasChanges = stat.mtimeMs > sinceTimestamp;
|
|
4355
|
-
const changedIds = hasChanges ? cachedSessions.map((s) => s.id) : [];
|
|
4356
|
-
return {
|
|
4357
|
-
hasChanges,
|
|
4358
|
-
changedIds,
|
|
4359
|
-
timestamp: Date.now()
|
|
4360
|
-
};
|
|
4361
|
-
} catch {
|
|
4362
|
-
return { hasChanges: false, timestamp: Date.now() };
|
|
4363
|
-
}
|
|
4364
|
-
}
|
|
4365
|
-
/**
|
|
4366
|
-
* 增量扫描 - 重新查询数据库
|
|
4367
|
-
*/
|
|
4368
|
-
incrementalScan(_cachedSessions, _changedIds) {
|
|
4369
|
-
return this.scan();
|
|
4370
|
-
}
|
|
4371
4147
|
getSessionData(sessionId) {
|
|
4372
4148
|
if (!this.dbPath) {
|
|
4373
4149
|
this.dbPath = this.findDbPath();
|
|
@@ -4766,11 +4542,10 @@ function buildCurrentPathEntries(entries) {
|
|
|
4766
4542
|
}
|
|
4767
4543
|
return path2.reverse();
|
|
4768
4544
|
}
|
|
4769
|
-
var PiAgent = class extends
|
|
4545
|
+
var PiAgent = class extends FileSystemSessionSource {
|
|
4770
4546
|
name = "pi";
|
|
4771
4547
|
displayName = "Pi";
|
|
4772
4548
|
basePath = null;
|
|
4773
|
-
sessionMetaMap = /* @__PURE__ */ new Map();
|
|
4774
4549
|
findBasePath() {
|
|
4775
4550
|
const roots = resolveProviderRoots();
|
|
4776
4551
|
return firstExisting(join9(roots.piRoot, "agent", "sessions"), "data/pi");
|
|
@@ -4818,69 +4593,6 @@ var PiAgent = class extends BaseAgent {
|
|
|
4818
4593
|
}
|
|
4819
4594
|
return head;
|
|
4820
4595
|
}
|
|
4821
|
-
getSessionMetaMap() {
|
|
4822
|
-
return this.sessionMetaMap;
|
|
4823
|
-
}
|
|
4824
|
-
setSessionMetaMap(meta) {
|
|
4825
|
-
this.sessionMetaMap = meta;
|
|
4826
|
-
}
|
|
4827
|
-
checkForChanges(_sinceTimestamp, cachedSessions) {
|
|
4828
|
-
if (!this.basePath) return { hasChanges: false, timestamp: Date.now() };
|
|
4829
|
-
const currentFiles = this.listSessionFiles();
|
|
4830
|
-
const currentIds = new Set(currentFiles.map((file) => extractSessionIdFromFilename(file)));
|
|
4831
|
-
const cachedIds = new Set(cachedSessions.map((session) => session.id));
|
|
4832
|
-
const changedIds = /* @__PURE__ */ new Set();
|
|
4833
|
-
for (const session of cachedSessions) {
|
|
4834
|
-
if (!currentIds.has(session.id)) {
|
|
4835
|
-
changedIds.add(session.id);
|
|
4836
|
-
continue;
|
|
4837
|
-
}
|
|
4838
|
-
const meta = this.sessionMetaMap.get(session.id);
|
|
4839
|
-
if (!meta) {
|
|
4840
|
-
changedIds.add(session.id);
|
|
4841
|
-
continue;
|
|
4842
|
-
}
|
|
4843
|
-
try {
|
|
4844
|
-
if (this.hasMetaChanged(meta)) changedIds.add(session.id);
|
|
4845
|
-
} catch {
|
|
4846
|
-
changedIds.add(session.id);
|
|
4847
|
-
}
|
|
4848
|
-
}
|
|
4849
|
-
const hasAddedSessions = currentFiles.some(
|
|
4850
|
-
(file) => !cachedIds.has(extractSessionIdFromFilename(file))
|
|
4851
|
-
);
|
|
4852
|
-
return {
|
|
4853
|
-
hasChanges: changedIds.size > 0 || hasAddedSessions,
|
|
4854
|
-
changedIds: [...changedIds],
|
|
4855
|
-
timestamp: Date.now()
|
|
4856
|
-
};
|
|
4857
|
-
}
|
|
4858
|
-
incrementalScan(cachedSessions, changedIds) {
|
|
4859
|
-
if (!this.basePath) return cachedSessions;
|
|
4860
|
-
const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
|
|
4861
|
-
const changedSet = new Set(changedIds);
|
|
4862
|
-
const currentFiles = this.listSessionFiles();
|
|
4863
|
-
const currentIds = new Set(currentFiles.map((file) => extractSessionIdFromFilename(file)));
|
|
4864
|
-
for (const session of cachedSessions) {
|
|
4865
|
-
if (!currentIds.has(session.id)) {
|
|
4866
|
-
sessionMap.delete(session.id);
|
|
4867
|
-
this.sessionMetaMap.delete(session.id);
|
|
4868
|
-
}
|
|
4869
|
-
}
|
|
4870
|
-
for (const file of currentFiles) {
|
|
4871
|
-
try {
|
|
4872
|
-
const sessionId = extractSessionIdFromFilename(file);
|
|
4873
|
-
if (!changedSet.has(sessionId) && sessionMap.has(sessionId)) continue;
|
|
4874
|
-
const head = getParsedSession(this.parseSessionHeadResult(file));
|
|
4875
|
-
if (head) {
|
|
4876
|
-
sessionMap.set(head.id, head);
|
|
4877
|
-
this.sessionMetaMap.set(head.id, this.buildSessionMeta(head, file));
|
|
4878
|
-
}
|
|
4879
|
-
} catch {
|
|
4880
|
-
}
|
|
4881
|
-
}
|
|
4882
|
-
return [...sessionMap.values()];
|
|
4883
|
-
}
|
|
4884
4596
|
getSessionData(sessionId) {
|
|
4885
4597
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
4886
4598
|
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
@@ -4943,11 +4655,6 @@ var PiAgent = class extends BaseAgent {
|
|
|
4943
4655
|
updatedAt: head.time_updated ?? head.time_created
|
|
4944
4656
|
};
|
|
4945
4657
|
}
|
|
4946
|
-
hasMetaChanged(meta) {
|
|
4947
|
-
if (meta.headIndexVersion !== HEAD_INDEX_VERSION3) return true;
|
|
4948
|
-
if (meta.parserVersion !== PARSER_VERSION2) return true;
|
|
4949
|
-
return statSync6(meta.sourcePath).mtimeMs !== meta.sourceMtimeMs;
|
|
4950
|
-
}
|
|
4951
4658
|
sourceFingerprint(file) {
|
|
4952
4659
|
const stat = statSync6(file);
|
|
4953
4660
|
return JSON.stringify([HEAD_INDEX_VERSION3, PARSER_VERSION2, stat.mtimeMs, stat.size]);
|
|
@@ -5805,12 +5512,16 @@ function extractSessionFileActivity(agentName, sessionId, projectIdentityKey, me
|
|
|
5805
5512
|
extractFileActivityOccurrences(messages)
|
|
5806
5513
|
);
|
|
5807
5514
|
}
|
|
5808
|
-
var CACHE_SCHEMA_VERSION = 13;
|
|
5809
|
-
var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
|
|
5810
5515
|
var CACHE_FILENAME = "codesesh.db";
|
|
5811
5516
|
var LEGACY_CACHE_FILENAME = "scan-cache.json";
|
|
5812
5517
|
var SEARCH_INDEX_BULK_SYNC_THRESHOLD = 100;
|
|
5813
5518
|
var ftsIntegrityCheckedPath = null;
|
|
5519
|
+
function getFtsIntegrityCheckedPath() {
|
|
5520
|
+
return ftsIntegrityCheckedPath;
|
|
5521
|
+
}
|
|
5522
|
+
function setFtsIntegrityCheckedPath(path2) {
|
|
5523
|
+
ftsIntegrityCheckedPath = path2;
|
|
5524
|
+
}
|
|
5814
5525
|
function getCacheDir2() {
|
|
5815
5526
|
return join10(homedir4(), ".cache", "codesesh");
|
|
5816
5527
|
}
|
|
@@ -5823,408 +5534,33 @@ function getLegacyCachePath() {
|
|
|
5823
5534
|
function hasCacheStorage() {
|
|
5824
5535
|
return existsSync11(getCachePath2());
|
|
5825
5536
|
}
|
|
5826
|
-
function
|
|
5827
|
-
|
|
5828
|
-
const db = openDb(cachePath);
|
|
5829
|
-
if (!db) return null;
|
|
5830
|
-
try {
|
|
5831
|
-
ensureSchema(db, cachePath);
|
|
5832
|
-
return fn(db);
|
|
5833
|
-
} catch {
|
|
5834
|
-
return null;
|
|
5835
|
-
} finally {
|
|
5836
|
-
db.close();
|
|
5837
|
-
}
|
|
5537
|
+
function likePattern(value) {
|
|
5538
|
+
return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
|
|
5838
5539
|
}
|
|
5839
|
-
function
|
|
5840
|
-
const
|
|
5841
|
-
if (
|
|
5842
|
-
|
|
5843
|
-
return fn(db);
|
|
5844
|
-
} catch {
|
|
5845
|
-
return null;
|
|
5846
|
-
} finally {
|
|
5847
|
-
db.close();
|
|
5848
|
-
}
|
|
5540
|
+
function filePathFtsQuery(value) {
|
|
5541
|
+
const path2 = normalizeFilePathSearch(value);
|
|
5542
|
+
if (path2.length < 3) return null;
|
|
5543
|
+
return `"${path2.replaceAll('"', '""')}"`;
|
|
5849
5544
|
}
|
|
5850
|
-
function
|
|
5851
|
-
|
|
5852
|
-
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
5853
|
-
key TEXT PRIMARY KEY,
|
|
5854
|
-
value TEXT NOT NULL
|
|
5855
|
-
);
|
|
5856
|
-
|
|
5857
|
-
CREATE TABLE IF NOT EXISTS agent_cache (
|
|
5858
|
-
agent_name TEXT PRIMARY KEY,
|
|
5859
|
-
timestamp INTEGER NOT NULL
|
|
5860
|
-
);
|
|
5861
|
-
|
|
5862
|
-
CREATE TABLE IF NOT EXISTS cached_sessions (
|
|
5863
|
-
agent_name TEXT NOT NULL,
|
|
5864
|
-
session_id TEXT NOT NULL,
|
|
5865
|
-
session_json TEXT NOT NULL,
|
|
5866
|
-
meta_json TEXT,
|
|
5867
|
-
PRIMARY KEY (agent_name, session_id)
|
|
5868
|
-
);
|
|
5869
|
-
|
|
5870
|
-
CREATE TABLE IF NOT EXISTS cache_initialization (
|
|
5871
|
-
agent_name TEXT PRIMARY KEY,
|
|
5872
|
-
initialized_at INTEGER NOT NULL,
|
|
5873
|
-
index_version TEXT NOT NULL,
|
|
5874
|
-
last_sync_at INTEGER NOT NULL
|
|
5875
|
-
);
|
|
5876
|
-
`);
|
|
5545
|
+
function escapeRegExp(value) {
|
|
5546
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5877
5547
|
}
|
|
5878
|
-
function
|
|
5879
|
-
|
|
5880
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
5881
|
-
agent_name TEXT NOT NULL,
|
|
5882
|
-
session_id TEXT NOT NULL,
|
|
5883
|
-
sort_index INTEGER NOT NULL DEFAULT 0,
|
|
5884
|
-
slug TEXT NOT NULL,
|
|
5885
|
-
title TEXT NOT NULL,
|
|
5886
|
-
source_path TEXT,
|
|
5887
|
-
directory TEXT NOT NULL,
|
|
5888
|
-
project_identity_kind TEXT NOT NULL,
|
|
5889
|
-
project_identity_key TEXT NOT NULL,
|
|
5890
|
-
project_display_name TEXT NOT NULL,
|
|
5891
|
-
time_created INTEGER NOT NULL,
|
|
5892
|
-
time_updated INTEGER,
|
|
5893
|
-
activity_time INTEGER NOT NULL,
|
|
5894
|
-
message_count INTEGER NOT NULL,
|
|
5895
|
-
total_input_tokens INTEGER NOT NULL,
|
|
5896
|
-
total_output_tokens INTEGER NOT NULL,
|
|
5897
|
-
total_cache_read_tokens INTEGER,
|
|
5898
|
-
total_cache_create_tokens INTEGER,
|
|
5899
|
-
total_cost REAL NOT NULL,
|
|
5900
|
-
cost_source TEXT,
|
|
5901
|
-
total_tokens INTEGER,
|
|
5902
|
-
model_usage_json TEXT,
|
|
5903
|
-
smart_tags_json TEXT,
|
|
5904
|
-
smart_tags_source_updated_at INTEGER,
|
|
5905
|
-
meta_json TEXT,
|
|
5906
|
-
PRIMARY KEY (agent_name, session_id)
|
|
5907
|
-
);
|
|
5908
|
-
|
|
5909
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_agent_activity
|
|
5910
|
-
ON sessions(agent_name, activity_time);
|
|
5911
|
-
|
|
5912
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
5913
|
-
ON sessions(project_identity_kind, project_identity_key, activity_time);
|
|
5914
|
-
|
|
5915
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
5916
|
-
agent_name TEXT NOT NULL,
|
|
5917
|
-
session_id TEXT NOT NULL,
|
|
5918
|
-
message_index INTEGER NOT NULL,
|
|
5919
|
-
message_id TEXT NOT NULL,
|
|
5920
|
-
role TEXT NOT NULL,
|
|
5921
|
-
time_created INTEGER NOT NULL,
|
|
5922
|
-
time_completed INTEGER,
|
|
5923
|
-
agent TEXT,
|
|
5924
|
-
mode TEXT,
|
|
5925
|
-
model TEXT,
|
|
5926
|
-
provider TEXT,
|
|
5927
|
-
tokens_json TEXT,
|
|
5928
|
-
cost REAL,
|
|
5929
|
-
cost_source TEXT,
|
|
5930
|
-
parts_json TEXT NOT NULL,
|
|
5931
|
-
subagent_id TEXT,
|
|
5932
|
-
nickname TEXT,
|
|
5933
|
-
content_text TEXT NOT NULL,
|
|
5934
|
-
tool_metadata_json TEXT,
|
|
5935
|
-
PRIMARY KEY (agent_name, session_id, message_index),
|
|
5936
|
-
FOREIGN KEY (agent_name, session_id)
|
|
5937
|
-
REFERENCES sessions(agent_name, session_id)
|
|
5938
|
-
ON DELETE CASCADE
|
|
5939
|
-
);
|
|
5940
|
-
|
|
5941
|
-
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
5942
|
-
ON messages(agent_name, session_id, message_index);
|
|
5943
|
-
`);
|
|
5944
|
-
createMessageToolTables(db);
|
|
5548
|
+
function normalizeFilePathSearch(value) {
|
|
5549
|
+
return value.trim().replace(/^"|"$/g, "");
|
|
5945
5550
|
}
|
|
5946
|
-
function
|
|
5947
|
-
|
|
5948
|
-
CREATE TABLE IF NOT EXISTS message_tools (
|
|
5949
|
-
agent_name TEXT NOT NULL,
|
|
5950
|
-
session_id TEXT NOT NULL,
|
|
5951
|
-
message_index INTEGER NOT NULL,
|
|
5952
|
-
tool_name TEXT NOT NULL,
|
|
5953
|
-
PRIMARY KEY (agent_name, session_id, message_index, tool_name),
|
|
5954
|
-
FOREIGN KEY (agent_name, session_id, message_index)
|
|
5955
|
-
REFERENCES messages(agent_name, session_id, message_index)
|
|
5956
|
-
ON DELETE CASCADE
|
|
5957
|
-
);
|
|
5958
|
-
|
|
5959
|
-
CREATE INDEX IF NOT EXISTS idx_message_tools_filter
|
|
5960
|
-
ON message_tools(tool_name, agent_name, session_id);
|
|
5961
|
-
`);
|
|
5551
|
+
function stringifyOptionalJson(value) {
|
|
5552
|
+
return value == null ? null : JSON.stringify(value);
|
|
5962
5553
|
}
|
|
5963
|
-
function
|
|
5964
|
-
|
|
5965
|
-
createSessionTables(db);
|
|
5966
|
-
}
|
|
5967
|
-
db.exec(`
|
|
5968
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
5969
|
-
content_text,
|
|
5970
|
-
content='messages',
|
|
5971
|
-
content_rowid='rowid'
|
|
5972
|
-
);
|
|
5973
|
-
`);
|
|
5974
|
-
createMessageSearchTriggers(db);
|
|
5554
|
+
function parseOptionalJson(value) {
|
|
5555
|
+
return value == null ? void 0 : JSON.parse(String(value));
|
|
5975
5556
|
}
|
|
5976
|
-
function
|
|
5977
|
-
|
|
5978
|
-
CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
|
|
5979
|
-
INSERT INTO messages_fts(rowid, content_text)
|
|
5980
|
-
VALUES (new.rowid, new.content_text);
|
|
5981
|
-
END;
|
|
5982
|
-
|
|
5983
|
-
CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
|
|
5984
|
-
INSERT INTO messages_fts(messages_fts, rowid, content_text)
|
|
5985
|
-
VALUES ('delete', old.rowid, old.content_text);
|
|
5986
|
-
END;
|
|
5987
|
-
|
|
5988
|
-
CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
|
|
5989
|
-
INSERT INTO messages_fts(messages_fts, rowid, content_text)
|
|
5990
|
-
VALUES ('delete', old.rowid, old.content_text);
|
|
5991
|
-
INSERT INTO messages_fts(rowid, content_text)
|
|
5992
|
-
VALUES (new.rowid, new.content_text);
|
|
5993
|
-
END;
|
|
5994
|
-
`);
|
|
5557
|
+
function sourcePathFromMeta(meta) {
|
|
5558
|
+
return typeof meta?.sourcePath === "string" ? meta.sourcePath : null;
|
|
5995
5559
|
}
|
|
5996
|
-
function
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
DROP TRIGGER IF EXISTS messages_au;
|
|
6001
|
-
`);
|
|
6002
|
-
}
|
|
6003
|
-
function createFileActivityTables(db) {
|
|
6004
|
-
db.exec(`
|
|
6005
|
-
CREATE TABLE IF NOT EXISTS session_file_activity (
|
|
6006
|
-
agent_name TEXT NOT NULL,
|
|
6007
|
-
session_id TEXT NOT NULL,
|
|
6008
|
-
project_identity_key TEXT NOT NULL,
|
|
6009
|
-
path TEXT NOT NULL,
|
|
6010
|
-
kind TEXT NOT NULL,
|
|
6011
|
-
count INTEGER NOT NULL,
|
|
6012
|
-
latest_time INTEGER NOT NULL,
|
|
6013
|
-
PRIMARY KEY (agent_name, session_id, project_identity_key, path, kind),
|
|
6014
|
-
FOREIGN KEY (agent_name, session_id)
|
|
6015
|
-
REFERENCES sessions(agent_name, session_id)
|
|
6016
|
-
ON DELETE CASCADE
|
|
6017
|
-
);
|
|
6018
|
-
|
|
6019
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest
|
|
6020
|
-
ON session_file_activity(project_identity_key, latest_time);
|
|
6021
|
-
|
|
6022
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_latest
|
|
6023
|
-
ON session_file_activity(latest_time DESC, count DESC, path);
|
|
6024
|
-
|
|
6025
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_agent_latest
|
|
6026
|
-
ON session_file_activity(agent_name, latest_time DESC, count DESC, path);
|
|
6027
|
-
|
|
6028
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest_ordered
|
|
6029
|
-
ON session_file_activity(project_identity_key, latest_time DESC, count DESC, path);
|
|
6030
|
-
|
|
6031
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_path
|
|
6032
|
-
ON session_file_activity(path);
|
|
6033
|
-
|
|
6034
|
-
CREATE INDEX IF NOT EXISTS idx_file_activity_kind
|
|
6035
|
-
ON session_file_activity(kind);
|
|
6036
|
-
`);
|
|
6037
|
-
createFileActivityPathSearchTables(db);
|
|
6038
|
-
}
|
|
6039
|
-
function createFileActivityPathSearchTables(db) {
|
|
6040
|
-
db.exec(`
|
|
6041
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS session_file_activity_path_fts USING fts5(
|
|
6042
|
-
path,
|
|
6043
|
-
content='session_file_activity',
|
|
6044
|
-
content_rowid='rowid',
|
|
6045
|
-
tokenize='trigram'
|
|
6046
|
-
);
|
|
6047
|
-
`);
|
|
6048
|
-
createFileActivityPathSearchTriggers(db);
|
|
6049
|
-
}
|
|
6050
|
-
function createFileActivityPathSearchTriggers(db) {
|
|
6051
|
-
db.exec(`
|
|
6052
|
-
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_ai
|
|
6053
|
-
AFTER INSERT ON session_file_activity BEGIN
|
|
6054
|
-
INSERT INTO session_file_activity_path_fts(rowid, path)
|
|
6055
|
-
VALUES (new.rowid, new.path);
|
|
6056
|
-
END;
|
|
6057
|
-
|
|
6058
|
-
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_ad
|
|
6059
|
-
AFTER DELETE ON session_file_activity BEGIN
|
|
6060
|
-
INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts, rowid, path)
|
|
6061
|
-
VALUES ('delete', old.rowid, old.path);
|
|
6062
|
-
END;
|
|
6063
|
-
|
|
6064
|
-
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_au
|
|
6065
|
-
AFTER UPDATE ON session_file_activity BEGIN
|
|
6066
|
-
INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts, rowid, path)
|
|
6067
|
-
VALUES ('delete', old.rowid, old.path);
|
|
6068
|
-
INSERT INTO session_file_activity_path_fts(rowid, path)
|
|
6069
|
-
VALUES (new.rowid, new.path);
|
|
6070
|
-
END;
|
|
6071
|
-
`);
|
|
6072
|
-
}
|
|
6073
|
-
function rebuildFileActivityPathIndex(db) {
|
|
6074
|
-
if (!tableExists(db, "session_file_activity_path_fts")) {
|
|
6075
|
-
return;
|
|
6076
|
-
}
|
|
6077
|
-
db.exec(
|
|
6078
|
-
"INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts) VALUES ('rebuild')"
|
|
6079
|
-
);
|
|
6080
|
-
}
|
|
6081
|
-
function createSearchTables(db) {
|
|
6082
|
-
db.exec(`
|
|
6083
|
-
CREATE TABLE IF NOT EXISTS session_documents (
|
|
6084
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6085
|
-
agent_name TEXT NOT NULL,
|
|
6086
|
-
session_id TEXT NOT NULL,
|
|
6087
|
-
slug TEXT NOT NULL,
|
|
6088
|
-
title TEXT NOT NULL,
|
|
6089
|
-
directory TEXT NOT NULL,
|
|
6090
|
-
time_created INTEGER NOT NULL,
|
|
6091
|
-
time_updated INTEGER,
|
|
6092
|
-
activity_time INTEGER NOT NULL,
|
|
6093
|
-
content_text TEXT NOT NULL,
|
|
6094
|
-
content_hash TEXT NOT NULL,
|
|
6095
|
-
indexed_at INTEGER NOT NULL,
|
|
6096
|
-
UNIQUE(agent_name, session_id)
|
|
6097
|
-
);
|
|
6098
|
-
|
|
6099
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
|
|
6100
|
-
title,
|
|
6101
|
-
content_text,
|
|
6102
|
-
content='session_documents',
|
|
6103
|
-
content_rowid='id'
|
|
6104
|
-
);
|
|
6105
|
-
`);
|
|
6106
|
-
createSearchTriggers(db);
|
|
6107
|
-
}
|
|
6108
|
-
function createSearchTriggers(db) {
|
|
6109
|
-
db.exec(`
|
|
6110
|
-
CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
|
|
6111
|
-
INSERT INTO session_documents_fts(rowid, title, content_text)
|
|
6112
|
-
VALUES (new.id, new.title, new.content_text);
|
|
6113
|
-
END;
|
|
6114
|
-
|
|
6115
|
-
CREATE TRIGGER IF NOT EXISTS session_documents_ad AFTER DELETE ON session_documents BEGIN
|
|
6116
|
-
INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
|
|
6117
|
-
VALUES ('delete', old.id, old.title, old.content_text);
|
|
6118
|
-
END;
|
|
6119
|
-
|
|
6120
|
-
CREATE TRIGGER IF NOT EXISTS session_documents_au AFTER UPDATE ON session_documents BEGIN
|
|
6121
|
-
INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
|
|
6122
|
-
VALUES ('delete', old.id, old.title, old.content_text);
|
|
6123
|
-
INSERT INTO session_documents_fts(rowid, title, content_text)
|
|
6124
|
-
VALUES (new.id, new.title, new.content_text);
|
|
6125
|
-
END;
|
|
6126
|
-
`);
|
|
6127
|
-
}
|
|
6128
|
-
function dropSearchTriggers(db) {
|
|
6129
|
-
db.exec(`
|
|
6130
|
-
DROP TRIGGER IF EXISTS session_documents_ai;
|
|
6131
|
-
DROP TRIGGER IF EXISTS session_documents_ad;
|
|
6132
|
-
DROP TRIGGER IF EXISTS session_documents_au;
|
|
6133
|
-
`);
|
|
6134
|
-
}
|
|
6135
|
-
function ensureProjectColumns(db) {
|
|
6136
|
-
if (!tableExists(db, "session_documents")) {
|
|
6137
|
-
return;
|
|
6138
|
-
}
|
|
6139
|
-
if (!columnExists(db, "session_documents", "project_identity_kind")) {
|
|
6140
|
-
db.exec(
|
|
6141
|
-
"ALTER TABLE session_documents ADD COLUMN project_identity_kind TEXT NOT NULL DEFAULT 'path'"
|
|
6142
|
-
);
|
|
6143
|
-
}
|
|
6144
|
-
if (!columnExists(db, "session_documents", "project_identity_key")) {
|
|
6145
|
-
db.exec(
|
|
6146
|
-
"ALTER TABLE session_documents ADD COLUMN project_identity_key TEXT NOT NULL DEFAULT ''"
|
|
6147
|
-
);
|
|
6148
|
-
}
|
|
6149
|
-
if (!columnExists(db, "session_documents", "project_display_name")) {
|
|
6150
|
-
db.exec(
|
|
6151
|
-
"ALTER TABLE session_documents ADD COLUMN project_display_name TEXT NOT NULL DEFAULT ''"
|
|
6152
|
-
);
|
|
6153
|
-
}
|
|
6154
|
-
}
|
|
6155
|
-
function createProjectTables(db) {
|
|
6156
|
-
ensureProjectColumns(db);
|
|
6157
|
-
db.exec(`
|
|
6158
|
-
CREATE TABLE IF NOT EXISTS project_sessions (
|
|
6159
|
-
agent_name TEXT NOT NULL,
|
|
6160
|
-
session_id TEXT NOT NULL,
|
|
6161
|
-
identity_kind TEXT NOT NULL,
|
|
6162
|
-
identity_key TEXT NOT NULL,
|
|
6163
|
-
display_name TEXT NOT NULL,
|
|
6164
|
-
directory TEXT NOT NULL,
|
|
6165
|
-
activity_time INTEGER NOT NULL,
|
|
6166
|
-
PRIMARY KEY (agent_name, session_id)
|
|
6167
|
-
);
|
|
6168
|
-
|
|
6169
|
-
CREATE INDEX IF NOT EXISTS idx_project_sessions_identity
|
|
6170
|
-
ON project_sessions(identity_kind, identity_key);
|
|
6171
|
-
`);
|
|
6172
|
-
createProjectGroupsView(db);
|
|
6173
|
-
}
|
|
6174
|
-
function createProjectGroupsView(db) {
|
|
6175
|
-
if (!tableExists(db, "sessions")) {
|
|
6176
|
-
db.exec(`
|
|
6177
|
-
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
6178
|
-
SELECT
|
|
6179
|
-
identity_kind,
|
|
6180
|
-
identity_key,
|
|
6181
|
-
MIN(display_name) AS display_name,
|
|
6182
|
-
GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
|
|
6183
|
-
COUNT(*) AS session_count,
|
|
6184
|
-
MAX(activity_time) AS last_activity
|
|
6185
|
-
FROM project_sessions
|
|
6186
|
-
GROUP BY identity_kind, identity_key;
|
|
6187
|
-
`);
|
|
6188
|
-
return;
|
|
6189
|
-
}
|
|
6190
|
-
db.exec(`
|
|
6191
|
-
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
6192
|
-
SELECT
|
|
6193
|
-
project_identity_kind AS identity_kind,
|
|
6194
|
-
project_identity_key AS identity_key,
|
|
6195
|
-
MIN(project_display_name) AS display_name,
|
|
6196
|
-
GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
|
|
6197
|
-
COUNT(*) AS session_count,
|
|
6198
|
-
MAX(activity_time) AS last_activity
|
|
6199
|
-
FROM sessions
|
|
6200
|
-
GROUP BY project_identity_kind, project_identity_key;
|
|
6201
|
-
`);
|
|
6202
|
-
}
|
|
6203
|
-
function recreateProjectGroupsView(db) {
|
|
6204
|
-
db.exec("DROP VIEW IF EXISTS project_groups_v");
|
|
6205
|
-
createProjectGroupsView(db);
|
|
6206
|
-
}
|
|
6207
|
-
function createLatestCacheSchema(db) {
|
|
6208
|
-
createCacheTables(db);
|
|
6209
|
-
createSessionTables(db);
|
|
6210
|
-
createMessageSearchTables(db);
|
|
6211
|
-
createFileActivityTables(db);
|
|
6212
|
-
createSearchTables(db);
|
|
6213
|
-
createProjectTables(db);
|
|
6214
|
-
}
|
|
6215
|
-
function stringifyOptionalJson(value) {
|
|
6216
|
-
return value == null ? null : JSON.stringify(value);
|
|
6217
|
-
}
|
|
6218
|
-
function parseOptionalJson(value) {
|
|
6219
|
-
return value == null ? void 0 : JSON.parse(String(value));
|
|
6220
|
-
}
|
|
6221
|
-
function sourcePathFromMeta(meta) {
|
|
6222
|
-
return typeof meta?.sourcePath === "string" ? meta.sourcePath : null;
|
|
6223
|
-
}
|
|
6224
|
-
function sourcePathFromMetaJson(metaJson) {
|
|
6225
|
-
if (!metaJson) return null;
|
|
6226
|
-
const meta = JSON.parse(metaJson);
|
|
6227
|
-
return sourcePathFromMeta(meta);
|
|
5560
|
+
function sourcePathFromMetaJson(metaJson) {
|
|
5561
|
+
if (!metaJson) return null;
|
|
5562
|
+
const meta = JSON.parse(metaJson);
|
|
5563
|
+
return sourcePathFromMeta(meta);
|
|
6228
5564
|
}
|
|
6229
5565
|
function prepareUpsertCachedSession(db) {
|
|
6230
5566
|
return db.prepare(`
|
|
@@ -6488,1126 +5824,1165 @@ function sessionFromRow(row) {
|
|
|
6488
5824
|
}
|
|
6489
5825
|
return session;
|
|
6490
5826
|
}
|
|
6491
|
-
function
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
5827
|
+
function messageFromBackfillRow(row) {
|
|
5828
|
+
const role = row.role === "assistant" || row.role === "tool" ? row.role : "user";
|
|
5829
|
+
return {
|
|
5830
|
+
id: String(row.message_id ?? ""),
|
|
5831
|
+
role,
|
|
5832
|
+
agent: row.agent ?? null,
|
|
5833
|
+
time_created: Number(row.time_created ?? 0),
|
|
5834
|
+
time_completed: row.time_completed == null ? null : Number(row.time_completed),
|
|
5835
|
+
mode: row.mode ?? null,
|
|
5836
|
+
model: row.model ?? null,
|
|
5837
|
+
provider: row.provider ?? null,
|
|
5838
|
+
parts: JSON.parse(String(row.parts_json ?? "[]")),
|
|
5839
|
+
subagent_id: row.subagent_id ?? void 0,
|
|
5840
|
+
nickname: row.nickname ?? void 0
|
|
5841
|
+
};
|
|
6500
5842
|
}
|
|
6501
|
-
function
|
|
6502
|
-
|
|
6503
|
-
|
|
5843
|
+
function messageFromCachedRow(row) {
|
|
5844
|
+
const message = messageFromBackfillRow(row);
|
|
5845
|
+
const tokens = parseOptionalJson(row.tokens_json);
|
|
5846
|
+
if (tokens) {
|
|
5847
|
+
message.tokens = tokens;
|
|
6504
5848
|
}
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
}
|
|
6508
|
-
function inferCacheSchemaVersion(db) {
|
|
6509
|
-
if (tableExists(db, "message_tools")) {
|
|
6510
|
-
return 11;
|
|
6511
|
-
}
|
|
6512
|
-
if (tableExists(db, "session_file_activity_path_fts")) {
|
|
6513
|
-
return 10;
|
|
6514
|
-
}
|
|
6515
|
-
if (tableExists(db, "messages_fts")) {
|
|
6516
|
-
return 9;
|
|
5849
|
+
if (row.cost != null) {
|
|
5850
|
+
message.cost = Number(row.cost);
|
|
6517
5851
|
}
|
|
6518
|
-
if (
|
|
6519
|
-
|
|
5852
|
+
if (row.cost_source) {
|
|
5853
|
+
message.cost_source = row.cost_source;
|
|
6520
5854
|
}
|
|
6521
|
-
|
|
6522
|
-
|
|
5855
|
+
return message;
|
|
5856
|
+
}
|
|
5857
|
+
function appendPlainText(value, chunks) {
|
|
5858
|
+
if (value == null) return;
|
|
5859
|
+
if (typeof value === "string") {
|
|
5860
|
+
const normalized = value.trim();
|
|
5861
|
+
if (normalized) {
|
|
5862
|
+
chunks.push(normalized);
|
|
5863
|
+
}
|
|
5864
|
+
return;
|
|
6523
5865
|
}
|
|
6524
|
-
if (
|
|
6525
|
-
|
|
5866
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
5867
|
+
chunks.push(String(value));
|
|
5868
|
+
return;
|
|
6526
5869
|
}
|
|
6527
|
-
if (
|
|
6528
|
-
|
|
5870
|
+
if (Array.isArray(value)) {
|
|
5871
|
+
for (const item of value) {
|
|
5872
|
+
appendPlainText(item, chunks);
|
|
5873
|
+
}
|
|
5874
|
+
return;
|
|
6529
5875
|
}
|
|
6530
|
-
if (
|
|
6531
|
-
|
|
5876
|
+
if (typeof value === "object") {
|
|
5877
|
+
for (const nested of Object.values(value)) {
|
|
5878
|
+
appendPlainText(nested, chunks);
|
|
5879
|
+
}
|
|
6532
5880
|
}
|
|
6533
|
-
return 0;
|
|
6534
5881
|
}
|
|
6535
|
-
function
|
|
6536
|
-
|
|
6537
|
-
if (userVersion > 0) {
|
|
6538
|
-
return userVersion;
|
|
6539
|
-
}
|
|
6540
|
-
const legacyVersion = readLegacyCacheVersion(db);
|
|
6541
|
-
return Math.max(legacyVersion, inferCacheSchemaVersion(db));
|
|
5882
|
+
function compactRecord(record) {
|
|
5883
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value != null));
|
|
6542
5884
|
}
|
|
6543
|
-
function
|
|
6544
|
-
return
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
"cached_sessions",
|
|
6548
|
-
"sessions",
|
|
6549
|
-
"messages",
|
|
6550
|
-
"message_tools",
|
|
6551
|
-
"session_file_activity",
|
|
6552
|
-
"session_file_activity_path_fts",
|
|
6553
|
-
"session_documents",
|
|
6554
|
-
"session_documents_fts",
|
|
6555
|
-
"project_sessions"
|
|
6556
|
-
].some((table) => tableExists(db, table));
|
|
5885
|
+
function normalizeToolName2(value) {
|
|
5886
|
+
if (typeof value !== "string") return null;
|
|
5887
|
+
const name = value.trim().toLowerCase();
|
|
5888
|
+
return name || null;
|
|
6557
5889
|
}
|
|
6558
|
-
function
|
|
6559
|
-
if (!
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
identity_key,
|
|
6569
|
-
display_name,
|
|
6570
|
-
directory,
|
|
6571
|
-
activity_time
|
|
6572
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6573
|
-
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
6574
|
-
identity_kind = excluded.identity_kind,
|
|
6575
|
-
identity_key = excluded.identity_key,
|
|
6576
|
-
display_name = excluded.display_name,
|
|
6577
|
-
directory = excluded.directory,
|
|
6578
|
-
activity_time = excluded.activity_time
|
|
6579
|
-
`);
|
|
6580
|
-
for (const row of rows) {
|
|
6581
|
-
if (!row.session_json || !row.agent_name || !row.session_id) {
|
|
6582
|
-
continue;
|
|
6583
|
-
}
|
|
6584
|
-
try {
|
|
6585
|
-
const session = JSON.parse(row.session_json);
|
|
6586
|
-
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
6587
|
-
upsert.run(
|
|
6588
|
-
row.agent_name,
|
|
6589
|
-
row.session_id,
|
|
6590
|
-
identity.kind,
|
|
6591
|
-
identity.key,
|
|
6592
|
-
identity.displayName,
|
|
6593
|
-
session.directory,
|
|
6594
|
-
session.time_updated ?? session.time_created
|
|
6595
|
-
);
|
|
6596
|
-
} catch {
|
|
6597
|
-
continue;
|
|
5890
|
+
function toolNamesFromMetadataJson(value) {
|
|
5891
|
+
if (!value) return [];
|
|
5892
|
+
try {
|
|
5893
|
+
const metadata = JSON.parse(String(value));
|
|
5894
|
+
if (!Array.isArray(metadata)) return [];
|
|
5895
|
+
const tools = /* @__PURE__ */ new Set();
|
|
5896
|
+
for (const item of metadata) {
|
|
5897
|
+
if (item == null || typeof item !== "object") continue;
|
|
5898
|
+
const toolName = normalizeToolName2(item.tool);
|
|
5899
|
+
if (toolName) tools.add(toolName);
|
|
6598
5900
|
}
|
|
5901
|
+
return [...tools];
|
|
5902
|
+
} catch {
|
|
5903
|
+
return [];
|
|
6599
5904
|
}
|
|
6600
5905
|
}
|
|
6601
|
-
function
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
UPDATE session_documents
|
|
6608
|
-
SET
|
|
6609
|
-
project_identity_kind = ?,
|
|
6610
|
-
project_identity_key = ?,
|
|
6611
|
-
project_display_name = ?
|
|
6612
|
-
WHERE id = ?
|
|
6613
|
-
`);
|
|
6614
|
-
for (const row of rows) {
|
|
6615
|
-
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6616
|
-
update.run(identity.kind, identity.key, identity.displayName, Number(row.id));
|
|
5906
|
+
function toolNamesFromMessage(message) {
|
|
5907
|
+
const tools = /* @__PURE__ */ new Set();
|
|
5908
|
+
for (const part of message.parts) {
|
|
5909
|
+
if (part.type !== "tool") continue;
|
|
5910
|
+
const toolName = normalizeToolName2(part.tool);
|
|
5911
|
+
if (toolName) tools.add(toolName);
|
|
6617
5912
|
}
|
|
5913
|
+
return [...tools];
|
|
6618
5914
|
}
|
|
6619
|
-
function
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
5915
|
+
function summarizeToolPart(part) {
|
|
5916
|
+
const state = part.state == null ? void 0 : compactRecord({
|
|
5917
|
+
status: part.state.status,
|
|
5918
|
+
error: part.state.error,
|
|
5919
|
+
metadata: part.state.metadata
|
|
5920
|
+
});
|
|
5921
|
+
return compactRecord({
|
|
5922
|
+
type: part.type,
|
|
5923
|
+
tool: part.tool,
|
|
5924
|
+
title: part.title,
|
|
5925
|
+
nickname: part.nickname,
|
|
5926
|
+
callID: part.callID,
|
|
5927
|
+
approval_status: part.approval_status,
|
|
5928
|
+
state
|
|
5929
|
+
});
|
|
6623
5930
|
}
|
|
6624
|
-
function
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
WHERE agent_name = ? AND session_id = ?
|
|
6639
|
-
`) : null;
|
|
6640
|
-
for (const row of rows) {
|
|
6641
|
-
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6642
|
-
update.run(identity.kind, identity.key, identity.displayName, row.agent_name, row.session_id);
|
|
6643
|
-
updateFileActivity?.run(identity.key, row.agent_name, row.session_id);
|
|
6644
|
-
}
|
|
5931
|
+
function buildMessageText(message) {
|
|
5932
|
+
const chunks = [];
|
|
5933
|
+
chunks.push(message.role);
|
|
5934
|
+
appendPlainText(message.agent, chunks);
|
|
5935
|
+
appendPlainText(message.model, chunks);
|
|
5936
|
+
for (const part of message.parts) {
|
|
5937
|
+
appendPlainText(part.type, chunks);
|
|
5938
|
+
appendPlainText(part.title, chunks);
|
|
5939
|
+
appendPlainText(part.nickname, chunks);
|
|
5940
|
+
appendPlainText(part.tool, chunks);
|
|
5941
|
+
appendPlainText(part.text, chunks);
|
|
5942
|
+
appendPlainText(part.input, chunks);
|
|
5943
|
+
appendPlainText(part.output, chunks);
|
|
5944
|
+
appendPlainText(part.state, chunks);
|
|
6645
5945
|
}
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
5946
|
+
return chunks.join("\n");
|
|
5947
|
+
}
|
|
5948
|
+
function normalizeMessages(session) {
|
|
5949
|
+
return session.messages.map((message, index) => {
|
|
5950
|
+
const toolMetadata = message.parts.filter((part) => part.type === "tool").map((part) => summarizeToolPart(part));
|
|
5951
|
+
return {
|
|
5952
|
+
index,
|
|
5953
|
+
id: message.id || `${session.id}:${index}`,
|
|
5954
|
+
role: message.role,
|
|
5955
|
+
timeCreated: message.time_created,
|
|
5956
|
+
timeCompleted: message.time_completed ?? null,
|
|
5957
|
+
agent: message.agent ?? null,
|
|
5958
|
+
mode: message.mode ?? null,
|
|
5959
|
+
model: message.model ?? null,
|
|
5960
|
+
provider: message.provider ?? null,
|
|
5961
|
+
tokensJson: stringifyOptionalJson(message.tokens),
|
|
5962
|
+
cost: message.cost ?? null,
|
|
5963
|
+
costSource: message.cost_source ?? null,
|
|
5964
|
+
partsJson: JSON.stringify(message.parts),
|
|
5965
|
+
subagentId: message.subagent_id ?? null,
|
|
5966
|
+
nickname: message.nickname ?? null,
|
|
5967
|
+
contentText: buildMessageText(message),
|
|
5968
|
+
toolMetadataJson: toolMetadata.length > 0 ? JSON.stringify(toolMetadata) : null,
|
|
5969
|
+
toolNames: toolNamesFromMessage(message)
|
|
5970
|
+
};
|
|
5971
|
+
});
|
|
5972
|
+
}
|
|
5973
|
+
function buildSessionContentFromMessages(title, messages) {
|
|
5974
|
+
const chunks = [];
|
|
5975
|
+
appendPlainText(title, chunks);
|
|
5976
|
+
for (const message of messages) {
|
|
5977
|
+
appendPlainText(message.contentText, chunks);
|
|
6660
5978
|
}
|
|
6661
|
-
|
|
6662
|
-
recreateProjectGroupsView(db);
|
|
5979
|
+
return chunks.join("\n");
|
|
6663
5980
|
}
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
const
|
|
6668
|
-
if (
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
try {
|
|
6677
|
-
const session = JSON.parse(row.session_json);
|
|
6678
|
-
upsertSessionRow(
|
|
6679
|
-
upsertSession,
|
|
6680
|
-
String(row.agent_name),
|
|
6681
|
-
session,
|
|
6682
|
-
row.meta_json ?? null,
|
|
6683
|
-
Number(row.sort_index ?? 0),
|
|
6684
|
-
sourcePathFromMetaJson(row.meta_json)
|
|
6685
|
-
);
|
|
6686
|
-
} catch {
|
|
6687
|
-
continue;
|
|
6688
|
-
}
|
|
6689
|
-
}
|
|
5981
|
+
var CACHE_SCHEMA_VERSION = 13;
|
|
5982
|
+
function withCacheDb(fn) {
|
|
5983
|
+
const cachePath = getCachePath2();
|
|
5984
|
+
const db = openDb(cachePath);
|
|
5985
|
+
if (!db) return null;
|
|
5986
|
+
try {
|
|
5987
|
+
ensureSchema(db, cachePath);
|
|
5988
|
+
return fn(db);
|
|
5989
|
+
} catch {
|
|
5990
|
+
return null;
|
|
5991
|
+
} finally {
|
|
5992
|
+
db.close();
|
|
6690
5993
|
}
|
|
6691
|
-
|
|
6692
|
-
|
|
5994
|
+
}
|
|
5995
|
+
function withCacheDbReadOnly(fn) {
|
|
5996
|
+
const db = openDbReadOnly(getCachePath2());
|
|
5997
|
+
if (!db) return null;
|
|
5998
|
+
try {
|
|
5999
|
+
return fn(db);
|
|
6000
|
+
} catch {
|
|
6001
|
+
return null;
|
|
6002
|
+
} finally {
|
|
6003
|
+
db.close();
|
|
6693
6004
|
}
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
d.title,
|
|
6701
|
-
d.directory,
|
|
6702
|
-
d.project_identity_kind,
|
|
6703
|
-
d.project_identity_key,
|
|
6704
|
-
d.project_display_name,
|
|
6705
|
-
d.time_created,
|
|
6706
|
-
d.time_updated,
|
|
6707
|
-
d.activity_time,
|
|
6708
|
-
d.id
|
|
6709
|
-
FROM session_documents d
|
|
6710
|
-
LEFT JOIN sessions s ON s.agent_name = d.agent_name AND s.session_id = d.session_id
|
|
6711
|
-
WHERE s.session_id IS NULL
|
|
6712
|
-
ORDER BY d.id
|
|
6713
|
-
`
|
|
6714
|
-
).all();
|
|
6715
|
-
for (const row of documentRows) {
|
|
6716
|
-
const directory = String(row.directory ?? "");
|
|
6717
|
-
const identity = row.project_identity_key && row.project_identity_kind && row.project_display_name ? {
|
|
6718
|
-
kind: row.project_identity_kind,
|
|
6719
|
-
key: String(row.project_identity_key),
|
|
6720
|
-
displayName: String(row.project_display_name)
|
|
6721
|
-
} : computeIdentity(directory, realFs);
|
|
6722
|
-
upsertSessionRow(
|
|
6723
|
-
upsertSession,
|
|
6724
|
-
String(row.agent_name),
|
|
6725
|
-
{
|
|
6726
|
-
id: String(row.session_id),
|
|
6727
|
-
slug: String(row.slug),
|
|
6728
|
-
title: String(row.title),
|
|
6729
|
-
directory,
|
|
6730
|
-
project_identity: identity,
|
|
6731
|
-
time_created: Number(row.time_created ?? row.activity_time ?? 0),
|
|
6732
|
-
time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
|
|
6733
|
-
stats: {
|
|
6734
|
-
message_count: 0,
|
|
6735
|
-
total_input_tokens: 0,
|
|
6736
|
-
total_output_tokens: 0,
|
|
6737
|
-
total_cost: 0
|
|
6738
|
-
}
|
|
6739
|
-
},
|
|
6740
|
-
null,
|
|
6741
|
-
Number(row.id ?? 0),
|
|
6742
|
-
null
|
|
6005
|
+
}
|
|
6006
|
+
function createCacheTables(db) {
|
|
6007
|
+
db.exec(`
|
|
6008
|
+
CREATE TABLE IF NOT EXISTS cache_meta (
|
|
6009
|
+
key TEXT PRIMARY KEY,
|
|
6010
|
+
value TEXT NOT NULL
|
|
6743
6011
|
);
|
|
6744
|
-
|
|
6012
|
+
|
|
6013
|
+
CREATE TABLE IF NOT EXISTS agent_cache (
|
|
6014
|
+
agent_name TEXT PRIMARY KEY,
|
|
6015
|
+
timestamp INTEGER NOT NULL
|
|
6016
|
+
);
|
|
6017
|
+
|
|
6018
|
+
CREATE TABLE IF NOT EXISTS cached_sessions (
|
|
6019
|
+
agent_name TEXT NOT NULL,
|
|
6020
|
+
session_id TEXT NOT NULL,
|
|
6021
|
+
session_json TEXT NOT NULL,
|
|
6022
|
+
meta_json TEXT,
|
|
6023
|
+
PRIMARY KEY (agent_name, session_id)
|
|
6024
|
+
);
|
|
6025
|
+
|
|
6026
|
+
CREATE TABLE IF NOT EXISTS cache_initialization (
|
|
6027
|
+
agent_name TEXT PRIMARY KEY,
|
|
6028
|
+
initialized_at INTEGER NOT NULL,
|
|
6029
|
+
index_version TEXT NOT NULL,
|
|
6030
|
+
last_sync_at INTEGER NOT NULL
|
|
6031
|
+
);
|
|
6032
|
+
`);
|
|
6745
6033
|
}
|
|
6746
|
-
function
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6034
|
+
function createSessionTables(db) {
|
|
6035
|
+
db.exec(`
|
|
6036
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
6037
|
+
agent_name TEXT NOT NULL,
|
|
6038
|
+
session_id TEXT NOT NULL,
|
|
6039
|
+
sort_index INTEGER NOT NULL DEFAULT 0,
|
|
6040
|
+
slug TEXT NOT NULL,
|
|
6041
|
+
title TEXT NOT NULL,
|
|
6042
|
+
source_path TEXT,
|
|
6043
|
+
directory TEXT NOT NULL,
|
|
6044
|
+
project_identity_kind TEXT NOT NULL,
|
|
6045
|
+
project_identity_key TEXT NOT NULL,
|
|
6046
|
+
project_display_name TEXT NOT NULL,
|
|
6047
|
+
time_created INTEGER NOT NULL,
|
|
6048
|
+
time_updated INTEGER,
|
|
6049
|
+
activity_time INTEGER NOT NULL,
|
|
6050
|
+
message_count INTEGER NOT NULL,
|
|
6051
|
+
total_input_tokens INTEGER NOT NULL,
|
|
6052
|
+
total_output_tokens INTEGER NOT NULL,
|
|
6053
|
+
total_cache_read_tokens INTEGER,
|
|
6054
|
+
total_cache_create_tokens INTEGER,
|
|
6055
|
+
total_cost REAL NOT NULL,
|
|
6056
|
+
cost_source TEXT,
|
|
6057
|
+
total_tokens INTEGER,
|
|
6058
|
+
model_usage_json TEXT,
|
|
6059
|
+
smart_tags_json TEXT,
|
|
6060
|
+
smart_tags_source_updated_at INTEGER,
|
|
6061
|
+
meta_json TEXT,
|
|
6062
|
+
PRIMARY KEY (agent_name, session_id)
|
|
6063
|
+
);
|
|
6064
|
+
|
|
6065
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_agent_activity
|
|
6066
|
+
ON sessions(agent_name, activity_time);
|
|
6067
|
+
|
|
6068
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
6069
|
+
ON sessions(project_identity_kind, project_identity_key, activity_time);
|
|
6070
|
+
|
|
6071
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
6072
|
+
agent_name TEXT NOT NULL,
|
|
6073
|
+
session_id TEXT NOT NULL,
|
|
6074
|
+
message_index INTEGER NOT NULL,
|
|
6075
|
+
message_id TEXT NOT NULL,
|
|
6076
|
+
role TEXT NOT NULL,
|
|
6077
|
+
time_created INTEGER NOT NULL,
|
|
6078
|
+
time_completed INTEGER,
|
|
6079
|
+
agent TEXT,
|
|
6080
|
+
mode TEXT,
|
|
6081
|
+
model TEXT,
|
|
6082
|
+
provider TEXT,
|
|
6083
|
+
tokens_json TEXT,
|
|
6084
|
+
cost REAL,
|
|
6085
|
+
cost_source TEXT,
|
|
6086
|
+
parts_json TEXT NOT NULL,
|
|
6087
|
+
subagent_id TEXT,
|
|
6088
|
+
nickname TEXT,
|
|
6089
|
+
content_text TEXT NOT NULL,
|
|
6090
|
+
tool_metadata_json TEXT,
|
|
6091
|
+
PRIMARY KEY (agent_name, session_id, message_index),
|
|
6092
|
+
FOREIGN KEY (agent_name, session_id)
|
|
6093
|
+
REFERENCES sessions(agent_name, session_id)
|
|
6094
|
+
ON DELETE CASCADE
|
|
6095
|
+
);
|
|
6096
|
+
|
|
6097
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
6098
|
+
ON messages(agent_name, session_id, message_index);
|
|
6099
|
+
`);
|
|
6100
|
+
createMessageToolTables(db);
|
|
6761
6101
|
}
|
|
6762
|
-
function
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6102
|
+
function createMessageToolTables(db) {
|
|
6103
|
+
db.exec(`
|
|
6104
|
+
CREATE TABLE IF NOT EXISTS message_tools (
|
|
6105
|
+
agent_name TEXT NOT NULL,
|
|
6106
|
+
session_id TEXT NOT NULL,
|
|
6107
|
+
message_index INTEGER NOT NULL,
|
|
6108
|
+
tool_name TEXT NOT NULL,
|
|
6109
|
+
PRIMARY KEY (agent_name, session_id, message_index, tool_name),
|
|
6110
|
+
FOREIGN KEY (agent_name, session_id, message_index)
|
|
6111
|
+
REFERENCES messages(agent_name, session_id, message_index)
|
|
6112
|
+
ON DELETE CASCADE
|
|
6113
|
+
);
|
|
6114
|
+
|
|
6115
|
+
CREATE INDEX IF NOT EXISTS idx_message_tools_filter
|
|
6116
|
+
ON message_tools(tool_name, agent_name, session_id);
|
|
6117
|
+
`);
|
|
6775
6118
|
}
|
|
6776
|
-
function
|
|
6777
|
-
createMessageToolTables(db);
|
|
6119
|
+
function createMessageSearchTables(db) {
|
|
6778
6120
|
if (!tableExists(db, "messages")) {
|
|
6779
|
-
|
|
6780
|
-
}
|
|
6781
|
-
db.exec("DELETE FROM message_tools");
|
|
6782
|
-
const rows = db.prepare(
|
|
6783
|
-
`
|
|
6784
|
-
SELECT agent_name, session_id, message_index, tool_metadata_json
|
|
6785
|
-
FROM messages
|
|
6786
|
-
WHERE tool_metadata_json IS NOT NULL
|
|
6787
|
-
`
|
|
6788
|
-
).all();
|
|
6789
|
-
const insertTool = prepareInsertMessageTool(db);
|
|
6790
|
-
for (const row of rows) {
|
|
6791
|
-
if (!row.agent_name || !row.session_id || row.message_index == null) {
|
|
6792
|
-
continue;
|
|
6793
|
-
}
|
|
6794
|
-
for (const toolName of toolNamesFromMetadataJson(row.tool_metadata_json)) {
|
|
6795
|
-
insertTool.run(row.agent_name, row.session_id, row.message_index, toolName);
|
|
6796
|
-
}
|
|
6121
|
+
createSessionTables(db);
|
|
6797
6122
|
}
|
|
6123
|
+
db.exec(`
|
|
6124
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
6125
|
+
content_text,
|
|
6126
|
+
content='messages',
|
|
6127
|
+
content_rowid='rowid'
|
|
6128
|
+
);
|
|
6129
|
+
`);
|
|
6130
|
+
createMessageSearchTriggers(db);
|
|
6798
6131
|
}
|
|
6799
|
-
function
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
agent,
|
|
6818
|
-
mode,
|
|
6819
|
-
model,
|
|
6820
|
-
provider,
|
|
6821
|
-
parts_json,
|
|
6822
|
-
subagent_id,
|
|
6823
|
-
nickname
|
|
6824
|
-
FROM messages
|
|
6825
|
-
WHERE agent_name = ? AND session_id = ?
|
|
6826
|
-
ORDER BY message_index
|
|
6132
|
+
function createMessageSearchTriggers(db) {
|
|
6133
|
+
db.exec(`
|
|
6134
|
+
CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
|
|
6135
|
+
INSERT INTO messages_fts(rowid, content_text)
|
|
6136
|
+
VALUES (new.rowid, new.content_text);
|
|
6137
|
+
END;
|
|
6138
|
+
|
|
6139
|
+
CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
|
|
6140
|
+
INSERT INTO messages_fts(messages_fts, rowid, content_text)
|
|
6141
|
+
VALUES ('delete', old.rowid, old.content_text);
|
|
6142
|
+
END;
|
|
6143
|
+
|
|
6144
|
+
CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
|
|
6145
|
+
INSERT INTO messages_fts(messages_fts, rowid, content_text)
|
|
6146
|
+
VALUES ('delete', old.rowid, old.content_text);
|
|
6147
|
+
INSERT INTO messages_fts(rowid, content_text)
|
|
6148
|
+
VALUES (new.rowid, new.content_text);
|
|
6149
|
+
END;
|
|
6827
6150
|
`);
|
|
6828
|
-
const deleteActivity = db.prepare(
|
|
6829
|
-
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6830
|
-
);
|
|
6831
|
-
const insertActivity = prepareInsertFileActivity(db);
|
|
6832
|
-
for (const session of sessions) {
|
|
6833
|
-
if (!session.agent_name || !session.session_id || !session.project_identity_key) {
|
|
6834
|
-
continue;
|
|
6835
|
-
}
|
|
6836
|
-
try {
|
|
6837
|
-
const rows = loadMessages.all(session.agent_name, session.session_id);
|
|
6838
|
-
const messages = rows.map((row) => messageFromBackfillRow(row));
|
|
6839
|
-
const activities = extractSessionFileActivity(
|
|
6840
|
-
String(session.agent_name),
|
|
6841
|
-
String(session.session_id),
|
|
6842
|
-
String(session.project_identity_key),
|
|
6843
|
-
messages
|
|
6844
|
-
);
|
|
6845
|
-
deleteActivity.run(session.agent_name, session.session_id);
|
|
6846
|
-
writeFileActivityRows(insertActivity, activities);
|
|
6847
|
-
} catch {
|
|
6848
|
-
continue;
|
|
6849
|
-
}
|
|
6850
|
-
}
|
|
6851
6151
|
}
|
|
6852
|
-
function
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6152
|
+
function dropMessageSearchTriggers(db) {
|
|
6153
|
+
db.exec(`
|
|
6154
|
+
DROP TRIGGER IF EXISTS messages_ai;
|
|
6155
|
+
DROP TRIGGER IF EXISTS messages_ad;
|
|
6156
|
+
DROP TRIGGER IF EXISTS messages_au;
|
|
6157
|
+
`);
|
|
6856
6158
|
}
|
|
6857
|
-
function
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6159
|
+
function createFileActivityTables(db) {
|
|
6160
|
+
db.exec(`
|
|
6161
|
+
CREATE TABLE IF NOT EXISTS session_file_activity (
|
|
6162
|
+
agent_name TEXT NOT NULL,
|
|
6163
|
+
session_id TEXT NOT NULL,
|
|
6164
|
+
project_identity_key TEXT NOT NULL,
|
|
6165
|
+
path TEXT NOT NULL,
|
|
6166
|
+
kind TEXT NOT NULL,
|
|
6167
|
+
count INTEGER NOT NULL,
|
|
6168
|
+
latest_time INTEGER NOT NULL,
|
|
6169
|
+
PRIMARY KEY (agent_name, session_id, project_identity_key, path, kind),
|
|
6170
|
+
FOREIGN KEY (agent_name, session_id)
|
|
6171
|
+
REFERENCES sessions(agent_name, session_id)
|
|
6172
|
+
ON DELETE CASCADE
|
|
6173
|
+
);
|
|
6174
|
+
|
|
6175
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest
|
|
6176
|
+
ON session_file_activity(project_identity_key, latest_time);
|
|
6177
|
+
|
|
6178
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_latest
|
|
6179
|
+
ON session_file_activity(latest_time DESC, count DESC, path);
|
|
6180
|
+
|
|
6181
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_agent_latest
|
|
6182
|
+
ON session_file_activity(agent_name, latest_time DESC, count DESC, path);
|
|
6183
|
+
|
|
6184
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_project_latest_ordered
|
|
6185
|
+
ON session_file_activity(project_identity_key, latest_time DESC, count DESC, path);
|
|
6186
|
+
|
|
6187
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_path
|
|
6188
|
+
ON session_file_activity(path);
|
|
6189
|
+
|
|
6190
|
+
CREATE INDEX IF NOT EXISTS idx_file_activity_kind
|
|
6191
|
+
ON session_file_activity(kind);
|
|
6192
|
+
`);
|
|
6193
|
+
createFileActivityPathSearchTables(db);
|
|
6862
6194
|
}
|
|
6863
|
-
function
|
|
6864
|
-
|
|
6195
|
+
function createFileActivityPathSearchTables(db) {
|
|
6196
|
+
db.exec(`
|
|
6197
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS session_file_activity_path_fts USING fts5(
|
|
6198
|
+
path,
|
|
6199
|
+
content='session_file_activity',
|
|
6200
|
+
content_rowid='rowid',
|
|
6201
|
+
tokenize='trigram'
|
|
6202
|
+
);
|
|
6203
|
+
`);
|
|
6204
|
+
createFileActivityPathSearchTriggers(db);
|
|
6205
|
+
}
|
|
6206
|
+
function createFileActivityPathSearchTriggers(db) {
|
|
6207
|
+
db.exec(`
|
|
6208
|
+
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_ai
|
|
6209
|
+
AFTER INSERT ON session_file_activity BEGIN
|
|
6210
|
+
INSERT INTO session_file_activity_path_fts(rowid, path)
|
|
6211
|
+
VALUES (new.rowid, new.path);
|
|
6212
|
+
END;
|
|
6213
|
+
|
|
6214
|
+
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_ad
|
|
6215
|
+
AFTER DELETE ON session_file_activity BEGIN
|
|
6216
|
+
INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts, rowid, path)
|
|
6217
|
+
VALUES ('delete', old.rowid, old.path);
|
|
6218
|
+
END;
|
|
6219
|
+
|
|
6220
|
+
CREATE TRIGGER IF NOT EXISTS session_file_activity_path_au
|
|
6221
|
+
AFTER UPDATE ON session_file_activity BEGIN
|
|
6222
|
+
INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts, rowid, path)
|
|
6223
|
+
VALUES ('delete', old.rowid, old.path);
|
|
6224
|
+
INSERT INTO session_file_activity_path_fts(rowid, path)
|
|
6225
|
+
VALUES (new.rowid, new.path);
|
|
6226
|
+
END;
|
|
6227
|
+
`);
|
|
6228
|
+
}
|
|
6229
|
+
function rebuildFileActivityPathIndex(db) {
|
|
6230
|
+
if (!tableExists(db, "session_file_activity_path_fts")) {
|
|
6865
6231
|
return;
|
|
6866
6232
|
}
|
|
6867
|
-
db.exec(
|
|
6233
|
+
db.exec(
|
|
6234
|
+
"INSERT INTO session_file_activity_path_fts(session_file_activity_path_fts) VALUES ('rebuild')"
|
|
6235
|
+
);
|
|
6868
6236
|
}
|
|
6869
|
-
function
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6237
|
+
function createSearchTables(db) {
|
|
6238
|
+
db.exec(`
|
|
6239
|
+
CREATE TABLE IF NOT EXISTS session_documents (
|
|
6240
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6241
|
+
agent_name TEXT NOT NULL,
|
|
6242
|
+
session_id TEXT NOT NULL,
|
|
6243
|
+
slug TEXT NOT NULL,
|
|
6244
|
+
title TEXT NOT NULL,
|
|
6245
|
+
directory TEXT NOT NULL,
|
|
6246
|
+
time_created INTEGER NOT NULL,
|
|
6247
|
+
time_updated INTEGER,
|
|
6248
|
+
activity_time INTEGER NOT NULL,
|
|
6249
|
+
content_text TEXT NOT NULL,
|
|
6250
|
+
content_hash TEXT NOT NULL,
|
|
6251
|
+
indexed_at INTEGER NOT NULL,
|
|
6252
|
+
UNIQUE(agent_name, session_id)
|
|
6253
|
+
);
|
|
6254
|
+
|
|
6255
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS session_documents_fts USING fts5(
|
|
6256
|
+
title,
|
|
6257
|
+
content_text,
|
|
6258
|
+
content='session_documents',
|
|
6259
|
+
content_rowid='id'
|
|
6260
|
+
);
|
|
6261
|
+
`);
|
|
6262
|
+
createSearchTriggers(db);
|
|
6263
|
+
}
|
|
6264
|
+
function createSearchTriggers(db) {
|
|
6265
|
+
db.exec(`
|
|
6266
|
+
CREATE TRIGGER IF NOT EXISTS session_documents_ai AFTER INSERT ON session_documents BEGIN
|
|
6267
|
+
INSERT INTO session_documents_fts(rowid, title, content_text)
|
|
6268
|
+
VALUES (new.id, new.title, new.content_text);
|
|
6269
|
+
END;
|
|
6270
|
+
|
|
6271
|
+
CREATE TRIGGER IF NOT EXISTS session_documents_ad AFTER DELETE ON session_documents BEGIN
|
|
6272
|
+
INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
|
|
6273
|
+
VALUES ('delete', old.id, old.title, old.content_text);
|
|
6274
|
+
END;
|
|
6275
|
+
|
|
6276
|
+
CREATE TRIGGER IF NOT EXISTS session_documents_au AFTER UPDATE ON session_documents BEGIN
|
|
6277
|
+
INSERT INTO session_documents_fts(session_documents_fts, rowid, title, content_text)
|
|
6278
|
+
VALUES ('delete', old.id, old.title, old.content_text);
|
|
6279
|
+
INSERT INTO session_documents_fts(rowid, title, content_text)
|
|
6280
|
+
VALUES (new.id, new.title, new.content_text);
|
|
6281
|
+
END;
|
|
6282
|
+
`);
|
|
6283
|
+
}
|
|
6284
|
+
function dropSearchTriggers(db) {
|
|
6285
|
+
db.exec(`
|
|
6286
|
+
DROP TRIGGER IF EXISTS session_documents_ai;
|
|
6287
|
+
DROP TRIGGER IF EXISTS session_documents_ad;
|
|
6288
|
+
DROP TRIGGER IF EXISTS session_documents_au;
|
|
6289
|
+
`);
|
|
6875
6290
|
}
|
|
6876
|
-
function
|
|
6877
|
-
if (!tableExists(db, "
|
|
6878
|
-
|
|
6291
|
+
function ensureProjectColumns(db) {
|
|
6292
|
+
if (!tableExists(db, "session_documents")) {
|
|
6293
|
+
return;
|
|
6879
6294
|
}
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
rebuildMessageSearchIndex(db);
|
|
6295
|
+
if (!columnExists(db, "session_documents", "project_identity_kind")) {
|
|
6296
|
+
db.exec(
|
|
6297
|
+
"ALTER TABLE session_documents ADD COLUMN project_identity_kind TEXT NOT NULL DEFAULT 'path'"
|
|
6298
|
+
);
|
|
6885
6299
|
}
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
if (ftsIntegrityCheckedPath === cachePath) {
|
|
6891
|
-
return;
|
|
6300
|
+
if (!columnExists(db, "session_documents", "project_identity_key")) {
|
|
6301
|
+
db.exec(
|
|
6302
|
+
"ALTER TABLE session_documents ADD COLUMN project_identity_key TEXT NOT NULL DEFAULT ''"
|
|
6303
|
+
);
|
|
6892
6304
|
}
|
|
6893
|
-
|
|
6305
|
+
if (!columnExists(db, "session_documents", "project_display_name")) {
|
|
6894
6306
|
db.exec(
|
|
6895
|
-
"
|
|
6307
|
+
"ALTER TABLE session_documents ADD COLUMN project_display_name TEXT NOT NULL DEFAULT ''"
|
|
6896
6308
|
);
|
|
6897
|
-
db.exec("INSERT INTO messages_fts(messages_fts, rank) VALUES ('integrity-check', 1)");
|
|
6898
|
-
ftsIntegrityCheckedPath = cachePath;
|
|
6899
|
-
} catch {
|
|
6900
|
-
rebuildSearchIndex(db);
|
|
6901
|
-
rebuildMessageSearchIndex(db);
|
|
6902
|
-
ftsIntegrityCheckedPath = cachePath;
|
|
6903
6309
|
}
|
|
6904
6310
|
}
|
|
6905
|
-
function
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6311
|
+
function createProjectTables(db) {
|
|
6312
|
+
ensureProjectColumns(db);
|
|
6313
|
+
db.exec(`
|
|
6314
|
+
CREATE TABLE IF NOT EXISTS project_sessions (
|
|
6315
|
+
agent_name TEXT NOT NULL,
|
|
6316
|
+
session_id TEXT NOT NULL,
|
|
6317
|
+
identity_kind TEXT NOT NULL,
|
|
6318
|
+
identity_key TEXT NOT NULL,
|
|
6319
|
+
display_name TEXT NOT NULL,
|
|
6320
|
+
directory TEXT NOT NULL,
|
|
6321
|
+
activity_time INTEGER NOT NULL,
|
|
6322
|
+
PRIMARY KEY (agent_name, session_id)
|
|
6323
|
+
);
|
|
6324
|
+
|
|
6325
|
+
CREATE INDEX IF NOT EXISTS idx_project_sessions_identity
|
|
6326
|
+
ON project_sessions(identity_kind, identity_key);
|
|
6327
|
+
`);
|
|
6328
|
+
createProjectGroupsView(db);
|
|
6915
6329
|
}
|
|
6916
|
-
function
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6330
|
+
function createProjectGroupsView(db) {
|
|
6331
|
+
if (!tableExists(db, "sessions")) {
|
|
6332
|
+
db.exec(`
|
|
6333
|
+
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
6334
|
+
SELECT
|
|
6335
|
+
identity_kind,
|
|
6336
|
+
identity_key,
|
|
6337
|
+
MIN(display_name) AS display_name,
|
|
6338
|
+
GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
|
|
6339
|
+
COUNT(*) AS session_count,
|
|
6340
|
+
MAX(activity_time) AS last_activity
|
|
6341
|
+
FROM project_sessions
|
|
6342
|
+
GROUP BY identity_kind, identity_key;
|
|
6343
|
+
`);
|
|
6921
6344
|
return;
|
|
6922
6345
|
}
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
"session_file_activity",
|
|
6936
|
-
"session_documents",
|
|
6937
|
-
"project_sessions"
|
|
6938
|
-
],
|
|
6939
|
-
migrations: [
|
|
6940
|
-
{ version: 3, migrate: createCacheTables },
|
|
6941
|
-
{ version: 4, migrate: createSearchTables },
|
|
6942
|
-
{ version: 5, migrate: migrateProjectIdentity },
|
|
6943
|
-
{
|
|
6944
|
-
version: 6,
|
|
6945
|
-
destructive: true,
|
|
6946
|
-
migrate(db2) {
|
|
6947
|
-
createLatestCacheSchema(db2);
|
|
6948
|
-
recreateSearchIndexSchema(db2);
|
|
6949
|
-
invalidateSearchContentHashes(db2);
|
|
6950
|
-
}
|
|
6951
|
-
},
|
|
6952
|
-
{ version: 7, migrate: backfillStructuredSessions },
|
|
6953
|
-
{ version: 8, migrate: backfillFileActivity },
|
|
6954
|
-
{
|
|
6955
|
-
version: 9,
|
|
6956
|
-
migrate(db2) {
|
|
6957
|
-
createMessageSearchTables(db2);
|
|
6958
|
-
rebuildMessageSearchIndex(db2);
|
|
6959
|
-
}
|
|
6960
|
-
},
|
|
6961
|
-
{
|
|
6962
|
-
version: 10,
|
|
6963
|
-
migrate(db2) {
|
|
6964
|
-
createFileActivityPathSearchTables(db2);
|
|
6965
|
-
rebuildFileActivityPathIndex(db2);
|
|
6966
|
-
}
|
|
6967
|
-
},
|
|
6968
|
-
{
|
|
6969
|
-
version: 11,
|
|
6970
|
-
migrate(db2) {
|
|
6971
|
-
backfillMessageTools(db2);
|
|
6972
|
-
}
|
|
6973
|
-
},
|
|
6974
|
-
{
|
|
6975
|
-
version: 12,
|
|
6976
|
-
migrate(db2) {
|
|
6977
|
-
refreshProjectIdentities(db2);
|
|
6978
|
-
}
|
|
6979
|
-
},
|
|
6980
|
-
{ version: 13, migrate: createCacheTables }
|
|
6981
|
-
]
|
|
6982
|
-
});
|
|
6983
|
-
createLatestCacheSchema(db);
|
|
6984
|
-
if (getUserVersion(db) <= CACHE_SCHEMA_VERSION) {
|
|
6985
|
-
setCacheSchemaVersion(db);
|
|
6986
|
-
}
|
|
6346
|
+
db.exec(`
|
|
6347
|
+
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
6348
|
+
SELECT
|
|
6349
|
+
project_identity_kind AS identity_kind,
|
|
6350
|
+
project_identity_key AS identity_key,
|
|
6351
|
+
MIN(project_display_name) AS display_name,
|
|
6352
|
+
GROUP_CONCAT(DISTINCT agent_name) AS sources_csv,
|
|
6353
|
+
COUNT(*) AS session_count,
|
|
6354
|
+
MAX(activity_time) AS last_activity
|
|
6355
|
+
FROM sessions
|
|
6356
|
+
GROUP BY project_identity_kind, project_identity_key;
|
|
6357
|
+
`);
|
|
6987
6358
|
}
|
|
6988
|
-
function
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
session.title,
|
|
6992
|
-
session.directory,
|
|
6993
|
-
session.time_created,
|
|
6994
|
-
session.time_updated ?? session.time_created,
|
|
6995
|
-
session.stats.message_count,
|
|
6996
|
-
session.stats.total_input_tokens,
|
|
6997
|
-
session.stats.total_output_tokens,
|
|
6998
|
-
session.stats.total_cache_read_tokens ?? 0,
|
|
6999
|
-
session.stats.total_cache_create_tokens ?? 0,
|
|
7000
|
-
session.stats.total_cost,
|
|
7001
|
-
session.stats.cost_source ?? "",
|
|
7002
|
-
session.stats.total_tokens ?? 0
|
|
7003
|
-
]);
|
|
6359
|
+
function recreateProjectGroupsView(db) {
|
|
6360
|
+
db.exec("DROP VIEW IF EXISTS project_groups_v");
|
|
6361
|
+
createProjectGroupsView(db);
|
|
7004
6362
|
}
|
|
7005
|
-
function
|
|
7006
|
-
|
|
6363
|
+
function createLatestCacheSchema(db) {
|
|
6364
|
+
createCacheTables(db);
|
|
6365
|
+
createSessionTables(db);
|
|
6366
|
+
createMessageSearchTables(db);
|
|
6367
|
+
createFileActivityTables(db);
|
|
6368
|
+
createSearchTables(db);
|
|
6369
|
+
createProjectTables(db);
|
|
7007
6370
|
}
|
|
7008
|
-
function
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
}
|
|
7018
|
-
if (/\s/.test(char) && !inQuote) {
|
|
7019
|
-
if (token) {
|
|
7020
|
-
tokens.push(token);
|
|
7021
|
-
token = "";
|
|
7022
|
-
}
|
|
7023
|
-
continue;
|
|
7024
|
-
}
|
|
7025
|
-
token += char;
|
|
7026
|
-
}
|
|
7027
|
-
if (token) {
|
|
7028
|
-
tokens.push(token);
|
|
7029
|
-
}
|
|
7030
|
-
return tokens;
|
|
6371
|
+
function recreateSearchIndexSchema(db) {
|
|
6372
|
+
db.exec(`
|
|
6373
|
+
DROP TRIGGER IF EXISTS session_documents_ai;
|
|
6374
|
+
DROP TRIGGER IF EXISTS session_documents_ad;
|
|
6375
|
+
DROP TRIGGER IF EXISTS session_documents_au;
|
|
6376
|
+
DROP TABLE IF EXISTS session_documents_fts;
|
|
6377
|
+
`);
|
|
6378
|
+
createSearchTables(db);
|
|
6379
|
+
rebuildSearchIndex(db);
|
|
7031
6380
|
}
|
|
7032
|
-
function
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
return trimmed.slice(1, -1).trim();
|
|
6381
|
+
function readLegacyCacheVersion(db) {
|
|
6382
|
+
if (!tableExists(db, "cache_meta") || !columnExists(db, "cache_meta", "key") || !columnExists(db, "cache_meta", "value")) {
|
|
6383
|
+
return 0;
|
|
7036
6384
|
}
|
|
7037
|
-
|
|
6385
|
+
const versionRow = db.prepare("SELECT value FROM cache_meta WHERE key = 'version'").get();
|
|
6386
|
+
return Number(versionRow?.value ?? 0);
|
|
7038
6387
|
}
|
|
7039
|
-
function
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
if (range) {
|
|
7043
|
-
filters.costMin = Number(range[1]);
|
|
7044
|
-
filters.costMax = Number(range[2]);
|
|
7045
|
-
return;
|
|
6388
|
+
function inferCacheSchemaVersion(db) {
|
|
6389
|
+
if (tableExists(db, "message_tools")) {
|
|
6390
|
+
return 11;
|
|
7046
6391
|
}
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
filters.costMaxExclusive = comparison[1] === "<";
|
|
7056
|
-
}
|
|
7057
|
-
return;
|
|
6392
|
+
if (tableExists(db, "session_file_activity_path_fts")) {
|
|
6393
|
+
return 10;
|
|
6394
|
+
}
|
|
6395
|
+
if (tableExists(db, "messages_fts")) {
|
|
6396
|
+
return 9;
|
|
6397
|
+
}
|
|
6398
|
+
if (tableExists(db, "session_file_activity")) {
|
|
6399
|
+
return 8;
|
|
7058
6400
|
}
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
6401
|
+
if (tableExists(db, "sessions") || tableExists(db, "messages")) {
|
|
6402
|
+
return 7;
|
|
6403
|
+
}
|
|
6404
|
+
if (tableExists(db, "project_sessions") || columnExists(db, "session_documents", "project_identity_key")) {
|
|
6405
|
+
return 5;
|
|
6406
|
+
}
|
|
6407
|
+
if (tableExists(db, "session_documents")) {
|
|
6408
|
+
return 4;
|
|
6409
|
+
}
|
|
6410
|
+
if (tableExists(db, "cached_sessions") || tableExists(db, "agent_cache")) {
|
|
6411
|
+
return 3;
|
|
7063
6412
|
}
|
|
6413
|
+
return 0;
|
|
7064
6414
|
}
|
|
7065
|
-
function
|
|
7066
|
-
|
|
7067
|
-
|
|
6415
|
+
function getCurrentCacheSchemaVersion(db) {
|
|
6416
|
+
const userVersion = getUserVersion(db);
|
|
6417
|
+
if (userVersion > 0) {
|
|
6418
|
+
return userVersion;
|
|
6419
|
+
}
|
|
6420
|
+
const legacyVersion = readLegacyCacheVersion(db);
|
|
6421
|
+
return Math.max(legacyVersion, inferCacheSchemaVersion(db));
|
|
7068
6422
|
}
|
|
7069
|
-
function
|
|
7070
|
-
return
|
|
6423
|
+
function hasAnyCacheSchema(db) {
|
|
6424
|
+
return [
|
|
6425
|
+
"cache_meta",
|
|
6426
|
+
"agent_cache",
|
|
6427
|
+
"cached_sessions",
|
|
6428
|
+
"sessions",
|
|
6429
|
+
"messages",
|
|
6430
|
+
"message_tools",
|
|
6431
|
+
"session_file_activity",
|
|
6432
|
+
"session_file_activity_path_fts",
|
|
6433
|
+
"session_documents",
|
|
6434
|
+
"session_documents_fts",
|
|
6435
|
+
"project_sessions"
|
|
6436
|
+
].some((table) => tableExists(db, table));
|
|
7071
6437
|
}
|
|
7072
|
-
function
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
6438
|
+
function backfillProjectSessions(db) {
|
|
6439
|
+
if (!tableExists(db, "cached_sessions") || !tableExists(db, "project_sessions")) {
|
|
6440
|
+
return;
|
|
6441
|
+
}
|
|
6442
|
+
const rows = db.prepare("SELECT agent_name, session_id, session_json FROM cached_sessions").all();
|
|
6443
|
+
const upsert = db.prepare(`
|
|
6444
|
+
INSERT INTO project_sessions(
|
|
6445
|
+
agent_name,
|
|
6446
|
+
session_id,
|
|
6447
|
+
identity_kind,
|
|
6448
|
+
identity_key,
|
|
6449
|
+
display_name,
|
|
6450
|
+
directory,
|
|
6451
|
+
activity_time
|
|
6452
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6453
|
+
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
6454
|
+
identity_kind = excluded.identity_kind,
|
|
6455
|
+
identity_key = excluded.identity_key,
|
|
6456
|
+
display_name = excluded.display_name,
|
|
6457
|
+
directory = excluded.directory,
|
|
6458
|
+
activity_time = excluded.activity_time
|
|
6459
|
+
`);
|
|
6460
|
+
for (const row of rows) {
|
|
6461
|
+
if (!row.session_json || !row.agent_name || !row.session_id) {
|
|
7080
6462
|
continue;
|
|
7081
6463
|
}
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
consumed = false;
|
|
7097
|
-
}
|
|
7098
|
-
} else if (key === "tag" || key === "signal") {
|
|
7099
|
-
const tag = value.toLowerCase();
|
|
7100
|
-
if (isSmartTag(tag)) {
|
|
7101
|
-
filters.tags = appendUnique(filters.tags, tag);
|
|
7102
|
-
} else {
|
|
7103
|
-
consumed = false;
|
|
7104
|
-
}
|
|
7105
|
-
} else if (key === "cost") {
|
|
7106
|
-
parseCostQualifier(value, filters);
|
|
7107
|
-
} else {
|
|
7108
|
-
consumed = false;
|
|
7109
|
-
}
|
|
7110
|
-
if (consumed) {
|
|
7111
|
-
hasQualifiers = true;
|
|
7112
|
-
} else {
|
|
7113
|
-
textTokens.push(token);
|
|
6464
|
+
try {
|
|
6465
|
+
const session = JSON.parse(row.session_json);
|
|
6466
|
+
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
6467
|
+
upsert.run(
|
|
6468
|
+
row.agent_name,
|
|
6469
|
+
row.session_id,
|
|
6470
|
+
identity.kind,
|
|
6471
|
+
identity.key,
|
|
6472
|
+
identity.displayName,
|
|
6473
|
+
session.directory,
|
|
6474
|
+
session.time_updated ?? session.time_created
|
|
6475
|
+
);
|
|
6476
|
+
} catch {
|
|
6477
|
+
continue;
|
|
7114
6478
|
}
|
|
7115
6479
|
}
|
|
7116
|
-
return {
|
|
7117
|
-
text: textTokens.join(" ").trim(),
|
|
7118
|
-
filters,
|
|
7119
|
-
hasQualifiers
|
|
7120
|
-
};
|
|
7121
|
-
}
|
|
7122
|
-
function toFtsQuery(input) {
|
|
7123
|
-
const tokens = splitSearchTokens(input);
|
|
7124
|
-
const mapped = tokens.map((token) => {
|
|
7125
|
-
if (/^OR$/i.test(token)) {
|
|
7126
|
-
return "OR";
|
|
7127
|
-
}
|
|
7128
|
-
if (token.startsWith('"') && token.endsWith('"')) {
|
|
7129
|
-
return `"${escapeFtsTerm(token.slice(1, -1))}"`;
|
|
7130
|
-
}
|
|
7131
|
-
return `"${escapeFtsTerm(token)}"`;
|
|
7132
|
-
}).filter(
|
|
7133
|
-
(token, index, values) => token !== "OR" || index > 0 && index < values.length - 1 && values[index - 1] !== "OR" && values[index + 1] !== "OR"
|
|
7134
|
-
);
|
|
7135
|
-
return mapped.join(" ");
|
|
7136
6480
|
}
|
|
7137
|
-
function
|
|
7138
|
-
if (
|
|
7139
|
-
if (typeof value === "string") {
|
|
7140
|
-
const normalized = value.trim();
|
|
7141
|
-
if (normalized) {
|
|
7142
|
-
chunks.push(normalized);
|
|
7143
|
-
}
|
|
6481
|
+
function backfillSessionDocumentProjects(db) {
|
|
6482
|
+
if (!tableExists(db, "session_documents") || !columnExists(db, "session_documents", "project_identity_key")) {
|
|
7144
6483
|
return;
|
|
7145
6484
|
}
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
6485
|
+
const rows = db.prepare("SELECT id, directory FROM session_documents").all();
|
|
6486
|
+
const update = db.prepare(`
|
|
6487
|
+
UPDATE session_documents
|
|
6488
|
+
SET
|
|
6489
|
+
project_identity_kind = ?,
|
|
6490
|
+
project_identity_key = ?,
|
|
6491
|
+
project_display_name = ?
|
|
6492
|
+
WHERE id = ?
|
|
6493
|
+
`);
|
|
6494
|
+
for (const row of rows) {
|
|
6495
|
+
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6496
|
+
update.run(identity.kind, identity.key, identity.displayName, Number(row.id));
|
|
7149
6497
|
}
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
6498
|
+
}
|
|
6499
|
+
function migrateProjectIdentity(db) {
|
|
6500
|
+
createProjectTables(db);
|
|
6501
|
+
backfillProjectSessions(db);
|
|
6502
|
+
backfillSessionDocumentProjects(db);
|
|
6503
|
+
}
|
|
6504
|
+
function refreshProjectIdentities(db) {
|
|
6505
|
+
if (tableExists(db, "sessions") && columnExists(db, "sessions", "project_identity_key") && columnExists(db, "sessions", "directory")) {
|
|
6506
|
+
const rows = db.prepare("SELECT agent_name, session_id, directory FROM sessions").all();
|
|
6507
|
+
const update = db.prepare(`
|
|
6508
|
+
UPDATE sessions
|
|
6509
|
+
SET
|
|
6510
|
+
project_identity_kind = ?,
|
|
6511
|
+
project_identity_key = ?,
|
|
6512
|
+
project_display_name = ?
|
|
6513
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6514
|
+
`);
|
|
6515
|
+
const updateFileActivity = tableExists(db, "session_file_activity") && columnExists(db, "session_file_activity", "project_identity_key") ? db.prepare(`
|
|
6516
|
+
UPDATE session_file_activity
|
|
6517
|
+
SET project_identity_key = ?
|
|
6518
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6519
|
+
`) : null;
|
|
6520
|
+
for (const row of rows) {
|
|
6521
|
+
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6522
|
+
update.run(identity.kind, identity.key, identity.displayName, row.agent_name, row.session_id);
|
|
6523
|
+
updateFileActivity?.run(identity.key, row.agent_name, row.session_id);
|
|
7153
6524
|
}
|
|
7154
|
-
return;
|
|
7155
6525
|
}
|
|
7156
|
-
if (
|
|
7157
|
-
|
|
7158
|
-
|
|
6526
|
+
if (tableExists(db, "project_sessions") && columnExists(db, "project_sessions", "identity_key") && columnExists(db, "project_sessions", "directory")) {
|
|
6527
|
+
const rows = db.prepare("SELECT agent_name, session_id, directory FROM project_sessions").all();
|
|
6528
|
+
const update = db.prepare(`
|
|
6529
|
+
UPDATE project_sessions
|
|
6530
|
+
SET
|
|
6531
|
+
identity_kind = ?,
|
|
6532
|
+
identity_key = ?,
|
|
6533
|
+
display_name = ?
|
|
6534
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6535
|
+
`);
|
|
6536
|
+
for (const row of rows) {
|
|
6537
|
+
const identity = computeIdentity(String(row.directory ?? ""), realFs);
|
|
6538
|
+
update.run(identity.kind, identity.key, identity.displayName, row.agent_name, row.session_id);
|
|
7159
6539
|
}
|
|
7160
6540
|
}
|
|
6541
|
+
backfillSessionDocumentProjects(db);
|
|
6542
|
+
recreateProjectGroupsView(db);
|
|
7161
6543
|
}
|
|
7162
|
-
function
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
if (
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
6544
|
+
function backfillStructuredSessions(db) {
|
|
6545
|
+
createSessionTables(db);
|
|
6546
|
+
recreateProjectGroupsView(db);
|
|
6547
|
+
const upsertSession = prepareUpsertSession(db);
|
|
6548
|
+
if (tableExists(db, "cached_sessions")) {
|
|
6549
|
+
const rows = db.prepare(
|
|
6550
|
+
"SELECT agent_name, session_id, session_json, meta_json, rowid AS sort_index FROM cached_sessions ORDER BY agent_name, rowid"
|
|
6551
|
+
).all();
|
|
6552
|
+
for (const row of rows) {
|
|
6553
|
+
if (!row.agent_name || !row.session_json) {
|
|
6554
|
+
continue;
|
|
6555
|
+
}
|
|
6556
|
+
try {
|
|
6557
|
+
const session = JSON.parse(row.session_json);
|
|
6558
|
+
upsertSessionRow(
|
|
6559
|
+
upsertSession,
|
|
6560
|
+
String(row.agent_name),
|
|
6561
|
+
session,
|
|
6562
|
+
row.meta_json ?? null,
|
|
6563
|
+
Number(row.sort_index ?? 0),
|
|
6564
|
+
sourcePathFromMetaJson(row.meta_json)
|
|
6565
|
+
);
|
|
6566
|
+
} catch {
|
|
6567
|
+
continue;
|
|
6568
|
+
}
|
|
7180
6569
|
}
|
|
7181
|
-
return [...tools];
|
|
7182
|
-
} catch {
|
|
7183
|
-
return [];
|
|
7184
|
-
}
|
|
7185
|
-
}
|
|
7186
|
-
function toolNamesFromMessage(message) {
|
|
7187
|
-
const tools = /* @__PURE__ */ new Set();
|
|
7188
|
-
for (const part of message.parts) {
|
|
7189
|
-
if (part.type !== "tool") continue;
|
|
7190
|
-
const toolName = normalizeToolName2(part.tool);
|
|
7191
|
-
if (toolName) tools.add(toolName);
|
|
7192
6570
|
}
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
function summarizeToolPart(part) {
|
|
7196
|
-
const state = part.state == null ? void 0 : compactRecord({
|
|
7197
|
-
status: part.state.status,
|
|
7198
|
-
error: part.state.error,
|
|
7199
|
-
metadata: part.state.metadata
|
|
7200
|
-
});
|
|
7201
|
-
return compactRecord({
|
|
7202
|
-
type: part.type,
|
|
7203
|
-
tool: part.tool,
|
|
7204
|
-
title: part.title,
|
|
7205
|
-
nickname: part.nickname,
|
|
7206
|
-
callID: part.callID,
|
|
7207
|
-
approval_status: part.approval_status,
|
|
7208
|
-
state
|
|
7209
|
-
});
|
|
7210
|
-
}
|
|
7211
|
-
function buildMessageText(message) {
|
|
7212
|
-
const chunks = [];
|
|
7213
|
-
chunks.push(message.role);
|
|
7214
|
-
appendPlainText(message.agent, chunks);
|
|
7215
|
-
appendPlainText(message.model, chunks);
|
|
7216
|
-
for (const part of message.parts) {
|
|
7217
|
-
appendPlainText(part.type, chunks);
|
|
7218
|
-
appendPlainText(part.title, chunks);
|
|
7219
|
-
appendPlainText(part.nickname, chunks);
|
|
7220
|
-
appendPlainText(part.tool, chunks);
|
|
7221
|
-
appendPlainText(part.text, chunks);
|
|
7222
|
-
appendPlainText(part.input, chunks);
|
|
7223
|
-
appendPlainText(part.output, chunks);
|
|
7224
|
-
appendPlainText(part.state, chunks);
|
|
6571
|
+
if (!tableExists(db, "session_documents")) {
|
|
6572
|
+
return;
|
|
7225
6573
|
}
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
6574
|
+
const documentRows = db.prepare(
|
|
6575
|
+
`
|
|
6576
|
+
SELECT
|
|
6577
|
+
d.agent_name,
|
|
6578
|
+
d.session_id,
|
|
6579
|
+
d.slug,
|
|
6580
|
+
d.title,
|
|
6581
|
+
d.directory,
|
|
6582
|
+
d.project_identity_kind,
|
|
6583
|
+
d.project_identity_key,
|
|
6584
|
+
d.project_display_name,
|
|
6585
|
+
d.time_created,
|
|
6586
|
+
d.time_updated,
|
|
6587
|
+
d.activity_time,
|
|
6588
|
+
d.id
|
|
6589
|
+
FROM session_documents d
|
|
6590
|
+
LEFT JOIN sessions s ON s.agent_name = d.agent_name AND s.session_id = d.session_id
|
|
6591
|
+
WHERE s.session_id IS NULL
|
|
6592
|
+
ORDER BY d.id
|
|
6593
|
+
`
|
|
6594
|
+
).all();
|
|
6595
|
+
for (const row of documentRows) {
|
|
6596
|
+
const directory = String(row.directory ?? "");
|
|
6597
|
+
const identity = row.project_identity_key && row.project_identity_kind && row.project_display_name ? {
|
|
6598
|
+
kind: row.project_identity_kind,
|
|
6599
|
+
key: String(row.project_identity_key),
|
|
6600
|
+
displayName: String(row.project_display_name)
|
|
6601
|
+
} : computeIdentity(directory, realFs);
|
|
6602
|
+
upsertSessionRow(
|
|
6603
|
+
upsertSession,
|
|
6604
|
+
String(row.agent_name),
|
|
6605
|
+
{
|
|
6606
|
+
id: String(row.session_id),
|
|
6607
|
+
slug: String(row.slug),
|
|
6608
|
+
title: String(row.title),
|
|
6609
|
+
directory,
|
|
6610
|
+
project_identity: identity,
|
|
6611
|
+
time_created: Number(row.time_created ?? row.activity_time ?? 0),
|
|
6612
|
+
time_updated: row.time_updated == null ? void 0 : Number(row.time_updated),
|
|
6613
|
+
stats: {
|
|
6614
|
+
message_count: 0,
|
|
6615
|
+
total_input_tokens: 0,
|
|
6616
|
+
total_output_tokens: 0,
|
|
6617
|
+
total_cost: 0
|
|
6618
|
+
}
|
|
6619
|
+
},
|
|
6620
|
+
null,
|
|
6621
|
+
Number(row.id ?? 0),
|
|
6622
|
+
null
|
|
6623
|
+
);
|
|
7258
6624
|
}
|
|
7259
|
-
return chunks.join("\n");
|
|
7260
6625
|
}
|
|
7261
|
-
function
|
|
7262
|
-
|
|
7263
|
-
if (!
|
|
6626
|
+
function backfillMessageTools(db) {
|
|
6627
|
+
createMessageToolTables(db);
|
|
6628
|
+
if (!tableExists(db, "messages")) {
|
|
7264
6629
|
return;
|
|
7265
6630
|
}
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
6631
|
+
db.exec("DELETE FROM message_tools");
|
|
6632
|
+
const rows = db.prepare(
|
|
6633
|
+
`
|
|
6634
|
+
SELECT agent_name, session_id, message_index, tool_metadata_json
|
|
6635
|
+
FROM messages
|
|
6636
|
+
WHERE tool_metadata_json IS NOT NULL
|
|
6637
|
+
`
|
|
6638
|
+
).all();
|
|
6639
|
+
const insertTool = prepareInsertMessageTool(db);
|
|
6640
|
+
for (const row of rows) {
|
|
6641
|
+
if (!row.agent_name || !row.session_id || row.message_index == null) {
|
|
6642
|
+
continue;
|
|
6643
|
+
}
|
|
6644
|
+
for (const toolName of toolNamesFromMetadataJson(row.tool_metadata_json)) {
|
|
6645
|
+
insertTool.run(row.agent_name, row.session_id, row.message_index, toolName);
|
|
6646
|
+
}
|
|
7269
6647
|
}
|
|
7270
6648
|
}
|
|
7271
|
-
function
|
|
7272
|
-
|
|
7273
|
-
|
|
6649
|
+
function backfillFileActivity(db) {
|
|
6650
|
+
createFileActivityTables(db);
|
|
6651
|
+
if (!tableExists(db, "sessions") || !tableExists(db, "messages")) {
|
|
6652
|
+
return;
|
|
7274
6653
|
}
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
}
|
|
7281
|
-
const rows = db.prepare(
|
|
6654
|
+
const sessions = db.prepare(
|
|
6655
|
+
`
|
|
6656
|
+
SELECT agent_name, session_id, project_identity_key
|
|
6657
|
+
FROM sessions
|
|
6658
|
+
ORDER BY agent_name, session_id
|
|
7282
6659
|
`
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
WHERE agent_name = ?
|
|
7309
|
-
ORDER BY sort_index, activity_time DESC
|
|
7310
|
-
`
|
|
7311
|
-
).all(agentName);
|
|
7312
|
-
const sessions = [];
|
|
7313
|
-
const meta = {};
|
|
7314
|
-
for (const row of rows) {
|
|
7315
|
-
const session = sessionFromRow(row);
|
|
7316
|
-
sessions.push(session);
|
|
7317
|
-
if (row.meta_json) {
|
|
7318
|
-
meta[session.id] = JSON.parse(row.meta_json);
|
|
7319
|
-
}
|
|
6660
|
+
).all();
|
|
6661
|
+
const loadMessages = db.prepare(`
|
|
6662
|
+
SELECT
|
|
6663
|
+
message_id,
|
|
6664
|
+
role,
|
|
6665
|
+
time_created,
|
|
6666
|
+
time_completed,
|
|
6667
|
+
agent,
|
|
6668
|
+
mode,
|
|
6669
|
+
model,
|
|
6670
|
+
provider,
|
|
6671
|
+
parts_json,
|
|
6672
|
+
subagent_id,
|
|
6673
|
+
nickname
|
|
6674
|
+
FROM messages
|
|
6675
|
+
WHERE agent_name = ? AND session_id = ?
|
|
6676
|
+
ORDER BY message_index
|
|
6677
|
+
`);
|
|
6678
|
+
const deleteActivity = db.prepare(
|
|
6679
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
6680
|
+
);
|
|
6681
|
+
const insertActivity = prepareInsertFileActivity(db);
|
|
6682
|
+
for (const session of sessions) {
|
|
6683
|
+
if (!session.agent_name || !session.session_id || !session.project_identity_key) {
|
|
6684
|
+
continue;
|
|
7320
6685
|
}
|
|
7321
|
-
|
|
7322
|
-
|
|
6686
|
+
try {
|
|
6687
|
+
const rows = loadMessages.all(session.agent_name, session.session_id);
|
|
6688
|
+
const messages = rows.map((row) => messageFromBackfillRow(row));
|
|
6689
|
+
const activities = extractSessionFileActivity(
|
|
6690
|
+
String(session.agent_name),
|
|
6691
|
+
String(session.session_id),
|
|
6692
|
+
String(session.project_identity_key),
|
|
6693
|
+
messages
|
|
6694
|
+
);
|
|
6695
|
+
deleteActivity.run(session.agent_name, session.session_id);
|
|
6696
|
+
writeFileActivityRows(insertActivity, activities);
|
|
6697
|
+
} catch {
|
|
6698
|
+
continue;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
function invalidateSearchContentHashes(db) {
|
|
6703
|
+
if (tableExists(db, "session_documents") && columnExists(db, "session_documents", "content_hash")) {
|
|
6704
|
+
db.exec("UPDATE session_documents SET content_hash = ''");
|
|
6705
|
+
}
|
|
7323
6706
|
}
|
|
7324
|
-
function
|
|
7325
|
-
if (!
|
|
7326
|
-
return
|
|
6707
|
+
function rebuildSearchIndex(db) {
|
|
6708
|
+
if (!tableExists(db, "session_documents_fts")) {
|
|
6709
|
+
return;
|
|
7327
6710
|
}
|
|
7328
|
-
|
|
7329
|
-
if (!tableExists(db, "cache_initialization")) return false;
|
|
7330
|
-
const row = db.prepare(
|
|
7331
|
-
`
|
|
7332
|
-
SELECT index_version
|
|
7333
|
-
FROM cache_initialization
|
|
7334
|
-
WHERE agent_name = ?
|
|
7335
|
-
`
|
|
7336
|
-
).get(agentName);
|
|
7337
|
-
return row?.index_version === indexVersion;
|
|
7338
|
-
}) ?? false;
|
|
6711
|
+
db.exec("INSERT INTO session_documents_fts(session_documents_fts) VALUES ('rebuild')");
|
|
7339
6712
|
}
|
|
7340
|
-
function
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
INSERT INTO cache_initialization(agent_name, initialized_at, index_version, last_sync_at)
|
|
7346
|
-
VALUES (?, ?, ?, ?)
|
|
7347
|
-
ON CONFLICT(agent_name) DO UPDATE SET
|
|
7348
|
-
index_version = excluded.index_version,
|
|
7349
|
-
last_sync_at = excluded.last_sync_at
|
|
7350
|
-
`
|
|
7351
|
-
).run(agentName, now, indexVersion, now);
|
|
7352
|
-
});
|
|
6713
|
+
function rebuildMessageSearchIndex(db) {
|
|
6714
|
+
if (!tableExists(db, "messages_fts")) {
|
|
6715
|
+
return;
|
|
6716
|
+
}
|
|
6717
|
+
db.exec("INSERT INTO messages_fts(messages_fts) VALUES ('rebuild')");
|
|
7353
6718
|
}
|
|
7354
|
-
function
|
|
7355
|
-
if (!
|
|
7356
|
-
|
|
6719
|
+
function ensureFtsReady(db) {
|
|
6720
|
+
if (!tableExists(db, "session_documents_fts")) {
|
|
6721
|
+
createSearchTables(db);
|
|
6722
|
+
}
|
|
6723
|
+
createSearchTriggers(db);
|
|
6724
|
+
const needsMessageSearchRebuild = !tableExists(db, "messages_fts");
|
|
6725
|
+
createMessageSearchTables(db);
|
|
6726
|
+
if (needsMessageSearchRebuild) {
|
|
6727
|
+
rebuildMessageSearchIndex(db);
|
|
7357
6728
|
}
|
|
7358
|
-
return withCacheDbReadOnly((db) => {
|
|
7359
|
-
const row = db.prepare(
|
|
7360
|
-
`
|
|
7361
|
-
SELECT
|
|
7362
|
-
session_id,
|
|
7363
|
-
sort_index,
|
|
7364
|
-
slug,
|
|
7365
|
-
title,
|
|
7366
|
-
source_path,
|
|
7367
|
-
directory,
|
|
7368
|
-
project_identity_kind,
|
|
7369
|
-
project_identity_key,
|
|
7370
|
-
project_display_name,
|
|
7371
|
-
time_created,
|
|
7372
|
-
time_updated,
|
|
7373
|
-
message_count,
|
|
7374
|
-
total_input_tokens,
|
|
7375
|
-
total_output_tokens,
|
|
7376
|
-
total_cache_read_tokens,
|
|
7377
|
-
total_cache_create_tokens,
|
|
7378
|
-
total_cost,
|
|
7379
|
-
cost_source,
|
|
7380
|
-
total_tokens,
|
|
7381
|
-
model_usage_json,
|
|
7382
|
-
smart_tags_json,
|
|
7383
|
-
smart_tags_source_updated_at,
|
|
7384
|
-
meta_json
|
|
7385
|
-
FROM sessions
|
|
7386
|
-
WHERE agent_name = ? AND session_id = ?
|
|
7387
|
-
`
|
|
7388
|
-
).get(agentName, sessionId);
|
|
7389
|
-
if (!row) {
|
|
7390
|
-
return null;
|
|
7391
|
-
}
|
|
7392
|
-
const messageRows = db.prepare(
|
|
7393
|
-
`
|
|
7394
|
-
SELECT
|
|
7395
|
-
message_id,
|
|
7396
|
-
role,
|
|
7397
|
-
time_created,
|
|
7398
|
-
time_completed,
|
|
7399
|
-
agent,
|
|
7400
|
-
mode,
|
|
7401
|
-
model,
|
|
7402
|
-
provider,
|
|
7403
|
-
tokens_json,
|
|
7404
|
-
cost,
|
|
7405
|
-
cost_source,
|
|
7406
|
-
parts_json,
|
|
7407
|
-
subagent_id,
|
|
7408
|
-
nickname
|
|
7409
|
-
FROM messages
|
|
7410
|
-
WHERE agent_name = ? AND session_id = ?
|
|
7411
|
-
ORDER BY message_index
|
|
7412
|
-
`
|
|
7413
|
-
).all(agentName, sessionId);
|
|
7414
|
-
const head = sessionFromRow(row);
|
|
7415
|
-
const fileActivityRows = db.prepare(
|
|
7416
|
-
`
|
|
7417
|
-
SELECT agent_name, session_id, project_identity_key, path, kind, count, latest_time
|
|
7418
|
-
FROM session_file_activity
|
|
7419
|
-
WHERE agent_name = ? AND session_id = ?
|
|
7420
|
-
ORDER BY latest_time DESC, count DESC, path
|
|
7421
|
-
LIMIT 500
|
|
7422
|
-
`
|
|
7423
|
-
).all(agentName, sessionId);
|
|
7424
|
-
return {
|
|
7425
|
-
...head,
|
|
7426
|
-
messages: messageRows.map((messageRow) => messageFromCachedRow(messageRow)),
|
|
7427
|
-
file_activity: fileActivityRows.map((activityRow) => fileActivityFromRow(activityRow))
|
|
7428
|
-
};
|
|
7429
|
-
});
|
|
7430
6729
|
}
|
|
7431
|
-
function
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
"
|
|
7440
|
-
);
|
|
7441
|
-
const deleteMessages = db.prepare(
|
|
7442
|
-
"DELETE FROM messages WHERE agent_name = ? AND session_id = ?"
|
|
7443
|
-
);
|
|
7444
|
-
const deleteMessageTools = db.prepare(
|
|
7445
|
-
"DELETE FROM message_tools WHERE agent_name = ? AND session_id = ?"
|
|
7446
|
-
);
|
|
7447
|
-
const deleteFileActivity = db.prepare(
|
|
7448
|
-
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
7449
|
-
);
|
|
7450
|
-
const deleteProjectSession = db.prepare(
|
|
7451
|
-
"DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
|
|
6730
|
+
function ensureFtsConsistency(db) {
|
|
6731
|
+
ensureFtsReady(db);
|
|
6732
|
+
const cachePath = getCachePath2();
|
|
6733
|
+
if (getFtsIntegrityCheckedPath() === cachePath) {
|
|
6734
|
+
return;
|
|
6735
|
+
}
|
|
6736
|
+
try {
|
|
6737
|
+
db.exec(
|
|
6738
|
+
"INSERT INTO session_documents_fts(session_documents_fts, rank) VALUES ('integrity-check', 1)"
|
|
7452
6739
|
);
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
6740
|
+
db.exec("INSERT INTO messages_fts(messages_fts, rank) VALUES ('integrity-check', 1)");
|
|
6741
|
+
setFtsIntegrityCheckedPath(cachePath);
|
|
6742
|
+
} catch {
|
|
6743
|
+
rebuildSearchIndex(db);
|
|
6744
|
+
rebuildMessageSearchIndex(db);
|
|
6745
|
+
setFtsIntegrityCheckedPath(cachePath);
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
function setCacheSchemaVersion(db) {
|
|
6749
|
+
createCacheTables(db);
|
|
6750
|
+
setUserVersion(db, CACHE_SCHEMA_VERSION);
|
|
6751
|
+
db.prepare(
|
|
6752
|
+
`
|
|
6753
|
+
INSERT INTO cache_meta(key, value)
|
|
6754
|
+
VALUES ('version', ?)
|
|
6755
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
6756
|
+
`
|
|
6757
|
+
).run(String(CACHE_SCHEMA_VERSION));
|
|
6758
|
+
}
|
|
6759
|
+
function ensureSchema(db, dbPath) {
|
|
6760
|
+
const currentVersion = getCurrentCacheSchemaVersion(db);
|
|
6761
|
+
if (currentVersion === 0 && !hasAnyCacheSchema(db)) {
|
|
6762
|
+
createLatestCacheSchema(db);
|
|
6763
|
+
setCacheSchemaVersion(db);
|
|
6764
|
+
return;
|
|
6765
|
+
}
|
|
6766
|
+
runSchemaMigrations(db, {
|
|
6767
|
+
dbPath,
|
|
6768
|
+
currentVersion,
|
|
6769
|
+
targetVersion: CACHE_SCHEMA_VERSION,
|
|
6770
|
+
backupLabel: "cache-migration",
|
|
6771
|
+
backupTables: [
|
|
6772
|
+
"agent_cache",
|
|
6773
|
+
"cache_initialization",
|
|
6774
|
+
"cached_sessions",
|
|
6775
|
+
"sessions",
|
|
6776
|
+
"messages",
|
|
6777
|
+
"message_tools",
|
|
6778
|
+
"session_file_activity",
|
|
6779
|
+
"session_documents",
|
|
6780
|
+
"project_sessions"
|
|
6781
|
+
],
|
|
6782
|
+
migrations: [
|
|
6783
|
+
{ version: 3, migrate: createCacheTables },
|
|
6784
|
+
{ version: 4, migrate: createSearchTables },
|
|
6785
|
+
{ version: 5, migrate: migrateProjectIdentity },
|
|
6786
|
+
{
|
|
6787
|
+
version: 6,
|
|
6788
|
+
destructive: true,
|
|
6789
|
+
migrate(db2) {
|
|
6790
|
+
createLatestCacheSchema(db2);
|
|
6791
|
+
recreateSearchIndexSchema(db2);
|
|
6792
|
+
invalidateSearchContentHashes(db2);
|
|
7479
6793
|
}
|
|
7480
|
-
}
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
6794
|
+
},
|
|
6795
|
+
{ version: 7, migrate: backfillStructuredSessions },
|
|
6796
|
+
{ version: 8, migrate: backfillFileActivity },
|
|
6797
|
+
{
|
|
6798
|
+
version: 9,
|
|
6799
|
+
migrate(db2) {
|
|
6800
|
+
createMessageSearchTables(db2);
|
|
6801
|
+
rebuildMessageSearchIndex(db2);
|
|
6802
|
+
}
|
|
6803
|
+
},
|
|
6804
|
+
{
|
|
6805
|
+
version: 10,
|
|
6806
|
+
migrate(db2) {
|
|
6807
|
+
createFileActivityPathSearchTables(db2);
|
|
6808
|
+
rebuildFileActivityPathIndex(db2);
|
|
6809
|
+
}
|
|
6810
|
+
},
|
|
6811
|
+
{
|
|
6812
|
+
version: 11,
|
|
6813
|
+
migrate(db2) {
|
|
6814
|
+
backfillMessageTools(db2);
|
|
6815
|
+
}
|
|
6816
|
+
},
|
|
6817
|
+
{
|
|
6818
|
+
version: 12,
|
|
6819
|
+
migrate(db2) {
|
|
6820
|
+
refreshProjectIdentities(db2);
|
|
6821
|
+
}
|
|
6822
|
+
},
|
|
6823
|
+
{ version: 13, migrate: createCacheTables }
|
|
6824
|
+
]
|
|
7499
6825
|
});
|
|
6826
|
+
createLatestCacheSchema(db);
|
|
6827
|
+
if (getUserVersion(db) <= CACHE_SCHEMA_VERSION) {
|
|
6828
|
+
setCacheSchemaVersion(db);
|
|
6829
|
+
}
|
|
7500
6830
|
}
|
|
7501
|
-
function
|
|
7502
|
-
if (
|
|
7503
|
-
return;
|
|
6831
|
+
function shouldBulkSyncSearchIndex(options, changedCount) {
|
|
6832
|
+
if (options.isBulk != null) {
|
|
6833
|
+
return options.isBulk;
|
|
7504
6834
|
}
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
deleteFileActivity.run(agentName, sessionId);
|
|
7543
|
-
deleteProjectSession.run(agentName, sessionId);
|
|
7544
|
-
deleteSession.run(agentName, sessionId);
|
|
7545
|
-
}
|
|
7546
|
-
for (const { session, sortIndex } of changes) {
|
|
7547
|
-
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
7548
|
-
const sessionMeta = meta[session.id];
|
|
7549
|
-
const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
|
|
7550
|
-
upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
|
|
7551
|
-
upsertSessionRow(
|
|
7552
|
-
upsertSession,
|
|
7553
|
-
agentName,
|
|
7554
|
-
session,
|
|
7555
|
-
metaJson,
|
|
7556
|
-
sortIndex,
|
|
7557
|
-
sourcePathFromMeta(sessionMeta)
|
|
7558
|
-
);
|
|
7559
|
-
writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
|
|
6835
|
+
const threshold = options.bulkThreshold ?? SEARCH_INDEX_BULK_SYNC_THRESHOLD;
|
|
6836
|
+
return threshold > 0 && changedCount >= threshold;
|
|
6837
|
+
}
|
|
6838
|
+
function sessionContentHash(session) {
|
|
6839
|
+
return JSON.stringify([
|
|
6840
|
+
session.slug,
|
|
6841
|
+
session.title,
|
|
6842
|
+
session.directory,
|
|
6843
|
+
session.time_created,
|
|
6844
|
+
session.time_updated ?? session.time_created,
|
|
6845
|
+
session.stats.message_count,
|
|
6846
|
+
session.stats.total_input_tokens,
|
|
6847
|
+
session.stats.total_output_tokens,
|
|
6848
|
+
session.stats.total_cache_read_tokens ?? 0,
|
|
6849
|
+
session.stats.total_cache_create_tokens ?? 0,
|
|
6850
|
+
session.stats.total_cost,
|
|
6851
|
+
session.stats.cost_source ?? "",
|
|
6852
|
+
session.stats.total_tokens ?? 0
|
|
6853
|
+
]);
|
|
6854
|
+
}
|
|
6855
|
+
function escapeFtsTerm(value) {
|
|
6856
|
+
return value.replaceAll('"', '""');
|
|
6857
|
+
}
|
|
6858
|
+
function splitSearchTokens(input) {
|
|
6859
|
+
const tokens = [];
|
|
6860
|
+
let token = "";
|
|
6861
|
+
let inQuote = false;
|
|
6862
|
+
for (const char of input) {
|
|
6863
|
+
if (char === '"') {
|
|
6864
|
+
inQuote = !inQuote;
|
|
6865
|
+
token += char;
|
|
6866
|
+
continue;
|
|
6867
|
+
}
|
|
6868
|
+
if (/\s/.test(char) && !inQuote) {
|
|
6869
|
+
if (token) {
|
|
6870
|
+
tokens.push(token);
|
|
6871
|
+
token = "";
|
|
7560
6872
|
}
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
}
|
|
6873
|
+
continue;
|
|
6874
|
+
}
|
|
6875
|
+
token += char;
|
|
6876
|
+
}
|
|
6877
|
+
if (token) {
|
|
6878
|
+
tokens.push(token);
|
|
6879
|
+
}
|
|
6880
|
+
return tokens;
|
|
7565
6881
|
}
|
|
7566
|
-
function
|
|
7567
|
-
|
|
7568
|
-
if (
|
|
7569
|
-
|
|
6882
|
+
function unwrapSearchValue(value) {
|
|
6883
|
+
const trimmed = value.trim();
|
|
6884
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
6885
|
+
return trimmed.slice(1, -1).trim();
|
|
6886
|
+
}
|
|
6887
|
+
return trimmed;
|
|
6888
|
+
}
|
|
6889
|
+
function parseCostQualifier(value, filters) {
|
|
6890
|
+
const raw = unwrapSearchValue(value);
|
|
6891
|
+
const range = raw.match(/^(\d+(?:\.\d+)?)\.\.(\d+(?:\.\d+)?)$/);
|
|
6892
|
+
if (range) {
|
|
6893
|
+
filters.costMin = Number(range[1]);
|
|
6894
|
+
filters.costMax = Number(range[2]);
|
|
7570
6895
|
return;
|
|
7571
6896
|
}
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
6897
|
+
const comparison = raw.match(/^(>=|>|<=|<)(\d+(?:\.\d+)?)$/);
|
|
6898
|
+
if (comparison) {
|
|
6899
|
+
const amount2 = Number(comparison[2]);
|
|
6900
|
+
if (comparison[1]?.includes(">")) {
|
|
6901
|
+
filters.costMin = amount2;
|
|
6902
|
+
filters.costMinExclusive = comparison[1] === ">";
|
|
6903
|
+
} else {
|
|
6904
|
+
filters.costMax = amount2;
|
|
6905
|
+
filters.costMaxExclusive = comparison[1] === "<";
|
|
6906
|
+
}
|
|
6907
|
+
return;
|
|
6908
|
+
}
|
|
6909
|
+
const amount = Number(raw);
|
|
6910
|
+
if (!Number.isNaN(amount)) {
|
|
6911
|
+
filters.costMin = amount;
|
|
6912
|
+
filters.costMax = amount;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
function appendUnique(values, value) {
|
|
6916
|
+
if (values?.includes(value)) return values;
|
|
6917
|
+
return [...values ?? [], value];
|
|
6918
|
+
}
|
|
6919
|
+
function isSmartTag(value) {
|
|
6920
|
+
return value === "bugfix" || value === "refactoring" || value === "feature-dev" || value === "testing" || value === "docs" || value === "git-ops" || value === "build-deploy" || value === "exploration" || value === "planning";
|
|
6921
|
+
}
|
|
6922
|
+
function parseSearchQuery(input) {
|
|
6923
|
+
const filters = {};
|
|
6924
|
+
const textTokens = [];
|
|
6925
|
+
let hasQualifiers = false;
|
|
6926
|
+
for (const token of splitSearchTokens(input)) {
|
|
6927
|
+
const match = token.match(/^([a-zA-Z][a-zA-Z_-]*):(.+)$/);
|
|
6928
|
+
if (!match) {
|
|
6929
|
+
textTokens.push(token);
|
|
7591
6930
|
continue;
|
|
7592
6931
|
}
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
6932
|
+
const key = match[1].toLowerCase();
|
|
6933
|
+
const value = unwrapSearchValue(match[2]);
|
|
6934
|
+
if (!value) continue;
|
|
6935
|
+
let consumed = true;
|
|
6936
|
+
if (key === "agent") filters.agent = value.toLowerCase();
|
|
6937
|
+
else if (key === "project") filters.project = value;
|
|
6938
|
+
else if (key === "projectkey" || key === "project-key") filters.projectKey = value;
|
|
6939
|
+
else if (key === "cwd") filters.cwd = value;
|
|
6940
|
+
else if (key === "tool") filters.tools = appendUnique(filters.tools, value.toLowerCase());
|
|
6941
|
+
else if (key === "file" || key === "path") filters.file = value;
|
|
6942
|
+
else if (key === "kind" || key === "filekind" || key === "file-kind") {
|
|
6943
|
+
if (value === "read" || value === "edit" || value === "write" || value === "delete") {
|
|
6944
|
+
filters.fileKind = value;
|
|
6945
|
+
} else {
|
|
6946
|
+
consumed = false;
|
|
6947
|
+
}
|
|
6948
|
+
} else if (key === "tag" || key === "signal") {
|
|
6949
|
+
const tag = value.toLowerCase();
|
|
6950
|
+
if (isSmartTag(tag)) {
|
|
6951
|
+
filters.tags = appendUnique(filters.tags, tag);
|
|
6952
|
+
} else {
|
|
6953
|
+
consumed = false;
|
|
6954
|
+
}
|
|
6955
|
+
} else if (key === "cost") {
|
|
6956
|
+
parseCostQualifier(value, filters);
|
|
6957
|
+
} else {
|
|
6958
|
+
consumed = false;
|
|
6959
|
+
}
|
|
6960
|
+
if (consumed) {
|
|
6961
|
+
hasQualifiers = true;
|
|
6962
|
+
} else {
|
|
6963
|
+
textTokens.push(token);
|
|
7596
6964
|
}
|
|
7597
6965
|
}
|
|
6966
|
+
return {
|
|
6967
|
+
text: textTokens.join(" ").trim(),
|
|
6968
|
+
filters,
|
|
6969
|
+
hasQualifiers
|
|
6970
|
+
};
|
|
7598
6971
|
}
|
|
7599
|
-
function
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
return {
|
|
7609
|
-
})
|
|
7610
|
-
|
|
6972
|
+
function toFtsQuery(input) {
|
|
6973
|
+
const tokens = splitSearchTokens(input);
|
|
6974
|
+
const mapped = tokens.map((token) => {
|
|
6975
|
+
if (/^OR$/i.test(token)) {
|
|
6976
|
+
return "OR";
|
|
6977
|
+
}
|
|
6978
|
+
if (token.startsWith('"') && token.endsWith('"')) {
|
|
6979
|
+
return `"${escapeFtsTerm(token.slice(1, -1))}"`;
|
|
6980
|
+
}
|
|
6981
|
+
return `"${escapeFtsTerm(token)}"`;
|
|
6982
|
+
}).filter(
|
|
6983
|
+
(token, index, values) => token !== "OR" || index > 0 && index < values.length - 1 && values[index - 1] !== "OR" && values[index + 1] !== "OR"
|
|
6984
|
+
);
|
|
6985
|
+
return mapped.join(" ");
|
|
7611
6986
|
}
|
|
7612
6987
|
function loadSearchIndexEntry(agentName, change, loadSessionData) {
|
|
7613
6988
|
try {
|
|
@@ -7943,17 +7318,6 @@ function sessionMatchesSearchCost(session, options) {
|
|
|
7943
7318
|
}
|
|
7944
7319
|
return true;
|
|
7945
7320
|
}
|
|
7946
|
-
function likePattern(value) {
|
|
7947
|
-
return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
|
|
7948
|
-
}
|
|
7949
|
-
function filePathFtsQuery(value) {
|
|
7950
|
-
const path2 = normalizeFilePathSearch(value);
|
|
7951
|
-
if (path2.length < 3) return null;
|
|
7952
|
-
return `"${path2.replaceAll('"', '""')}"`;
|
|
7953
|
-
}
|
|
7954
|
-
function escapeRegExp(value) {
|
|
7955
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7956
|
-
}
|
|
7957
7321
|
function buildSessionSearchFilters(options) {
|
|
7958
7322
|
const clauses = [];
|
|
7959
7323
|
const params = [];
|
|
@@ -8081,307 +7445,656 @@ function buildTermSnippet(text, terms) {
|
|
|
8081
7445
|
const end = Math.min(text.length, index + term.length + 80);
|
|
8082
7446
|
return `${start > 0 ? "\u2026 " : ""}${highlightTerm(text.slice(start, end), term)}${end < text.length ? " \u2026" : ""}`;
|
|
8083
7447
|
}
|
|
8084
|
-
function messageMatchType(row) {
|
|
8085
|
-
if (row.role === "user") return "user_message";
|
|
8086
|
-
if (row.role === "tool" || row.mode === "tool" || row.tool_metadata_json) return "tool_output";
|
|
8087
|
-
return "assistant_reply";
|
|
7448
|
+
function messageMatchType(row) {
|
|
7449
|
+
if (row.role === "user") return "user_message";
|
|
7450
|
+
if (row.role === "tool" || row.mode === "tool" || row.tool_metadata_json) return "tool_output";
|
|
7451
|
+
return "assistant_reply";
|
|
7452
|
+
}
|
|
7453
|
+
function searchResultRowKey(row) {
|
|
7454
|
+
return `${String(row.agent_name)}\0${String(row.session_id)}`;
|
|
7455
|
+
}
|
|
7456
|
+
function fetchMessageSearchMatches(db, rows, ftsQuery, terms) {
|
|
7457
|
+
const candidates = rows.filter((row) => !textMatchesTerms(String(row.title ?? ""), terms));
|
|
7458
|
+
if (candidates.length === 0) {
|
|
7459
|
+
return /* @__PURE__ */ new Map();
|
|
7460
|
+
}
|
|
7461
|
+
const clauses = [];
|
|
7462
|
+
const params = [ftsQuery];
|
|
7463
|
+
for (const row of candidates) {
|
|
7464
|
+
clauses.push("(m.agent_name = ? AND m.session_id = ?)");
|
|
7465
|
+
params.push(String(row.agent_name), String(row.session_id));
|
|
7466
|
+
}
|
|
7467
|
+
const messageRows = db.prepare(
|
|
7468
|
+
`
|
|
7469
|
+
SELECT
|
|
7470
|
+
m.agent_name,
|
|
7471
|
+
m.session_id,
|
|
7472
|
+
m.message_index,
|
|
7473
|
+
m.role,
|
|
7474
|
+
m.mode,
|
|
7475
|
+
m.content_text,
|
|
7476
|
+
m.tool_metadata_json
|
|
7477
|
+
FROM messages_fts
|
|
7478
|
+
JOIN messages m ON m.rowid = messages_fts.rowid
|
|
7479
|
+
WHERE messages_fts MATCH ?
|
|
7480
|
+
AND (${clauses.join(" OR ")})
|
|
7481
|
+
ORDER BY m.message_index
|
|
7482
|
+
`
|
|
7483
|
+
).all(...params);
|
|
7484
|
+
const matches = /* @__PURE__ */ new Map();
|
|
7485
|
+
for (const message of messageRows) {
|
|
7486
|
+
const key = searchResultRowKey(message);
|
|
7487
|
+
if (matches.has(key)) continue;
|
|
7488
|
+
const text = String(message.content_text ?? "");
|
|
7489
|
+
if (!textMatchesTerms(text, terms)) continue;
|
|
7490
|
+
matches.set(key, {
|
|
7491
|
+
snippet: buildTermSnippet(text, terms),
|
|
7492
|
+
matchType: messageMatchType(message)
|
|
7493
|
+
});
|
|
7494
|
+
}
|
|
7495
|
+
return matches;
|
|
7496
|
+
}
|
|
7497
|
+
function resolveSearchMatch(row, terms, messageMatches) {
|
|
7498
|
+
const title = String(row.title ?? "");
|
|
7499
|
+
if (terms.terms.length === 0) {
|
|
7500
|
+
return {
|
|
7501
|
+
snippet: `Recent session \xB7 ${String(row.directory ?? "")}`,
|
|
7502
|
+
matchType: "recent"
|
|
7503
|
+
};
|
|
7504
|
+
}
|
|
7505
|
+
if (textMatchesTerms(title, terms)) {
|
|
7506
|
+
return { snippet: buildTermSnippet(title, terms), matchType: "title" };
|
|
7507
|
+
}
|
|
7508
|
+
const messageMatch = messageMatches.get(searchResultRowKey(row));
|
|
7509
|
+
if (messageMatch) {
|
|
7510
|
+
return messageMatch;
|
|
7511
|
+
}
|
|
7512
|
+
return {
|
|
7513
|
+
snippet: String(row.snippet ?? ""),
|
|
7514
|
+
matchType: "assistant_reply"
|
|
7515
|
+
};
|
|
7516
|
+
}
|
|
7517
|
+
function rowsToSearchResults(db, rows, textQuery, ftsQuery = toFtsQuery(textQuery)) {
|
|
7518
|
+
const terms = parseTextTerms(textQuery);
|
|
7519
|
+
const messageMatches = terms.terms.length > 0 && ftsQuery ? fetchMessageSearchMatches(db, rows, ftsQuery, terms) : /* @__PURE__ */ new Map();
|
|
7520
|
+
return rows.map((row) => {
|
|
7521
|
+
const match = resolveSearchMatch(row, terms, messageMatches);
|
|
7522
|
+
return {
|
|
7523
|
+
agentName: String(row.agent_name),
|
|
7524
|
+
session: sessionHeadFromSearchRow(row),
|
|
7525
|
+
snippet: match.snippet,
|
|
7526
|
+
matchType: match.matchType
|
|
7527
|
+
};
|
|
7528
|
+
});
|
|
7529
|
+
}
|
|
7530
|
+
function searchSessions(query, options = {}) {
|
|
7531
|
+
const search = mergeSearchQueryOptions(query, options);
|
|
7532
|
+
const normalizedQuery = search.text.trim();
|
|
7533
|
+
if (!hasCacheStorage()) {
|
|
7534
|
+
return [];
|
|
7535
|
+
}
|
|
7536
|
+
const results = withCacheDb((db) => {
|
|
7537
|
+
ensureFtsReady(db);
|
|
7538
|
+
const filters = buildSessionSearchFilters(search.options);
|
|
7539
|
+
if (!normalizedQuery) {
|
|
7540
|
+
const rows2 = db.prepare(
|
|
7541
|
+
`
|
|
7542
|
+
SELECT
|
|
7543
|
+
${searchSessionColumns()},
|
|
7544
|
+
'' AS snippet
|
|
7545
|
+
FROM sessions s
|
|
7546
|
+
WHERE 1 = 1
|
|
7547
|
+
${filters.where}
|
|
7548
|
+
ORDER BY s.activity_time DESC
|
|
7549
|
+
LIMIT ?
|
|
7550
|
+
`
|
|
7551
|
+
).all(...filters.params, search.options.limit ?? 50);
|
|
7552
|
+
return rowsToSearchResults(db, rows2, "");
|
|
7553
|
+
}
|
|
7554
|
+
const ftsQuery = toFtsQuery(normalizedQuery);
|
|
7555
|
+
if (!ftsQuery) return [];
|
|
7556
|
+
const rows = db.prepare(
|
|
7557
|
+
`
|
|
7558
|
+
SELECT
|
|
7559
|
+
${searchSessionColumns()},
|
|
7560
|
+
COALESCE(
|
|
7561
|
+
NULLIF(snippet(session_documents_fts, 1, '<mark>', '</mark>', ' \u2026 ', 18), ''),
|
|
7562
|
+
highlight(session_documents_fts, 0, '<mark>', '</mark>')
|
|
7563
|
+
) AS snippet
|
|
7564
|
+
FROM session_documents_fts
|
|
7565
|
+
JOIN session_documents d ON d.id = session_documents_fts.rowid
|
|
7566
|
+
JOIN sessions s ON s.agent_name = d.agent_name AND s.session_id = d.session_id
|
|
7567
|
+
WHERE session_documents_fts MATCH ?
|
|
7568
|
+
${filters.where}
|
|
7569
|
+
ORDER BY bm25(session_documents_fts, 8.0, 1.0), s.activity_time DESC
|
|
7570
|
+
LIMIT ?
|
|
7571
|
+
`
|
|
7572
|
+
).all(ftsQuery, ...filters.params, search.options.limit ?? 50);
|
|
7573
|
+
return rowsToSearchResults(db, rows, normalizedQuery, ftsQuery);
|
|
7574
|
+
});
|
|
7575
|
+
return results ?? [];
|
|
7576
|
+
}
|
|
7577
|
+
function fileActivityFilters(options) {
|
|
7578
|
+
const path2 = options.path ? normalizeFilePathSearch(options.path) : "";
|
|
7579
|
+
return {
|
|
7580
|
+
projectKey: options.projectKey ?? null,
|
|
7581
|
+
projectLike: options.project ? likePattern(options.project) : null,
|
|
7582
|
+
cwdKey: options.cwd ? computeIdentity(options.cwd, realFs).key : null,
|
|
7583
|
+
cwdLike: options.cwd ? likePattern(options.cwd) : null,
|
|
7584
|
+
path: path2,
|
|
7585
|
+
pathLike: path2 ? likePattern(path2) : null
|
|
7586
|
+
};
|
|
7587
|
+
}
|
|
7588
|
+
function fileActivityFromRow(row) {
|
|
7589
|
+
return {
|
|
7590
|
+
agent_name: String(row.agent_name),
|
|
7591
|
+
session_id: String(row.session_id),
|
|
7592
|
+
project_identity_key: String(row.project_identity_key ?? ""),
|
|
7593
|
+
path: String(row.path ?? ""),
|
|
7594
|
+
kind: row.kind ?? "read",
|
|
7595
|
+
count: Number(row.count ?? 0),
|
|
7596
|
+
latest_time: Number(row.latest_time ?? 0)
|
|
7597
|
+
};
|
|
7598
|
+
}
|
|
7599
|
+
function buildFileActivityWhere(options) {
|
|
7600
|
+
const filters = fileActivityFilters(options);
|
|
7601
|
+
const clauses = [];
|
|
7602
|
+
const params = [];
|
|
7603
|
+
if (options.agent != null) {
|
|
7604
|
+
clauses.push("fa.agent_name = ?");
|
|
7605
|
+
params.push(options.agent);
|
|
7606
|
+
}
|
|
7607
|
+
if (options.sessionId != null) {
|
|
7608
|
+
clauses.push("fa.session_id = ?");
|
|
7609
|
+
params.push(options.sessionId);
|
|
7610
|
+
}
|
|
7611
|
+
if (filters.projectKey != null) {
|
|
7612
|
+
clauses.push("fa.project_identity_key = ?");
|
|
7613
|
+
params.push(filters.projectKey);
|
|
7614
|
+
}
|
|
7615
|
+
if (filters.projectLike != null) {
|
|
7616
|
+
clauses.push(
|
|
7617
|
+
"(LOWER(fa.project_identity_key) LIKE ? ESCAPE '\\' OR LOWER(s.project_display_name) LIKE ? ESCAPE '\\' OR LOWER(s.directory) LIKE ? ESCAPE '\\')"
|
|
7618
|
+
);
|
|
7619
|
+
params.push(filters.projectLike, filters.projectLike, filters.projectLike);
|
|
7620
|
+
}
|
|
7621
|
+
if (filters.cwdKey != null) {
|
|
7622
|
+
clauses.push("(s.project_identity_key = ? OR LOWER(s.directory) LIKE ? ESCAPE '\\')");
|
|
7623
|
+
params.push(filters.cwdKey, filters.cwdLike);
|
|
7624
|
+
}
|
|
7625
|
+
if (filters.pathLike != null) {
|
|
7626
|
+
const pathQuery = filePathFtsQuery(filters.path);
|
|
7627
|
+
if (pathQuery) {
|
|
7628
|
+
clauses.push(
|
|
7629
|
+
"fa.rowid IN (SELECT rowid FROM session_file_activity_path_fts WHERE path MATCH ?)"
|
|
7630
|
+
);
|
|
7631
|
+
params.push(pathQuery);
|
|
7632
|
+
} else {
|
|
7633
|
+
clauses.push("LOWER(fa.path) LIKE ? ESCAPE '\\'");
|
|
7634
|
+
params.push(filters.pathLike);
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
if (options.kind != null) {
|
|
7638
|
+
clauses.push("fa.kind = ?");
|
|
7639
|
+
params.push(options.kind);
|
|
7640
|
+
}
|
|
7641
|
+
if (options.from != null) {
|
|
7642
|
+
clauses.push("fa.latest_time >= ?");
|
|
7643
|
+
params.push(options.from);
|
|
7644
|
+
}
|
|
7645
|
+
if (options.to != null) {
|
|
7646
|
+
clauses.push("fa.latest_time <= ?");
|
|
7647
|
+
params.push(options.to);
|
|
7648
|
+
}
|
|
7649
|
+
return {
|
|
7650
|
+
where: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
7651
|
+
params
|
|
7652
|
+
};
|
|
7653
|
+
}
|
|
7654
|
+
function listFileActivity(options = {}) {
|
|
7655
|
+
if (!hasCacheStorage()) {
|
|
7656
|
+
return [];
|
|
7657
|
+
}
|
|
7658
|
+
const filters = buildFileActivityWhere(options);
|
|
7659
|
+
const queryRows = (db) => db.prepare(
|
|
7660
|
+
`
|
|
7661
|
+
SELECT
|
|
7662
|
+
fa.agent_name,
|
|
7663
|
+
fa.session_id,
|
|
7664
|
+
fa.project_identity_key,
|
|
7665
|
+
fa.path,
|
|
7666
|
+
fa.kind,
|
|
7667
|
+
fa.count,
|
|
7668
|
+
fa.latest_time,
|
|
7669
|
+
s.slug,
|
|
7670
|
+
s.title,
|
|
7671
|
+
s.directory,
|
|
7672
|
+
s.project_identity_kind,
|
|
7673
|
+
s.project_display_name,
|
|
7674
|
+
s.time_created,
|
|
7675
|
+
s.time_updated,
|
|
7676
|
+
s.message_count,
|
|
7677
|
+
s.total_input_tokens,
|
|
7678
|
+
s.total_output_tokens,
|
|
7679
|
+
s.total_cache_read_tokens,
|
|
7680
|
+
s.total_cache_create_tokens,
|
|
7681
|
+
s.total_cost,
|
|
7682
|
+
s.cost_source,
|
|
7683
|
+
s.total_tokens
|
|
7684
|
+
FROM session_file_activity fa
|
|
7685
|
+
JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
|
|
7686
|
+
${filters.where}
|
|
7687
|
+
ORDER BY fa.latest_time DESC, fa.count DESC, fa.path
|
|
7688
|
+
LIMIT ?
|
|
7689
|
+
`
|
|
7690
|
+
).all(...filters.params, options.limit ?? 50);
|
|
7691
|
+
let rows = withCacheDbReadOnly(queryRows);
|
|
7692
|
+
if (rows == null && options.path) {
|
|
7693
|
+
rows = withCacheDb(queryRows);
|
|
7694
|
+
}
|
|
7695
|
+
return (rows ?? []).map((row) => ({
|
|
7696
|
+
...fileActivityFromRow(row),
|
|
7697
|
+
session: sessionHeadFromSearchRow(row)
|
|
7698
|
+
}));
|
|
7699
|
+
}
|
|
7700
|
+
function listSessionFileActivity(agentName, sessionId) {
|
|
7701
|
+
return listFileActivity({ agent: agentName, sessionId, limit: 500 }).map(
|
|
7702
|
+
({ session: _session, ...activity }) => activity
|
|
7703
|
+
);
|
|
8088
7704
|
}
|
|
8089
|
-
function
|
|
8090
|
-
|
|
7705
|
+
function highlightFilePath(path2, query) {
|
|
7706
|
+
const needle = normalizeFilePathSearch(query);
|
|
7707
|
+
if (!needle) return path2;
|
|
7708
|
+
const lower = path2.toLowerCase();
|
|
7709
|
+
const index = lower.indexOf(needle.toLowerCase());
|
|
7710
|
+
if (index < 0) return path2;
|
|
7711
|
+
return `${path2.slice(0, index)}<mark>${path2.slice(index, index + needle.length)}</mark>${path2.slice(
|
|
7712
|
+
index + needle.length
|
|
7713
|
+
)}`;
|
|
8091
7714
|
}
|
|
8092
|
-
function
|
|
8093
|
-
const
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
).all(...params);
|
|
8120
|
-
const matches = /* @__PURE__ */ new Map();
|
|
8121
|
-
for (const message of messageRows) {
|
|
8122
|
-
const key = searchResultRowKey(message);
|
|
8123
|
-
if (matches.has(key)) continue;
|
|
8124
|
-
const text = String(message.content_text ?? "");
|
|
8125
|
-
if (!textMatchesTerms(text, terms)) continue;
|
|
8126
|
-
matches.set(key, {
|
|
8127
|
-
snippet: buildTermSnippet(text, terms),
|
|
8128
|
-
matchType: messageMatchType(message)
|
|
7715
|
+
function searchFileActivitySessions(query, options = {}) {
|
|
7716
|
+
const search = mergeSearchQueryOptions(query, options);
|
|
7717
|
+
const path2 = normalizeFilePathSearch(search.options.file ?? search.text);
|
|
7718
|
+
if (!path2) return [];
|
|
7719
|
+
const rows = listFileActivity({
|
|
7720
|
+
agent: search.options.agent,
|
|
7721
|
+
projectKey: search.options.projectKey,
|
|
7722
|
+
project: search.options.project,
|
|
7723
|
+
cwd: search.options.cwd,
|
|
7724
|
+
path: path2,
|
|
7725
|
+
kind: search.options.fileKind,
|
|
7726
|
+
from: search.options.from,
|
|
7727
|
+
to: search.options.to,
|
|
7728
|
+
limit: (search.options.limit ?? 50) * 3
|
|
7729
|
+
});
|
|
7730
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7731
|
+
const results = [];
|
|
7732
|
+
for (const row of rows) {
|
|
7733
|
+
const key = `${row.agent_name}/${row.session_id}`;
|
|
7734
|
+
if (seen.has(key)) continue;
|
|
7735
|
+
if (!sessionMatchesSearchCost(row.session, search.options)) continue;
|
|
7736
|
+
seen.add(key);
|
|
7737
|
+
results.push({
|
|
7738
|
+
agentName: row.agent_name,
|
|
7739
|
+
session: row.session,
|
|
7740
|
+
snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
|
|
7741
|
+
matchType: "file_path"
|
|
8129
7742
|
});
|
|
7743
|
+
if (results.length >= (search.options.limit ?? 50)) break;
|
|
8130
7744
|
}
|
|
8131
|
-
return
|
|
7745
|
+
return results;
|
|
8132
7746
|
}
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
matchType: "recent"
|
|
8139
|
-
};
|
|
7747
|
+
var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
|
|
7748
|
+
function deleteLegacyCacheFile() {
|
|
7749
|
+
const legacyPath = getLegacyCachePath();
|
|
7750
|
+
if (!existsSync12(legacyPath)) {
|
|
7751
|
+
return;
|
|
8140
7752
|
}
|
|
8141
|
-
|
|
8142
|
-
|
|
7753
|
+
try {
|
|
7754
|
+
unlinkSync(legacyPath);
|
|
7755
|
+
} catch {
|
|
8143
7756
|
}
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
7757
|
+
}
|
|
7758
|
+
function loadCachedSessions(agentName) {
|
|
7759
|
+
if (!hasCacheStorage()) {
|
|
7760
|
+
return null;
|
|
8147
7761
|
}
|
|
8148
|
-
return {
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
7762
|
+
return withCacheDb((db) => {
|
|
7763
|
+
const timestampRow = db.prepare("SELECT timestamp AS value FROM agent_cache WHERE agent_name = ?").get(agentName);
|
|
7764
|
+
const timestamp = Number(timestampRow?.value ?? 0);
|
|
7765
|
+
if (!timestamp) {
|
|
7766
|
+
return null;
|
|
7767
|
+
}
|
|
7768
|
+
const rows = db.prepare(
|
|
7769
|
+
`
|
|
7770
|
+
SELECT
|
|
7771
|
+
session_id,
|
|
7772
|
+
sort_index,
|
|
7773
|
+
slug,
|
|
7774
|
+
title,
|
|
7775
|
+
source_path,
|
|
7776
|
+
directory,
|
|
7777
|
+
project_identity_kind,
|
|
7778
|
+
project_identity_key,
|
|
7779
|
+
project_display_name,
|
|
7780
|
+
time_created,
|
|
7781
|
+
time_updated,
|
|
7782
|
+
message_count,
|
|
7783
|
+
total_input_tokens,
|
|
7784
|
+
total_output_tokens,
|
|
7785
|
+
total_cache_read_tokens,
|
|
7786
|
+
total_cache_create_tokens,
|
|
7787
|
+
total_cost,
|
|
7788
|
+
cost_source,
|
|
7789
|
+
total_tokens,
|
|
7790
|
+
model_usage_json,
|
|
7791
|
+
smart_tags_json,
|
|
7792
|
+
smart_tags_source_updated_at,
|
|
7793
|
+
meta_json
|
|
7794
|
+
FROM sessions
|
|
7795
|
+
WHERE agent_name = ?
|
|
7796
|
+
ORDER BY sort_index, activity_time DESC
|
|
7797
|
+
`
|
|
7798
|
+
).all(agentName);
|
|
7799
|
+
const sessions = [];
|
|
7800
|
+
const meta = {};
|
|
7801
|
+
for (const row of rows) {
|
|
7802
|
+
const session = sessionFromRow(row);
|
|
7803
|
+
sessions.push(session);
|
|
7804
|
+
if (row.meta_json) {
|
|
7805
|
+
meta[session.id] = JSON.parse(row.meta_json);
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7808
|
+
return { sessions, meta, timestamp };
|
|
7809
|
+
});
|
|
8152
7810
|
}
|
|
8153
|
-
function
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
return
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
7811
|
+
function isAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATION_VERSION) {
|
|
7812
|
+
if (!hasCacheStorage()) {
|
|
7813
|
+
return false;
|
|
7814
|
+
}
|
|
7815
|
+
return withCacheDbReadOnly((db) => {
|
|
7816
|
+
if (!tableExists(db, "cache_initialization")) return false;
|
|
7817
|
+
const row = db.prepare(
|
|
7818
|
+
`
|
|
7819
|
+
SELECT index_version
|
|
7820
|
+
FROM cache_initialization
|
|
7821
|
+
WHERE agent_name = ?
|
|
7822
|
+
`
|
|
7823
|
+
).get(agentName);
|
|
7824
|
+
return row?.index_version === indexVersion;
|
|
7825
|
+
}) ?? false;
|
|
7826
|
+
}
|
|
7827
|
+
function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATION_VERSION) {
|
|
7828
|
+
withCacheDb((db) => {
|
|
7829
|
+
const now = Date.now();
|
|
7830
|
+
db.prepare(
|
|
7831
|
+
`
|
|
7832
|
+
INSERT INTO cache_initialization(agent_name, initialized_at, index_version, last_sync_at)
|
|
7833
|
+
VALUES (?, ?, ?, ?)
|
|
7834
|
+
ON CONFLICT(agent_name) DO UPDATE SET
|
|
7835
|
+
index_version = excluded.index_version,
|
|
7836
|
+
last_sync_at = excluded.last_sync_at
|
|
7837
|
+
`
|
|
7838
|
+
).run(agentName, now, indexVersion, now);
|
|
8164
7839
|
});
|
|
8165
7840
|
}
|
|
8166
|
-
function
|
|
8167
|
-
const search = mergeSearchQueryOptions(query, options);
|
|
8168
|
-
const normalizedQuery = search.text.trim();
|
|
7841
|
+
function loadCachedSessionData(agentName, sessionId) {
|
|
8169
7842
|
if (!hasCacheStorage()) {
|
|
8170
|
-
return
|
|
7843
|
+
return null;
|
|
8171
7844
|
}
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
7845
|
+
return withCacheDbReadOnly((db) => {
|
|
7846
|
+
const row = db.prepare(
|
|
7847
|
+
`
|
|
7848
|
+
SELECT
|
|
7849
|
+
session_id,
|
|
7850
|
+
sort_index,
|
|
7851
|
+
slug,
|
|
7852
|
+
title,
|
|
7853
|
+
source_path,
|
|
7854
|
+
directory,
|
|
7855
|
+
project_identity_kind,
|
|
7856
|
+
project_identity_key,
|
|
7857
|
+
project_display_name,
|
|
7858
|
+
time_created,
|
|
7859
|
+
time_updated,
|
|
7860
|
+
message_count,
|
|
7861
|
+
total_input_tokens,
|
|
7862
|
+
total_output_tokens,
|
|
7863
|
+
total_cache_read_tokens,
|
|
7864
|
+
total_cache_create_tokens,
|
|
7865
|
+
total_cost,
|
|
7866
|
+
cost_source,
|
|
7867
|
+
total_tokens,
|
|
7868
|
+
model_usage_json,
|
|
7869
|
+
smart_tags_json,
|
|
7870
|
+
smart_tags_source_updated_at,
|
|
7871
|
+
meta_json
|
|
7872
|
+
FROM sessions
|
|
7873
|
+
WHERE agent_name = ? AND session_id = ?
|
|
8177
7874
|
`
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
FROM sessions s
|
|
8182
|
-
WHERE 1 = 1
|
|
8183
|
-
${filters.where}
|
|
8184
|
-
ORDER BY s.activity_time DESC
|
|
8185
|
-
LIMIT ?
|
|
8186
|
-
`
|
|
8187
|
-
).all(...filters.params, search.options.limit ?? 50);
|
|
8188
|
-
return rowsToSearchResults(db, rows2, "");
|
|
7875
|
+
).get(agentName, sessionId);
|
|
7876
|
+
if (!row) {
|
|
7877
|
+
return null;
|
|
8189
7878
|
}
|
|
8190
|
-
const
|
|
8191
|
-
if (!ftsQuery) return [];
|
|
8192
|
-
const rows = db.prepare(
|
|
7879
|
+
const messageRows = db.prepare(
|
|
8193
7880
|
`
|
|
8194
7881
|
SELECT
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
7882
|
+
message_id,
|
|
7883
|
+
role,
|
|
7884
|
+
time_created,
|
|
7885
|
+
time_completed,
|
|
7886
|
+
agent,
|
|
7887
|
+
mode,
|
|
7888
|
+
model,
|
|
7889
|
+
provider,
|
|
7890
|
+
tokens_json,
|
|
7891
|
+
cost,
|
|
7892
|
+
cost_source,
|
|
7893
|
+
parts_json,
|
|
7894
|
+
subagent_id,
|
|
7895
|
+
nickname
|
|
7896
|
+
FROM messages
|
|
7897
|
+
WHERE agent_name = ? AND session_id = ?
|
|
7898
|
+
ORDER BY message_index
|
|
8207
7899
|
`
|
|
8208
|
-
).all(
|
|
8209
|
-
|
|
7900
|
+
).all(agentName, sessionId);
|
|
7901
|
+
const head = sessionFromRow(row);
|
|
7902
|
+
const fileActivityRows = db.prepare(
|
|
7903
|
+
`
|
|
7904
|
+
SELECT agent_name, session_id, project_identity_key, path, kind, count, latest_time
|
|
7905
|
+
FROM session_file_activity
|
|
7906
|
+
WHERE agent_name = ? AND session_id = ?
|
|
7907
|
+
ORDER BY latest_time DESC, count DESC, path
|
|
7908
|
+
LIMIT 500
|
|
7909
|
+
`
|
|
7910
|
+
).all(agentName, sessionId);
|
|
7911
|
+
return {
|
|
7912
|
+
...head,
|
|
7913
|
+
messages: messageRows.map((messageRow) => messageFromCachedRow(messageRow)),
|
|
7914
|
+
file_activity: fileActivityRows.map((activityRow) => fileActivityFromRow(activityRow))
|
|
7915
|
+
};
|
|
8210
7916
|
});
|
|
8211
|
-
return results ?? [];
|
|
8212
|
-
}
|
|
8213
|
-
function normalizeFilePathSearch(value) {
|
|
8214
|
-
return value.trim().replace(/^"|"$/g, "");
|
|
8215
|
-
}
|
|
8216
|
-
function fileActivityFilters(options) {
|
|
8217
|
-
const path2 = options.path ? normalizeFilePathSearch(options.path) : "";
|
|
8218
|
-
return {
|
|
8219
|
-
projectKey: options.projectKey ?? null,
|
|
8220
|
-
projectLike: options.project ? likePattern(options.project) : null,
|
|
8221
|
-
cwdKey: options.cwd ? computeIdentity(options.cwd, realFs).key : null,
|
|
8222
|
-
cwdLike: options.cwd ? likePattern(options.cwd) : null,
|
|
8223
|
-
path: path2,
|
|
8224
|
-
pathLike: path2 ? likePattern(path2) : null
|
|
8225
|
-
};
|
|
8226
7917
|
}
|
|
8227
|
-
function
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
7918
|
+
function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
7919
|
+
withCacheDb((db) => {
|
|
7920
|
+
const deleteAgent = db.prepare("DELETE FROM agent_cache WHERE agent_name = ?");
|
|
7921
|
+
const deleteLegacySessions = db.prepare("DELETE FROM cached_sessions WHERE agent_name = ?");
|
|
7922
|
+
const deleteSession = db.prepare(
|
|
7923
|
+
"DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
|
|
7924
|
+
);
|
|
7925
|
+
const deleteSearchDocument = db.prepare(
|
|
7926
|
+
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
7927
|
+
);
|
|
7928
|
+
const deleteMessages = db.prepare(
|
|
7929
|
+
"DELETE FROM messages WHERE agent_name = ? AND session_id = ?"
|
|
7930
|
+
);
|
|
7931
|
+
const deleteMessageTools = db.prepare(
|
|
7932
|
+
"DELETE FROM message_tools WHERE agent_name = ? AND session_id = ?"
|
|
7933
|
+
);
|
|
7934
|
+
const deleteFileActivity = db.prepare(
|
|
7935
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
7936
|
+
);
|
|
7937
|
+
const deleteProjectSession = db.prepare(
|
|
7938
|
+
"DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
|
|
7939
|
+
);
|
|
7940
|
+
const deleteProjectSessions = db.prepare("DELETE FROM project_sessions WHERE agent_name = ?");
|
|
7941
|
+
const upsertAgent = db.prepare(`
|
|
7942
|
+
INSERT INTO agent_cache(agent_name, timestamp)
|
|
7943
|
+
VALUES (?, ?)
|
|
7944
|
+
ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
|
|
7945
|
+
`);
|
|
7946
|
+
const upsertCachedSession = prepareUpsertCachedSession(db);
|
|
7947
|
+
const upsertSession = prepareUpsertSession(db);
|
|
7948
|
+
const upsertProjectSession = prepareUpsertProjectSession(db);
|
|
7949
|
+
const write = db.transaction(() => {
|
|
7950
|
+
const timestamp = Date.now();
|
|
7951
|
+
const sessionIds = new Set(sessions.map((session) => session.id));
|
|
7952
|
+
const existingSessionIds = db.prepare("SELECT session_id FROM sessions WHERE agent_name = ?").all(agentName);
|
|
7953
|
+
deleteAgent.run(agentName);
|
|
7954
|
+
deleteLegacySessions.run(agentName);
|
|
7955
|
+
deleteProjectSessions.run(agentName);
|
|
7956
|
+
upsertAgent.run(agentName, timestamp);
|
|
7957
|
+
for (const row of existingSessionIds) {
|
|
7958
|
+
const sessionId = String(row.session_id);
|
|
7959
|
+
if (!sessionIds.has(sessionId)) {
|
|
7960
|
+
deleteSearchDocument.run(agentName, sessionId);
|
|
7961
|
+
deleteMessageTools.run(agentName, sessionId);
|
|
7962
|
+
deleteMessages.run(agentName, sessionId);
|
|
7963
|
+
deleteFileActivity.run(agentName, sessionId);
|
|
7964
|
+
deleteProjectSession.run(agentName, sessionId);
|
|
7965
|
+
deleteSession.run(agentName, sessionId);
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7968
|
+
sessions.forEach((session, index) => {
|
|
7969
|
+
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
7970
|
+
const sessionMeta = meta[session.id];
|
|
7971
|
+
const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
|
|
7972
|
+
upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
|
|
7973
|
+
upsertSessionRow(
|
|
7974
|
+
upsertSession,
|
|
7975
|
+
agentName,
|
|
7976
|
+
session,
|
|
7977
|
+
metaJson,
|
|
7978
|
+
index,
|
|
7979
|
+
sourcePathFromMeta(sessionMeta)
|
|
7980
|
+
);
|
|
7981
|
+
writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
|
|
7982
|
+
});
|
|
7983
|
+
});
|
|
7984
|
+
write();
|
|
7985
|
+
deleteLegacyCacheFile();
|
|
7986
|
+
});
|
|
8237
7987
|
}
|
|
8238
|
-
function
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
const params = [];
|
|
8242
|
-
if (options.agent != null) {
|
|
8243
|
-
clauses.push("fa.agent_name = ?");
|
|
8244
|
-
params.push(options.agent);
|
|
8245
|
-
}
|
|
8246
|
-
if (options.sessionId != null) {
|
|
8247
|
-
clauses.push("fa.session_id = ?");
|
|
8248
|
-
params.push(options.sessionId);
|
|
8249
|
-
}
|
|
8250
|
-
if (filters.projectKey != null) {
|
|
8251
|
-
clauses.push("fa.project_identity_key = ?");
|
|
8252
|
-
params.push(filters.projectKey);
|
|
7988
|
+
function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
|
|
7989
|
+
if (changes.length === 0 && removedSessionIds.length === 0) {
|
|
7990
|
+
return;
|
|
8253
7991
|
}
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
"
|
|
7992
|
+
withCacheDb((db) => {
|
|
7993
|
+
const deleteLegacySession = db.prepare(
|
|
7994
|
+
"DELETE FROM cached_sessions WHERE agent_name = ? AND session_id = ?"
|
|
8257
7995
|
);
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
7996
|
+
const deleteSession = db.prepare(
|
|
7997
|
+
"DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
|
|
7998
|
+
);
|
|
7999
|
+
const deleteSearchDocument = db.prepare(
|
|
8000
|
+
"DELETE FROM session_documents WHERE agent_name = ? AND session_id = ?"
|
|
8001
|
+
);
|
|
8002
|
+
const deleteMessages = db.prepare(
|
|
8003
|
+
"DELETE FROM messages WHERE agent_name = ? AND session_id = ?"
|
|
8004
|
+
);
|
|
8005
|
+
const deleteMessageTools = db.prepare(
|
|
8006
|
+
"DELETE FROM message_tools WHERE agent_name = ? AND session_id = ?"
|
|
8007
|
+
);
|
|
8008
|
+
const deleteFileActivity = db.prepare(
|
|
8009
|
+
"DELETE FROM session_file_activity WHERE agent_name = ? AND session_id = ?"
|
|
8010
|
+
);
|
|
8011
|
+
const deleteProjectSession = db.prepare(
|
|
8012
|
+
"DELETE FROM project_sessions WHERE agent_name = ? AND session_id = ?"
|
|
8013
|
+
);
|
|
8014
|
+
const upsertAgent = db.prepare(`
|
|
8015
|
+
INSERT INTO agent_cache(agent_name, timestamp)
|
|
8016
|
+
VALUES (?, ?)
|
|
8017
|
+
ON CONFLICT(agent_name) DO UPDATE SET timestamp = excluded.timestamp
|
|
8018
|
+
`);
|
|
8019
|
+
const upsertCachedSession = prepareUpsertCachedSession(db);
|
|
8020
|
+
const upsertSession = prepareUpsertSession(db);
|
|
8021
|
+
const upsertProjectSession = prepareUpsertProjectSession(db);
|
|
8022
|
+
const write = db.transaction(() => {
|
|
8023
|
+
upsertAgent.run(agentName, Date.now());
|
|
8024
|
+
for (const sessionId of new Set(removedSessionIds)) {
|
|
8025
|
+
deleteLegacySession.run(agentName, sessionId);
|
|
8026
|
+
deleteSearchDocument.run(agentName, sessionId);
|
|
8027
|
+
deleteMessageTools.run(agentName, sessionId);
|
|
8028
|
+
deleteMessages.run(agentName, sessionId);
|
|
8029
|
+
deleteFileActivity.run(agentName, sessionId);
|
|
8030
|
+
deleteProjectSession.run(agentName, sessionId);
|
|
8031
|
+
deleteSession.run(agentName, sessionId);
|
|
8032
|
+
}
|
|
8033
|
+
for (const { session, sortIndex } of changes) {
|
|
8034
|
+
const identity = session.project_identity ?? computeIdentity(session.directory, realFs);
|
|
8035
|
+
const sessionMeta = meta[session.id];
|
|
8036
|
+
const metaJson = sessionMeta ? JSON.stringify(sessionMeta) : null;
|
|
8037
|
+
upsertCachedSession.run(agentName, session.id, JSON.stringify(session), metaJson);
|
|
8038
|
+
upsertSessionRow(
|
|
8039
|
+
upsertSession,
|
|
8040
|
+
agentName,
|
|
8041
|
+
session,
|
|
8042
|
+
metaJson,
|
|
8043
|
+
sortIndex,
|
|
8044
|
+
sourcePathFromMeta(sessionMeta)
|
|
8045
|
+
);
|
|
8046
|
+
writeProjectSessionRow(upsertProjectSession, agentName, session, identity);
|
|
8047
|
+
}
|
|
8048
|
+
});
|
|
8049
|
+
write();
|
|
8050
|
+
deleteLegacyCacheFile();
|
|
8051
|
+
});
|
|
8292
8052
|
}
|
|
8293
|
-
function
|
|
8053
|
+
function clearCache() {
|
|
8054
|
+
setFtsIntegrityCheckedPath(null);
|
|
8294
8055
|
if (!hasCacheStorage()) {
|
|
8295
|
-
|
|
8056
|
+
deleteLegacyCacheFile();
|
|
8057
|
+
return;
|
|
8296
8058
|
}
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
s.total_tokens
|
|
8323
|
-
FROM session_file_activity fa
|
|
8324
|
-
JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
|
|
8325
|
-
${filters.where}
|
|
8326
|
-
ORDER BY fa.latest_time DESC, fa.count DESC, fa.path
|
|
8327
|
-
LIMIT ?
|
|
8328
|
-
`
|
|
8329
|
-
).all(...filters.params, options.limit ?? 50);
|
|
8330
|
-
let rows = withCacheDbReadOnly(queryRows);
|
|
8331
|
-
if (rows == null && options.path) {
|
|
8332
|
-
rows = withCacheDb(queryRows);
|
|
8059
|
+
withCacheDb((db) => {
|
|
8060
|
+
db.exec(`
|
|
8061
|
+
DELETE FROM agent_cache;
|
|
8062
|
+
DELETE FROM cache_initialization;
|
|
8063
|
+
DELETE FROM cached_sessions;
|
|
8064
|
+
DELETE FROM session_documents;
|
|
8065
|
+
DELETE FROM session_file_activity;
|
|
8066
|
+
DELETE FROM message_tools;
|
|
8067
|
+
DELETE FROM messages;
|
|
8068
|
+
DELETE FROM sessions;
|
|
8069
|
+
DELETE FROM project_sessions;
|
|
8070
|
+
`);
|
|
8071
|
+
});
|
|
8072
|
+
deleteLegacyCacheFile();
|
|
8073
|
+
const cachePath = getCachePath2();
|
|
8074
|
+
const walPath = `${cachePath}-wal`;
|
|
8075
|
+
const shmPath = `${cachePath}-shm`;
|
|
8076
|
+
for (const filePath of [walPath, shmPath]) {
|
|
8077
|
+
if (!existsSync12(filePath)) {
|
|
8078
|
+
continue;
|
|
8079
|
+
}
|
|
8080
|
+
try {
|
|
8081
|
+
rmSync(filePath, { force: true });
|
|
8082
|
+
} catch {
|
|
8083
|
+
}
|
|
8333
8084
|
}
|
|
8334
|
-
return (rows ?? []).map((row) => ({
|
|
8335
|
-
...fileActivityFromRow(row),
|
|
8336
|
-
session: sessionHeadFromSearchRow(row)
|
|
8337
|
-
}));
|
|
8338
|
-
}
|
|
8339
|
-
function listSessionFileActivity(agentName, sessionId) {
|
|
8340
|
-
return listFileActivity({ agent: agentName, sessionId, limit: 500 }).map(
|
|
8341
|
-
({ session: _session, ...activity }) => activity
|
|
8342
|
-
);
|
|
8343
|
-
}
|
|
8344
|
-
function highlightFilePath(path2, query) {
|
|
8345
|
-
const needle = normalizeFilePathSearch(query);
|
|
8346
|
-
if (!needle) return path2;
|
|
8347
|
-
const lower = path2.toLowerCase();
|
|
8348
|
-
const index = lower.indexOf(needle.toLowerCase());
|
|
8349
|
-
if (index < 0) return path2;
|
|
8350
|
-
return `${path2.slice(0, index)}<mark>${path2.slice(index, index + needle.length)}</mark>${path2.slice(
|
|
8351
|
-
index + needle.length
|
|
8352
|
-
)}`;
|
|
8353
8085
|
}
|
|
8354
|
-
function
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
if (!path2) return [];
|
|
8358
|
-
const rows = listFileActivity({
|
|
8359
|
-
agent: search.options.agent,
|
|
8360
|
-
projectKey: search.options.projectKey,
|
|
8361
|
-
project: search.options.project,
|
|
8362
|
-
cwd: search.options.cwd,
|
|
8363
|
-
path: path2,
|
|
8364
|
-
kind: search.options.fileKind,
|
|
8365
|
-
from: search.options.from,
|
|
8366
|
-
to: search.options.to,
|
|
8367
|
-
limit: (search.options.limit ?? 50) * 3
|
|
8368
|
-
});
|
|
8369
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8370
|
-
const results = [];
|
|
8371
|
-
for (const row of rows) {
|
|
8372
|
-
const key = `${row.agent_name}/${row.session_id}`;
|
|
8373
|
-
if (seen.has(key)) continue;
|
|
8374
|
-
if (!sessionMatchesSearchCost(row.session, search.options)) continue;
|
|
8375
|
-
seen.add(key);
|
|
8376
|
-
results.push({
|
|
8377
|
-
agentName: row.agent_name,
|
|
8378
|
-
session: row.session,
|
|
8379
|
-
snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
|
|
8380
|
-
matchType: "file_path"
|
|
8381
|
-
});
|
|
8382
|
-
if (results.length >= (search.options.limit ?? 50)) break;
|
|
8086
|
+
function getCacheInfo() {
|
|
8087
|
+
if (!hasCacheStorage()) {
|
|
8088
|
+
return { lastScanTime: null, size: 0 };
|
|
8383
8089
|
}
|
|
8384
|
-
|
|
8090
|
+
const info = withCacheDb((db) => {
|
|
8091
|
+
const timestampRow = db.prepare("SELECT MAX(timestamp) AS value FROM agent_cache").get();
|
|
8092
|
+
const sizeRow = db.prepare("SELECT COUNT(*) AS value FROM sessions").get();
|
|
8093
|
+
const lastScanTime = Number(timestampRow?.value ?? 0) || null;
|
|
8094
|
+
const size = Number(sizeRow?.value ?? 0);
|
|
8095
|
+
return { lastScanTime, size };
|
|
8096
|
+
});
|
|
8097
|
+
return info ?? { lastScanTime: null, size: 0 };
|
|
8385
8098
|
}
|
|
8386
8099
|
function listCachedProjectGroups(sessions) {
|
|
8387
8100
|
if (sessions) {
|
|
@@ -8423,57 +8136,89 @@ function createIdentityResolver() {
|
|
|
8423
8136
|
return identity;
|
|
8424
8137
|
};
|
|
8425
8138
|
}
|
|
8426
|
-
function
|
|
8139
|
+
function attachMissingProjectIdentities(sessions) {
|
|
8427
8140
|
const resolveIdentity = createIdentityResolver();
|
|
8428
8141
|
return sessions.map((session) => {
|
|
8429
8142
|
if (session.project_identity) return session;
|
|
8430
|
-
return {
|
|
8431
|
-
...session,
|
|
8432
|
-
project_identity: resolveIdentity(session.directory)
|
|
8433
|
-
};
|
|
8143
|
+
return { ...session, project_identity: resolveIdentity(session.directory) };
|
|
8434
8144
|
});
|
|
8435
8145
|
}
|
|
8436
|
-
function
|
|
8437
|
-
let result = sessions;
|
|
8438
|
-
if (options.cwd) {
|
|
8439
|
-
result = filterSessionsByProjectScope(result, options.cwd);
|
|
8440
|
-
}
|
|
8441
|
-
if (options.from != null) {
|
|
8442
|
-
result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
|
|
8443
|
-
}
|
|
8444
|
-
if (options.to != null) {
|
|
8445
|
-
result = result.filter((s) => (s.time_updated ?? s.time_created) <= options.to);
|
|
8446
|
-
}
|
|
8447
|
-
return result;
|
|
8448
|
-
}
|
|
8449
|
-
function buildAgentCacheMeta(agent) {
|
|
8146
|
+
function buildAgentCacheMeta(agent, sessionIds) {
|
|
8450
8147
|
const metaMap = agent.getSessionMetaMap?.();
|
|
8451
8148
|
const meta = {};
|
|
8452
8149
|
if (!metaMap) return meta;
|
|
8453
8150
|
for (const [id, data] of metaMap.entries()) {
|
|
8151
|
+
if (sessionIds && !sessionIds.has(id)) continue;
|
|
8454
8152
|
meta[id] = { id, ...data };
|
|
8455
8153
|
}
|
|
8456
8154
|
return meta;
|
|
8457
8155
|
}
|
|
8458
|
-
function
|
|
8459
|
-
return JSON.stringify(
|
|
8156
|
+
function sessionSignature(session) {
|
|
8157
|
+
return JSON.stringify([
|
|
8158
|
+
session.title,
|
|
8159
|
+
session.directory,
|
|
8160
|
+
session.time_created,
|
|
8161
|
+
session.time_updated ?? session.time_created,
|
|
8162
|
+
session.stats.message_count,
|
|
8163
|
+
session.stats.total_input_tokens,
|
|
8164
|
+
session.stats.total_output_tokens,
|
|
8165
|
+
session.stats.total_cost,
|
|
8166
|
+
session.stats.total_tokens ?? 0,
|
|
8167
|
+
session.smart_tags_source_updated_at ?? null
|
|
8168
|
+
]);
|
|
8169
|
+
}
|
|
8170
|
+
function sortSessions(sessions) {
|
|
8171
|
+
return [...sessions].sort(
|
|
8172
|
+
(a, b) => (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created)
|
|
8173
|
+
);
|
|
8460
8174
|
}
|
|
8461
|
-
function
|
|
8175
|
+
function computeSessionDiff(cachedSessions, updatedSessions, changedIds = [], signature = sessionSignature) {
|
|
8462
8176
|
const cachedMap = new Map(cachedSessions.map((session) => [session.id, session]));
|
|
8463
8177
|
const updatedIds = new Set(updatedSessions.map((session) => session.id));
|
|
8464
8178
|
const changedIdSet = new Set(changedIds);
|
|
8465
|
-
const removedSessionIds = cachedSessions.filter((session) => !updatedIds.has(session.id)).map((session) => session.id);
|
|
8466
8179
|
const changes = [];
|
|
8180
|
+
const removedSessionIds = [];
|
|
8181
|
+
let newCount = 0;
|
|
8182
|
+
let updatedCount = 0;
|
|
8467
8183
|
updatedSessions.forEach((session, sortIndex) => {
|
|
8468
8184
|
const cached = cachedMap.get(session.id);
|
|
8469
|
-
if (!cached
|
|
8185
|
+
if (!cached) {
|
|
8186
|
+
newCount += 1;
|
|
8187
|
+
changes.push({ session, sortIndex });
|
|
8188
|
+
return;
|
|
8189
|
+
}
|
|
8190
|
+
const hasSignatureChange = signature(cached) !== signature(session);
|
|
8191
|
+
if (changedIdSet.has(session.id) || hasSignatureChange) {
|
|
8192
|
+
updatedCount += 1;
|
|
8470
8193
|
changes.push({ session, sortIndex });
|
|
8471
8194
|
}
|
|
8472
8195
|
});
|
|
8473
|
-
|
|
8196
|
+
for (const session of cachedSessions) {
|
|
8197
|
+
if (!updatedIds.has(session.id)) {
|
|
8198
|
+
removedSessionIds.push(session.id);
|
|
8199
|
+
}
|
|
8200
|
+
}
|
|
8201
|
+
return {
|
|
8202
|
+
changes,
|
|
8203
|
+
removedSessionIds,
|
|
8204
|
+
counts: { new: newCount, updated: updatedCount, removed: removedSessionIds.length }
|
|
8205
|
+
};
|
|
8206
|
+
}
|
|
8207
|
+
function filterSessions(sessions, options) {
|
|
8208
|
+
let result = sessions;
|
|
8209
|
+
if (options.cwd) {
|
|
8210
|
+
result = filterSessionsByProjectScope(result, options.cwd);
|
|
8211
|
+
}
|
|
8212
|
+
if (options.from != null) {
|
|
8213
|
+
result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
|
|
8214
|
+
}
|
|
8215
|
+
if (options.to != null) {
|
|
8216
|
+
result = result.filter((s) => (s.time_updated ?? s.time_created) <= options.to);
|
|
8217
|
+
}
|
|
8218
|
+
return result;
|
|
8474
8219
|
}
|
|
8475
8220
|
function saveCachedSessionDiff(agent, cachedSessions, updatedSessions, changedIds = []) {
|
|
8476
|
-
const diff =
|
|
8221
|
+
const diff = computeSessionDiff(cachedSessions, updatedSessions, changedIds, sessionSignature);
|
|
8477
8222
|
saveCachedSessionChanges(
|
|
8478
8223
|
agent.name,
|
|
8479
8224
|
diff.changes,
|
|
@@ -8573,19 +8318,16 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
8573
8318
|
const agentStart = performance.now();
|
|
8574
8319
|
const timing = { total: 0 };
|
|
8575
8320
|
const useCache = options.useCache ?? true;
|
|
8576
|
-
const canValidateCache = Boolean(agent.checkForChanges && agent.incrementalScan);
|
|
8577
8321
|
if (useCache) {
|
|
8578
8322
|
const t0 = performance.now();
|
|
8579
8323
|
const cached = loadCachedSessions(agent.name);
|
|
8580
8324
|
timing.cacheLoad = performance.now() - t0;
|
|
8581
8325
|
if (cached !== null) {
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
metaMap.set(id, meta);
|
|
8586
|
-
}
|
|
8587
|
-
agent.setSessionMetaMap(metaMap);
|
|
8326
|
+
const metaMap = /* @__PURE__ */ new Map();
|
|
8327
|
+
for (const [id, meta] of Object.entries(cached.meta)) {
|
|
8328
|
+
metaMap.set(id, meta);
|
|
8588
8329
|
}
|
|
8330
|
+
agent.setSessionMetaMap(metaMap);
|
|
8589
8331
|
if (options.cacheOnly) {
|
|
8590
8332
|
onProgress?.({
|
|
8591
8333
|
agent: agent.name,
|
|
@@ -8594,7 +8336,7 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
8594
8336
|
});
|
|
8595
8337
|
onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
|
|
8596
8338
|
const t32 = performance.now();
|
|
8597
|
-
const cachedWithIdentity2 =
|
|
8339
|
+
const cachedWithIdentity2 = attachMissingProjectIdentities(cached.sessions);
|
|
8598
8340
|
timing.identity = performance.now() - t32;
|
|
8599
8341
|
const filtered3 = filterSessions(cachedWithIdentity2, options);
|
|
8600
8342
|
timing.total = performance.now() - agentStart;
|
|
@@ -8615,58 +8357,56 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
8615
8357
|
phase: "cache",
|
|
8616
8358
|
cachedCount: cached.sessions.length
|
|
8617
8359
|
});
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8360
|
+
onProgress?.({ agent: agent.name, phase: "checking" });
|
|
8361
|
+
const t1 = performance.now();
|
|
8362
|
+
const checkResult = await Promise.resolve(
|
|
8363
|
+
agent.checkForChanges(cached.timestamp, cached.sessions)
|
|
8364
|
+
);
|
|
8365
|
+
timing.checkChanges = performance.now() - t1;
|
|
8366
|
+
if (checkResult.hasChanges) {
|
|
8367
|
+
onProgress?.({
|
|
8368
|
+
agent: agent.name,
|
|
8369
|
+
phase: "incremental",
|
|
8370
|
+
changedCount: checkResult.changedIds?.length
|
|
8371
|
+
});
|
|
8372
|
+
const t2 = performance.now();
|
|
8373
|
+
const updatedSessions = await Promise.resolve(
|
|
8374
|
+
agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
|
|
8623
8375
|
);
|
|
8624
|
-
timing.
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
agent.incrementalScan(cached.sessions, checkResult.changedIds || [])
|
|
8634
|
-
);
|
|
8635
|
-
timing.scan = performance.now() - t2;
|
|
8636
|
-
const t32 = performance.now();
|
|
8637
|
-
const sessionsWithIdentity = attachProjectIdentities(updatedSessions);
|
|
8638
|
-
timing.identity = performance.now() - t32;
|
|
8639
|
-
const t42 = performance.now();
|
|
8640
|
-
const tagged2 = options.includeSmartTags === false ? { sessions: sessionsWithIdentity, changed: false } : await ensureSessionTags(agent, sessionsWithIdentity, options.smartTagWorkerUrl);
|
|
8641
|
-
timing.tags = performance.now() - t42;
|
|
8642
|
-
if (options.writeCache !== false) {
|
|
8643
|
-
saveCachedSessionDiff(
|
|
8644
|
-
agent,
|
|
8645
|
-
cached.sessions,
|
|
8646
|
-
tagged2.sessions,
|
|
8647
|
-
checkResult.changedIds ?? []
|
|
8648
|
-
);
|
|
8649
|
-
}
|
|
8650
|
-
onProgress?.({
|
|
8651
|
-
agent: agent.name,
|
|
8652
|
-
phase: "complete",
|
|
8653
|
-
newCount: tagged2.sessions.length
|
|
8654
|
-
});
|
|
8655
|
-
const filtered3 = filterSessions(tagged2.sessions, options);
|
|
8656
|
-
timing.total = performance.now() - agentStart;
|
|
8657
|
-
return {
|
|
8376
|
+
timing.scan = performance.now() - t2;
|
|
8377
|
+
const t32 = performance.now();
|
|
8378
|
+
const sessionsWithIdentity = attachMissingProjectIdentities(updatedSessions);
|
|
8379
|
+
timing.identity = performance.now() - t32;
|
|
8380
|
+
const t42 = performance.now();
|
|
8381
|
+
const tagged2 = options.includeSmartTags === false ? { sessions: sessionsWithIdentity, changed: false } : await ensureSessionTags(agent, sessionsWithIdentity, options.smartTagWorkerUrl);
|
|
8382
|
+
timing.tags = performance.now() - t42;
|
|
8383
|
+
if (options.writeCache !== false) {
|
|
8384
|
+
saveCachedSessionDiff(
|
|
8658
8385
|
agent,
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
cacheTimestamp: checkResult.timestamp
|
|
8664
|
-
};
|
|
8386
|
+
cached.sessions,
|
|
8387
|
+
tagged2.sessions,
|
|
8388
|
+
checkResult.changedIds ?? []
|
|
8389
|
+
);
|
|
8665
8390
|
}
|
|
8666
|
-
onProgress?.({
|
|
8391
|
+
onProgress?.({
|
|
8392
|
+
agent: agent.name,
|
|
8393
|
+
phase: "complete",
|
|
8394
|
+
newCount: tagged2.sessions.length
|
|
8395
|
+
});
|
|
8396
|
+
const filtered3 = filterSessions(tagged2.sessions, options);
|
|
8397
|
+
timing.total = performance.now() - agentStart;
|
|
8398
|
+
return {
|
|
8399
|
+
agent,
|
|
8400
|
+
heads: filtered3,
|
|
8401
|
+
fromCache: true,
|
|
8402
|
+
refreshed: true,
|
|
8403
|
+
timing,
|
|
8404
|
+
cacheTimestamp: checkResult.timestamp
|
|
8405
|
+
};
|
|
8667
8406
|
}
|
|
8407
|
+
onProgress?.({ agent: agent.name, phase: "complete", newCount: cached.sessions.length });
|
|
8668
8408
|
const t3 = performance.now();
|
|
8669
|
-
const cachedWithIdentity =
|
|
8409
|
+
const cachedWithIdentity = attachMissingProjectIdentities(cached.sessions);
|
|
8670
8410
|
timing.identity = performance.now() - t3;
|
|
8671
8411
|
const t4 = performance.now();
|
|
8672
8412
|
const tagged = options.includeSmartTags === false ? { sessions: cachedWithIdentity, changed: false } : await ensureSessionTags(agent, cachedWithIdentity, options.smartTagWorkerUrl);
|
|
@@ -8718,7 +8458,7 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
|
|
|
8718
8458
|
perf.end(scanMarker);
|
|
8719
8459
|
timing.scan = performance.now() - t0;
|
|
8720
8460
|
const t1 = performance.now();
|
|
8721
|
-
const headsWithIdentity =
|
|
8461
|
+
const headsWithIdentity = attachMissingProjectIdentities(heads);
|
|
8722
8462
|
timing.identity = performance.now() - t1;
|
|
8723
8463
|
const t2 = performance.now();
|
|
8724
8464
|
const tagged = options.includeSmartTags === false ? { sessions: headsWithIdentity, changed: false } : await ensureSessionTags(agent, headsWithIdentity, options.smartTagWorkerUrl);
|
|
@@ -9090,6 +8830,161 @@ function deleteBookmark(agentKey, sessionId) {
|
|
|
9090
8830
|
).run(agentKey, sessionId);
|
|
9091
8831
|
});
|
|
9092
8832
|
}
|
|
8833
|
+
var DASHBOARD_RECENT_LIMIT = 10;
|
|
8834
|
+
function getTotalTokens(stats) {
|
|
8835
|
+
return stats.total_tokens ?? stats.total_input_tokens + stats.total_output_tokens;
|
|
8836
|
+
}
|
|
8837
|
+
function getSessionAgentName(session) {
|
|
8838
|
+
return session.slug.split("/")[0]?.toLowerCase() || "unknown";
|
|
8839
|
+
}
|
|
8840
|
+
function getSessionActivityTime(session) {
|
|
8841
|
+
return session.time_updated ?? session.time_created;
|
|
8842
|
+
}
|
|
8843
|
+
function toLocalDateKey(ts) {
|
|
8844
|
+
const d = new Date(ts);
|
|
8845
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(
|
|
8846
|
+
d.getDate()
|
|
8847
|
+
).padStart(2, "0")}`;
|
|
8848
|
+
}
|
|
8849
|
+
function startOfLocalDay(ts) {
|
|
8850
|
+
const d = new Date(ts);
|
|
8851
|
+
d.setHours(0, 0, 0, 0);
|
|
8852
|
+
return d.getTime();
|
|
8853
|
+
}
|
|
8854
|
+
function buildDashboard(sessions, options) {
|
|
8855
|
+
const { byAgentNames, scope, from, to, agentInfoMap } = options;
|
|
8856
|
+
const agentMetrics = /* @__PURE__ */ new Map();
|
|
8857
|
+
const agentMetricKeyByName = /* @__PURE__ */ new Map();
|
|
8858
|
+
for (const name of byAgentNames) {
|
|
8859
|
+
if (scope.agent && name.toLowerCase() !== scope.agent) continue;
|
|
8860
|
+
agentMetrics.set(name, { sessions: 0, messages: 0, tokens: 0 });
|
|
8861
|
+
agentMetricKeyByName.set(name.toLowerCase(), name);
|
|
8862
|
+
}
|
|
8863
|
+
let totalSessions = 0;
|
|
8864
|
+
let totalMessages = 0;
|
|
8865
|
+
let totalTokens = 0;
|
|
8866
|
+
let totalCost = 0;
|
|
8867
|
+
let hasEstimatedCost = false;
|
|
8868
|
+
let latestActivity = 0;
|
|
8869
|
+
const recentCandidates = [];
|
|
8870
|
+
const modelAgg = /* @__PURE__ */ new Map();
|
|
8871
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
8872
|
+
const dailyTokenMap = /* @__PURE__ */ new Map();
|
|
8873
|
+
if (from != null) {
|
|
8874
|
+
const bucketStart = startOfLocalDay(from);
|
|
8875
|
+
const bucketDays = Math.floor((startOfLocalDay(to) - bucketStart) / 864e5) + 1;
|
|
8876
|
+
for (let i = 0; i < bucketDays; i += 1) {
|
|
8877
|
+
const ts = bucketStart + i * 864e5;
|
|
8878
|
+
const key = toLocalDateKey(ts);
|
|
8879
|
+
dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
|
|
8880
|
+
dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
|
|
8881
|
+
}
|
|
8882
|
+
}
|
|
8883
|
+
for (const session of sessions) {
|
|
8884
|
+
const agentName = getSessionAgentName(session);
|
|
8885
|
+
if (scope.agent && agentName !== scope.agent) continue;
|
|
8886
|
+
if (scope.projectKey) {
|
|
8887
|
+
const identity = session.project_identity;
|
|
8888
|
+
if (!identity || identity.key !== scope.projectKey) continue;
|
|
8889
|
+
if (scope.projectKind && identity.kind !== scope.projectKind) continue;
|
|
8890
|
+
}
|
|
8891
|
+
const activity = getSessionActivityTime(session);
|
|
8892
|
+
if (from != null && activity < from) continue;
|
|
8893
|
+
if (activity > to) continue;
|
|
8894
|
+
const messageCount = session.stats.message_count;
|
|
8895
|
+
const sessionTokens = getTotalTokens(session.stats);
|
|
8896
|
+
totalSessions += 1;
|
|
8897
|
+
totalMessages += messageCount;
|
|
8898
|
+
totalTokens += sessionTokens;
|
|
8899
|
+
totalCost += session.stats.total_cost ?? 0;
|
|
8900
|
+
if (session.stats.cost_source === "estimated") hasEstimatedCost = true;
|
|
8901
|
+
if (activity > latestActivity) latestActivity = activity;
|
|
8902
|
+
const metricKey = agentMetricKeyByName.get(agentName);
|
|
8903
|
+
if (metricKey) {
|
|
8904
|
+
const metric = agentMetrics.get(metricKey);
|
|
8905
|
+
metric.sessions += 1;
|
|
8906
|
+
metric.messages += messageCount;
|
|
8907
|
+
metric.tokens += sessionTokens;
|
|
8908
|
+
}
|
|
8909
|
+
const key = toLocalDateKey(activity);
|
|
8910
|
+
let bucket = dailyMap.get(key);
|
|
8911
|
+
if (!bucket) {
|
|
8912
|
+
bucket = { date: key, sessions: 0, messages: 0 };
|
|
8913
|
+
dailyMap.set(key, bucket);
|
|
8914
|
+
}
|
|
8915
|
+
bucket.sessions += 1;
|
|
8916
|
+
bucket.messages += messageCount;
|
|
8917
|
+
let tokenBucket = dailyTokenMap.get(key);
|
|
8918
|
+
if (!tokenBucket) {
|
|
8919
|
+
tokenBucket = { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 };
|
|
8920
|
+
dailyTokenMap.set(key, tokenBucket);
|
|
8921
|
+
}
|
|
8922
|
+
const cacheRead = session.stats.total_cache_read_tokens ?? 0;
|
|
8923
|
+
const cacheCreate = session.stats.total_cache_create_tokens ?? 0;
|
|
8924
|
+
const pureInput = session.stats.total_input_tokens - cacheRead - cacheCreate;
|
|
8925
|
+
tokenBucket.input += Math.max(0, pureInput);
|
|
8926
|
+
tokenBucket.output += session.stats.total_output_tokens;
|
|
8927
|
+
tokenBucket.cache_read += cacheRead;
|
|
8928
|
+
tokenBucket.cache_create += cacheCreate;
|
|
8929
|
+
if (session.model_usage) {
|
|
8930
|
+
for (const [model, tokens] of Object.entries(session.model_usage)) {
|
|
8931
|
+
const entry = modelAgg.get(model);
|
|
8932
|
+
if (entry) {
|
|
8933
|
+
entry.tokens += tokens;
|
|
8934
|
+
entry.sessions += 1;
|
|
8935
|
+
} else {
|
|
8936
|
+
modelAgg.set(model, { tokens, sessions: 1 });
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
}
|
|
8940
|
+
let recentIndex = recentCandidates.length;
|
|
8941
|
+
for (let i = 0; i < recentCandidates.length; i += 1) {
|
|
8942
|
+
if (activity > recentCandidates[i].activity) {
|
|
8943
|
+
recentIndex = i;
|
|
8944
|
+
break;
|
|
8945
|
+
}
|
|
8946
|
+
}
|
|
8947
|
+
if (recentIndex < DASHBOARD_RECENT_LIMIT) {
|
|
8948
|
+
recentCandidates.splice(recentIndex, 0, { session, activity });
|
|
8949
|
+
if (recentCandidates.length > DASHBOARD_RECENT_LIMIT) recentCandidates.pop();
|
|
8950
|
+
}
|
|
8951
|
+
}
|
|
8952
|
+
const perAgent = [...agentMetrics.entries()].map(([name, metrics]) => {
|
|
8953
|
+
const info = agentInfoMap?.get(name);
|
|
8954
|
+
return {
|
|
8955
|
+
name,
|
|
8956
|
+
displayName: info?.displayName ?? name,
|
|
8957
|
+
icon: info?.icon ?? "",
|
|
8958
|
+
sessions: metrics.sessions,
|
|
8959
|
+
messages: metrics.messages,
|
|
8960
|
+
tokens: metrics.tokens
|
|
8961
|
+
};
|
|
8962
|
+
}).filter((item) => item.sessions > 0).sort((a, b) => b.sessions - a.sessions);
|
|
8963
|
+
const dailyActivity = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
8964
|
+
const dailyTokenActivity = [...dailyTokenMap.values()].sort(
|
|
8965
|
+
(a, b) => a.date.localeCompare(b.date)
|
|
8966
|
+
);
|
|
8967
|
+
const modelDistribution = [...modelAgg.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
|
|
8968
|
+
const recentSessions = recentCandidates.map(({ session }) => {
|
|
8969
|
+
const agentKey = getSessionAgentName(session);
|
|
8970
|
+
return { ...session, agentName: agentKey };
|
|
8971
|
+
});
|
|
8972
|
+
return {
|
|
8973
|
+
totals: {
|
|
8974
|
+
sessions: totalSessions,
|
|
8975
|
+
messages: totalMessages,
|
|
8976
|
+
tokens: totalTokens,
|
|
8977
|
+
cost: totalCost,
|
|
8978
|
+
cost_source: totalCost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
8979
|
+
latestActivity: latestActivity || void 0
|
|
8980
|
+
},
|
|
8981
|
+
perAgent,
|
|
8982
|
+
dailyActivity,
|
|
8983
|
+
dailyTokenActivity,
|
|
8984
|
+
modelDistribution,
|
|
8985
|
+
recentSessions
|
|
8986
|
+
};
|
|
8987
|
+
}
|
|
9093
8988
|
|
|
9094
8989
|
export {
|
|
9095
8990
|
registerAgent,
|
|
@@ -9102,6 +8997,8 @@ export {
|
|
|
9102
8997
|
filteredSession,
|
|
9103
8998
|
getParsedSession,
|
|
9104
8999
|
BaseAgent,
|
|
9000
|
+
FileSystemSessionSource,
|
|
9001
|
+
DatabaseSessionSource,
|
|
9105
9002
|
firstExisting,
|
|
9106
9003
|
resolveProviderRoots,
|
|
9107
9004
|
getCursorDataPath,
|
|
@@ -9148,6 +9045,12 @@ export {
|
|
|
9148
9045
|
summarizeFileActivity,
|
|
9149
9046
|
extractSessionFileActivity,
|
|
9150
9047
|
parseSearchQuery,
|
|
9048
|
+
syncSessionSearchIndex,
|
|
9049
|
+
syncSessionSearchIndexChanges,
|
|
9050
|
+
searchSessions,
|
|
9051
|
+
listFileActivity,
|
|
9052
|
+
listSessionFileActivity,
|
|
9053
|
+
searchFileActivitySessions,
|
|
9151
9054
|
loadCachedSessions,
|
|
9152
9055
|
isAgentCacheInitialized,
|
|
9153
9056
|
markAgentCacheInitialized,
|
|
@@ -9156,13 +9059,12 @@ export {
|
|
|
9156
9059
|
saveCachedSessionChanges,
|
|
9157
9060
|
clearCache,
|
|
9158
9061
|
getCacheInfo,
|
|
9159
|
-
syncSessionSearchIndex,
|
|
9160
|
-
syncSessionSearchIndexChanges,
|
|
9161
|
-
searchSessions,
|
|
9162
|
-
listFileActivity,
|
|
9163
|
-
listSessionFileActivity,
|
|
9164
|
-
searchFileActivitySessions,
|
|
9165
9062
|
listCachedProjectGroups,
|
|
9063
|
+
attachMissingProjectIdentities,
|
|
9064
|
+
buildAgentCacheMeta,
|
|
9065
|
+
sessionSignature,
|
|
9066
|
+
sortSessions,
|
|
9067
|
+
computeSessionDiff,
|
|
9166
9068
|
filterSessions,
|
|
9167
9069
|
scanSessions,
|
|
9168
9070
|
scanSessionsAsync,
|
|
@@ -9170,6 +9072,13 @@ export {
|
|
|
9170
9072
|
listBookmarks,
|
|
9171
9073
|
upsertBookmark,
|
|
9172
9074
|
importBookmarks,
|
|
9173
|
-
deleteBookmark
|
|
9075
|
+
deleteBookmark,
|
|
9076
|
+
DASHBOARD_RECENT_LIMIT,
|
|
9077
|
+
getTotalTokens,
|
|
9078
|
+
getSessionAgentName,
|
|
9079
|
+
getSessionActivityTime,
|
|
9080
|
+
toLocalDateKey,
|
|
9081
|
+
startOfLocalDay,
|
|
9082
|
+
buildDashboard
|
|
9174
9083
|
};
|
|
9175
|
-
//# sourceMappingURL=chunk-
|
|
9084
|
+
//# sourceMappingURL=chunk-ZQMIB7QO.js.map
|