codesesh 0.17.0 → 0.19.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/README.md +6 -4
- package/dist/{chunk-7APNDHQ6.js → chunk-G2BTNW3C.js} +2262 -495
- package/dist/chunk-G2BTNW3C.js.map +1 -0
- package/dist/{chunk-A4U2SMJJ.js → chunk-KD3DGWZY.js} +20 -12
- package/dist/chunk-KD3DGWZY.js.map +1 -0
- package/dist/{dist-KEPJFHOC.js → dist-ALXOBKV2.js} +26 -4
- package/dist/index.js +382 -114
- package/dist/index.js.map +1 -1
- package/dist/scan-refresh-worker.js +243 -24
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +73 -39
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +2 -2
- package/dist/web/assets/AgentIcon-CEj7HO1m.js +1 -0
- package/dist/web/assets/BookmarkButton-DLEnIyeE.js +1 -0
- package/dist/web/assets/Dashboard-DUG0IFcy.js +39 -0
- package/dist/web/assets/DialogTitle-Dv6DQPIb.js +1 -0
- package/dist/web/assets/InteractiveReceipt-xnRqJvfd.js +1 -0
- package/dist/web/assets/PrismHighlighter-C7OfXHq2.js +6 -0
- package/dist/web/assets/Projects-BMaZc6J5.js +1 -0
- package/dist/web/assets/SearchFilterBar-iP6pzlZo.js +1 -0
- package/dist/web/assets/SearchResultsPanel-RuiLPkbP.js +1 -0
- package/dist/web/assets/SessionDetail-De1Wl3mu.js +54 -0
- package/dist/web/assets/index-B-CV7pFu.css +2 -0
- package/dist/web/assets/index-CQI8F_8X.js +1930 -0
- package/dist/web/assets/index.dom-CCLJ9_FR.js +1 -0
- package/dist/web/assets/jsx-runtime-n5LQ9ujS.js +1 -0
- package/dist/web/assets/session-indexes-BuxBafvh.js +4 -0
- package/dist/web/assets/session-title-CNhapdPJ.js +1 -0
- package/dist/web/assets/useTheme-CwDcVdGz.js +1 -0
- package/dist/web/assets/utils-B6KiDbIe.js +1 -0
- package/dist/web/index.html +10 -7
- package/package.json +3 -3
- package/dist/chunk-7APNDHQ6.js.map +0 -1
- package/dist/chunk-A4U2SMJJ.js.map +0 -1
- package/dist/web/assets/index-B076CRkD.js +0 -109
- package/dist/web/assets/index-CWNbVhet.css +0 -2
- package/dist/web/assets/markdown-dR4kAewZ.js +0 -14
- package/dist/web/assets/react-D9s532MZ.js +0 -1821
- package/dist/web/assets/rolldown-runtime-QTnfLwEv.js +0 -1
- package/dist/web/assets/syntax-mZXQuVcd.js +0 -6
- package/dist/web/assets/vendor-i9U7_fNF.js +0 -41
- /package/dist/{dist-KEPJFHOC.js.map → dist-ALXOBKV2.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// ../core/dist/chunk-
|
|
3
|
+
// ../core/dist/chunk-B67H5WZJ.mjs
|
|
4
4
|
function toRecord(value) {
|
|
5
5
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6
6
|
}
|
|
@@ -120,6 +120,76 @@ function normalizeMessageParts(value) {
|
|
|
120
120
|
return normalized ? [normalized] : [];
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
+
function mergeSessionsUpdatedEvents(previous, next) {
|
|
124
|
+
const changedSessionHeads = /* @__PURE__ */ new Map();
|
|
125
|
+
const removedSessionRefs = /* @__PURE__ */ new Map();
|
|
126
|
+
const sessionKey2 = (agentName, sessionId) => `${agentName}\0${sessionId}`;
|
|
127
|
+
const addChanged = (item) => {
|
|
128
|
+
const key = sessionKey2(item.reference.agentName, item.reference.sessionId);
|
|
129
|
+
removedSessionRefs.delete(key);
|
|
130
|
+
changedSessionHeads.set(key, item);
|
|
131
|
+
};
|
|
132
|
+
const addRemoved = (item) => {
|
|
133
|
+
const key = sessionKey2(item.agentName, item.sessionId);
|
|
134
|
+
changedSessionHeads.delete(key);
|
|
135
|
+
removedSessionRefs.set(key, item);
|
|
136
|
+
};
|
|
137
|
+
for (const item of previous.changedSessionHeads) addChanged(item);
|
|
138
|
+
for (const item of previous.removedSessionRefs) addRemoved(item);
|
|
139
|
+
for (const item of next.changedSessionHeads) addChanged(item);
|
|
140
|
+
for (const item of next.removedSessionRefs) addRemoved(item);
|
|
141
|
+
return {
|
|
142
|
+
type: "sessions-updated",
|
|
143
|
+
changedAgents: Array.from(/* @__PURE__ */ new Set([...previous.changedAgents, ...next.changedAgents])),
|
|
144
|
+
newSessions: previous.newSessions + next.newSessions,
|
|
145
|
+
updatedSessions: previous.updatedSessions + next.updatedSessions,
|
|
146
|
+
removedSessions: previous.removedSessions + next.removedSessions,
|
|
147
|
+
totalSessions: next.totalSessions,
|
|
148
|
+
timestamp: next.timestamp,
|
|
149
|
+
changedSessionHeads: [...changedSessionHeads.values()],
|
|
150
|
+
removedSessionRefs: [...removedSessionRefs.values()]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function startOfCalendarDay(timestamp) {
|
|
154
|
+
const date = new Date(timestamp);
|
|
155
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
156
|
+
}
|
|
157
|
+
function addCalendarDays(timestamp, days) {
|
|
158
|
+
const date = new Date(timestamp);
|
|
159
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days).getTime();
|
|
160
|
+
}
|
|
161
|
+
function toCalendarDayNumber(timestamp) {
|
|
162
|
+
const date = new Date(timestamp);
|
|
163
|
+
return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / 864e5;
|
|
164
|
+
}
|
|
165
|
+
function countCalendarDays(from, to) {
|
|
166
|
+
return Math.max(1, toCalendarDayNumber(to) - toCalendarDayNumber(from) + 1);
|
|
167
|
+
}
|
|
168
|
+
function toCalendarDayKey(timestamp) {
|
|
169
|
+
const date = new Date(timestamp);
|
|
170
|
+
return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(
|
|
171
|
+
2,
|
|
172
|
+
"0"
|
|
173
|
+
)}-${String(date.getDate()).padStart(2, "0")}`;
|
|
174
|
+
}
|
|
175
|
+
var PROJECT_IDENTITY_KINDS = [
|
|
176
|
+
"git_remote",
|
|
177
|
+
"git_common_dir",
|
|
178
|
+
"manifest_path",
|
|
179
|
+
"synthetic",
|
|
180
|
+
"path",
|
|
181
|
+
"loose"
|
|
182
|
+
];
|
|
183
|
+
var KIND_LOOKUP = new Set(PROJECT_IDENTITY_KINDS);
|
|
184
|
+
function isProjectIdentityKind(value) {
|
|
185
|
+
return KIND_LOOKUP.has(value);
|
|
186
|
+
}
|
|
187
|
+
function getProjectIdentityKey(identity) {
|
|
188
|
+
return `${identity.kind}:${identity.key}`;
|
|
189
|
+
}
|
|
190
|
+
function matchesProjectIdentity(identity, expected) {
|
|
191
|
+
return identity?.kind === expected.kind && identity.key === expected.key;
|
|
192
|
+
}
|
|
123
193
|
var UNKNOWN_AGENT_NAME = "unknown";
|
|
124
194
|
function normalizeSessionReference(reference) {
|
|
125
195
|
return {
|
|
@@ -144,6 +214,13 @@ function formatSessionReference(reference) {
|
|
|
144
214
|
function getSessionAgentKey(session) {
|
|
145
215
|
return parseSessionReference(session.slug)?.agentName ?? UNKNOWN_AGENT_NAME;
|
|
146
216
|
}
|
|
217
|
+
function agentRoutePath(agentName) {
|
|
218
|
+
return `/${encodeURIComponent(agentName.trim().toLowerCase())}`;
|
|
219
|
+
}
|
|
220
|
+
function sessionRoutePath(reference) {
|
|
221
|
+
const normalized = normalizeSessionReference(reference);
|
|
222
|
+
return `${agentRoutePath(normalized.agentName)}/${encodeURIComponent(normalized.sessionId)}`;
|
|
223
|
+
}
|
|
147
224
|
function compareSessionActivityDesc(a, b) {
|
|
148
225
|
return (b.time_updated ?? b.time_created) - (a.time_updated ?? a.time_created);
|
|
149
226
|
}
|
|
@@ -176,6 +253,44 @@ function mergeSortedSessions(shards) {
|
|
|
176
253
|
}
|
|
177
254
|
return merged;
|
|
178
255
|
}
|
|
256
|
+
function referenceKey(reference) {
|
|
257
|
+
return `${reference.agentName.trim().toLowerCase()}\0${reference.sessionId}`;
|
|
258
|
+
}
|
|
259
|
+
function sessionKey(session) {
|
|
260
|
+
return `${getSessionAgentKey(session)}\0${session.id}`;
|
|
261
|
+
}
|
|
262
|
+
function hasActivityInWindow(session, from, to) {
|
|
263
|
+
const activity = session.time_updated ?? session.time_created;
|
|
264
|
+
return (from == null || activity >= from) && (to == null || activity <= to);
|
|
265
|
+
}
|
|
266
|
+
function isChildSession(session) {
|
|
267
|
+
return session.parent_reference != null;
|
|
268
|
+
}
|
|
269
|
+
function getRootSessions(sessions) {
|
|
270
|
+
return sessions.filter((session) => !session.parent_reference);
|
|
271
|
+
}
|
|
272
|
+
function filterSessionTreeByActivityWindow(sessions, from, to) {
|
|
273
|
+
if (from == null && to == null) return sessions;
|
|
274
|
+
const available = new Set(sessions.map(sessionKey));
|
|
275
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
276
|
+
for (const session of sessions) {
|
|
277
|
+
const parent = session.parent_reference;
|
|
278
|
+
if (!parent || !available.has(referenceKey(parent))) continue;
|
|
279
|
+
const parentKey = referenceKey(parent);
|
|
280
|
+
const children = childrenByParent.get(parentKey);
|
|
281
|
+
if (children) children.push(sessionKey(session));
|
|
282
|
+
else childrenByParent.set(parentKey, [sessionKey(session)]);
|
|
283
|
+
}
|
|
284
|
+
const visible = /* @__PURE__ */ new Set();
|
|
285
|
+
const pending2 = getRootSessions(sessions).filter((session) => hasActivityInWindow(session, from, to)).map(sessionKey);
|
|
286
|
+
while (pending2.length > 0) {
|
|
287
|
+
const key = pending2.pop();
|
|
288
|
+
if (visible.has(key)) continue;
|
|
289
|
+
visible.add(key);
|
|
290
|
+
for (const childKey of childrenByParent.get(key) ?? []) pending2.push(childKey);
|
|
291
|
+
}
|
|
292
|
+
return sessions.filter((session) => visible.has(sessionKey(session)));
|
|
293
|
+
}
|
|
179
294
|
var SAMPLE_SESSION_HEAD = {
|
|
180
295
|
id: "session-1",
|
|
181
296
|
slug: "claudecode/session-1",
|
|
@@ -253,8 +368,8 @@ var SAMPLE_DASHBOARD_DATA = {
|
|
|
253
368
|
};
|
|
254
369
|
|
|
255
370
|
// ../core/dist/index.mjs
|
|
256
|
-
import { existsSync as
|
|
257
|
-
import { join as
|
|
371
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
|
|
372
|
+
import { join as join5, basename as basename3, dirname as dirname2 } from "path";
|
|
258
373
|
import { existsSync, readdirSync, statSync } from "fs";
|
|
259
374
|
import { join } from "path";
|
|
260
375
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -263,38 +378,50 @@ import { join as join2 } from "path";
|
|
|
263
378
|
import { closeSync, openSync, readSync } from "fs";
|
|
264
379
|
import { StringDecoder } from "string_decoder";
|
|
265
380
|
import { basename } from "path";
|
|
266
|
-
import { existsSync as
|
|
381
|
+
import { existsSync as existsSync4, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
267
382
|
import { homedir as homedir2 } from "os";
|
|
268
|
-
import { join as
|
|
269
|
-
import {
|
|
270
|
-
import {
|
|
271
|
-
import {
|
|
383
|
+
import { join as join4 } from "path";
|
|
384
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
385
|
+
import { basename as basename2, dirname, join as join3 } from "path";
|
|
386
|
+
import { join as join7 } from "path";
|
|
387
|
+
import { existsSync as existsSync6 } from "fs";
|
|
388
|
+
import { basename as basename4, dirname as dirname3, join as join6 } from "path";
|
|
272
389
|
import { createRequire } from "module";
|
|
273
390
|
import { createHash } from "crypto";
|
|
274
|
-
import { existsSync as
|
|
275
|
-
import { join as
|
|
276
|
-
import {
|
|
277
|
-
import {
|
|
278
|
-
import {
|
|
391
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
|
|
392
|
+
import { join as join8, basename as basename5, dirname as dirname4 } from "path";
|
|
393
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
394
|
+
import { basename as basename6, dirname as dirname5, join as join9, resolve } from "path";
|
|
395
|
+
import {
|
|
396
|
+
closeSync as closeSync2,
|
|
397
|
+
existsSync as existsSync9,
|
|
398
|
+
openSync as openSync2,
|
|
399
|
+
readdirSync as readdirSync6,
|
|
400
|
+
readFileSync as readFileSync5,
|
|
401
|
+
readSync as readSync2,
|
|
402
|
+
statSync as statSync6
|
|
403
|
+
} from "fs";
|
|
404
|
+
import { join as join10, basename as basename7 } from "path";
|
|
405
|
+
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync7 } from "fs";
|
|
279
406
|
import { homedir as homedir3, platform as platform2 } from "os";
|
|
280
|
-
import { join as
|
|
281
|
-
import { existsSync as
|
|
282
|
-
import { basename as
|
|
407
|
+
import { join as join11, normalize } from "path";
|
|
408
|
+
import { existsSync as existsSync11 } from "fs";
|
|
409
|
+
import { basename as basename8, join as join12 } from "path";
|
|
283
410
|
import { homedir as homedir4, platform as platform3 } from "os";
|
|
284
|
-
import { join as
|
|
411
|
+
import { join as join13 } from "path";
|
|
285
412
|
import { availableParallelism } from "os";
|
|
286
413
|
import { Worker } from "worker_threads";
|
|
287
|
-
import { existsSync as
|
|
414
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
288
415
|
import { spawnSync } from "child_process";
|
|
289
416
|
import * as os from "os";
|
|
290
417
|
import * as path from "path";
|
|
291
|
-
import { resolve, sep } from "path";
|
|
292
|
-
import { existsSync as
|
|
293
|
-
import { existsSync as
|
|
294
|
-
import { join as
|
|
418
|
+
import { resolve as resolve2, sep } from "path";
|
|
419
|
+
import { existsSync as existsSync14, rmSync as rmSync2, unlinkSync } from "fs";
|
|
420
|
+
import { existsSync as existsSync13 } from "fs";
|
|
421
|
+
import { join as join14 } from "path";
|
|
295
422
|
import { homedir as homedir6 } from "os";
|
|
296
423
|
import { homedir as homedir7, platform as platform4 } from "os";
|
|
297
|
-
import { join as
|
|
424
|
+
import { join as join15 } from "path";
|
|
298
425
|
var registrations = [];
|
|
299
426
|
function registerAgent(reg) {
|
|
300
427
|
registrations.push(reg);
|
|
@@ -389,6 +516,9 @@ function diffSessionSources(refs, cachedSessions, cachedMeta, options) {
|
|
|
389
516
|
return { changedIds, removedIds };
|
|
390
517
|
}
|
|
391
518
|
var BaseAgent = class {
|
|
519
|
+
filterCachedSessions(sessions) {
|
|
520
|
+
return sessions;
|
|
521
|
+
}
|
|
392
522
|
getUri(sessionId) {
|
|
393
523
|
return `${this.name}://${sessionId}`;
|
|
394
524
|
}
|
|
@@ -396,6 +526,14 @@ var BaseAgent = class {
|
|
|
396
526
|
var FileSystemSessionSource = class extends BaseAgent {
|
|
397
527
|
sessionMetaMap = /* @__PURE__ */ new Map();
|
|
398
528
|
sourceFileStats = /* @__PURE__ */ new Map();
|
|
529
|
+
/**
|
|
530
|
+
* 变更集合扩展:当某些会话变更会影响其他会话的派生数据时
|
|
531
|
+
* (如 subagent 文件变更需要父会话重新聚合 token 统计),
|
|
532
|
+
* 子类返回需要一并重解析的会话 ID 集合。默认无关联,原样返回。
|
|
533
|
+
*/
|
|
534
|
+
expandChangedSessionIds(changedIds, _refs) {
|
|
535
|
+
return changedIds;
|
|
536
|
+
}
|
|
399
537
|
scan(options) {
|
|
400
538
|
const sources = this.listSessionSources(options);
|
|
401
539
|
const sessions = [];
|
|
@@ -493,10 +631,11 @@ var FileSystemSessionSource = class extends BaseAgent {
|
|
|
493
631
|
* refs 未传时回退为自行枚举,供独立调用方(如测试)沿用旧行为。
|
|
494
632
|
*/
|
|
495
633
|
incrementalScan(cachedSessions, changedIds, refs) {
|
|
634
|
+
const sources = refs ?? this.listSessionSources();
|
|
496
635
|
const sessionMap = new Map(cachedSessions.map((session) => [session.id, session]));
|
|
497
|
-
const changedSet = new Set(changedIds);
|
|
636
|
+
const changedSet = new Set(this.expandChangedSessionIds(changedIds, sources));
|
|
498
637
|
const currentIds = /* @__PURE__ */ new Set();
|
|
499
|
-
for (const ref of
|
|
638
|
+
for (const ref of sources) {
|
|
500
639
|
currentIds.add(ref.sessionId);
|
|
501
640
|
if (!changedSet.has(ref.sessionId)) continue;
|
|
502
641
|
const head = this.scanSessionSource(ref.sourcePath);
|
|
@@ -547,8 +686,41 @@ var SingleFileSessionSource = class extends FileSystemSessionSource {
|
|
|
547
686
|
};
|
|
548
687
|
}
|
|
549
688
|
};
|
|
689
|
+
var SessionScanError = class extends Error {
|
|
690
|
+
constructor(agentName, stage, options) {
|
|
691
|
+
super(`${agentName} session scan failed while ${stage}`, options);
|
|
692
|
+
this.agentName = agentName;
|
|
693
|
+
this.stage = stage;
|
|
694
|
+
this.name = "SessionScanError";
|
|
695
|
+
}
|
|
696
|
+
agentName;
|
|
697
|
+
stage;
|
|
698
|
+
};
|
|
699
|
+
function sqliteSourceFiles(dbPath) {
|
|
700
|
+
return [dbPath, `${dbPath}-wal`];
|
|
701
|
+
}
|
|
702
|
+
function statOrNull(path2) {
|
|
703
|
+
try {
|
|
704
|
+
return statSync(path2);
|
|
705
|
+
} catch {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function sqliteSourceFingerprint(dbPath) {
|
|
710
|
+
return sqliteSourceFiles(dbPath).map((path2) => {
|
|
711
|
+
const stats = statOrNull(path2);
|
|
712
|
+
return stats ? `${stats.size}:${Math.round(stats.mtimeMs)}` : "-";
|
|
713
|
+
}).join("|");
|
|
714
|
+
}
|
|
715
|
+
function latestSqliteSourceMtime(dbPath) {
|
|
716
|
+
return sqliteSourceFiles(dbPath).reduce((latest, path2) => {
|
|
717
|
+
const stats = statOrNull(path2);
|
|
718
|
+
return stats && stats.mtimeMs > latest ? stats.mtimeMs : latest;
|
|
719
|
+
}, 0);
|
|
720
|
+
}
|
|
550
721
|
var DatabaseSessionSource = class extends BaseAgent {
|
|
551
722
|
sessionMetaMap = /* @__PURE__ */ new Map();
|
|
723
|
+
lastSourceFingerprint = null;
|
|
552
724
|
/** 记录单个会话的缓存 meta(sourcePath = dbPath)。 */
|
|
553
725
|
rememberSession(sessionId) {
|
|
554
726
|
const dbPath = this.getDatabasePath();
|
|
@@ -562,7 +734,12 @@ var DatabaseSessionSource = class extends BaseAgent {
|
|
|
562
734
|
this.sessionMetaMap = meta;
|
|
563
735
|
}
|
|
564
736
|
/**
|
|
565
|
-
*
|
|
737
|
+
* 变更检测:数据库内部变更难以按行定位,按库文件集合的指纹判定。
|
|
738
|
+
*
|
|
739
|
+
* In WAL mode a commit appends to the sidecar and leaves the main file
|
|
740
|
+
* untouched until checkpoint, so watching the database alone misses recent
|
|
741
|
+
* writes entirely. Size is part of the fingerprint because an uncheckpointed
|
|
742
|
+
* commit may land in the same millisecond as the previous one.
|
|
566
743
|
*/
|
|
567
744
|
checkForChanges(sinceTimestamp, _cachedSessions) {
|
|
568
745
|
const dbPath = this.getDatabasePath();
|
|
@@ -570,7 +747,10 @@ var DatabaseSessionSource = class extends BaseAgent {
|
|
|
570
747
|
return { hasChanges: false, timestamp: Date.now() };
|
|
571
748
|
}
|
|
572
749
|
try {
|
|
573
|
-
const
|
|
750
|
+
const fingerprint = sqliteSourceFingerprint(dbPath);
|
|
751
|
+
const previous = this.lastSourceFingerprint;
|
|
752
|
+
this.lastSourceFingerprint = fingerprint;
|
|
753
|
+
const hasChanges = previous == null ? latestSqliteSourceMtime(dbPath) > sinceTimestamp : fingerprint !== previous;
|
|
574
754
|
return {
|
|
575
755
|
hasChanges,
|
|
576
756
|
timestamp: Date.now()
|
|
@@ -630,17 +810,17 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
|
|
|
630
810
|
try {
|
|
631
811
|
const buffer = Buffer.alloc(chunkBytes);
|
|
632
812
|
const decoder = new StringDecoder("utf8");
|
|
633
|
-
let
|
|
813
|
+
let pending2 = [];
|
|
634
814
|
let bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
|
|
635
815
|
while (bytesRead > 0) {
|
|
636
816
|
const decoded = decoder.write(buffer.subarray(0, bytesRead));
|
|
637
817
|
const lastBreak = decoded.lastIndexOf("\n");
|
|
638
818
|
if (lastBreak === -1) {
|
|
639
|
-
if (decoded)
|
|
819
|
+
if (decoded) pending2.push(decoded);
|
|
640
820
|
} else {
|
|
641
|
-
|
|
642
|
-
const complete =
|
|
643
|
-
|
|
821
|
+
pending2.push(decoded.slice(0, lastBreak));
|
|
822
|
+
const complete = pending2.join("");
|
|
823
|
+
pending2 = [decoded.slice(lastBreak + 1)];
|
|
644
824
|
for (const line of complete.split("\n")) {
|
|
645
825
|
const trimmed = line.trim();
|
|
646
826
|
if (trimmed) yield trimmed;
|
|
@@ -648,8 +828,8 @@ function* readJsonlFileLines(filePath, chunkBytes = READ_CHUNK_BYTES) {
|
|
|
648
828
|
}
|
|
649
829
|
bytesRead = readSync(fd, buffer, 0, chunkBytes, -1);
|
|
650
830
|
}
|
|
651
|
-
|
|
652
|
-
const tail =
|
|
831
|
+
pending2.push(decoder.end());
|
|
832
|
+
const tail = pending2.join("").trim();
|
|
653
833
|
if (tail) yield tail;
|
|
654
834
|
} finally {
|
|
655
835
|
closeSync(fd);
|
|
@@ -833,6 +1013,56 @@ var aliases_default = {
|
|
|
833
1013
|
"gpt-5.1-codex-high": "gpt-5.3-codex",
|
|
834
1014
|
"gpt-5.2-low": "gpt-5.2"
|
|
835
1015
|
};
|
|
1016
|
+
var PRIVATE_DIR_MODE = 448;
|
|
1017
|
+
var PRIVATE_FILE_MODE = 384;
|
|
1018
|
+
var SQLITE_SIDECAR_SUFFIXES = ["-wal", "-shm", "-journal"];
|
|
1019
|
+
var isPosix = process.platform !== "win32";
|
|
1020
|
+
function reportChmodFailure(path2, error) {
|
|
1021
|
+
getCoreDiagnostics()?.warn("storage.permissions_failed", {
|
|
1022
|
+
path: path2,
|
|
1023
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function restrict(path2, mode) {
|
|
1027
|
+
if (!isPosix || !existsSync3(path2)) return;
|
|
1028
|
+
try {
|
|
1029
|
+
if ((statSync2(path2).mode & 511) === mode) return;
|
|
1030
|
+
chmodSync(path2, mode);
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
reportChmodFailure(path2, error);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function ensurePrivateDirectory(path2) {
|
|
1036
|
+
mkdirSync(path2, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
1037
|
+
restrict(path2, PRIVATE_DIR_MODE);
|
|
1038
|
+
}
|
|
1039
|
+
function restrictPrivateFile(path2) {
|
|
1040
|
+
restrict(path2, PRIVATE_FILE_MODE);
|
|
1041
|
+
}
|
|
1042
|
+
function restrictExistingPrivateFiles(directory, owns) {
|
|
1043
|
+
try {
|
|
1044
|
+
for (const entry of readdirSync2(directory)) {
|
|
1045
|
+
if (owns(entry)) restrictPrivateFile(join3(directory, entry));
|
|
1046
|
+
}
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
reportChmodFailure(directory, error);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
var sweptBackupsFor = /* @__PURE__ */ new Set();
|
|
1052
|
+
function restrictExistingBackups(dbPath) {
|
|
1053
|
+
if (sweptBackupsFor.has(dbPath)) return;
|
|
1054
|
+
sweptBackupsFor.add(dbPath);
|
|
1055
|
+
const prefix = `${basename2(dbPath)}.`;
|
|
1056
|
+
restrictExistingPrivateFiles(
|
|
1057
|
+
dirname(dbPath),
|
|
1058
|
+
(name) => name.startsWith(prefix) && name.endsWith(".bak")
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
function restrictPrivateDatabase(dbPath) {
|
|
1062
|
+
restrictPrivateFile(dbPath);
|
|
1063
|
+
for (const suffix of SQLITE_SIDECAR_SUFFIXES) restrictPrivateFile(`${dbPath}${suffix}`);
|
|
1064
|
+
restrictExistingBackups(dbPath);
|
|
1065
|
+
}
|
|
836
1066
|
var snapshot_default = {
|
|
837
1067
|
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
838
1068
|
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
@@ -879,7 +1109,9 @@ var snapshot_default = {
|
|
|
879
1109
|
var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
880
1110
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
881
1111
|
var WEB_SEARCH_COST = 0.01;
|
|
882
|
-
var
|
|
1112
|
+
var REFRESH_TIMEOUT_MS = 1e4;
|
|
1113
|
+
var published = { id: 1, pricing: loadSnapshot() };
|
|
1114
|
+
var pending = null;
|
|
883
1115
|
loadDiskCache();
|
|
884
1116
|
function normalizeKey(key) {
|
|
885
1117
|
return key.trim().toLowerCase();
|
|
@@ -888,10 +1120,10 @@ function costNumber(value, fallback) {
|
|
|
888
1120
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
889
1121
|
}
|
|
890
1122
|
function getCacheDir() {
|
|
891
|
-
return
|
|
1123
|
+
return join4(homedir2(), ".cache", "codesesh");
|
|
892
1124
|
}
|
|
893
1125
|
function getCachePath() {
|
|
894
|
-
return
|
|
1126
|
+
return join4(getCacheDir(), "litellm-pricing.json");
|
|
895
1127
|
}
|
|
896
1128
|
function loadSnapshot() {
|
|
897
1129
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -957,7 +1189,7 @@ function parseLiteLLMData(data) {
|
|
|
957
1189
|
}
|
|
958
1190
|
function loadDiskCache() {
|
|
959
1191
|
const path2 = getCachePath();
|
|
960
|
-
if (!
|
|
1192
|
+
if (!existsSync4(path2)) return;
|
|
961
1193
|
try {
|
|
962
1194
|
const cached = JSON.parse(readFileSync(path2, "utf-8"));
|
|
963
1195
|
if (Date.now() - cached.timestamp <= CACHE_TTL_MS) {
|
|
@@ -967,20 +1199,51 @@ function loadDiskCache() {
|
|
|
967
1199
|
if (!pricing) continue;
|
|
968
1200
|
next.set(normalizeKey(name), pricing);
|
|
969
1201
|
}
|
|
970
|
-
|
|
1202
|
+
published = { id: published.id + 1, pricing: next };
|
|
971
1203
|
}
|
|
972
1204
|
} catch {
|
|
973
1205
|
}
|
|
974
1206
|
}
|
|
975
1207
|
function getPricingRegistry() {
|
|
976
|
-
return
|
|
1208
|
+
return published.pricing;
|
|
1209
|
+
}
|
|
1210
|
+
function getPricingGeneration() {
|
|
1211
|
+
return published;
|
|
1212
|
+
}
|
|
1213
|
+
function publishPendingPricing() {
|
|
1214
|
+
if (!pending) return false;
|
|
1215
|
+
published = { id: published.id + 1, pricing: pending };
|
|
1216
|
+
pending = null;
|
|
1217
|
+
getCoreDiagnostics()?.info?.("pricing.generation.published", {
|
|
1218
|
+
generation: published.id,
|
|
1219
|
+
models: published.pricing.size
|
|
1220
|
+
});
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1223
|
+
function hasPendingPricing() {
|
|
1224
|
+
return pending !== null;
|
|
977
1225
|
}
|
|
978
1226
|
function hasBillablePricing(pricing) {
|
|
979
1227
|
return pricing.inputCostPerToken > 0 || pricing.outputCostPerToken > 0 || pricing.cacheReadCostPerToken > 0 || pricing.cacheCreateCostPerToken > 0;
|
|
980
1228
|
}
|
|
981
|
-
|
|
1229
|
+
function writeDiskCacheAtomically(path2, pricing) {
|
|
1230
|
+
const temporaryPath = `${path2}.${process.pid}.tmp`;
|
|
1231
|
+
const payload = JSON.stringify({ timestamp: Date.now(), data: Object.fromEntries(pricing) });
|
|
1232
|
+
try {
|
|
1233
|
+
ensurePrivateDirectory(getCacheDir());
|
|
1234
|
+
writeFileSync(temporaryPath, payload);
|
|
1235
|
+
restrictPrivateFile(temporaryPath);
|
|
1236
|
+
renameSync(temporaryPath, path2);
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
rmSync(temporaryPath, { force: true });
|
|
1239
|
+
getCoreDiagnostics()?.warn("pricing.cache_write_failed", {
|
|
1240
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
async function refreshPricingCache(options = {}) {
|
|
982
1245
|
const path2 = getCachePath();
|
|
983
|
-
if (
|
|
1246
|
+
if (existsSync4(path2)) {
|
|
984
1247
|
try {
|
|
985
1248
|
const cached = JSON.parse(readFileSync(path2, "utf-8"));
|
|
986
1249
|
if (typeof cached.timestamp === "number" && Date.now() - cached.timestamp <= CACHE_TTL_MS) {
|
|
@@ -989,8 +1252,11 @@ async function refreshPricingCache() {
|
|
|
989
1252
|
} catch {
|
|
990
1253
|
}
|
|
991
1254
|
}
|
|
1255
|
+
const timeout = AbortSignal.timeout(options.timeoutMs ?? REFRESH_TIMEOUT_MS);
|
|
1256
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
1257
|
+
getCoreDiagnostics()?.info?.("pricing.refresh.started", { generation: published.id });
|
|
992
1258
|
try {
|
|
993
|
-
const response = await fetch(LITELLM_URL);
|
|
1259
|
+
const response = await fetch(LITELLM_URL, { signal });
|
|
994
1260
|
if (!response.ok) return false;
|
|
995
1261
|
const data = await response.json();
|
|
996
1262
|
const remote = parseLiteLLMData(data);
|
|
@@ -999,11 +1265,19 @@ async function refreshPricingCache() {
|
|
|
999
1265
|
for (const [name, pricing] of remote.entries()) {
|
|
1000
1266
|
next.set(name, pricing);
|
|
1001
1267
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1268
|
+
pending = next;
|
|
1269
|
+
writeDiskCacheAtomically(path2, next);
|
|
1270
|
+
getCoreDiagnostics()?.info?.("pricing.refresh.completed", {
|
|
1271
|
+
generation: published.id,
|
|
1272
|
+
models: next.size
|
|
1273
|
+
});
|
|
1005
1274
|
return true;
|
|
1006
|
-
} catch {
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
getCoreDiagnostics()?.warn("pricing.refresh.failed", {
|
|
1277
|
+
generation: published.id,
|
|
1278
|
+
aborted: signal.aborted,
|
|
1279
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1280
|
+
});
|
|
1007
1281
|
return false;
|
|
1008
1282
|
}
|
|
1009
1283
|
}
|
|
@@ -1313,7 +1587,7 @@ var TranscriptBuilder = class {
|
|
|
1313
1587
|
};
|
|
1314
1588
|
}
|
|
1315
1589
|
};
|
|
1316
|
-
var HEAD_INDEX_VERSION = "claudecode-head-
|
|
1590
|
+
var HEAD_INDEX_VERSION = "claudecode-head-v4";
|
|
1317
1591
|
function resolveClaudeCodeDataRoot() {
|
|
1318
1592
|
return resolveHomePath("CLAUDE_CONFIG_DIR", ".claude");
|
|
1319
1593
|
}
|
|
@@ -1369,23 +1643,27 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1369
1643
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1370
1644
|
sessionsIndexCache = {};
|
|
1371
1645
|
sessionsIndexMtime = {};
|
|
1646
|
+
childContextsBySource = /* @__PURE__ */ new Map();
|
|
1647
|
+
childContextCache = /* @__PURE__ */ new Map();
|
|
1648
|
+
childSessionIdByToolUseId = /* @__PURE__ */ new Map();
|
|
1649
|
+
childIndexReady = false;
|
|
1372
1650
|
findBasePath() {
|
|
1373
|
-
return firstExisting(
|
|
1651
|
+
return firstExisting(join5(resolveClaudeCodeDataRoot(), "projects"), "data/claudecode");
|
|
1374
1652
|
}
|
|
1375
1653
|
getSessionWatchPlan() {
|
|
1376
1654
|
const dataRoot = resolveClaudeCodeDataRoot();
|
|
1377
1655
|
return {
|
|
1378
1656
|
status: "supported",
|
|
1379
|
-
targets: [{ root: dataRoot, path:
|
|
1657
|
+
targets: [{ root: dataRoot, path: join5(dataRoot, "projects") }, { path: "data/claudecode" }]
|
|
1380
1658
|
};
|
|
1381
1659
|
}
|
|
1382
1660
|
isAvailable() {
|
|
1383
1661
|
this.basePath = this.findBasePath();
|
|
1384
1662
|
if (!this.basePath) return false;
|
|
1385
1663
|
try {
|
|
1386
|
-
for (const entry of
|
|
1387
|
-
const dir =
|
|
1388
|
-
if (
|
|
1664
|
+
for (const entry of readdirSync3(this.basePath)) {
|
|
1665
|
+
const dir = join5(this.basePath, entry);
|
|
1666
|
+
if (existsSync5(dir) && readdirSync3(dir).some((f) => f.endsWith(".jsonl"))) {
|
|
1389
1667
|
return true;
|
|
1390
1668
|
}
|
|
1391
1669
|
}
|
|
@@ -1401,33 +1679,119 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1401
1679
|
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
1402
1680
|
indexMtimes.set(projectDir, this.readFileMtimeMs(indexPath));
|
|
1403
1681
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
})
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1682
|
+
const projectDirSet = new Set(projectDirs);
|
|
1683
|
+
const allSources = this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
|
|
1684
|
+
recursive: true
|
|
1685
|
+
});
|
|
1686
|
+
this.indexChildContexts(allSources, projectDirs);
|
|
1687
|
+
const indexedSources = allSources.flatMap((source) => {
|
|
1688
|
+
const child = this.childContextsBySource.get(source.file);
|
|
1689
|
+
if (!child && !projectDirSet.has(dirname2(source.file))) return [];
|
|
1690
|
+
return [
|
|
1691
|
+
{
|
|
1692
|
+
source,
|
|
1693
|
+
sessionId: child?.sessionId ?? basename3(source.file, ".jsonl"),
|
|
1694
|
+
child
|
|
1695
|
+
}
|
|
1696
|
+
];
|
|
1697
|
+
});
|
|
1698
|
+
let selectedSources = indexedSources.filter(
|
|
1699
|
+
({ source }) => matchesScanWindow(source.stat.mtimeMs, options)
|
|
1700
|
+
);
|
|
1701
|
+
if (options?.from != null || options?.to != null) {
|
|
1702
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
1703
|
+
for (const indexed of indexedSources) {
|
|
1704
|
+
const parentId = indexed.child?.parentSessionId;
|
|
1705
|
+
if (!parentId) continue;
|
|
1706
|
+
const children = childrenByParent.get(parentId);
|
|
1707
|
+
if (children) children.push(indexed);
|
|
1708
|
+
else childrenByParent.set(parentId, [indexed]);
|
|
1709
|
+
}
|
|
1710
|
+
const windowSelectedById = new Map(
|
|
1711
|
+
selectedSources.map((indexed) => [indexed.sessionId, indexed])
|
|
1712
|
+
);
|
|
1713
|
+
const connectedSelected = /* @__PURE__ */ new Map();
|
|
1714
|
+
const acceptedIds = /* @__PURE__ */ new Set();
|
|
1715
|
+
const visitingIds = /* @__PURE__ */ new Set();
|
|
1716
|
+
const hasSelectedParent = (sessionId) => {
|
|
1717
|
+
if (acceptedIds.has(sessionId)) return true;
|
|
1718
|
+
if (visitingIds.has(sessionId)) return false;
|
|
1719
|
+
const indexed = windowSelectedById.get(sessionId);
|
|
1720
|
+
if (!indexed) return false;
|
|
1721
|
+
const parentId = indexed.child?.parentSessionId;
|
|
1722
|
+
if (!parentId) {
|
|
1723
|
+
acceptedIds.add(sessionId);
|
|
1724
|
+
return true;
|
|
1725
|
+
}
|
|
1726
|
+
visitingIds.add(sessionId);
|
|
1727
|
+
const connected = hasSelectedParent(parentId);
|
|
1728
|
+
visitingIds.delete(sessionId);
|
|
1729
|
+
if (connected) acceptedIds.add(sessionId);
|
|
1730
|
+
return connected;
|
|
1731
|
+
};
|
|
1732
|
+
for (const indexed of selectedSources) {
|
|
1733
|
+
if (hasSelectedParent(indexed.sessionId)) {
|
|
1734
|
+
connectedSelected.set(indexed.sessionId, indexed);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (options?.includeRelatedSessions !== false) {
|
|
1738
|
+
const pending2 = [...connectedSelected.values()].filter(({ child }) => !child?.parentSessionId).map(({ sessionId }) => sessionId);
|
|
1739
|
+
while (pending2.length > 0) {
|
|
1740
|
+
const parentId = pending2.pop();
|
|
1741
|
+
for (const child of childrenByParent.get(parentId) ?? []) {
|
|
1742
|
+
if (connectedSelected.has(child.sessionId)) continue;
|
|
1743
|
+
connectedSelected.set(child.sessionId, child);
|
|
1744
|
+
pending2.push(child.sessionId);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
selectedSources = [...connectedSelected.values()];
|
|
1749
|
+
}
|
|
1750
|
+
return selectedSources.map(({ source: { file, stat }, sessionId, child }) => {
|
|
1751
|
+
const projectDir = child?.projectDir ?? dirname2(file);
|
|
1752
|
+
return {
|
|
1753
|
+
sessionId,
|
|
1754
|
+
sourcePath: file,
|
|
1755
|
+
fingerprint: this.sourceFingerprint(
|
|
1756
|
+
stat,
|
|
1757
|
+
indexMtimes.get(projectDir) ?? null,
|
|
1758
|
+
child?.metaMtimeMs
|
|
1759
|
+
)
|
|
1760
|
+
};
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
setSessionMetaMap(meta) {
|
|
1764
|
+
super.setSessionMetaMap(meta);
|
|
1765
|
+
this.childContextsBySource.clear();
|
|
1766
|
+
this.childSessionIdByToolUseId.clear();
|
|
1767
|
+
this.childIndexReady = false;
|
|
1412
1768
|
}
|
|
1413
1769
|
parseFileSessionHead(sourcePath) {
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1770
|
+
const child = this.getChildContext(sourcePath);
|
|
1771
|
+
const projectDir = child?.projectDir ?? dirname2(sourcePath);
|
|
1772
|
+
return getParsedSession(this.parseSessionHeadResult(sourcePath, projectDir, child));
|
|
1416
1773
|
}
|
|
1417
1774
|
getSessionData(sessionId) {
|
|
1418
1775
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
1419
1776
|
if (!meta) {
|
|
1420
1777
|
throw new Error(`Session not found: ${sessionId}`);
|
|
1421
1778
|
}
|
|
1422
|
-
if (!
|
|
1779
|
+
if (!existsSync5(meta.sourcePath)) {
|
|
1423
1780
|
throw new Error(`Session file missing: ${meta.sourcePath}`);
|
|
1424
1781
|
}
|
|
1782
|
+
this.ensureChildIndex();
|
|
1425
1783
|
const builder = new TranscriptBuilder();
|
|
1426
1784
|
const assistantUuidToToolCalls = /* @__PURE__ */ new Map();
|
|
1427
1785
|
const countedUsageKeys = /* @__PURE__ */ new Set();
|
|
1428
1786
|
for (const record of readJsonlFile(meta.sourcePath)) {
|
|
1429
1787
|
try {
|
|
1430
|
-
this.convertRecord(
|
|
1788
|
+
this.convertRecord(
|
|
1789
|
+
record,
|
|
1790
|
+
builder,
|
|
1791
|
+
assistantUuidToToolCalls,
|
|
1792
|
+
countedUsageKeys,
|
|
1793
|
+
this.childSessionIdByToolUseId
|
|
1794
|
+
);
|
|
1431
1795
|
} catch {
|
|
1432
1796
|
}
|
|
1433
1797
|
}
|
|
@@ -1438,6 +1802,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1438
1802
|
title: meta.title,
|
|
1439
1803
|
slug: `claudecode/${meta.id}`,
|
|
1440
1804
|
directory: meta.directory,
|
|
1805
|
+
parent_reference: meta.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: meta.parentSessionId },
|
|
1441
1806
|
version: void 0,
|
|
1442
1807
|
time_created: meta.createdAt,
|
|
1443
1808
|
time_updated: meta.updatedAt,
|
|
@@ -1449,43 +1814,143 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1449
1814
|
listProjectDirs() {
|
|
1450
1815
|
if (!this.basePath) return [];
|
|
1451
1816
|
try {
|
|
1452
|
-
return
|
|
1817
|
+
return readdirSync3(this.basePath).map((e) => join5(this.basePath, e)).filter((p) => existsSync5(p));
|
|
1453
1818
|
} catch {
|
|
1454
1819
|
return [];
|
|
1455
1820
|
}
|
|
1456
1821
|
}
|
|
1822
|
+
ensureChildIndex() {
|
|
1823
|
+
if (this.childIndexReady) return;
|
|
1824
|
+
this.basePath ??= this.findBasePath();
|
|
1825
|
+
if (!this.basePath) {
|
|
1826
|
+
this.childIndexReady = true;
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const projectDirs = this.listProjectDirs();
|
|
1830
|
+
this.indexChildContexts(
|
|
1831
|
+
this.walkFiles(projectDirs, (entry) => entry.name.endsWith(".jsonl"), {
|
|
1832
|
+
recursive: true
|
|
1833
|
+
}),
|
|
1834
|
+
projectDirs
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
indexChildContexts(sources, projectDirs) {
|
|
1838
|
+
this.childContextsBySource.clear();
|
|
1839
|
+
this.childSessionIdByToolUseId.clear();
|
|
1840
|
+
const projectDirSet = new Set(projectDirs);
|
|
1841
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
1842
|
+
const knownSessionIds = /* @__PURE__ */ new Set();
|
|
1843
|
+
for (const source of sources) {
|
|
1844
|
+
const child = this.readChildContext(source.file);
|
|
1845
|
+
if (!child) {
|
|
1846
|
+
if (projectDirSet.has(dirname2(source.file))) {
|
|
1847
|
+
knownSessionIds.add(basename3(source.file, ".jsonl"));
|
|
1848
|
+
}
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
contexts.set(source.file, child);
|
|
1852
|
+
knownSessionIds.add(child.sessionId);
|
|
1853
|
+
}
|
|
1854
|
+
for (const [sourcePath, child] of contexts) {
|
|
1855
|
+
const parentSessionId = child.parentSessionId && knownSessionIds.has(child.parentSessionId) ? child.parentSessionId : null;
|
|
1856
|
+
const normalized = parentSessionId === child.parentSessionId ? child : { ...child, parentSessionId };
|
|
1857
|
+
this.childContextsBySource.set(sourcePath, normalized);
|
|
1858
|
+
if (child.toolUseId) {
|
|
1859
|
+
this.childSessionIdByToolUseId.set(child.toolUseId, normalized.sessionId);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
this.childIndexReady = true;
|
|
1863
|
+
}
|
|
1864
|
+
getChildContext(sourcePath) {
|
|
1865
|
+
const cached = this.childContextsBySource.get(sourcePath);
|
|
1866
|
+
if (cached) return cached;
|
|
1867
|
+
const child = this.readChildContext(sourcePath);
|
|
1868
|
+
if (!child) return null;
|
|
1869
|
+
this.childContextsBySource.set(sourcePath, child);
|
|
1870
|
+
if (child.toolUseId) {
|
|
1871
|
+
this.childSessionIdByToolUseId.set(child.toolUseId, child.sessionId);
|
|
1872
|
+
}
|
|
1873
|
+
return child;
|
|
1874
|
+
}
|
|
1875
|
+
readChildContext(sourcePath) {
|
|
1876
|
+
const subagentsDir = dirname2(sourcePath);
|
|
1877
|
+
if (basename3(subagentsDir) !== "subagents") return null;
|
|
1878
|
+
const parentDir = dirname2(subagentsDir);
|
|
1879
|
+
const projectDir = dirname2(parentDir);
|
|
1880
|
+
const fileStem = basename3(sourcePath, ".jsonl");
|
|
1881
|
+
const metaPath = join5(subagentsDir, fileStem + ".meta.json");
|
|
1882
|
+
const metaMtimeMs = this.readFileMtimeMs(metaPath);
|
|
1883
|
+
const cached = this.childContextCache.get(sourcePath);
|
|
1884
|
+
if (cached?.metaMtimeMs === metaMtimeMs) return cached.context;
|
|
1885
|
+
let metadata = null;
|
|
1886
|
+
if (metaMtimeMs !== null) {
|
|
1887
|
+
try {
|
|
1888
|
+
metadata = asRecord(JSON.parse(readFileSync2(metaPath, "utf-8"))) ?? null;
|
|
1889
|
+
} catch {
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
const sessionId = asString(metadata?.["agentId"])?.trim() || fileStem.replace(/^agent-/, "");
|
|
1893
|
+
if (!sessionId) return null;
|
|
1894
|
+
const parentAgentId = asString(metadata?.["parentAgentId"])?.trim();
|
|
1895
|
+
const candidateParentId = parentAgentId || basename3(parentDir) || null;
|
|
1896
|
+
const parentSessionId = candidateParentId && this.hasChildParent(sourcePath, projectDir, candidateParentId) ? candidateParentId : null;
|
|
1897
|
+
const name = asString(metadata?.["name"])?.trim();
|
|
1898
|
+
const description = asString(metadata?.["description"])?.trim();
|
|
1899
|
+
const context = {
|
|
1900
|
+
sessionId,
|
|
1901
|
+
projectDir,
|
|
1902
|
+
parentSessionId,
|
|
1903
|
+
explicitTitle: name || description || null,
|
|
1904
|
+
metaMtimeMs,
|
|
1905
|
+
toolUseId: asString(metadata?.["toolUseId"])?.trim() || null
|
|
1906
|
+
};
|
|
1907
|
+
this.childContextCache.set(sourcePath, { metaMtimeMs, context });
|
|
1908
|
+
return context;
|
|
1909
|
+
}
|
|
1910
|
+
hasChildParent(sourcePath, projectDir, parentSessionId) {
|
|
1911
|
+
const subagentsDir = dirname2(sourcePath);
|
|
1912
|
+
return [
|
|
1913
|
+
join5(projectDir, parentSessionId + ".jsonl"),
|
|
1914
|
+
join5(subagentsDir, "agent-" + parentSessionId + ".jsonl"),
|
|
1915
|
+
join5(subagentsDir, parentSessionId + ".jsonl")
|
|
1916
|
+
].some((path2) => existsSync5(path2));
|
|
1917
|
+
}
|
|
1457
1918
|
createFileSessionMeta(head, source) {
|
|
1458
|
-
const
|
|
1919
|
+
const child = this.getChildContext(source.file);
|
|
1920
|
+
const projectDir = child?.projectDir ?? dirname2(source.file);
|
|
1459
1921
|
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
1460
1922
|
const indexMtime = this.readFileMtimeMs(indexPath);
|
|
1461
1923
|
return this.buildFileSessionMeta({
|
|
1462
1924
|
head,
|
|
1463
1925
|
source,
|
|
1464
|
-
fingerprint: this.sourceFingerprint(source.stat, indexMtime),
|
|
1926
|
+
fingerprint: this.sourceFingerprint(source.stat, indexMtime, child?.metaMtimeMs),
|
|
1465
1927
|
extras: {
|
|
1466
1928
|
indexPath: indexMtime === null ? null : indexPath,
|
|
1467
1929
|
indexMtimeMs: indexMtime,
|
|
1468
1930
|
headIndexVersion: HEAD_INDEX_VERSION,
|
|
1469
|
-
model: head.stats.total_tokens ? "unknown" : void 0
|
|
1931
|
+
model: head.stats.total_tokens ? "unknown" : void 0,
|
|
1932
|
+
parentSessionId: head.parent_reference?.sessionId ?? null
|
|
1470
1933
|
}
|
|
1471
1934
|
});
|
|
1472
1935
|
}
|
|
1473
1936
|
/** Fingerprint depends on an already-fetched stat to avoid re-statting the same file. */
|
|
1474
|
-
sourceFingerprint(stat, indexMtime) {
|
|
1475
|
-
|
|
1937
|
+
sourceFingerprint(stat, indexMtime, metaMtime) {
|
|
1938
|
+
const fingerprint = [HEAD_INDEX_VERSION, stat.mtimeMs, stat.size, indexMtime];
|
|
1939
|
+
if (metaMtime !== void 0) fingerprint.push(metaMtime);
|
|
1940
|
+
return JSON.stringify(fingerprint);
|
|
1476
1941
|
}
|
|
1477
1942
|
getSessionsIndexPath(projectDir) {
|
|
1478
|
-
return
|
|
1943
|
+
return join5(projectDir, "sessions-index.json");
|
|
1479
1944
|
}
|
|
1480
1945
|
loadSessionsIndex(projectDir) {
|
|
1481
|
-
const cacheKey =
|
|
1946
|
+
const cacheKey = basename3(projectDir);
|
|
1482
1947
|
const indexPath = this.getSessionsIndexPath(projectDir);
|
|
1483
1948
|
const mtime = this.readFileMtimeMs(indexPath);
|
|
1484
1949
|
if (cacheKey in this.sessionsIndexCache && this.sessionsIndexMtime[cacheKey] === mtime) {
|
|
1485
1950
|
return this.sessionsIndexCache[cacheKey];
|
|
1486
1951
|
}
|
|
1487
1952
|
const map = /* @__PURE__ */ new Map();
|
|
1488
|
-
if (
|
|
1953
|
+
if (existsSync5(indexPath)) {
|
|
1489
1954
|
try {
|
|
1490
1955
|
const data = JSON.parse(readFileSync2(indexPath, "utf-8"));
|
|
1491
1956
|
const entries = data?.entries ?? [];
|
|
@@ -1502,11 +1967,11 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1502
1967
|
this.sessionsIndexMtime[cacheKey] = mtime;
|
|
1503
1968
|
return map;
|
|
1504
1969
|
}
|
|
1505
|
-
parseSessionHeadResult(filePath, projectDir) {
|
|
1506
|
-
const sessionId =
|
|
1970
|
+
parseSessionHeadResult(filePath, projectDir, child) {
|
|
1971
|
+
const sessionId = child?.sessionId ?? basename3(filePath, ".jsonl");
|
|
1507
1972
|
const index = this.loadSessionsIndex(projectDir);
|
|
1508
1973
|
const indexEntry = index.get(sessionId);
|
|
1509
|
-
const explicitTitle = indexEntry?.summary ? String(indexEntry.summary) : null;
|
|
1974
|
+
const explicitTitle = child?.explicitTitle ?? (indexEntry?.summary ? String(indexEntry.summary) : null);
|
|
1510
1975
|
let createdAt = 0;
|
|
1511
1976
|
let updatedAt = 0;
|
|
1512
1977
|
let lineIndex = 0;
|
|
@@ -1531,7 +1996,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1531
1996
|
continue;
|
|
1532
1997
|
}
|
|
1533
1998
|
if (lineIndex === 0) {
|
|
1534
|
-
createdAt = parseTimestampMs(data) ||
|
|
1999
|
+
createdAt = parseTimestampMs(data) || statSync3(filePath).mtimeMs;
|
|
1535
2000
|
updatedAt = createdAt;
|
|
1536
2001
|
}
|
|
1537
2002
|
const recordIndex = lineIndex;
|
|
@@ -1605,6 +2070,7 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1605
2070
|
slug: `claudecode/${sessionId}`,
|
|
1606
2071
|
title,
|
|
1607
2072
|
directory,
|
|
2073
|
+
parent_reference: child?.parentSessionId == null ? void 0 : { agentName: this.name, sessionId: child.parentSessionId },
|
|
1608
2074
|
time_created: createdAt,
|
|
1609
2075
|
time_updated: updatedAt,
|
|
1610
2076
|
stats: {
|
|
@@ -1637,19 +2103,25 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1637
2103
|
return null;
|
|
1638
2104
|
}
|
|
1639
2105
|
// --- Record conversion ---
|
|
1640
|
-
convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
|
|
2106
|
+
convertRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
|
|
1641
2107
|
if (data["isMeta"] === true) return;
|
|
1642
2108
|
const msgType = String(data["type"] ?? "");
|
|
1643
2109
|
if (isInternalEventType(msgType)) return;
|
|
1644
2110
|
if (msgType === "assistant") {
|
|
1645
|
-
this.convertAssistantRecord(
|
|
2111
|
+
this.convertAssistantRecord(
|
|
2112
|
+
data,
|
|
2113
|
+
builder,
|
|
2114
|
+
assistantUuidToToolCalls,
|
|
2115
|
+
countedUsageKeys,
|
|
2116
|
+
childSessionIdByToolUseId
|
|
2117
|
+
);
|
|
1646
2118
|
} else if (msgType === "user") {
|
|
1647
2119
|
this.convertUserRecord(data, builder, assistantUuidToToolCalls);
|
|
1648
2120
|
} else if (msgType === "tool_result") {
|
|
1649
2121
|
this.convertToolResultRecord(data, builder);
|
|
1650
2122
|
}
|
|
1651
2123
|
}
|
|
1652
|
-
convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys) {
|
|
2124
|
+
convertAssistantRecord(data, builder, assistantUuidToToolCalls, countedUsageKeys, childSessionIdByToolUseId) {
|
|
1653
2125
|
const msg = asRecord(data["message"]) ?? {};
|
|
1654
2126
|
const timestampMs = parseTimestampMs(data);
|
|
1655
2127
|
const rawContent = asArray(msg["content"]) ?? [];
|
|
@@ -1689,12 +2161,14 @@ var ClaudeCodeAgent = class extends SingleFileSessionSource {
|
|
|
1689
2161
|
}
|
|
1690
2162
|
if (partType !== "tool_use") continue;
|
|
1691
2163
|
const toolCallId = String(part["id"] ?? "").trim();
|
|
1692
|
-
const
|
|
2164
|
+
const subagentId = childSessionIdByToolUseId.get(toolCallId);
|
|
2165
|
+
const toolPart2 = this.buildToolPart(part, timestampMs);
|
|
1693
2166
|
const message = builder.appendToolCall(
|
|
1694
|
-
|
|
1695
|
-
{ id: uuid, timestampMs, agent: "claude" },
|
|
2167
|
+
toolPart2,
|
|
2168
|
+
{ id: uuid, timestampMs, agent: "claude", subagentId },
|
|
1696
2169
|
{ modeOnCreate: "tool" }
|
|
1697
2170
|
);
|
|
2171
|
+
if (subagentId) message.subagent_id = subagentId;
|
|
1698
2172
|
this.applyAssistantMetadata(message, data, msg, countedUsageKeys);
|
|
1699
2173
|
if (toolCallId) {
|
|
1700
2174
|
toolCallIds.push(toolCallId);
|
|
@@ -1967,11 +2441,12 @@ function backupDatabase(db, dbPath, label) {
|
|
|
1967
2441
|
return null;
|
|
1968
2442
|
}
|
|
1969
2443
|
const timestamp = new Date(Date.now()).toISOString().replaceAll(":", "").replaceAll(".", "-");
|
|
1970
|
-
let backupPath =
|
|
1971
|
-
for (let counter = 1;
|
|
1972
|
-
backupPath =
|
|
2444
|
+
let backupPath = join6(dirname3(dbPath), `${basename4(dbPath)}.${timestamp}.${label}.bak`);
|
|
2445
|
+
for (let counter = 1; existsSync6(backupPath); counter += 1) {
|
|
2446
|
+
backupPath = join6(dirname3(dbPath), `${basename4(dbPath)}.${timestamp}.${label}.${counter}.bak`);
|
|
1973
2447
|
}
|
|
1974
2448
|
db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
|
|
2449
|
+
restrictPrivateFile(backupPath);
|
|
1975
2450
|
return backupPath;
|
|
1976
2451
|
}
|
|
1977
2452
|
function backupDatabaseIfPopulated(db, dbPath, label, tables) {
|
|
@@ -2066,8 +2541,9 @@ function openDb(dbPath) {
|
|
|
2066
2541
|
return null;
|
|
2067
2542
|
}
|
|
2068
2543
|
try {
|
|
2069
|
-
|
|
2544
|
+
ensurePrivateDirectory(dirname3(dbPath));
|
|
2070
2545
|
const db = DatabaseConstructor(dbPath);
|
|
2546
|
+
restrictPrivateDatabase(dbPath);
|
|
2071
2547
|
try {
|
|
2072
2548
|
db.pragma("journal_mode = WAL");
|
|
2073
2549
|
db.pragma("synchronous = NORMAL");
|
|
@@ -2089,6 +2565,24 @@ function isSqliteAvailable() {
|
|
|
2089
2565
|
return DatabaseConstructor !== null;
|
|
2090
2566
|
}
|
|
2091
2567
|
var MESSAGE_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool"]);
|
|
2568
|
+
var SESSION_ID_QUERY_CHUNK_SIZE = 500;
|
|
2569
|
+
function compareSessionRowsByActivityDesc(left, right) {
|
|
2570
|
+
const leftUpdated = Number(left.time_updated ?? left.time_created ?? 0);
|
|
2571
|
+
const rightUpdated = Number(right.time_updated ?? right.time_created ?? 0);
|
|
2572
|
+
return rightUpdated - leftUpdated || Number(right.time_created ?? 0) - Number(left.time_created ?? 0) || String(right.id ?? "").localeCompare(String(left.id ?? ""));
|
|
2573
|
+
}
|
|
2574
|
+
function accumulateTokenStats(stats, msgData, agentName) {
|
|
2575
|
+
const cost = Number(msgData.cost ?? 0);
|
|
2576
|
+
const tokens = parseTokens(msgData.tokens, agentName);
|
|
2577
|
+
const inputTokens = Number(tokens?.input ?? 0);
|
|
2578
|
+
const outputTokens = Number(tokens?.output ?? 0);
|
|
2579
|
+
const model = parseModel(msgData.modelID, agentName);
|
|
2580
|
+
const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
|
|
2581
|
+
if (estimatedCost !== null) stats.cost_source = "estimated";
|
|
2582
|
+
stats.total_cost += cost || estimatedCost || 0;
|
|
2583
|
+
stats.total_input_tokens += inputTokens;
|
|
2584
|
+
stats.total_output_tokens += outputTokens;
|
|
2585
|
+
}
|
|
2092
2586
|
function parseJsonRecord(raw, agentName, field) {
|
|
2093
2587
|
const parsed = asRecord(JSON.parse(String(raw ?? "{}")));
|
|
2094
2588
|
if (parsed) return parsed;
|
|
@@ -2138,34 +2632,54 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2138
2632
|
scan(options) {
|
|
2139
2633
|
if (!this.dbPath) return [];
|
|
2140
2634
|
const db = openDbReadOnly(this.dbPath);
|
|
2141
|
-
if (!db)
|
|
2635
|
+
if (!db) throw new SessionScanError(this.name, "opening the database");
|
|
2142
2636
|
try {
|
|
2143
2637
|
const cutoffTime = options?.from ?? Date.now() - 3650 * 24 * 60 * 60 * 1e3;
|
|
2144
2638
|
const hasMessageTable = Boolean(
|
|
2145
2639
|
db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'message'").get()
|
|
2146
2640
|
);
|
|
2641
|
+
const hasTaskType = columnExists(db, "session", "task_type");
|
|
2642
|
+
const hasParentId = columnExists(db, "session", "parent_id");
|
|
2643
|
+
const childPredicate = hasParentId ? "AND s.parent_id IS NULL" : hasTaskType ? "AND (s.task_type IS NULL OR s.task_type != 'subagent_child')" : "";
|
|
2147
2644
|
let rows;
|
|
2645
|
+
const parentIdSelect = hasParentId ? ", s.parent_id" : "";
|
|
2148
2646
|
if (hasMessageTable) {
|
|
2149
2647
|
rows = db.prepare(`
|
|
2150
2648
|
SELECT
|
|
2151
2649
|
s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
|
|
2152
|
-
s.version, s.summary_files
|
|
2650
|
+
s.version, s.summary_files${parentIdSelect}
|
|
2153
2651
|
FROM session s
|
|
2154
2652
|
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
2155
|
-
|
|
2653
|
+
${childPredicate}
|
|
2654
|
+
ORDER BY COALESCE(s.time_updated, s.time_created) DESC, s.time_created DESC, s.id DESC
|
|
2156
2655
|
`).all(cutoffTime);
|
|
2157
2656
|
} else {
|
|
2158
2657
|
rows = db.prepare(`
|
|
2159
2658
|
SELECT s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
|
|
2160
|
-
s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data
|
|
2659
|
+
s.version, s.summary_files, 0 AS message_count, NULL AS model_message_data${parentIdSelect}
|
|
2161
2660
|
FROM session s
|
|
2162
2661
|
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
2163
|
-
|
|
2662
|
+
${childPredicate}
|
|
2663
|
+
ORDER BY COALESCE(s.time_updated, s.time_created) DESC, s.time_created DESC, s.id DESC
|
|
2164
2664
|
`).all(cutoffTime);
|
|
2165
2665
|
}
|
|
2666
|
+
if (hasParentId && options?.includeRelatedSessions !== false) {
|
|
2667
|
+
const rootIds = rows.map((row) => String(row.id ?? "")).filter(Boolean);
|
|
2668
|
+
const relatedRows = this.readRelatedSessionRows(db, rootIds);
|
|
2669
|
+
const knownIds = new Set(rootIds);
|
|
2670
|
+
rows.push(...relatedRows.filter((row) => !knownIds.has(String(row.id ?? ""))));
|
|
2671
|
+
}
|
|
2672
|
+
rows.sort(compareSessionRowsByActivityDesc);
|
|
2673
|
+
const sessionIds = new Set(rows.map((row) => String(row.id ?? "")).filter(Boolean));
|
|
2166
2674
|
const headContexts = hasMessageTable ? this.buildHeadContexts(
|
|
2167
|
-
this.readHeadMessageRows(
|
|
2168
|
-
|
|
2675
|
+
this.readHeadMessageRows(
|
|
2676
|
+
db,
|
|
2677
|
+
cutoffTime,
|
|
2678
|
+
hasParentId,
|
|
2679
|
+
sessionIds.size > 0 ? sessionIds : void 0
|
|
2680
|
+
),
|
|
2681
|
+
this.readHeadPartRows(db, cutoffTime, sessionIds.size > 0 ? sessionIds : void 0),
|
|
2682
|
+
hasParentId
|
|
2169
2683
|
) : /* @__PURE__ */ new Map();
|
|
2170
2684
|
const heads = [];
|
|
2171
2685
|
options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
|
|
@@ -2187,8 +2701,8 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2187
2701
|
options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
|
|
2188
2702
|
}
|
|
2189
2703
|
return heads;
|
|
2190
|
-
} catch {
|
|
2191
|
-
|
|
2704
|
+
} catch (error) {
|
|
2705
|
+
throw new SessionScanError(this.name, "reading sessions", { cause: error });
|
|
2192
2706
|
} finally {
|
|
2193
2707
|
db.close();
|
|
2194
2708
|
}
|
|
@@ -2207,6 +2721,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2207
2721
|
slug: `${this.name}/${id}`,
|
|
2208
2722
|
title: resolveSessionTitle(String(row.title ?? ""), messageTitle, null),
|
|
2209
2723
|
directory: String(row.directory ?? ""),
|
|
2724
|
+
parent_reference: row.parent_id == null || String(row.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(row.parent_id) },
|
|
2210
2725
|
time_created: timeCreated,
|
|
2211
2726
|
time_updated: timeUpdated,
|
|
2212
2727
|
stats: {
|
|
@@ -2218,28 +2733,103 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2218
2733
|
}
|
|
2219
2734
|
});
|
|
2220
2735
|
}
|
|
2221
|
-
readHeadMessageRows(db, cutoffTime) {
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
JOIN session s ON s.id = m.session_id
|
|
2227
|
-
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
2228
|
-
ORDER BY m.session_id, m.time_created ASC
|
|
2736
|
+
readHeadMessageRows(db, cutoffTime, withParentId, sessionIds) {
|
|
2737
|
+
const parentIdSelect = withParentId ? ", s.parent_id" : "";
|
|
2738
|
+
const ids = sessionIds ? [...sessionIds] : [];
|
|
2739
|
+
if (ids.length === 0) {
|
|
2740
|
+
return db.prepare(
|
|
2229
2741
|
`
|
|
2230
|
-
|
|
2742
|
+
SELECT m.id, m.session_id, m.data, m.time_created${parentIdSelect}
|
|
2743
|
+
FROM message m
|
|
2744
|
+
JOIN session s ON s.id = m.session_id
|
|
2745
|
+
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
2746
|
+
ORDER BY m.session_id, m.time_created ASC
|
|
2747
|
+
`
|
|
2748
|
+
).all(cutoffTime);
|
|
2749
|
+
}
|
|
2750
|
+
const rows = [];
|
|
2751
|
+
for (let offset = 0; offset < ids.length; offset += SESSION_ID_QUERY_CHUNK_SIZE) {
|
|
2752
|
+
const chunk = ids.slice(offset, offset + SESSION_ID_QUERY_CHUNK_SIZE);
|
|
2753
|
+
rows.push(
|
|
2754
|
+
...db.prepare(
|
|
2755
|
+
`
|
|
2756
|
+
SELECT m.id, m.session_id, m.data, m.time_created${parentIdSelect}
|
|
2757
|
+
FROM message m
|
|
2758
|
+
JOIN session s ON s.id = m.session_id
|
|
2759
|
+
WHERE s.id IN (${chunk.map(() => "?").join(",")})
|
|
2760
|
+
ORDER BY m.session_id, m.time_created ASC
|
|
2761
|
+
`
|
|
2762
|
+
).all(...chunk)
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
return rows;
|
|
2231
2766
|
}
|
|
2232
|
-
readHeadPartRows(db, cutoffTime) {
|
|
2233
|
-
|
|
2767
|
+
readHeadPartRows(db, cutoffTime, sessionIds) {
|
|
2768
|
+
const ids = sessionIds ? [...sessionIds] : [];
|
|
2769
|
+
if (ids.length === 0) {
|
|
2770
|
+
return db.prepare(
|
|
2771
|
+
`
|
|
2772
|
+
SELECT p.message_id, p.data, p.time_created
|
|
2773
|
+
FROM part p
|
|
2774
|
+
JOIN message m ON m.id = p.message_id
|
|
2775
|
+
JOIN session s ON s.id = m.session_id
|
|
2776
|
+
WHERE COALESCE(s.time_updated, s.time_created) >= ?
|
|
2777
|
+
ORDER BY p.message_id, p.time_created ASC, p.id ASC
|
|
2778
|
+
`
|
|
2779
|
+
).all(cutoffTime);
|
|
2780
|
+
}
|
|
2781
|
+
const rows = [];
|
|
2782
|
+
for (let offset = 0; offset < ids.length; offset += SESSION_ID_QUERY_CHUNK_SIZE) {
|
|
2783
|
+
const chunk = ids.slice(offset, offset + SESSION_ID_QUERY_CHUNK_SIZE);
|
|
2784
|
+
rows.push(
|
|
2785
|
+
...db.prepare(
|
|
2786
|
+
`
|
|
2787
|
+
SELECT p.message_id, p.data, p.time_created
|
|
2788
|
+
FROM part p
|
|
2789
|
+
JOIN message m ON m.id = p.message_id
|
|
2790
|
+
JOIN session s ON s.id = m.session_id
|
|
2791
|
+
WHERE s.id IN (${chunk.map(() => "?").join(",")})
|
|
2792
|
+
ORDER BY p.message_id, p.time_created ASC, p.id ASC
|
|
2793
|
+
`
|
|
2794
|
+
).all(...chunk)
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
return rows;
|
|
2798
|
+
}
|
|
2799
|
+
readRelatedSessionRows(db, rootIds) {
|
|
2800
|
+
if (rootIds.length === 0) return [];
|
|
2801
|
+
const rows = db.prepare(
|
|
2234
2802
|
`
|
|
2235
|
-
SELECT
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
WHERE
|
|
2240
|
-
ORDER BY
|
|
2803
|
+
SELECT
|
|
2804
|
+
s.id, s.title, s.time_created, s.time_updated, s.slug, s.directory,
|
|
2805
|
+
s.version, s.summary_files, s.parent_id
|
|
2806
|
+
FROM session s
|
|
2807
|
+
WHERE s.parent_id IS NOT NULL
|
|
2808
|
+
ORDER BY s.time_created ASC
|
|
2241
2809
|
`
|
|
2242
|
-
).all(
|
|
2810
|
+
).all();
|
|
2811
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2812
|
+
for (const row of rows) {
|
|
2813
|
+
const parentId = String(row.parent_id ?? "");
|
|
2814
|
+
if (!parentId) continue;
|
|
2815
|
+
const children = childrenByParent.get(parentId);
|
|
2816
|
+
if (children) children.push(row);
|
|
2817
|
+
else childrenByParent.set(parentId, [row]);
|
|
2818
|
+
}
|
|
2819
|
+
const result = [];
|
|
2820
|
+
const pending2 = [...rootIds];
|
|
2821
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2822
|
+
while (pending2.length > 0) {
|
|
2823
|
+
const parentId = pending2.pop();
|
|
2824
|
+
for (const row of childrenByParent.get(parentId) ?? []) {
|
|
2825
|
+
const id = String(row.id ?? "");
|
|
2826
|
+
if (!id || seen.has(id)) continue;
|
|
2827
|
+
seen.add(id);
|
|
2828
|
+
result.push(row);
|
|
2829
|
+
pending2.push(id);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return result;
|
|
2243
2833
|
}
|
|
2244
2834
|
parsePartRow(partRow) {
|
|
2245
2835
|
const partData = parseJsonRecord(partRow.data, this.name, "part.data");
|
|
@@ -2250,9 +2840,21 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2250
2840
|
]);
|
|
2251
2841
|
return part ? cleanMessagePart(part) : null;
|
|
2252
2842
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2843
|
+
/**
|
|
2844
|
+
* Every part of one session in a single read. Fetching per message turned a
|
|
2845
|
+
* detail into M+2 queries, and without an index on part(message_id) each one
|
|
2846
|
+
* scanned the whole table.
|
|
2847
|
+
*/
|
|
2848
|
+
readSessionPartRows(db, sessionId) {
|
|
2849
|
+
return db.prepare(
|
|
2850
|
+
`
|
|
2851
|
+
SELECT p.message_id, p.data, p.time_created
|
|
2852
|
+
FROM part p
|
|
2853
|
+
JOIN message m ON m.id = p.message_id
|
|
2854
|
+
WHERE m.session_id = ?
|
|
2855
|
+
ORDER BY p.message_id, p.time_created ASC, p.id ASC
|
|
2856
|
+
`
|
|
2857
|
+
).all(sessionId);
|
|
2256
2858
|
}
|
|
2257
2859
|
buildPartsByMessage(partRows) {
|
|
2258
2860
|
const partsByMessage = /* @__PURE__ */ new Map();
|
|
@@ -2270,16 +2872,10 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2270
2872
|
}
|
|
2271
2873
|
return partsByMessage;
|
|
2272
2874
|
}
|
|
2273
|
-
buildHeadContexts(messageRows, partRows) {
|
|
2875
|
+
buildHeadContexts(messageRows, partRows, withParentId) {
|
|
2274
2876
|
const partsByMessage = this.buildPartsByMessage(partRows);
|
|
2275
2877
|
const contexts = /* @__PURE__ */ new Map();
|
|
2276
|
-
|
|
2277
|
-
const sessionId = String(row.session_id ?? "");
|
|
2278
|
-
if (!sessionId) continue;
|
|
2279
|
-
const msgData = parseJsonRecord(row.data, this.name, "message.data");
|
|
2280
|
-
if (isInternalEventType(msgData.type)) continue;
|
|
2281
|
-
const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
|
|
2282
|
-
if (parts.length === 0) continue;
|
|
2878
|
+
const ensureContext = (sessionId) => {
|
|
2283
2879
|
let context = contexts.get(sessionId);
|
|
2284
2880
|
if (!context) {
|
|
2285
2881
|
context = {
|
|
@@ -2293,16 +2889,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2293
2889
|
};
|
|
2294
2890
|
contexts.set(sessionId, context);
|
|
2295
2891
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
const
|
|
2302
|
-
if (
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2892
|
+
return context;
|
|
2893
|
+
};
|
|
2894
|
+
for (const row of messageRows) {
|
|
2895
|
+
const sessionId = String(row.session_id ?? "");
|
|
2896
|
+
if (!sessionId) continue;
|
|
2897
|
+
const msgData = parseJsonRecord(row.data, this.name, "message.data");
|
|
2898
|
+
if (isInternalEventType(msgData.type)) continue;
|
|
2899
|
+
const parentId = withParentId ? String(row.parent_id ?? "") : "";
|
|
2900
|
+
const isChild = parentId !== "";
|
|
2901
|
+
if (isChild) {
|
|
2902
|
+
const childContext = ensureContext(sessionId);
|
|
2903
|
+
const parts2 = partsByMessage.get(String(row.id ?? "")) ?? [];
|
|
2904
|
+
if (parts2.length > 0) {
|
|
2905
|
+
accumulateTokenStats(childContext.stats, msgData, this.name);
|
|
2906
|
+
childContext.stats.message_count += 1;
|
|
2907
|
+
if (!childContext.messageTitle && String(msgData.role ?? "") === "user") {
|
|
2908
|
+
childContext.messageTitle = firstUserMessageTitle([
|
|
2909
|
+
{
|
|
2910
|
+
id: String(row.id ?? ""),
|
|
2911
|
+
role: "user",
|
|
2912
|
+
agent: null,
|
|
2913
|
+
time_created: Number(row.time_created ?? 0),
|
|
2914
|
+
parts: parts2
|
|
2915
|
+
}
|
|
2916
|
+
]);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
const parentContext = ensureContext(parentId);
|
|
2920
|
+
accumulateTokenStats(parentContext.stats, msgData, this.name);
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
const parts = partsByMessage.get(String(row.id ?? "")) ?? [];
|
|
2924
|
+
if (parts.length === 0) continue;
|
|
2925
|
+
const context = ensureContext(sessionId);
|
|
2926
|
+
accumulateTokenStats(context.stats, msgData, this.name);
|
|
2306
2927
|
context.stats.message_count += 1;
|
|
2307
2928
|
if (!context.messageTitle && String(msgData.role ?? "") === "user") {
|
|
2308
2929
|
context.messageTitle = firstUserMessageTitle([
|
|
@@ -2323,6 +2944,29 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2323
2944
|
}
|
|
2324
2945
|
return contexts;
|
|
2325
2946
|
}
|
|
2947
|
+
sumChildTokenStats(db, parentSessionId) {
|
|
2948
|
+
if (!columnExists(db, "session", "parent_id")) return [];
|
|
2949
|
+
const childRows = db.prepare("SELECT id FROM session WHERE parent_id = ?").all(parentSessionId);
|
|
2950
|
+
const results = [];
|
|
2951
|
+
for (const child of childRows) {
|
|
2952
|
+
const childId = String(child.id ?? "");
|
|
2953
|
+
if (!childId) continue;
|
|
2954
|
+
const msgRows = db.prepare("SELECT data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(childId);
|
|
2955
|
+
const stats = {
|
|
2956
|
+
message_count: 0,
|
|
2957
|
+
total_input_tokens: 0,
|
|
2958
|
+
total_output_tokens: 0,
|
|
2959
|
+
total_cost: 0
|
|
2960
|
+
};
|
|
2961
|
+
for (const row of msgRows) {
|
|
2962
|
+
const msgData = parseJsonRecord(row.data, this.name, "message.data");
|
|
2963
|
+
if (isInternalEventType(msgData.type)) continue;
|
|
2964
|
+
accumulateTokenStats(stats, msgData, this.name);
|
|
2965
|
+
}
|
|
2966
|
+
results.push(stats);
|
|
2967
|
+
}
|
|
2968
|
+
return results;
|
|
2969
|
+
}
|
|
2326
2970
|
getSessionData(sessionId) {
|
|
2327
2971
|
if (!this.dbPath) {
|
|
2328
2972
|
this.dbPath = this.findDbPath();
|
|
@@ -2345,11 +2989,9 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2345
2989
|
const timeCreated = Number(sessionRow.time_created ?? 0);
|
|
2346
2990
|
const timeUpdated = Number(sessionRow.time_updated ?? timeCreated);
|
|
2347
2991
|
const messages = [];
|
|
2348
|
-
let totalCost = 0;
|
|
2349
|
-
let totalInputTokens = 0;
|
|
2350
|
-
let totalOutputTokens = 0;
|
|
2351
2992
|
let hasEstimatedCost = false;
|
|
2352
|
-
const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC").all(sessionId);
|
|
2993
|
+
const msgRows = db.prepare("SELECT * FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
|
|
2994
|
+
const partsByMessage = this.buildPartsByMessage(this.readSessionPartRows(db, sessionId));
|
|
2353
2995
|
for (const msgRow of msgRows) {
|
|
2354
2996
|
const msgData = parseJsonRecord(msgRow.data, this.name, "message.data");
|
|
2355
2997
|
if (isInternalEventType(msgData.type)) continue;
|
|
@@ -2360,7 +3002,7 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2360
3002
|
const model = parseModel(msgData.modelID, this.name);
|
|
2361
3003
|
const estimatedCost = cost > 0 ? null : estimateTokenCost(model, { input: inputTokens, output: outputTokens });
|
|
2362
3004
|
const resolvedCost = cost || estimatedCost || 0;
|
|
2363
|
-
const parts =
|
|
3005
|
+
const parts = partsByMessage.get(String(msgRow.id ?? "")) ?? [];
|
|
2364
3006
|
if (parts.length === 0) continue;
|
|
2365
3007
|
messages.push({
|
|
2366
3008
|
id: String(msgRow.id ?? ""),
|
|
@@ -2382,28 +3024,41 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2382
3024
|
firstUserMessageTitle(cleanedMessages),
|
|
2383
3025
|
null
|
|
2384
3026
|
);
|
|
3027
|
+
const stats = {
|
|
3028
|
+
message_count: cleanedMessages.length,
|
|
3029
|
+
total_input_tokens: 0,
|
|
3030
|
+
total_output_tokens: 0,
|
|
3031
|
+
total_cost: 0
|
|
3032
|
+
};
|
|
2385
3033
|
for (const message of cleanedMessages) {
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
3034
|
+
stats.total_cost += message.cost ?? 0;
|
|
3035
|
+
stats.total_input_tokens += message.tokens?.input ?? 0;
|
|
3036
|
+
stats.total_output_tokens += message.tokens?.output ?? 0;
|
|
2389
3037
|
if (message.cost_source === "estimated") hasEstimatedCost = true;
|
|
2390
3038
|
}
|
|
3039
|
+
for (const childStats of this.sumChildTokenStats(db, sessionId)) {
|
|
3040
|
+
stats.total_cost += childStats.total_cost;
|
|
3041
|
+
stats.total_input_tokens += childStats.total_input_tokens;
|
|
3042
|
+
stats.total_output_tokens += childStats.total_output_tokens;
|
|
3043
|
+
if (childStats.cost_source === "estimated") hasEstimatedCost = true;
|
|
3044
|
+
}
|
|
2391
3045
|
return {
|
|
2392
3046
|
reference: { agentName: this.name, sessionId: id },
|
|
2393
3047
|
id,
|
|
2394
3048
|
title,
|
|
2395
3049
|
slug,
|
|
2396
3050
|
directory,
|
|
3051
|
+
parent_reference: sessionRow.parent_id == null || String(sessionRow.parent_id) === "" ? void 0 : { agentName: this.name, sessionId: String(sessionRow.parent_id) },
|
|
2397
3052
|
version: asString(sessionRow.version) ?? void 0,
|
|
2398
3053
|
time_created: timeCreated,
|
|
2399
3054
|
time_updated: timeUpdated,
|
|
2400
3055
|
summary_files: sessionRow.summary_files ?? void 0,
|
|
2401
3056
|
stats: {
|
|
2402
|
-
message_count:
|
|
2403
|
-
total_input_tokens:
|
|
2404
|
-
total_output_tokens:
|
|
2405
|
-
total_cost:
|
|
2406
|
-
cost_source:
|
|
3057
|
+
message_count: stats.message_count,
|
|
3058
|
+
total_input_tokens: stats.total_input_tokens,
|
|
3059
|
+
total_output_tokens: stats.total_output_tokens,
|
|
3060
|
+
total_cost: stats.total_cost,
|
|
3061
|
+
cost_source: stats.total_cost > 0 ? hasEstimatedCost ? "estimated" : "recorded" : void 0
|
|
2407
3062
|
},
|
|
2408
3063
|
messages: cleanedMessages
|
|
2409
3064
|
};
|
|
@@ -2413,18 +3068,18 @@ var OpenCodeSqliteAgent = class extends DatabaseSessionSource {
|
|
|
2413
3068
|
}
|
|
2414
3069
|
};
|
|
2415
3070
|
function resolveOpenCodeDataRoot() {
|
|
2416
|
-
return
|
|
3071
|
+
return join7(resolveDataHome(), "opencode");
|
|
2417
3072
|
}
|
|
2418
3073
|
function findOpenCodeDbPath() {
|
|
2419
3074
|
if (!isSqliteAvailable()) return null;
|
|
2420
|
-
return firstExisting(
|
|
3075
|
+
return firstExisting(join7(resolveOpenCodeDataRoot(), "opencode.db"), "data/opencode/opencode.db");
|
|
2421
3076
|
}
|
|
2422
3077
|
function getOpenCodeSessionWatchPlan() {
|
|
2423
3078
|
const dataRoot = resolveOpenCodeDataRoot();
|
|
2424
3079
|
return {
|
|
2425
3080
|
status: "supported",
|
|
2426
3081
|
targets: [
|
|
2427
|
-
{ root: dataRoot, path:
|
|
3082
|
+
{ root: dataRoot, path: join7(dataRoot, "opencode.db") },
|
|
2428
3083
|
{ root: "data/opencode", path: "data/opencode/opencode.db" }
|
|
2429
3084
|
]
|
|
2430
3085
|
};
|
|
@@ -2521,14 +3176,14 @@ function kimiContentText(content) {
|
|
|
2521
3176
|
}).join(" ");
|
|
2522
3177
|
}
|
|
2523
3178
|
function extractFirstUserTitle(contextFile, wireFile) {
|
|
2524
|
-
if (contextFile &&
|
|
3179
|
+
if (contextFile && existsSync7(contextFile)) {
|
|
2525
3180
|
for (const record of readJsonlFile(contextFile)) {
|
|
2526
3181
|
if (record.role !== "user") continue;
|
|
2527
3182
|
const title = normalizeTitleText(kimiContentText(record.content));
|
|
2528
3183
|
if (title) return title;
|
|
2529
3184
|
}
|
|
2530
3185
|
}
|
|
2531
|
-
if (wireFile &&
|
|
3186
|
+
if (wireFile && existsSync7(wireFile)) {
|
|
2532
3187
|
for (const record of readJsonlFile(wireFile)) {
|
|
2533
3188
|
const message = asRecord(record.message) ?? {};
|
|
2534
3189
|
if (message.type !== "TurnBegin") continue;
|
|
@@ -2548,25 +3203,25 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
2548
3203
|
projectMap = /* @__PURE__ */ new Map();
|
|
2549
3204
|
defaultModel = null;
|
|
2550
3205
|
findBasePath() {
|
|
2551
|
-
return firstExisting(
|
|
3206
|
+
return firstExisting(join8(resolveKimiDataRoot(), "sessions"), "data/kimi");
|
|
2552
3207
|
}
|
|
2553
3208
|
getSessionWatchPlan() {
|
|
2554
3209
|
const dataRoot = resolveKimiDataRoot();
|
|
2555
3210
|
return {
|
|
2556
3211
|
status: "supported",
|
|
2557
|
-
targets: [{ root: dataRoot, path:
|
|
3212
|
+
targets: [{ root: dataRoot, path: join8(dataRoot, "sessions") }, { path: "data/kimi" }]
|
|
2558
3213
|
};
|
|
2559
3214
|
}
|
|
2560
3215
|
/** Parse kimi.json and build md5(project_path) → cwd mapping */
|
|
2561
3216
|
loadKimiConfig() {
|
|
2562
3217
|
const dataRoot = resolveKimiDataRoot();
|
|
2563
|
-
const configPath =
|
|
2564
|
-
const tomlPath =
|
|
2565
|
-
if (
|
|
3218
|
+
const configPath = join8(dataRoot, "kimi.json");
|
|
3219
|
+
const tomlPath = join8(dataRoot, "config.toml");
|
|
3220
|
+
if (existsSync7(tomlPath)) {
|
|
2566
3221
|
const configText = readFileSync3(tomlPath, "utf-8");
|
|
2567
3222
|
this.defaultModel = configText.match(/^default_model\s*=\s*"([^"]+)"/m)?.[1] ?? null;
|
|
2568
3223
|
}
|
|
2569
|
-
if (!
|
|
3224
|
+
if (!existsSync7(configPath)) return;
|
|
2570
3225
|
try {
|
|
2571
3226
|
const raw = asRecord(JSON.parse(readFileSync3(configPath, "utf-8")));
|
|
2572
3227
|
const workDirs = asArray(raw?.work_dirs);
|
|
@@ -2595,14 +3250,14 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
2595
3250
|
if (!this.basePath) return [];
|
|
2596
3251
|
const dirs = [];
|
|
2597
3252
|
try {
|
|
2598
|
-
for (const hashEntry of
|
|
3253
|
+
for (const hashEntry of readdirSync4(this.basePath, { withFileTypes: true })) {
|
|
2599
3254
|
if (!hashEntry.isDirectory()) continue;
|
|
2600
|
-
const hashPath =
|
|
3255
|
+
const hashPath = join8(this.basePath, hashEntry.name);
|
|
2601
3256
|
try {
|
|
2602
|
-
for (const sessionEntry of
|
|
3257
|
+
for (const sessionEntry of readdirSync4(hashPath, { withFileTypes: true })) {
|
|
2603
3258
|
if (!sessionEntry.isDirectory()) continue;
|
|
2604
|
-
const sessionPath =
|
|
2605
|
-
if (
|
|
3259
|
+
const sessionPath = join8(hashPath, sessionEntry.name);
|
|
3260
|
+
if (existsSync7(join8(sessionPath, "metadata.json")) || existsSync7(join8(sessionPath, "state.json"))) {
|
|
2606
3261
|
dirs.push(sessionPath);
|
|
2607
3262
|
}
|
|
2608
3263
|
}
|
|
@@ -2619,32 +3274,32 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
2619
3274
|
*/
|
|
2620
3275
|
resolveSessionSourceResult(sessionDir) {
|
|
2621
3276
|
try {
|
|
2622
|
-
const sessionId =
|
|
2623
|
-
const projectHash =
|
|
2624
|
-
const contextFile =
|
|
2625
|
-
const wireFile =
|
|
2626
|
-
const existingContextFile =
|
|
2627
|
-
const existingWireFile =
|
|
3277
|
+
const sessionId = basename5(sessionDir);
|
|
3278
|
+
const projectHash = basename5(dirname4(sessionDir));
|
|
3279
|
+
const contextFile = join8(sessionDir, "context.jsonl");
|
|
3280
|
+
const wireFile = join8(sessionDir, "wire.jsonl");
|
|
3281
|
+
const existingContextFile = existsSync7(contextFile) ? contextFile : null;
|
|
3282
|
+
const existingWireFile = existsSync7(wireFile) ? wireFile : null;
|
|
2628
3283
|
if (!existingContextFile && !existingWireFile) {
|
|
2629
3284
|
return skippedSession("missing transcript");
|
|
2630
3285
|
}
|
|
2631
|
-
const statePath =
|
|
2632
|
-
const metaPath =
|
|
3286
|
+
const statePath = join8(sessionDir, "state.json");
|
|
3287
|
+
const metaPath = join8(sessionDir, "metadata.json");
|
|
2633
3288
|
let explicitTitle = "";
|
|
2634
3289
|
let wireMtime = null;
|
|
2635
3290
|
let metaFile = "";
|
|
2636
|
-
if (
|
|
3291
|
+
if (existsSync7(statePath)) {
|
|
2637
3292
|
const state = asRecord(JSON.parse(readFileSync3(statePath, "utf-8"))) ?? {};
|
|
2638
3293
|
explicitTitle = String(state.custom_title ?? "");
|
|
2639
3294
|
wireMtime = readWireMtime(state);
|
|
2640
3295
|
metaFile = statePath;
|
|
2641
|
-
} else if (
|
|
3296
|
+
} else if (existsSync7(metaPath)) {
|
|
2642
3297
|
const meta = asRecord(JSON.parse(readFileSync3(metaPath, "utf-8"))) ?? {};
|
|
2643
3298
|
explicitTitle = String(meta.title ?? "");
|
|
2644
3299
|
wireMtime = readWireMtime(meta);
|
|
2645
3300
|
metaFile = metaPath;
|
|
2646
3301
|
}
|
|
2647
|
-
const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ?
|
|
3302
|
+
const createdAt = wireMtime !== null ? wireMtime * 1e3 : metaFile ? statSync4(metaFile).mtimeMs : statSync4(sessionDir).mtimeMs;
|
|
2648
3303
|
return parsedSession({
|
|
2649
3304
|
id: sessionId,
|
|
2650
3305
|
sourcePath: sessionDir,
|
|
@@ -2765,8 +3420,8 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
2765
3420
|
return this.buildSessionData(meta, builder, stats);
|
|
2766
3421
|
}
|
|
2767
3422
|
getSessionDataFromWire(meta) {
|
|
2768
|
-
const wirePath = meta.wireFile ??
|
|
2769
|
-
if (!
|
|
3423
|
+
const wirePath = meta.wireFile ?? join8(meta.sourcePath, "wire.jsonl");
|
|
3424
|
+
if (!existsSync7(wirePath)) throw new Error("wire.jsonl is missing");
|
|
2770
3425
|
const builder = new TranscriptBuilder();
|
|
2771
3426
|
const ignoredToolCallIds = /* @__PURE__ */ new Set();
|
|
2772
3427
|
const openToolArgumentBuffer = /* @__PURE__ */ new Map();
|
|
@@ -2847,7 +3502,7 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
2847
3502
|
const rawArgs = function_.arguments;
|
|
2848
3503
|
const normalizedArgs = normalizeToolArguments(rawArgs);
|
|
2849
3504
|
const buffer = typeof rawArgs === "string" && typeof normalizedArgs !== "string" ? rawArgs : null;
|
|
2850
|
-
const
|
|
3505
|
+
const toolPart2 = {
|
|
2851
3506
|
type: "tool",
|
|
2852
3507
|
tool: toolName,
|
|
2853
3508
|
callID: callId,
|
|
@@ -2856,7 +3511,7 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
2856
3511
|
time_created: timestampMs
|
|
2857
3512
|
};
|
|
2858
3513
|
builder.appendToolCall(
|
|
2859
|
-
|
|
3514
|
+
toolPart2,
|
|
2860
3515
|
{ id: `wire-${seq}`, timestampMs: 0, agent: "kimi" },
|
|
2861
3516
|
{ markModeAsTool: true, target: "current" }
|
|
2862
3517
|
);
|
|
@@ -3004,10 +3659,10 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
3004
3659
|
total_tokens: 0,
|
|
3005
3660
|
message_count: 0
|
|
3006
3661
|
};
|
|
3007
|
-
const wirePath =
|
|
3008
|
-
if (!
|
|
3009
|
-
const contextPath =
|
|
3010
|
-
const hasContext =
|
|
3662
|
+
const wirePath = join8(sessionDir, "wire.jsonl");
|
|
3663
|
+
if (!existsSync7(wirePath)) return stats;
|
|
3664
|
+
const contextPath = join8(sessionDir, "context.jsonl");
|
|
3665
|
+
const hasContext = existsSync7(contextPath);
|
|
3011
3666
|
try {
|
|
3012
3667
|
for (const record of readJsonlFile(wirePath)) {
|
|
3013
3668
|
const tokenUsage = asRecord(asRecord(record.message)?.usage);
|
|
@@ -3053,6 +3708,576 @@ var KimiAgent = class extends FileSystemSessionSource {
|
|
|
3053
3708
|
};
|
|
3054
3709
|
}
|
|
3055
3710
|
};
|
|
3711
|
+
var KIMI_CODE_TOOL_TITLE_MAP = {
|
|
3712
|
+
Read: "read",
|
|
3713
|
+
Write: "write",
|
|
3714
|
+
Edit: "edit",
|
|
3715
|
+
Bash: "bash",
|
|
3716
|
+
TodoList: "todo",
|
|
3717
|
+
AskUserQuestion: "ask",
|
|
3718
|
+
EnterPlanMode: "plan mode",
|
|
3719
|
+
ExitPlanMode: "plan approved",
|
|
3720
|
+
ReadFile: "read",
|
|
3721
|
+
Glob: "glob",
|
|
3722
|
+
StrReplaceFile: "edit",
|
|
3723
|
+
Grep: "grep",
|
|
3724
|
+
WriteFile: "write",
|
|
3725
|
+
Shell: "bash"
|
|
3726
|
+
};
|
|
3727
|
+
var KIMI_CODE_IGNORED_TOOLS = /* @__PURE__ */ new Set(["SetTodoList"]);
|
|
3728
|
+
function resolveKimiCodeDataRoot() {
|
|
3729
|
+
return resolveHomePath("KIMI_CODE_HOME", ".kimi-code");
|
|
3730
|
+
}
|
|
3731
|
+
function mapToolTitle2(toolName) {
|
|
3732
|
+
return KIMI_CODE_TOOL_TITLE_MAP[toolName] ?? toolName;
|
|
3733
|
+
}
|
|
3734
|
+
function normalizeToolArguments2(raw) {
|
|
3735
|
+
if (typeof raw !== "string") return raw;
|
|
3736
|
+
try {
|
|
3737
|
+
return JSON.parse(raw);
|
|
3738
|
+
} catch {
|
|
3739
|
+
return raw;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
function parseTimestamp(raw) {
|
|
3743
|
+
if (typeof raw === "number") return Number.isFinite(raw) ? raw : null;
|
|
3744
|
+
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
3745
|
+
const numeric = Number(raw);
|
|
3746
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
3747
|
+
const parsed = Date.parse(raw);
|
|
3748
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
3749
|
+
}
|
|
3750
|
+
function timestampFromRecord(record) {
|
|
3751
|
+
return parseTimestamp(record.time) ?? 0;
|
|
3752
|
+
}
|
|
3753
|
+
function contentText(content) {
|
|
3754
|
+
if (typeof content === "string") return content;
|
|
3755
|
+
if (!Array.isArray(content)) {
|
|
3756
|
+
const record = asRecord(content);
|
|
3757
|
+
return record ? String(record.text ?? "") : "";
|
|
3758
|
+
}
|
|
3759
|
+
return content.map((item) => {
|
|
3760
|
+
if (typeof item === "string") return item;
|
|
3761
|
+
const record = asRecord(item);
|
|
3762
|
+
return record ? String(record.text ?? "") : "";
|
|
3763
|
+
}).join(" ");
|
|
3764
|
+
}
|
|
3765
|
+
function contentParts(content, timestampMs) {
|
|
3766
|
+
const values = Array.isArray(content) ? content : [content];
|
|
3767
|
+
const parts = [];
|
|
3768
|
+
for (const value of values) {
|
|
3769
|
+
if (typeof value === "string") {
|
|
3770
|
+
const text = cleanInternalText(value);
|
|
3771
|
+
if (text) parts.push({ type: "text", text, time_created: timestampMs });
|
|
3772
|
+
continue;
|
|
3773
|
+
}
|
|
3774
|
+
const record = asRecord(value);
|
|
3775
|
+
if (!record) continue;
|
|
3776
|
+
const type = asString(record.type) ?? "";
|
|
3777
|
+
if (type === "text") {
|
|
3778
|
+
const text = cleanInternalText(asString(record.text) ?? "");
|
|
3779
|
+
if (text) parts.push({ type: "text", text, time_created: timestampMs });
|
|
3780
|
+
continue;
|
|
3781
|
+
}
|
|
3782
|
+
if (type === "think") {
|
|
3783
|
+
const text = cleanInternalText(asString(record.think) ?? "");
|
|
3784
|
+
if (text) parts.push({ type: "reasoning", text, time_created: timestampMs });
|
|
3785
|
+
continue;
|
|
3786
|
+
}
|
|
3787
|
+
if (type === "plan") {
|
|
3788
|
+
const text = cleanInternalText(asString(record.text) ?? "");
|
|
3789
|
+
if (text) {
|
|
3790
|
+
parts.push({
|
|
3791
|
+
type: "plan",
|
|
3792
|
+
text,
|
|
3793
|
+
approval_status: record.approved === false ? "fail" : "success",
|
|
3794
|
+
time_created: timestampMs
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
continue;
|
|
3798
|
+
}
|
|
3799
|
+
if (type === "image") {
|
|
3800
|
+
const source = asRecord(record.source);
|
|
3801
|
+
const imageUrl = asRecord(record.imageUrl);
|
|
3802
|
+
const url = asString(record.url) ?? asString(imageUrl?.url) ?? (source?.kind === "url" ? asString(source.url) : void 0);
|
|
3803
|
+
const data = asString(record.data) ?? (source?.kind === "base64" ? asString(source.data) : void 0);
|
|
3804
|
+
const mimeType = asString(record.mime_type) ?? asString(record.media_type) ?? (source?.kind === "base64" ? asString(source.media_type) : void 0) ?? "application/octet-stream";
|
|
3805
|
+
if (url) parts.push({ type: "image", url, mime_type: mimeType, time_created: timestampMs });
|
|
3806
|
+
else if (data) {
|
|
3807
|
+
parts.push({
|
|
3808
|
+
type: "image",
|
|
3809
|
+
data,
|
|
3810
|
+
mime_type: mimeType,
|
|
3811
|
+
time_created: timestampMs
|
|
3812
|
+
});
|
|
3813
|
+
}
|
|
3814
|
+
continue;
|
|
3815
|
+
}
|
|
3816
|
+
if (type === "image_url") {
|
|
3817
|
+
const imageUrl = asRecord(record.imageUrl);
|
|
3818
|
+
const url = asString(imageUrl?.url) ?? asString(record.url);
|
|
3819
|
+
if (url) parts.push({ type: "image", url, time_created: timestampMs });
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
return parts;
|
|
3823
|
+
}
|
|
3824
|
+
function toolOutputParts(output, timestampMs) {
|
|
3825
|
+
if (typeof output === "string") {
|
|
3826
|
+
const text2 = cleanInternalText(output);
|
|
3827
|
+
return text2 ? [{ type: "text", text: text2, time_created: timestampMs }] : [];
|
|
3828
|
+
}
|
|
3829
|
+
if (Array.isArray(output)) return contentParts(output, timestampMs);
|
|
3830
|
+
if (output == null) return [];
|
|
3831
|
+
const record = asRecord(output);
|
|
3832
|
+
if (record?.type === "text") return contentParts([record], timestampMs);
|
|
3833
|
+
const text = cleanInternalText(JSON.stringify(output, null, 2));
|
|
3834
|
+
return text ? [{ type: "text", text, time_created: timestampMs }] : [];
|
|
3835
|
+
}
|
|
3836
|
+
function toolPart(toolName, callId, input, timestampMs) {
|
|
3837
|
+
return {
|
|
3838
|
+
type: "tool",
|
|
3839
|
+
tool: toolName,
|
|
3840
|
+
callID: callId,
|
|
3841
|
+
title: mapToolTitle2(toolName),
|
|
3842
|
+
state: {
|
|
3843
|
+
status: "running",
|
|
3844
|
+
...input === void 0 ? {} : { input },
|
|
3845
|
+
output: null
|
|
3846
|
+
},
|
|
3847
|
+
time_created: timestampMs
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3850
|
+
function toolCallParts(message, timestampMs, ignoredToolCallIds) {
|
|
3851
|
+
const calls = asArray(message.toolCalls) ?? [];
|
|
3852
|
+
const parts = [];
|
|
3853
|
+
for (const call of calls) {
|
|
3854
|
+
const callRecord = asRecord(call);
|
|
3855
|
+
const functionRecord = asRecord(callRecord?.function) ?? callRecord;
|
|
3856
|
+
const toolName = asString(functionRecord?.name)?.trim() ?? "";
|
|
3857
|
+
const callId = asString(callRecord?.id)?.trim() ?? "";
|
|
3858
|
+
if (!toolName || !callId) continue;
|
|
3859
|
+
if (KIMI_CODE_IGNORED_TOOLS.has(toolName)) {
|
|
3860
|
+
ignoredToolCallIds.add(callId);
|
|
3861
|
+
continue;
|
|
3862
|
+
}
|
|
3863
|
+
const rawArguments = functionRecord?.arguments ?? callRecord?.arguments;
|
|
3864
|
+
parts.push(toolPart(toolName, callId, normalizeToolArguments2(rawArguments), timestampMs));
|
|
3865
|
+
}
|
|
3866
|
+
return parts;
|
|
3867
|
+
}
|
|
3868
|
+
function addToolResolution(builder, callId, output, timestampMs, isError = false, note) {
|
|
3869
|
+
const outputParts = toolOutputParts(output, timestampMs);
|
|
3870
|
+
return builder.resolveToolCall(callId, {
|
|
3871
|
+
output: outputParts,
|
|
3872
|
+
status: isError ? "error" : "completed",
|
|
3873
|
+
...note ? { metadata: { note: cleanInternalText(note) } } : {}
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
function usageNumber(usage, field) {
|
|
3877
|
+
return asNumber(usage[field]) ?? 0;
|
|
3878
|
+
}
|
|
3879
|
+
function emptyStats() {
|
|
3880
|
+
return {
|
|
3881
|
+
message_count: 0,
|
|
3882
|
+
total_input_tokens: 0,
|
|
3883
|
+
total_output_tokens: 0,
|
|
3884
|
+
total_cost: 0
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
function buildUsageTotals() {
|
|
3888
|
+
return {
|
|
3889
|
+
totalCost: 0,
|
|
3890
|
+
totalInputTokens: 0,
|
|
3891
|
+
totalOutputTokens: 0,
|
|
3892
|
+
totalCacheReadTokens: 0,
|
|
3893
|
+
totalCacheCreateTokens: 0,
|
|
3894
|
+
modelUsage: {}
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
function applyUsage(builder, record, activeModel, totals) {
|
|
3898
|
+
const usage = asRecord(record.usage);
|
|
3899
|
+
if (!usage) return;
|
|
3900
|
+
const cacheRead = usageNumber(usage, "inputCacheRead");
|
|
3901
|
+
const cacheCreate = usageNumber(usage, "inputCacheCreation");
|
|
3902
|
+
const inputOther = usageNumber(usage, "inputOther");
|
|
3903
|
+
const output = usageNumber(usage, "output");
|
|
3904
|
+
const input = inputOther + cacheRead + cacheCreate;
|
|
3905
|
+
const model = asString(record.model) ?? activeModel;
|
|
3906
|
+
const tokens = {
|
|
3907
|
+
input,
|
|
3908
|
+
output,
|
|
3909
|
+
cache_read: cacheRead,
|
|
3910
|
+
cache_create: cacheCreate
|
|
3911
|
+
};
|
|
3912
|
+
const cost = estimateTokenCost(model, tokens);
|
|
3913
|
+
totals.totalInputTokens += input;
|
|
3914
|
+
totals.totalOutputTokens += output;
|
|
3915
|
+
totals.totalCacheReadTokens += cacheRead;
|
|
3916
|
+
totals.totalCacheCreateTokens += cacheCreate;
|
|
3917
|
+
if (model) totals.modelUsage[model] = (totals.modelUsage[model] ?? 0) + input + output;
|
|
3918
|
+
if (cost !== null) totals.totalCost += cost;
|
|
3919
|
+
builder.attachUsageToLatestAssistant(tokens, {
|
|
3920
|
+
model,
|
|
3921
|
+
cost: cost ?? void 0,
|
|
3922
|
+
costSource: cost === null ? void 0 : "estimated"
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
function readState(stateFile) {
|
|
3926
|
+
try {
|
|
3927
|
+
return asRecord(JSON.parse(readFileSync4(stateFile, "utf-8"))) ?? null;
|
|
3928
|
+
} catch {
|
|
3929
|
+
return null;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
var KimiCodeAgent = class extends FileSystemSessionSource {
|
|
3933
|
+
name = "kimi-code";
|
|
3934
|
+
displayName = "Kimi-Code";
|
|
3935
|
+
basePath = null;
|
|
3936
|
+
workDirBySessionPath = /* @__PURE__ */ new Map();
|
|
3937
|
+
findBasePath() {
|
|
3938
|
+
const sessionsPath = join9(resolveKimiCodeDataRoot(), "sessions");
|
|
3939
|
+
return existsSync8(sessionsPath) ? sessionsPath : null;
|
|
3940
|
+
}
|
|
3941
|
+
getSessionWatchPlan() {
|
|
3942
|
+
const dataRoot = resolveKimiCodeDataRoot();
|
|
3943
|
+
return {
|
|
3944
|
+
status: "supported",
|
|
3945
|
+
targets: [
|
|
3946
|
+
{ root: dataRoot, path: join9(dataRoot, "sessions") },
|
|
3947
|
+
{ root: dataRoot, path: join9(dataRoot, "session_index.jsonl") }
|
|
3948
|
+
]
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
isAvailable() {
|
|
3952
|
+
this.basePath = this.findBasePath();
|
|
3953
|
+
if (!this.basePath) return false;
|
|
3954
|
+
try {
|
|
3955
|
+
return this.listSessionDirs().length > 0;
|
|
3956
|
+
} catch {
|
|
3957
|
+
return false;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
loadSessionIndex() {
|
|
3961
|
+
this.workDirBySessionPath.clear();
|
|
3962
|
+
if (!this.basePath) return;
|
|
3963
|
+
const indexPath = join9(dirname5(this.basePath), "session_index.jsonl");
|
|
3964
|
+
if (!existsSync8(indexPath)) return;
|
|
3965
|
+
for (const record of readJsonlFile(indexPath)) {
|
|
3966
|
+
const sessionPath = asString(record.sessionDir);
|
|
3967
|
+
const workDir = asString(record.workDir);
|
|
3968
|
+
if (!sessionPath || !workDir) continue;
|
|
3969
|
+
this.workDirBySessionPath.set(resolve(sessionPath), workDir);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
listSessionDirs() {
|
|
3973
|
+
if (!this.basePath) return [];
|
|
3974
|
+
const dirs = [];
|
|
3975
|
+
try {
|
|
3976
|
+
for (const bucket of readdirSync5(this.basePath, { withFileTypes: true })) {
|
|
3977
|
+
if (!bucket.isDirectory()) continue;
|
|
3978
|
+
const bucketPath = join9(this.basePath, bucket.name);
|
|
3979
|
+
for (const session of readdirSync5(bucketPath, { withFileTypes: true })) {
|
|
3980
|
+
if (!session.isDirectory()) continue;
|
|
3981
|
+
const sessionPath = join9(bucketPath, session.name);
|
|
3982
|
+
if (existsSync8(join9(sessionPath, "state.json")) && existsSync8(join9(sessionPath, "agents", "main", "wire.jsonl"))) {
|
|
3983
|
+
dirs.push(sessionPath);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
} catch {
|
|
3988
|
+
return dirs;
|
|
3989
|
+
}
|
|
3990
|
+
return dirs;
|
|
3991
|
+
}
|
|
3992
|
+
resolveSessionSourceResult(sessionDir) {
|
|
3993
|
+
try {
|
|
3994
|
+
const stateFile = join9(sessionDir, "state.json");
|
|
3995
|
+
const wireFile = join9(sessionDir, "agents", "main", "wire.jsonl");
|
|
3996
|
+
if (!existsSync8(stateFile) || !existsSync8(wireFile)) return skippedSession("missing wire");
|
|
3997
|
+
const state = readState(stateFile);
|
|
3998
|
+
if (!state) return skippedSession("malformed state");
|
|
3999
|
+
const stateMtime = statSync5(stateFile).mtimeMs;
|
|
4000
|
+
const wireMtime = statSync5(wireFile).mtimeMs;
|
|
4001
|
+
const createdAt = parseTimestamp(state.createdAt) ?? parseTimestamp(state.created_at) ?? stateMtime;
|
|
4002
|
+
const updatedAt = Math.max(
|
|
4003
|
+
parseTimestamp(state.updatedAt) ?? parseTimestamp(state.updated_at) ?? createdAt,
|
|
4004
|
+
wireMtime
|
|
4005
|
+
);
|
|
4006
|
+
const custom = asRecord(state.custom);
|
|
4007
|
+
const workDir = asString(state.workDir) ?? asString(custom?.cwd) ?? this.workDirBySessionPath.get(resolve(sessionDir)) ?? "";
|
|
4008
|
+
const explicitTitle = asString(state.title) ?? asString(state.customTitle) ?? "";
|
|
4009
|
+
return parsedSession({
|
|
4010
|
+
id: basename6(sessionDir),
|
|
4011
|
+
sourcePath: sessionDir,
|
|
4012
|
+
stateFile,
|
|
4013
|
+
wireFile,
|
|
4014
|
+
workDir,
|
|
4015
|
+
createdAt,
|
|
4016
|
+
updatedAt,
|
|
4017
|
+
explicitTitle
|
|
4018
|
+
});
|
|
4019
|
+
} catch {
|
|
4020
|
+
return skippedSession("malformed session");
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
sourceFingerprint(source) {
|
|
4024
|
+
return JSON.stringify([
|
|
4025
|
+
this.readFileMtimeMs(source.stateFile),
|
|
4026
|
+
this.readFileMtimeMs(source.wireFile),
|
|
4027
|
+
source.workDir
|
|
4028
|
+
]);
|
|
4029
|
+
}
|
|
4030
|
+
listSessionSources(options) {
|
|
4031
|
+
if (!this.basePath) return [];
|
|
4032
|
+
this.loadSessionIndex();
|
|
4033
|
+
const refs = [];
|
|
4034
|
+
for (const sessionDir of this.listSessionDirs()) {
|
|
4035
|
+
const source = getParsedSession(this.resolveSessionSourceResult(sessionDir));
|
|
4036
|
+
if (!source || !matchesScanWindow(source.createdAt, options)) continue;
|
|
4037
|
+
refs.push({
|
|
4038
|
+
sessionId: source.id,
|
|
4039
|
+
sourcePath: source.sourcePath,
|
|
4040
|
+
fingerprint: this.sourceFingerprint(source)
|
|
4041
|
+
});
|
|
4042
|
+
}
|
|
4043
|
+
return refs;
|
|
4044
|
+
}
|
|
4045
|
+
checkForChanges(sinceTimestamp, cachedSessions) {
|
|
4046
|
+
const result = super.checkForChanges(sinceTimestamp, cachedSessions);
|
|
4047
|
+
const emptySessionIds = cachedSessions.filter((session) => !this.hasMessages(session)).map((session) => session.id);
|
|
4048
|
+
if (emptySessionIds.length === 0) return result;
|
|
4049
|
+
return {
|
|
4050
|
+
...result,
|
|
4051
|
+
hasChanges: true,
|
|
4052
|
+
changedIds: [.../* @__PURE__ */ new Set([...result.changedIds ?? [], ...emptySessionIds])]
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
filterCachedSessions(sessions) {
|
|
4056
|
+
return this.removeEmptyCachedSessions(sessions);
|
|
4057
|
+
}
|
|
4058
|
+
incrementalScan(cachedSessions, changedIds, refs) {
|
|
4059
|
+
const visibleSessions = this.removeEmptyCachedSessions(cachedSessions);
|
|
4060
|
+
return super.incrementalScan(visibleSessions, changedIds, refs);
|
|
4061
|
+
}
|
|
4062
|
+
hasMessages(session) {
|
|
4063
|
+
return session.stats.message_count > 0;
|
|
4064
|
+
}
|
|
4065
|
+
removeEmptyCachedSessions(sessions) {
|
|
4066
|
+
return sessions.filter((session) => {
|
|
4067
|
+
if (this.hasMessages(session)) return true;
|
|
4068
|
+
this.sessionMetaMap.delete(session.id);
|
|
4069
|
+
return false;
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
scanSessionSource(sourcePath) {
|
|
4073
|
+
this.loadSessionIndex();
|
|
4074
|
+
const source = getParsedSession(this.resolveSessionSourceResult(sourcePath));
|
|
4075
|
+
if (!source) return null;
|
|
4076
|
+
const parsed = this.parseWire(source);
|
|
4077
|
+
const transcript = parsed.builder.finish(parsed.stats);
|
|
4078
|
+
if (transcript.messages.length === 0) {
|
|
4079
|
+
this.sessionMetaMap.delete(source.id);
|
|
4080
|
+
return null;
|
|
4081
|
+
}
|
|
4082
|
+
const title = resolveSessionTitle(source.explicitTitle, parsed.firstUserTitle, null);
|
|
4083
|
+
const meta = {
|
|
4084
|
+
...source,
|
|
4085
|
+
title,
|
|
4086
|
+
sourceMtimeMs: source.createdAt,
|
|
4087
|
+
sourceFingerprint: this.sourceFingerprint(source)
|
|
4088
|
+
};
|
|
4089
|
+
this.sessionMetaMap.set(meta.id, meta);
|
|
4090
|
+
return {
|
|
4091
|
+
id: meta.id,
|
|
4092
|
+
slug: `${this.name}/${meta.id}`,
|
|
4093
|
+
title: meta.title,
|
|
4094
|
+
directory: meta.workDir,
|
|
4095
|
+
time_created: meta.createdAt,
|
|
4096
|
+
time_updated: meta.updatedAt,
|
|
4097
|
+
stats: transcript.stats,
|
|
4098
|
+
...Object.keys(parsed.modelUsage).length > 0 ? { model_usage: parsed.modelUsage } : {}
|
|
4099
|
+
};
|
|
4100
|
+
}
|
|
4101
|
+
getSessionData(sessionId) {
|
|
4102
|
+
const meta = this.sessionMetaMap.get(sessionId);
|
|
4103
|
+
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
4104
|
+
const parsed = this.parseWire(meta);
|
|
4105
|
+
const transcript = parsed.builder.finish(parsed.stats);
|
|
4106
|
+
return {
|
|
4107
|
+
reference: { agentName: this.name, sessionId: meta.id },
|
|
4108
|
+
id: meta.id,
|
|
4109
|
+
title: meta.title,
|
|
4110
|
+
slug: `${this.name}/${meta.id}`,
|
|
4111
|
+
directory: meta.workDir,
|
|
4112
|
+
time_created: meta.createdAt,
|
|
4113
|
+
time_updated: meta.updatedAt,
|
|
4114
|
+
stats: transcript.stats,
|
|
4115
|
+
messages: transcript.messages
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
parseWire(source) {
|
|
4119
|
+
const builder = new TranscriptBuilder();
|
|
4120
|
+
const totals = buildUsageTotals();
|
|
4121
|
+
const ignoredToolCallIds = /* @__PURE__ */ new Set();
|
|
4122
|
+
let activeModel = null;
|
|
4123
|
+
let activeProvider = null;
|
|
4124
|
+
let firstUserTitle = null;
|
|
4125
|
+
let sequence = 0;
|
|
4126
|
+
for (const record of readJsonlFile(source.wireFile)) {
|
|
4127
|
+
sequence += 1;
|
|
4128
|
+
try {
|
|
4129
|
+
const timestampMs = timestampFromRecord(record);
|
|
4130
|
+
const recordType = asString(record.type) ?? "";
|
|
4131
|
+
if (recordType === "llm.request") {
|
|
4132
|
+
activeModel = asString(record.model) ?? activeModel;
|
|
4133
|
+
activeProvider = asString(record.provider) ?? activeProvider;
|
|
4134
|
+
continue;
|
|
4135
|
+
}
|
|
4136
|
+
if (recordType === "config.update") {
|
|
4137
|
+
activeModel = asString(record.modelAlias) ?? activeModel;
|
|
4138
|
+
continue;
|
|
4139
|
+
}
|
|
4140
|
+
if (recordType === "usage.record") {
|
|
4141
|
+
applyUsage(builder, record, activeModel, totals);
|
|
4142
|
+
continue;
|
|
4143
|
+
}
|
|
4144
|
+
if (recordType === "context.append_message") {
|
|
4145
|
+
const message = asRecord(record.message);
|
|
4146
|
+
if (!message) continue;
|
|
4147
|
+
const role = asString(message.role) ?? "";
|
|
4148
|
+
const messageTimestamp = timestampMs;
|
|
4149
|
+
if (role === "user") {
|
|
4150
|
+
const text = normalizeTitleText(contentText(message.content));
|
|
4151
|
+
if (!firstUserTitle && text) firstUserTitle = text;
|
|
4152
|
+
}
|
|
4153
|
+
if (role === "tool") {
|
|
4154
|
+
const callId = asString(message.toolCallId)?.trim() ?? "";
|
|
4155
|
+
const output = message.content;
|
|
4156
|
+
if (callId && ignoredToolCallIds.has(callId)) continue;
|
|
4157
|
+
if (callId && addToolResolution(builder, callId, output, messageTimestamp)) continue;
|
|
4158
|
+
const outputParts = toolOutputParts(output, messageTimestamp);
|
|
4159
|
+
if (outputParts.length > 0) {
|
|
4160
|
+
builder.appendMessage({
|
|
4161
|
+
id: `wire-${sequence}`,
|
|
4162
|
+
role: "tool",
|
|
4163
|
+
timestampMs: messageTimestamp,
|
|
4164
|
+
parts: outputParts
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
continue;
|
|
4168
|
+
}
|
|
4169
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
4170
|
+
const parts = [
|
|
4171
|
+
...contentParts(message.content, messageTimestamp),
|
|
4172
|
+
...role === "assistant" ? toolCallParts(message, messageTimestamp, ignoredToolCallIds) : []
|
|
4173
|
+
];
|
|
4174
|
+
if (parts.length === 0) continue;
|
|
4175
|
+
const allTools = parts.every((part) => part.type === "tool");
|
|
4176
|
+
const input = {
|
|
4177
|
+
id: `wire-${sequence}`,
|
|
4178
|
+
role,
|
|
4179
|
+
timestampMs: messageTimestamp,
|
|
4180
|
+
parts,
|
|
4181
|
+
...role === "assistant" ? {
|
|
4182
|
+
agent: this.name,
|
|
4183
|
+
mode: allTools ? "tool" : void 0,
|
|
4184
|
+
model: activeModel,
|
|
4185
|
+
provider: activeProvider
|
|
4186
|
+
} : {}
|
|
4187
|
+
};
|
|
4188
|
+
builder.appendMessage(input);
|
|
4189
|
+
continue;
|
|
4190
|
+
}
|
|
4191
|
+
if (recordType === "context.append_loop_event") {
|
|
4192
|
+
const event = asRecord(record.event);
|
|
4193
|
+
if (!event) continue;
|
|
4194
|
+
const eventType = asString(event.type) ?? "";
|
|
4195
|
+
const metadata = {
|
|
4196
|
+
id: `wire-${sequence}`,
|
|
4197
|
+
timestampMs,
|
|
4198
|
+
agent: this.name,
|
|
4199
|
+
model: activeModel,
|
|
4200
|
+
provider: activeProvider
|
|
4201
|
+
};
|
|
4202
|
+
if (eventType === "step.begin") {
|
|
4203
|
+
builder.beginTurn();
|
|
4204
|
+
continue;
|
|
4205
|
+
}
|
|
4206
|
+
if (eventType === "content.part") {
|
|
4207
|
+
const part = asRecord(event.part);
|
|
4208
|
+
if (!part) continue;
|
|
4209
|
+
const parts = contentParts([part], timestampMs);
|
|
4210
|
+
for (const contentPart of parts) {
|
|
4211
|
+
if (contentPart.type === "tool") continue;
|
|
4212
|
+
if (contentPart.type === "image" && builder.appendToCurrentAssistant(contentPart)) {
|
|
4213
|
+
continue;
|
|
4214
|
+
}
|
|
4215
|
+
builder.appendAssistantPart(contentPart, metadata, { grouping: "current" });
|
|
4216
|
+
}
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
if (eventType === "tool.call") {
|
|
4220
|
+
const toolName = asString(event.name)?.trim() ?? "";
|
|
4221
|
+
const callId = asString(event.toolCallId)?.trim() ?? "";
|
|
4222
|
+
if (!toolName || !callId) continue;
|
|
4223
|
+
if (KIMI_CODE_IGNORED_TOOLS.has(toolName)) {
|
|
4224
|
+
ignoredToolCallIds.add(callId);
|
|
4225
|
+
continue;
|
|
4226
|
+
}
|
|
4227
|
+
builder.appendToolCall(toolPart(toolName, callId, event.args, timestampMs), metadata, {
|
|
4228
|
+
markModeAsTool: true,
|
|
4229
|
+
target: "current"
|
|
4230
|
+
});
|
|
4231
|
+
continue;
|
|
4232
|
+
}
|
|
4233
|
+
if (eventType === "tool.result") {
|
|
4234
|
+
const callId = asString(event.toolCallId)?.trim() ?? "";
|
|
4235
|
+
const result = asRecord(event.result);
|
|
4236
|
+
if (!callId || ignoredToolCallIds.has(callId)) continue;
|
|
4237
|
+
const output = result?.output;
|
|
4238
|
+
const isError = result?.isError === true;
|
|
4239
|
+
const note = asString(result?.note);
|
|
4240
|
+
if (addToolResolution(builder, callId, output, timestampMs, isError, note)) continue;
|
|
4241
|
+
const outputParts = toolOutputParts(output, timestampMs);
|
|
4242
|
+
if (outputParts.length > 0) {
|
|
4243
|
+
builder.appendMessage({
|
|
4244
|
+
id: `wire-${sequence}`,
|
|
4245
|
+
role: "tool",
|
|
4246
|
+
timestampMs,
|
|
4247
|
+
parts: outputParts
|
|
4248
|
+
});
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
continue;
|
|
4252
|
+
}
|
|
4253
|
+
if (recordType === "context.apply_compaction") {
|
|
4254
|
+
const summary = cleanInternalText(asString(record.summary) ?? "");
|
|
4255
|
+
if (summary) {
|
|
4256
|
+
builder.appendMessage({
|
|
4257
|
+
id: `wire-${sequence}`,
|
|
4258
|
+
role: "user",
|
|
4259
|
+
timestampMs,
|
|
4260
|
+
parts: [{ type: "text", text: summary, time_created: timestampMs }]
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
} catch {
|
|
4265
|
+
continue;
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
const stats = {
|
|
4269
|
+
...emptyStats(),
|
|
4270
|
+
total_input_tokens: totals.totalInputTokens,
|
|
4271
|
+
total_output_tokens: totals.totalOutputTokens,
|
|
4272
|
+
total_cost: Number(totals.totalCost.toFixed(8)),
|
|
4273
|
+
total_tokens: totals.totalInputTokens + totals.totalOutputTokens,
|
|
4274
|
+
...totals.totalCacheReadTokens > 0 ? { total_cache_read_tokens: totals.totalCacheReadTokens } : {},
|
|
4275
|
+
...totals.totalCacheCreateTokens > 0 ? { total_cache_create_tokens: totals.totalCacheCreateTokens } : {},
|
|
4276
|
+
...totals.totalCost > 0 ? { cost_source: "estimated" } : {}
|
|
4277
|
+
};
|
|
4278
|
+
return { builder, stats, firstUserTitle, modelUsage: totals.modelUsage };
|
|
4279
|
+
}
|
|
4280
|
+
};
|
|
3056
4281
|
var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
3057
4282
|
var EXEC_OUTPUT_ENVELOPE_RE = /^Script completed\nWall time [^\n]*\nOutput:\n?/;
|
|
3058
4283
|
function stripExecOutputEnvelope(text) {
|
|
@@ -3314,7 +4539,7 @@ var PROPOSED_PLAN_PATTERN = /<proposed_plan>\s*([\s\S]*?)\s*<\/proposed_plan>/;
|
|
|
3314
4539
|
var PLAN_APPROVAL_PREFIX = "PLEASE IMPLEMENT THIS PLAN";
|
|
3315
4540
|
var SUBAGENT_NOTIFICATION_PATTERN = /<subagent_notification>\s*([\s\S]*?)\s*<\/subagent_notification>/;
|
|
3316
4541
|
var HEAD_INDEX_VERSION2 = "codex-head-v1";
|
|
3317
|
-
var PARSER_VERSION = "codex-parser-
|
|
4542
|
+
var PARSER_VERSION = "codex-parser-v6";
|
|
3318
4543
|
function resolveCodexDataRoot() {
|
|
3319
4544
|
return resolveHomePath("CODEX_HOME", ".codex");
|
|
3320
4545
|
}
|
|
@@ -3337,7 +4562,7 @@ var CODEX_TOOL_TITLE_MAP = {
|
|
|
3337
4562
|
subagent: "subagent"
|
|
3338
4563
|
};
|
|
3339
4564
|
function extractSessionId(filename) {
|
|
3340
|
-
const stem =
|
|
4565
|
+
const stem = basename7(filename, ".jsonl");
|
|
3341
4566
|
const parts = stem.split("-");
|
|
3342
4567
|
if (parts.length >= 5) {
|
|
3343
4568
|
return parts.slice(-5).join("-");
|
|
@@ -3366,6 +4591,14 @@ function narrowRecordField(value, field) {
|
|
|
3366
4591
|
function extractPayload(data) {
|
|
3367
4592
|
return narrowRecordField(data["payload"], "payload") ?? {};
|
|
3368
4593
|
}
|
|
4594
|
+
function extractThreadMeta(firstRecord) {
|
|
4595
|
+
if (firstRecord["type"] !== "session_meta") return null;
|
|
4596
|
+
const payload = extractPayload(firstRecord);
|
|
4597
|
+
const threadSource = asString(payload["thread_source"]) ?? "";
|
|
4598
|
+
const parentThreadId = asString(payload["parent_thread_id"]) ?? null;
|
|
4599
|
+
const agentNickname = asString(payload["agent_nickname"]) ?? null;
|
|
4600
|
+
return { threadSource, parentThreadId, agentNickname };
|
|
4601
|
+
}
|
|
3369
4602
|
function extractTokenUsage(payload) {
|
|
3370
4603
|
const info = narrowRecordField(payload["info"], "token_count.info");
|
|
3371
4604
|
return {
|
|
@@ -3391,7 +4624,7 @@ function resolveToolIdentity(name, namespace) {
|
|
|
3391
4624
|
metadata: { name, namespace: namespaceText }
|
|
3392
4625
|
};
|
|
3393
4626
|
}
|
|
3394
|
-
function
|
|
4627
|
+
function normalizeToolArguments3(raw) {
|
|
3395
4628
|
if (typeof raw === "string") {
|
|
3396
4629
|
try {
|
|
3397
4630
|
return JSON.parse(raw);
|
|
@@ -3418,6 +4651,16 @@ function flattenOutputText(output) {
|
|
|
3418
4651
|
}
|
|
3419
4652
|
return "";
|
|
3420
4653
|
}
|
|
4654
|
+
function extractAssistantOutputText(payload) {
|
|
4655
|
+
const content = payload["content"];
|
|
4656
|
+
if (Array.isArray(content)) {
|
|
4657
|
+
return content.map((item) => {
|
|
4658
|
+
const record = asRecord(item);
|
|
4659
|
+
return record && String(record["type"] ?? "") === "output_text" ? String(record["text"] ?? "") : "";
|
|
4660
|
+
}).filter(Boolean).join("\n");
|
|
4661
|
+
}
|
|
4662
|
+
return "";
|
|
4663
|
+
}
|
|
3421
4664
|
var PATCH_BEGIN_RE = /\*\*\* Begin Patch/;
|
|
3422
4665
|
var PATCH_END_RE = /\*\*\* End Patch/;
|
|
3423
4666
|
var PATCH_HEADER_RE = /\*\*\*\s+(Add|Delete|Update|Move)\s+File:\s*(.+)/;
|
|
@@ -3500,6 +4743,18 @@ function extractPatchContent(lines, startIndex) {
|
|
|
3500
4743
|
}
|
|
3501
4744
|
return { text: contentLines.join("\n"), nextLineIndex: i };
|
|
3502
4745
|
}
|
|
4746
|
+
function compareSourceActivityDesc(left, right) {
|
|
4747
|
+
const leftTimestamp = sourceTimestamp(left.file, left.stat.mtimeMs);
|
|
4748
|
+
const rightTimestamp = sourceTimestamp(right.file, right.stat.mtimeMs);
|
|
4749
|
+
return rightTimestamp - leftTimestamp || left.file.localeCompare(right.file);
|
|
4750
|
+
}
|
|
4751
|
+
function sourceTimestamp(filePath, fallback) {
|
|
4752
|
+
const match = basename7(filePath).match(/^rollout-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})-/);
|
|
4753
|
+
if (!match) return fallback;
|
|
4754
|
+
const timestamp = match[1].replace(/-(\d{2})-(\d{2})$/, ":$1:$2");
|
|
4755
|
+
const parsed = Date.parse(timestamp);
|
|
4756
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
4757
|
+
}
|
|
3503
4758
|
var CodexAgent = class extends SingleFileSessionSource {
|
|
3504
4759
|
name = "codex";
|
|
3505
4760
|
displayName = "Codex";
|
|
@@ -3507,17 +4762,19 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3507
4762
|
sessionIndexCache = /* @__PURE__ */ new Map();
|
|
3508
4763
|
sessionIndexMtime;
|
|
3509
4764
|
sessionIndexPath;
|
|
4765
|
+
subagentIndex = null;
|
|
4766
|
+
subagentStatsByParent = /* @__PURE__ */ new Map();
|
|
3510
4767
|
// ---- BaseAgent implementation ----
|
|
3511
4768
|
findBasePath() {
|
|
3512
|
-
return firstExisting(
|
|
4769
|
+
return firstExisting(join10(resolveCodexDataRoot(), "sessions"));
|
|
3513
4770
|
}
|
|
3514
4771
|
getSessionWatchPlan() {
|
|
3515
4772
|
const dataRoot = resolveCodexDataRoot();
|
|
3516
4773
|
return {
|
|
3517
4774
|
status: "supported",
|
|
3518
4775
|
targets: [
|
|
3519
|
-
{ path:
|
|
3520
|
-
{ path:
|
|
4776
|
+
{ path: join10(dataRoot, "sessions") },
|
|
4777
|
+
{ path: join10(dataRoot, "session_index.jsonl") }
|
|
3521
4778
|
]
|
|
3522
4779
|
};
|
|
3523
4780
|
}
|
|
@@ -3529,16 +4786,119 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3529
4786
|
listSessionSources(options) {
|
|
3530
4787
|
if (!this.basePath) return [];
|
|
3531
4788
|
this.loadSessionIndex();
|
|
3532
|
-
return this.
|
|
4789
|
+
return this.listScanSources(options).map(({ file, stat }) => ({
|
|
3533
4790
|
sessionId: extractSessionId(file),
|
|
3534
4791
|
sourcePath: file,
|
|
3535
4792
|
fingerprint: this.sourceFingerprint(file, stat)
|
|
3536
4793
|
}));
|
|
3537
4794
|
}
|
|
4795
|
+
setSessionMetaMap(meta) {
|
|
4796
|
+
super.setSessionMetaMap(meta);
|
|
4797
|
+
this.subagentIndex = null;
|
|
4798
|
+
this.subagentStatsByParent.clear();
|
|
4799
|
+
}
|
|
4800
|
+
/**
|
|
4801
|
+
* A changed subagent file leaves its parent's aggregated token stats stale,
|
|
4802
|
+
* so the parent must re-parse alongside the child.
|
|
4803
|
+
*/
|
|
4804
|
+
expandChangedSessionIds(changedIds, refs) {
|
|
4805
|
+
if (changedIds.length === 0) return changedIds;
|
|
4806
|
+
const pathById = new Map((refs ?? []).map((ref) => [ref.sessionId, ref.sourcePath]));
|
|
4807
|
+
const expanded = new Set(changedIds);
|
|
4808
|
+
for (const id of changedIds) {
|
|
4809
|
+
const sourcePath = pathById.get(id) ?? this.sessionMetaMap.get(id)?.sourcePath;
|
|
4810
|
+
const threadMeta = sourcePath ? this.readThreadMeta(sourcePath) : null;
|
|
4811
|
+
const parentId = threadMeta ? threadMeta.threadSource === "subagent" ? threadMeta.parentThreadId : null : this.sessionMetaMap.get(id)?.parentThreadId ?? null;
|
|
4812
|
+
if (!parentId) continue;
|
|
4813
|
+
expanded.add(parentId);
|
|
4814
|
+
this.subagentIndex = null;
|
|
4815
|
+
this.subagentStatsByParent.delete(parentId);
|
|
4816
|
+
}
|
|
4817
|
+
return [...expanded];
|
|
4818
|
+
}
|
|
4819
|
+
readThreadMeta(filePath) {
|
|
4820
|
+
try {
|
|
4821
|
+
const firstLine = this.readFilePrefix(filePath).split("\n").filter((l) => l.trim())[0];
|
|
4822
|
+
if (!firstLine) return null;
|
|
4823
|
+
return extractThreadMeta(JSON.parse(firstLine));
|
|
4824
|
+
} catch {
|
|
4825
|
+
return null;
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
parseTokenStats(filePath) {
|
|
4829
|
+
let totalInputTokens = 0;
|
|
4830
|
+
let totalOutputTokens = 0;
|
|
4831
|
+
let totalCacheReadTokens = 0;
|
|
4832
|
+
let totalCost = 0;
|
|
4833
|
+
let activeModel = null;
|
|
4834
|
+
let prevCumulativeTotal = 0;
|
|
4835
|
+
let prevInput = 0;
|
|
4836
|
+
let prevOutput = 0;
|
|
4837
|
+
let prevReasoning = 0;
|
|
4838
|
+
let prevCachedInput = 0;
|
|
4839
|
+
for (const line of readJsonlFileLines(filePath)) {
|
|
4840
|
+
try {
|
|
4841
|
+
const data = JSON.parse(line);
|
|
4842
|
+
const recordType = String(data["type"] ?? "");
|
|
4843
|
+
const payload = extractPayload(data);
|
|
4844
|
+
if (recordType === "session_meta" || recordType === "turn_context") {
|
|
4845
|
+
const nextModel = extractModelName(payload["model"]);
|
|
4846
|
+
if (nextModel) activeModel = nextModel;
|
|
4847
|
+
continue;
|
|
4848
|
+
}
|
|
4849
|
+
if (recordType === "event_msg" && String(payload["type"] ?? "") === "token_count") {
|
|
4850
|
+
const { totalUsage, lastUsage } = extractTokenUsage(payload);
|
|
4851
|
+
const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
|
|
4852
|
+
if (cumulativeTotal <= 0 || cumulativeTotal === prevCumulativeTotal) continue;
|
|
4853
|
+
prevCumulativeTotal = cumulativeTotal;
|
|
4854
|
+
let inputTokens = 0;
|
|
4855
|
+
let outputTokens = 0;
|
|
4856
|
+
let reasoningTokens = 0;
|
|
4857
|
+
let cacheReadTokens = 0;
|
|
4858
|
+
if (lastUsage) {
|
|
4859
|
+
inputTokens = Number(lastUsage["input_tokens"] ?? 0);
|
|
4860
|
+
outputTokens = Number(lastUsage["output_tokens"] ?? 0);
|
|
4861
|
+
reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
|
|
4862
|
+
cacheReadTokens = extractCachedInputTokens(lastUsage);
|
|
4863
|
+
} else if (totalUsage) {
|
|
4864
|
+
inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
|
|
4865
|
+
outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
|
|
4866
|
+
reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
|
|
4867
|
+
cacheReadTokens = extractCachedInputTokens(totalUsage) - prevCachedInput;
|
|
4868
|
+
prevInput = Number(totalUsage["input_tokens"] ?? 0);
|
|
4869
|
+
prevOutput = Number(totalUsage["output_tokens"] ?? 0);
|
|
4870
|
+
prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
|
|
4871
|
+
prevCachedInput = extractCachedInputTokens(totalUsage);
|
|
4872
|
+
}
|
|
4873
|
+
const totalInput = Math.max(0, inputTokens);
|
|
4874
|
+
const totalCacheRead = Math.max(0, cacheReadTokens);
|
|
4875
|
+
totalInputTokens += totalInput;
|
|
4876
|
+
totalOutputTokens += outputTokens + reasoningTokens;
|
|
4877
|
+
totalCacheReadTokens += totalCacheRead;
|
|
4878
|
+
totalCost += estimateTokenCost(activeModel, {
|
|
4879
|
+
input: totalInput,
|
|
4880
|
+
output: outputTokens,
|
|
4881
|
+
reasoning: reasoningTokens || void 0,
|
|
4882
|
+
cache_read: totalCacheRead || void 0
|
|
4883
|
+
}) ?? 0;
|
|
4884
|
+
}
|
|
4885
|
+
} catch {
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
return {
|
|
4889
|
+
message_count: 0,
|
|
4890
|
+
total_input_tokens: totalInputTokens,
|
|
4891
|
+
total_output_tokens: totalOutputTokens,
|
|
4892
|
+
total_cache_read_tokens: totalCacheReadTokens || void 0,
|
|
4893
|
+
total_cost: totalCost,
|
|
4894
|
+
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
4895
|
+
};
|
|
4896
|
+
}
|
|
3538
4897
|
getSessionData(sessionId) {
|
|
3539
4898
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
3540
4899
|
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
3541
|
-
if (!
|
|
4900
|
+
if (!existsSync9(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
|
|
4901
|
+
this.basePath ??= this.findBasePath();
|
|
3542
4902
|
const transcript = new TranscriptBuilder();
|
|
3543
4903
|
let totalInputTokens = 0;
|
|
3544
4904
|
let totalOutputTokens = 0;
|
|
@@ -3621,18 +4981,119 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3621
4981
|
total_cost: totalCost,
|
|
3622
4982
|
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
3623
4983
|
});
|
|
4984
|
+
this.applyChildStats(result.stats, meta.id);
|
|
4985
|
+
const childMessages = this.collectChildMessages(meta.id);
|
|
4986
|
+
for (const message of childMessages) {
|
|
4987
|
+
const messageText = message.parts.find((part) => part.type === "text")?.text;
|
|
4988
|
+
const alreadyVisible = result.messages.some(
|
|
4989
|
+
(existing) => message.subagent_id !== void 0 && existing.subagent_id === message.subagent_id || existing.subagent_id === void 0 && message.nickname !== void 0 && messageText !== void 0 && existing.nickname === message.nickname && existing.parts.some((part) => part.type === "text" && part.text === messageText)
|
|
4990
|
+
);
|
|
4991
|
+
if (!alreadyVisible) result.messages.push(message);
|
|
4992
|
+
}
|
|
4993
|
+
result.stats.message_count = result.messages.length;
|
|
3624
4994
|
return {
|
|
3625
4995
|
reference: { agentName: this.name, sessionId: meta.id },
|
|
3626
4996
|
id: meta.id,
|
|
3627
4997
|
title: meta.title,
|
|
3628
4998
|
slug: `codex/${meta.id}`,
|
|
3629
4999
|
directory: meta.directory,
|
|
5000
|
+
parent_reference: meta.parentThreadId == null ? void 0 : { agentName: this.name, sessionId: meta.parentThreadId },
|
|
3630
5001
|
time_created: meta.createdAt,
|
|
3631
5002
|
time_updated: meta.updatedAt,
|
|
3632
5003
|
stats: result.stats,
|
|
3633
5004
|
messages: result.messages
|
|
3634
5005
|
};
|
|
3635
5006
|
}
|
|
5007
|
+
/**
|
|
5008
|
+
* Builds the complete parent→children map in one prefix sweep. A cache miss
|
|
5009
|
+
* afterwards means "no children", so per-session directory rescans (the old
|
|
5010
|
+
* O(N²) finalization hotspot) never happen.
|
|
5011
|
+
*/
|
|
5012
|
+
ensureSubagentIndex() {
|
|
5013
|
+
if (this.subagentIndex) return this.subagentIndex;
|
|
5014
|
+
this.basePath ??= this.findBasePath();
|
|
5015
|
+
const index = { childFilesByParent: /* @__PURE__ */ new Map(), subagentFiles: /* @__PURE__ */ new Set() };
|
|
5016
|
+
for (const file of this.listRolloutFilePaths()) {
|
|
5017
|
+
const threadMeta = this.readThreadMeta(file);
|
|
5018
|
+
if (threadMeta?.threadSource !== "subagent") continue;
|
|
5019
|
+
index.subagentFiles.add(file);
|
|
5020
|
+
if (!threadMeta.parentThreadId) continue;
|
|
5021
|
+
const files = index.childFilesByParent.get(threadMeta.parentThreadId);
|
|
5022
|
+
if (files) files.push(file);
|
|
5023
|
+
else index.childFilesByParent.set(threadMeta.parentThreadId, [file]);
|
|
5024
|
+
}
|
|
5025
|
+
this.subagentIndex = index;
|
|
5026
|
+
return index;
|
|
5027
|
+
}
|
|
5028
|
+
applyChildStats(target, sessionId) {
|
|
5029
|
+
for (const stats of this.collectChildStats(sessionId)) {
|
|
5030
|
+
target.total_input_tokens += stats.total_input_tokens ?? 0;
|
|
5031
|
+
target.total_output_tokens += stats.total_output_tokens ?? 0;
|
|
5032
|
+
target.total_cost += stats.total_cost ?? 0;
|
|
5033
|
+
if (stats.total_cache_read_tokens) {
|
|
5034
|
+
target.total_cache_read_tokens = (target.total_cache_read_tokens ?? 0) + stats.total_cache_read_tokens;
|
|
5035
|
+
}
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
collectChildStats(parentSessionId) {
|
|
5039
|
+
const files = this.collectChildFiles(parentSessionId);
|
|
5040
|
+
if (files.length === 0) return [];
|
|
5041
|
+
const cached = this.subagentStatsByParent.get(parentSessionId);
|
|
5042
|
+
if (cached) return cached;
|
|
5043
|
+
const stats = files.map((file) => this.parseTokenStats(file));
|
|
5044
|
+
this.subagentStatsByParent.set(parentSessionId, stats);
|
|
5045
|
+
return stats;
|
|
5046
|
+
}
|
|
5047
|
+
collectChildFiles(parentSessionId) {
|
|
5048
|
+
return this.ensureSubagentIndex().childFilesByParent.get(parentSessionId) ?? [];
|
|
5049
|
+
}
|
|
5050
|
+
collectChildMessages(parentSessionId) {
|
|
5051
|
+
return this.collectChildFiles(parentSessionId).flatMap((file) => {
|
|
5052
|
+
const message = this.parseChildFinalMessage(file);
|
|
5053
|
+
return message ? [message] : [];
|
|
5054
|
+
}).sort((left, right) => left.time_created - right.time_created);
|
|
5055
|
+
}
|
|
5056
|
+
parseChildFinalMessage(filePath) {
|
|
5057
|
+
const sessionId = extractSessionId(filePath);
|
|
5058
|
+
const threadMeta = this.readThreadMeta(filePath);
|
|
5059
|
+
let latestOutput = null;
|
|
5060
|
+
let finalOutput = null;
|
|
5061
|
+
for (const record of readJsonlFile(filePath)) {
|
|
5062
|
+
try {
|
|
5063
|
+
const recordType = String(record["type"] ?? "");
|
|
5064
|
+
if (recordType !== "response_item") continue;
|
|
5065
|
+
const payload = extractPayload(record);
|
|
5066
|
+
if (String(payload["type"] ?? "") !== "message") continue;
|
|
5067
|
+
if (String(payload["role"] ?? "") !== "assistant") continue;
|
|
5068
|
+
const text = cleanInternalText(extractAssistantOutputText(payload));
|
|
5069
|
+
if (!text) continue;
|
|
5070
|
+
const candidate = {
|
|
5071
|
+
id: asString(payload["id"]) ?? `codex-subagent-${sessionId}`,
|
|
5072
|
+
text,
|
|
5073
|
+
timestampMs: parseTimestampMs2(record) || parseTimestampMs2(payload) || statSync6(filePath).mtimeMs,
|
|
5074
|
+
isFinal: String(record["phase"] ?? "") === "final_answer" || String(payload["phase"] ?? "") === "final_answer"
|
|
5075
|
+
};
|
|
5076
|
+
latestOutput = candidate;
|
|
5077
|
+
if (candidate.isFinal) finalOutput = candidate;
|
|
5078
|
+
} catch {
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
const selected = finalOutput ?? latestOutput;
|
|
5082
|
+
if (!selected) return null;
|
|
5083
|
+
return {
|
|
5084
|
+
id: selected.id,
|
|
5085
|
+
role: "assistant",
|
|
5086
|
+
agent: "codex",
|
|
5087
|
+
time_created: selected.timestampMs,
|
|
5088
|
+
mode: null,
|
|
5089
|
+
model: null,
|
|
5090
|
+
provider: null,
|
|
5091
|
+
cost: 0,
|
|
5092
|
+
subagent_id: sessionId,
|
|
5093
|
+
nickname: threadMeta?.agentNickname ?? void 0,
|
|
5094
|
+
parts: [{ type: "text", text: selected.text, time_created: selected.timestampMs }]
|
|
5095
|
+
};
|
|
5096
|
+
}
|
|
3636
5097
|
// ---- File listing ----
|
|
3637
5098
|
listRolloutFiles(options) {
|
|
3638
5099
|
if (!this.basePath) return [];
|
|
@@ -3642,6 +5103,54 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3642
5103
|
{ scanWindow: options }
|
|
3643
5104
|
);
|
|
3644
5105
|
}
|
|
5106
|
+
/** Path-only listing for the subagent index: no per-file stat needed. */
|
|
5107
|
+
listRolloutFilePaths() {
|
|
5108
|
+
if (!this.basePath) return [];
|
|
5109
|
+
const paths = [];
|
|
5110
|
+
const walk = (directory) => {
|
|
5111
|
+
let entries;
|
|
5112
|
+
try {
|
|
5113
|
+
entries = readdirSync6(directory, { withFileTypes: true });
|
|
5114
|
+
} catch {
|
|
5115
|
+
return;
|
|
5116
|
+
}
|
|
5117
|
+
for (const entry of entries) {
|
|
5118
|
+
const filePath = join10(directory, entry.name);
|
|
5119
|
+
if (entry.isDirectory()) walk(filePath);
|
|
5120
|
+
else if (entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
|
|
5121
|
+
paths.push(filePath);
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
};
|
|
5125
|
+
walk(this.basePath);
|
|
5126
|
+
return paths;
|
|
5127
|
+
}
|
|
5128
|
+
listScanSources(options) {
|
|
5129
|
+
const windowed = this.listRolloutFiles(options).sort(compareSourceActivityDesc);
|
|
5130
|
+
if (options?.from == null && options?.to == null) return windowed;
|
|
5131
|
+
const { childFilesByParent, subagentFiles } = this.ensureSubagentIndex();
|
|
5132
|
+
const rootFiles = windowed.filter((source) => !subagentFiles.has(source.file));
|
|
5133
|
+
const rootIds = new Set(rootFiles.map(({ file }) => extractSessionId(file)));
|
|
5134
|
+
if (rootIds.size === 0) return rootFiles;
|
|
5135
|
+
const selected = new Map(rootFiles.map((source) => [source.file, source]));
|
|
5136
|
+
const pending2 = [...rootIds];
|
|
5137
|
+
const seenParents = /* @__PURE__ */ new Set();
|
|
5138
|
+
while (pending2.length > 0) {
|
|
5139
|
+
const parentId = pending2.pop();
|
|
5140
|
+
if (seenParents.has(parentId)) continue;
|
|
5141
|
+
seenParents.add(parentId);
|
|
5142
|
+
for (const file of childFilesByParent.get(parentId) ?? []) {
|
|
5143
|
+
if (selected.has(file)) continue;
|
|
5144
|
+
try {
|
|
5145
|
+
selected.set(file, this.sessionSourceFile(file));
|
|
5146
|
+
} catch {
|
|
5147
|
+
continue;
|
|
5148
|
+
}
|
|
5149
|
+
pending2.push(extractSessionId(file));
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
return [...selected.values()].sort(compareSourceActivityDesc);
|
|
5153
|
+
}
|
|
3645
5154
|
createFileSessionMeta(head, source) {
|
|
3646
5155
|
const indexPath = this.getSessionIndexPath();
|
|
3647
5156
|
const indexMtime = this.sessionIndexMtime ?? null;
|
|
@@ -3654,7 +5163,8 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3654
5163
|
indexMtimeMs: indexMtime,
|
|
3655
5164
|
headIndexVersion: HEAD_INDEX_VERSION2,
|
|
3656
5165
|
parserVersion: PARSER_VERSION,
|
|
3657
|
-
model: null
|
|
5166
|
+
model: null,
|
|
5167
|
+
parentThreadId: head.parent_reference?.sessionId ?? null
|
|
3658
5168
|
}
|
|
3659
5169
|
});
|
|
3660
5170
|
}
|
|
@@ -3670,7 +5180,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3670
5180
|
]);
|
|
3671
5181
|
}
|
|
3672
5182
|
getSessionIndexPath() {
|
|
3673
|
-
this.sessionIndexPath ??=
|
|
5183
|
+
this.sessionIndexPath ??= join10(resolveCodexDataRoot(), "session_index.jsonl");
|
|
3674
5184
|
return this.sessionIndexPath;
|
|
3675
5185
|
}
|
|
3676
5186
|
// ---- Session index ----
|
|
@@ -3684,7 +5194,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3684
5194
|
return;
|
|
3685
5195
|
}
|
|
3686
5196
|
try {
|
|
3687
|
-
const content =
|
|
5197
|
+
const content = readFileSync5(indexPath, "utf-8");
|
|
3688
5198
|
const cache = /* @__PURE__ */ new Map();
|
|
3689
5199
|
for (const record of parseJsonlLines(content)) {
|
|
3690
5200
|
const sid = String(record["id"] ?? "").trim();
|
|
@@ -3716,7 +5226,9 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3716
5226
|
}
|
|
3717
5227
|
parseFileSessionHead(filePath, options) {
|
|
3718
5228
|
this.loadSessionIndex();
|
|
3719
|
-
|
|
5229
|
+
const head = this.parseSessionHead(filePath, options);
|
|
5230
|
+
if (head) this.applyChildStats(head.stats, head.id);
|
|
5231
|
+
return head;
|
|
3720
5232
|
}
|
|
3721
5233
|
parseSessionHead(filePath, options) {
|
|
3722
5234
|
return getParsedSession(this.parseSessionHeadResult(filePath, options));
|
|
@@ -3727,6 +5239,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3727
5239
|
}
|
|
3728
5240
|
const sessionId = extractSessionId(filePath);
|
|
3729
5241
|
let firstPayload = {};
|
|
5242
|
+
let parentThreadId = null;
|
|
3730
5243
|
let createdAt = 0;
|
|
3731
5244
|
let lineCount = 0;
|
|
3732
5245
|
let messageTitle = null;
|
|
@@ -3756,7 +5269,9 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3756
5269
|
return skippedSession("malformed first record");
|
|
3757
5270
|
}
|
|
3758
5271
|
firstPayload = extractPayload(firstRecord);
|
|
3759
|
-
|
|
5272
|
+
const threadMeta = extractThreadMeta(firstRecord);
|
|
5273
|
+
parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
|
|
5274
|
+
createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(firstPayload) || statSync6(filePath).mtimeMs;
|
|
3760
5275
|
updatedAt = createdAt;
|
|
3761
5276
|
}
|
|
3762
5277
|
try {
|
|
@@ -3850,6 +5365,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3850
5365
|
slug: `codex/${sessionId}`,
|
|
3851
5366
|
title,
|
|
3852
5367
|
directory,
|
|
5368
|
+
parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
|
|
3853
5369
|
time_created: createdAt,
|
|
3854
5370
|
time_updated: updatedAt,
|
|
3855
5371
|
stats: {
|
|
@@ -3863,9 +5379,6 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3863
5379
|
model_usage: Object.keys(modelUsageMap).length > 0 ? modelUsageMap : void 0
|
|
3864
5380
|
});
|
|
3865
5381
|
}
|
|
3866
|
-
parseFastSessionHead(filePath) {
|
|
3867
|
-
return getParsedSession(this.parseFastSessionHeadResult(filePath));
|
|
3868
|
-
}
|
|
3869
5382
|
parseFastSessionHeadResult(filePath) {
|
|
3870
5383
|
const prefix = this.readFilePrefix(filePath);
|
|
3871
5384
|
const lines = prefix.split("\n").filter((l) => l.trim());
|
|
@@ -3877,8 +5390,10 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3877
5390
|
} catch {
|
|
3878
5391
|
return skippedSession("malformed first record");
|
|
3879
5392
|
}
|
|
5393
|
+
const threadMeta = extractThreadMeta(firstRecord);
|
|
5394
|
+
const parentThreadId = threadMeta?.threadSource === "subagent" ? threadMeta.parentThreadId : null;
|
|
3880
5395
|
const payload = extractPayload(firstRecord);
|
|
3881
|
-
const stat =
|
|
5396
|
+
const stat = statSync6(filePath);
|
|
3882
5397
|
const createdAt = parseTimestampMs2(firstRecord) || parseTimestampMs2(payload) || stat.mtimeMs;
|
|
3883
5398
|
const indexTitle = this.getTitleForSession(sessionId);
|
|
3884
5399
|
const messageTitle = this.extractTitleFromLines(lines);
|
|
@@ -3890,6 +5405,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3890
5405
|
slug: `codex/${sessionId}`,
|
|
3891
5406
|
title,
|
|
3892
5407
|
directory,
|
|
5408
|
+
parent_reference: parentThreadId == null ? void 0 : { agentName: this.name, sessionId: parentThreadId },
|
|
3893
5409
|
time_created: createdAt,
|
|
3894
5410
|
time_updated: stat.mtimeMs,
|
|
3895
5411
|
stats: {
|
|
@@ -3985,19 +5501,8 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
3985
5501
|
}
|
|
3986
5502
|
// ---- Assistant message ----
|
|
3987
5503
|
convertAssistantMessage(payload, transcript, timestampMs, pendingPlan, activeModel) {
|
|
3988
|
-
const
|
|
3989
|
-
if (!
|
|
3990
|
-
const textParts = [];
|
|
3991
|
-
for (const item of content) {
|
|
3992
|
-
const ci = asRecord(item);
|
|
3993
|
-
if (!ci) continue;
|
|
3994
|
-
if (String(ci["type"] ?? "") === "output_text") {
|
|
3995
|
-
const text = String(ci["text"] ?? "");
|
|
3996
|
-
if (text.trim()) textParts.push(text);
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
if (textParts.length === 0) return pendingPlan;
|
|
4000
|
-
const fullText = textParts.join("\n");
|
|
5504
|
+
const fullText = extractAssistantOutputText(payload);
|
|
5505
|
+
if (!fullText) return pendingPlan;
|
|
4001
5506
|
const planMatch = fullText.match(PROPOSED_PLAN_PATTERN);
|
|
4002
5507
|
if (planMatch) {
|
|
4003
5508
|
const planText2 = planMatch[1].trim();
|
|
@@ -4112,8 +5617,8 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
4112
5617
|
const name = String(payload["name"] ?? "").trim();
|
|
4113
5618
|
if (!name) return;
|
|
4114
5619
|
const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
|
|
4115
|
-
const arguments_ =
|
|
4116
|
-
const
|
|
5620
|
+
const arguments_ = normalizeToolArguments3(payload["arguments"]);
|
|
5621
|
+
const toolPart2 = {
|
|
4117
5622
|
type: "tool",
|
|
4118
5623
|
tool: toolIdentity.tool,
|
|
4119
5624
|
callID: callId,
|
|
@@ -4127,7 +5632,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
4127
5632
|
time_created: timestampMs
|
|
4128
5633
|
};
|
|
4129
5634
|
transcript.appendToolCall(
|
|
4130
|
-
|
|
5635
|
+
toolPart2,
|
|
4131
5636
|
{ id: "", timestampMs, agent: "codex", model: activeModel },
|
|
4132
5637
|
{ markModeAsTool: true }
|
|
4133
5638
|
);
|
|
@@ -4159,7 +5664,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
4159
5664
|
const toolIdentity = resolveToolIdentity(name, payload["namespace"]);
|
|
4160
5665
|
const rawInput = payload["input"];
|
|
4161
5666
|
const normalizedInput = normalizeCustomToolArguments(name, rawInput);
|
|
4162
|
-
const
|
|
5667
|
+
const toolPart2 = {
|
|
4163
5668
|
type: "tool",
|
|
4164
5669
|
tool: toolIdentity.tool,
|
|
4165
5670
|
callID: callId,
|
|
@@ -4173,7 +5678,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
4173
5678
|
time_created: timestampMs
|
|
4174
5679
|
};
|
|
4175
5680
|
transcript.appendToolCall(
|
|
4176
|
-
|
|
5681
|
+
toolPart2,
|
|
4177
5682
|
{ id: "", timestampMs, agent: "codex", model: activeModel },
|
|
4178
5683
|
{ markModeAsTool: true }
|
|
4179
5684
|
);
|
|
@@ -4190,7 +5695,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
4190
5695
|
const { name, namespace } = splitExecToolName(call.name);
|
|
4191
5696
|
const toolIdentity = resolveToolIdentity(name, namespace);
|
|
4192
5697
|
const arguments_ = name === "apply_patch" ? parseApplyPatchInput(getExecPatchText(call.args)) : call.args;
|
|
4193
|
-
const
|
|
5698
|
+
const toolPart2 = {
|
|
4194
5699
|
type: "tool",
|
|
4195
5700
|
tool: toolIdentity.tool,
|
|
4196
5701
|
callID: callId,
|
|
@@ -4204,7 +5709,7 @@ var CodexAgent = class extends SingleFileSessionSource {
|
|
|
4204
5709
|
time_created: timestampMs
|
|
4205
5710
|
};
|
|
4206
5711
|
transcript.appendToolCall(
|
|
4207
|
-
|
|
5712
|
+
toolPart2,
|
|
4208
5713
|
{ id: "", timestampMs, agent: "codex", model: activeModel },
|
|
4209
5714
|
{ markModeAsTool: true }
|
|
4210
5715
|
);
|
|
@@ -4289,15 +5794,15 @@ function resolveCursorDataRoot() {
|
|
|
4289
5794
|
if (override) return override;
|
|
4290
5795
|
const currentPlatform = platform2();
|
|
4291
5796
|
if (currentPlatform === "darwin") {
|
|
4292
|
-
return firstExisting(
|
|
5797
|
+
return firstExisting(join11(homedir3(), "Library", "Application Support", "Cursor", "User"));
|
|
4293
5798
|
}
|
|
4294
5799
|
if (currentPlatform === "linux") {
|
|
4295
|
-
const configRoot = readEnvPath("XDG_CONFIG_HOME") ??
|
|
4296
|
-
return firstExisting(
|
|
5800
|
+
const configRoot = readEnvPath("XDG_CONFIG_HOME") ?? join11(homedir3(), ".config");
|
|
5801
|
+
return firstExisting(join11(configRoot, "Cursor", "User"));
|
|
4297
5802
|
}
|
|
4298
5803
|
if (currentPlatform === "win32") {
|
|
4299
|
-
const appData = readEnvPath("APPDATA") ??
|
|
4300
|
-
return firstExisting(
|
|
5804
|
+
const appData = readEnvPath("APPDATA") ?? join11(homedir3(), "AppData", "Roaming");
|
|
5805
|
+
return firstExisting(join11(appData, "Cursor", "User"));
|
|
4301
5806
|
}
|
|
4302
5807
|
return null;
|
|
4303
5808
|
}
|
|
@@ -4358,6 +5863,24 @@ function parseComposerRow(value) {
|
|
|
4358
5863
|
chatMessages: parseChatMessages(record.chatMessages)
|
|
4359
5864
|
};
|
|
4360
5865
|
}
|
|
5866
|
+
function composerIdFromBubbleKey(key) {
|
|
5867
|
+
const start = key.indexOf(":") + 1;
|
|
5868
|
+
const end = key.indexOf(":", start);
|
|
5869
|
+
return end === -1 ? key.slice(start) : key.slice(start, end);
|
|
5870
|
+
}
|
|
5871
|
+
function groupBubbleRows(rows) {
|
|
5872
|
+
const byKey = [];
|
|
5873
|
+
for (const row of rows) {
|
|
5874
|
+
const bubble = parseBubbleRow(row.value);
|
|
5875
|
+
if (bubble) byKey.push({ rowId: row.row_id, key: row.key, bubble });
|
|
5876
|
+
}
|
|
5877
|
+
byKey.sort((left, right) => left.key.localeCompare(right.key));
|
|
5878
|
+
return {
|
|
5879
|
+
composerId: rows.length > 0 ? composerIdFromBubbleKey(rows[0].key) : "",
|
|
5880
|
+
byKey,
|
|
5881
|
+
byRowId: [...byKey].sort((left, right) => left.rowId - right.rowId)
|
|
5882
|
+
};
|
|
5883
|
+
}
|
|
4361
5884
|
function parseBubbleRow(value) {
|
|
4362
5885
|
const record = safeParseJsonRecord(value);
|
|
4363
5886
|
if (!record) return null;
|
|
@@ -4408,7 +5931,7 @@ var CURSOR_TOOL_TITLE_MAP = {
|
|
|
4408
5931
|
ripgrep_raw_search: "grep",
|
|
4409
5932
|
glob_file_search: "glob"
|
|
4410
5933
|
};
|
|
4411
|
-
function
|
|
5934
|
+
function mapToolTitle3(toolName) {
|
|
4412
5935
|
return CURSOR_TOOL_TITLE_MAP[toolName] ?? toolName;
|
|
4413
5936
|
}
|
|
4414
5937
|
function normalizeToolOutputParts2(output, timestampMs) {
|
|
@@ -4469,9 +5992,9 @@ function buildToolPart(action, timestampMs) {
|
|
|
4469
5992
|
const toolName = action.tool ?? "unknown";
|
|
4470
5993
|
return {
|
|
4471
5994
|
type: "tool",
|
|
4472
|
-
tool:
|
|
5995
|
+
tool: mapToolTitle3(toolName),
|
|
4473
5996
|
callID: action.type ? `${action.type}:${String(action.input?.id ?? "")}` : "",
|
|
4474
|
-
title: `Tool: ${
|
|
5997
|
+
title: `Tool: ${mapToolTitle3(toolName)}`,
|
|
4475
5998
|
state: buildToolState(action),
|
|
4476
5999
|
time_created: timestampMs
|
|
4477
6000
|
};
|
|
@@ -4517,7 +6040,7 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4517
6040
|
if (!isSqliteAvailable()) return null;
|
|
4518
6041
|
const dataPath = resolveCursorDataRoot();
|
|
4519
6042
|
if (!dataPath) return null;
|
|
4520
|
-
return
|
|
6043
|
+
return join11(dataPath, "globalStorage", "state.vscdb");
|
|
4521
6044
|
}
|
|
4522
6045
|
getSessionWatchPlan() {
|
|
4523
6046
|
const dataPath = resolveCursorDataRoot();
|
|
@@ -4526,9 +6049,9 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4526
6049
|
targets: dataPath ? [
|
|
4527
6050
|
{
|
|
4528
6051
|
root: dataPath,
|
|
4529
|
-
path:
|
|
6052
|
+
path: join11(dataPath, "globalStorage", "state.vscdb")
|
|
4530
6053
|
},
|
|
4531
|
-
{ root: dataPath, path:
|
|
6054
|
+
{ root: dataPath, path: join11(dataPath, "workspaceStorage") }
|
|
4532
6055
|
] : []
|
|
4533
6056
|
};
|
|
4534
6057
|
}
|
|
@@ -4540,34 +6063,34 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4540
6063
|
const map = /* @__PURE__ */ new Map();
|
|
4541
6064
|
const dataPath = resolveCursorDataRoot();
|
|
4542
6065
|
if (!dataPath) return map;
|
|
4543
|
-
const wsStoragePath =
|
|
4544
|
-
if (!
|
|
6066
|
+
const wsStoragePath = join11(dataPath, "workspaceStorage");
|
|
6067
|
+
if (!existsSync10(wsStoragePath)) return map;
|
|
4545
6068
|
let entryNames;
|
|
4546
6069
|
try {
|
|
4547
|
-
entryNames =
|
|
6070
|
+
entryNames = readdirSync7(wsStoragePath);
|
|
4548
6071
|
} catch {
|
|
4549
6072
|
return map;
|
|
4550
6073
|
}
|
|
4551
6074
|
for (const name of entryNames) {
|
|
4552
|
-
const wsDir =
|
|
6075
|
+
const wsDir = join11(wsStoragePath, name);
|
|
4553
6076
|
try {
|
|
4554
|
-
if (!
|
|
6077
|
+
if (!statSync7(wsDir).isDirectory()) continue;
|
|
4555
6078
|
} catch {
|
|
4556
6079
|
continue;
|
|
4557
6080
|
}
|
|
4558
|
-
const wsJsonPath =
|
|
4559
|
-
if (!
|
|
6081
|
+
const wsJsonPath = join11(wsDir, "workspace.json");
|
|
6082
|
+
if (!existsSync10(wsJsonPath)) continue;
|
|
4560
6083
|
let workspacePath;
|
|
4561
6084
|
try {
|
|
4562
|
-
const data = asRecord(JSON.parse(
|
|
6085
|
+
const data = asRecord(JSON.parse(readFileSync6(wsJsonPath, "utf-8")));
|
|
4563
6086
|
const uri = narrowString("workspaceJson.folder", data?.folder) ?? narrowString("workspaceJson.workspace", data?.workspace) ?? "";
|
|
4564
6087
|
if (!uri) continue;
|
|
4565
6088
|
workspacePath = normalize(decodeURIComponent(uri.replace(/^file:\/\//, "")));
|
|
4566
6089
|
} catch {
|
|
4567
6090
|
continue;
|
|
4568
6091
|
}
|
|
4569
|
-
const wsDbPath =
|
|
4570
|
-
if (!
|
|
6092
|
+
const wsDbPath = join11(wsDir, "state.vscdb");
|
|
6093
|
+
if (!existsSync10(wsDbPath)) continue;
|
|
4571
6094
|
const wsDb = openDbReadOnly(wsDbPath);
|
|
4572
6095
|
if (!wsDb) continue;
|
|
4573
6096
|
try {
|
|
@@ -4590,7 +6113,7 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4590
6113
|
}
|
|
4591
6114
|
isAvailable() {
|
|
4592
6115
|
this.dbPath = this.findDbPath();
|
|
4593
|
-
return this.dbPath !== null &&
|
|
6116
|
+
return this.dbPath !== null && existsSync10(this.dbPath);
|
|
4594
6117
|
}
|
|
4595
6118
|
scan(options) {
|
|
4596
6119
|
if (!this.dbPath) return [];
|
|
@@ -4598,13 +6121,15 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4598
6121
|
const dbMarker = perf.start("openDatabase");
|
|
4599
6122
|
const db = this.openDatabase();
|
|
4600
6123
|
perf.end(dbMarker);
|
|
4601
|
-
if (!db)
|
|
6124
|
+
if (!db) throw new SessionScanError(this.name, "opening the database");
|
|
4602
6125
|
const wsMarker = perf.start("buildWorkspacePathMap");
|
|
4603
6126
|
const workspacePathMap = this.buildWorkspacePathMap();
|
|
4604
6127
|
perf.end(wsMarker);
|
|
4605
6128
|
try {
|
|
4606
6129
|
const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'").all();
|
|
4607
|
-
const
|
|
6130
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
6131
|
+
const pending2 = [];
|
|
6132
|
+
let order = 0;
|
|
4608
6133
|
options?.onProgress?.({ total: rows.length, processed: 0, sessions: 0 });
|
|
4609
6134
|
let processed = 0;
|
|
4610
6135
|
for (const row of rows) {
|
|
@@ -4620,8 +6145,8 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4620
6145
|
const fastMessageCount = composer.chatMessages?.length ?? 0;
|
|
4621
6146
|
const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
|
|
4622
6147
|
if (options?.fast) {
|
|
4623
|
-
const
|
|
4624
|
-
const
|
|
6148
|
+
const directory = workspacePathMap.get(composerId) ?? "";
|
|
6149
|
+
const totalCost = estimateTokenCost(composer.modelConfig?.modelName ?? composer.model, {
|
|
4625
6150
|
input: composer.inputTokenCount ?? 0,
|
|
4626
6151
|
output: composer.outputTokenCount ?? 0
|
|
4627
6152
|
}) ?? 0;
|
|
@@ -4630,20 +6155,21 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4630
6155
|
id: composerId,
|
|
4631
6156
|
slug: `cursor/${composerId}`,
|
|
4632
6157
|
title: fastTitle,
|
|
4633
|
-
directory
|
|
6158
|
+
directory,
|
|
4634
6159
|
time_created: createdAt,
|
|
4635
6160
|
time_updated: updatedAt || void 0,
|
|
4636
6161
|
stats: {
|
|
4637
6162
|
message_count: fastMessageCount,
|
|
4638
6163
|
total_input_tokens: composer.inputTokenCount ?? 0,
|
|
4639
6164
|
total_output_tokens: composer.outputTokenCount ?? 0,
|
|
4640
|
-
total_cost:
|
|
4641
|
-
cost_source:
|
|
6165
|
+
total_cost: totalCost,
|
|
6166
|
+
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
4642
6167
|
}
|
|
4643
6168
|
})
|
|
4644
6169
|
);
|
|
4645
6170
|
if (!head) continue;
|
|
4646
|
-
|
|
6171
|
+
emitted.set(order, head);
|
|
6172
|
+
order += 1;
|
|
4647
6173
|
this.composerCache.set(composerId, composer);
|
|
4648
6174
|
this.sessionMetaMap.set(composerId, {
|
|
4649
6175
|
id: composerId,
|
|
@@ -4651,74 +6177,38 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4651
6177
|
});
|
|
4652
6178
|
continue;
|
|
4653
6179
|
}
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
const parsedMessages = cleanParsedMessages(
|
|
4657
|
-
this.loadMessagesFromBubbles(
|
|
4658
|
-
db,
|
|
4659
|
-
composerId,
|
|
4660
|
-
sessionId,
|
|
4661
|
-
composer.modelConfig?.modelName ?? composer.model ?? null
|
|
4662
|
-
)
|
|
4663
|
-
);
|
|
4664
|
-
const messages = getParsedSession(
|
|
4665
|
-
parsedMessages.length === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession(parsedMessages)
|
|
4666
|
-
);
|
|
4667
|
-
if (!messages) continue;
|
|
4668
|
-
const messageCount = messages.length;
|
|
4669
|
-
const title = this.extractTitle(composer, messages);
|
|
4670
|
-
const directory = workspacePathMap.get(composerId) ?? "";
|
|
4671
|
-
const modelUsageMap = {};
|
|
4672
|
-
let totalCost = 0;
|
|
4673
|
-
for (const msg of messages) {
|
|
4674
|
-
totalCost += msg.cost ?? 0;
|
|
4675
|
-
if (msg.model) {
|
|
4676
|
-
const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
|
|
4677
|
-
if (msgTokens > 0) {
|
|
4678
|
-
modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
|
|
4679
|
-
}
|
|
4680
|
-
}
|
|
4681
|
-
}
|
|
4682
|
-
const hasModelUsage = Object.keys(modelUsageMap).length > 0;
|
|
4683
|
-
heads.push({
|
|
4684
|
-
id: sessionId,
|
|
4685
|
-
slug: `cursor/${sessionId}`,
|
|
4686
|
-
title,
|
|
4687
|
-
directory,
|
|
4688
|
-
time_created: createdAt,
|
|
4689
|
-
time_updated: updatedAt || void 0,
|
|
4690
|
-
stats: {
|
|
4691
|
-
message_count: messageCount,
|
|
4692
|
-
total_input_tokens: composer.inputTokenCount ?? 0,
|
|
4693
|
-
total_output_tokens: composer.outputTokenCount ?? 0,
|
|
4694
|
-
total_cost: totalCost,
|
|
4695
|
-
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
4696
|
-
},
|
|
4697
|
-
model_usage: hasModelUsage ? modelUsageMap : void 0
|
|
4698
|
-
});
|
|
4699
|
-
this.composerCache.set(sessionId, composer);
|
|
4700
|
-
this.composerCache.set(`__mapping__${composerId}`, {
|
|
4701
|
-
sessionId
|
|
4702
|
-
});
|
|
4703
|
-
if (directory) {
|
|
4704
|
-
this.composerCache.set(`__dir__${composerId}`, {
|
|
4705
|
-
directory
|
|
4706
|
-
});
|
|
4707
|
-
}
|
|
4708
|
-
this.sessionMetaMap.set(sessionId, {
|
|
4709
|
-
id: sessionId,
|
|
4710
|
-
sourcePath: this.dbPath || ""
|
|
4711
|
-
});
|
|
6180
|
+
pending2.push({ composer, composerId, createdAt, updatedAt, hasSubagents, order });
|
|
6181
|
+
order += 1;
|
|
4712
6182
|
} catch {
|
|
4713
6183
|
} finally {
|
|
4714
6184
|
processed += 1;
|
|
4715
|
-
options?.onProgress?.({ total: rows.length, processed, sessions:
|
|
6185
|
+
options?.onProgress?.({ total: rows.length, processed, sessions: emitted.size });
|
|
4716
6186
|
}
|
|
4717
6187
|
}
|
|
6188
|
+
const bubbleMarker = perf.start("cursor:bubbles");
|
|
6189
|
+
const wanted = new Map(pending2.map((entry) => [entry.composerId, entry]));
|
|
6190
|
+
this.forEachComposerBubbles(db, wanted, (bubbles) => {
|
|
6191
|
+
const entry = wanted.get(bubbles.composerId);
|
|
6192
|
+
if (!entry) return;
|
|
6193
|
+
wanted.delete(bubbles.composerId);
|
|
6194
|
+
const head = this.buildScanHead(entry, bubbles, workspacePathMap);
|
|
6195
|
+
if (head) emitted.set(entry.order, head);
|
|
6196
|
+
});
|
|
6197
|
+
for (const entry of wanted.values()) {
|
|
6198
|
+
const head = this.buildScanHead(
|
|
6199
|
+
entry,
|
|
6200
|
+
{ composerId: entry.composerId, byKey: [], byRowId: [] },
|
|
6201
|
+
workspacePathMap
|
|
6202
|
+
);
|
|
6203
|
+
if (head) emitted.set(entry.order, head);
|
|
6204
|
+
}
|
|
6205
|
+
perf.end(bubbleMarker);
|
|
6206
|
+
const heads = [...emitted.entries()].sort(([left], [right]) => left - right).map(([, head]) => head);
|
|
6207
|
+
options?.onProgress?.({ total: rows.length, processed, sessions: heads.length });
|
|
4718
6208
|
perf.end(scanMarker);
|
|
4719
6209
|
return heads;
|
|
4720
|
-
} catch {
|
|
4721
|
-
|
|
6210
|
+
} catch (error) {
|
|
6211
|
+
throw new SessionScanError(this.name, "reading composers", { cause: error });
|
|
4722
6212
|
} finally {
|
|
4723
6213
|
db.close();
|
|
4724
6214
|
}
|
|
@@ -4806,20 +6296,90 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4806
6296
|
if (!this.dbPath) return null;
|
|
4807
6297
|
return openDbReadOnly(this.dbPath);
|
|
4808
6298
|
}
|
|
4809
|
-
/**
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
6299
|
+
/**
|
|
6300
|
+
* Streams every bubble row once, in key order, handing each composer's group
|
|
6301
|
+
* to the caller. A key-ordered scan keeps a composer's bubbles contiguous, so
|
|
6302
|
+
* only one group is materialized at a time.
|
|
6303
|
+
*/
|
|
6304
|
+
forEachComposerBubbles(db, wanted, onGroup) {
|
|
6305
|
+
if (wanted.size === 0) return;
|
|
6306
|
+
let currentId = null;
|
|
6307
|
+
let group = [];
|
|
6308
|
+
const flush = () => {
|
|
6309
|
+
if (currentId != null && group.length > 0) onGroup(groupBubbleRows(group));
|
|
6310
|
+
group = [];
|
|
6311
|
+
};
|
|
6312
|
+
const rows = db.prepare(
|
|
6313
|
+
"SELECT rowid AS row_id, key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%' ORDER BY key"
|
|
6314
|
+
).iterate();
|
|
6315
|
+
for (const row of rows) {
|
|
6316
|
+
const composerId = composerIdFromBubbleKey(row.key);
|
|
6317
|
+
if (composerId !== currentId) {
|
|
6318
|
+
flush();
|
|
6319
|
+
currentId = composerId;
|
|
6320
|
+
}
|
|
6321
|
+
if (wanted.has(composerId)) group.push(row);
|
|
6322
|
+
}
|
|
6323
|
+
flush();
|
|
6324
|
+
}
|
|
6325
|
+
/** Builds the head for one composer from bubbles that were parsed once. */
|
|
6326
|
+
buildScanHead(entry, bubbles, workspacePathMap) {
|
|
6327
|
+
const { composer, composerId, createdAt, updatedAt, hasSubagents } = entry;
|
|
6328
|
+
const requestId = this.requestIdFromBubbles(bubbles);
|
|
6329
|
+
const sessionId = requestId || composerId;
|
|
6330
|
+
const parsedMessages = cleanParsedMessages(
|
|
6331
|
+
this.messagesFromBubbles(bubbles, composer.modelConfig?.modelName ?? composer.model ?? null)
|
|
6332
|
+
);
|
|
6333
|
+
const messages = getParsedSession(
|
|
6334
|
+
parsedMessages.length === 0 && !hasSubagents ? filteredSession("no visible messages") : parsedSession(parsedMessages)
|
|
6335
|
+
);
|
|
6336
|
+
if (!messages) return null;
|
|
6337
|
+
const title = this.extractTitle(composer, messages);
|
|
6338
|
+
const directory = workspacePathMap.get(composerId) ?? "";
|
|
6339
|
+
const modelUsageMap = {};
|
|
6340
|
+
let totalCost = 0;
|
|
6341
|
+
for (const msg of messages) {
|
|
6342
|
+
totalCost += msg.cost ?? 0;
|
|
6343
|
+
if (msg.model) {
|
|
6344
|
+
const msgTokens = (msg.tokens?.input ?? 0) + (msg.tokens?.output ?? 0);
|
|
6345
|
+
if (msgTokens > 0) {
|
|
6346
|
+
modelUsageMap[msg.model] = (modelUsageMap[msg.model] ?? 0) + msgTokens;
|
|
4820
6347
|
}
|
|
4821
6348
|
}
|
|
4822
|
-
}
|
|
6349
|
+
}
|
|
6350
|
+
const hasModelUsage = Object.keys(modelUsageMap).length > 0;
|
|
6351
|
+
this.composerCache.set(sessionId, composer);
|
|
6352
|
+
this.composerCache.set(`__mapping__${composerId}`, {
|
|
6353
|
+
sessionId
|
|
6354
|
+
});
|
|
6355
|
+
if (directory) {
|
|
6356
|
+
this.composerCache.set(`__dir__${composerId}`, {
|
|
6357
|
+
directory
|
|
6358
|
+
});
|
|
6359
|
+
}
|
|
6360
|
+
this.sessionMetaMap.set(sessionId, { id: sessionId, sourcePath: this.dbPath || "" });
|
|
6361
|
+
return {
|
|
6362
|
+
id: sessionId,
|
|
6363
|
+
slug: `cursor/${sessionId}`,
|
|
6364
|
+
title,
|
|
6365
|
+
directory,
|
|
6366
|
+
time_created: createdAt,
|
|
6367
|
+
time_updated: updatedAt || void 0,
|
|
6368
|
+
stats: {
|
|
6369
|
+
message_count: messages.length,
|
|
6370
|
+
total_input_tokens: composer.inputTokenCount ?? 0,
|
|
6371
|
+
total_output_tokens: composer.outputTokenCount ?? 0,
|
|
6372
|
+
total_cost: totalCost,
|
|
6373
|
+
cost_source: totalCost > 0 ? "estimated" : void 0
|
|
6374
|
+
},
|
|
6375
|
+
model_usage: hasModelUsage ? modelUsageMap : void 0
|
|
6376
|
+
};
|
|
6377
|
+
}
|
|
6378
|
+
/** First request id in key order, matching what agent-dump reports. */
|
|
6379
|
+
requestIdFromBubbles(bubbles) {
|
|
6380
|
+
for (const entry of bubbles.byKey) {
|
|
6381
|
+
const requestId = entry.bubble.requestId?.trim();
|
|
6382
|
+
if (requestId) return requestId;
|
|
4823
6383
|
}
|
|
4824
6384
|
return null;
|
|
4825
6385
|
}
|
|
@@ -4868,18 +6428,26 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4868
6428
|
return 0;
|
|
4869
6429
|
}
|
|
4870
6430
|
}
|
|
4871
|
-
/** Load
|
|
6431
|
+
/** Load one composer's bubbles, for the detail path that only needs a single session. */
|
|
4872
6432
|
loadMessagesFromBubbles(db, composerId, _sessionId, initialModelName) {
|
|
4873
|
-
const messages = [];
|
|
4874
6433
|
try {
|
|
4875
|
-
const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ?
|
|
6434
|
+
const rows = db.prepare("SELECT rowid AS row_id, key, value FROM cursorDiskKV WHERE key LIKE ?").all(`bubbleId:${composerId}:%`);
|
|
6435
|
+
return this.messagesFromBubbles(groupBubbleRows(rows), initialModelName);
|
|
6436
|
+
} catch {
|
|
6437
|
+
return [];
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
/** Build messages from bubbles already parsed once, in insertion order. */
|
|
6441
|
+
messagesFromBubbles(bubbles, initialModelName) {
|
|
6442
|
+
const messages = [];
|
|
6443
|
+
{
|
|
4876
6444
|
let activeModelName = initialModelName;
|
|
4877
6445
|
let messageIndex = 0;
|
|
4878
|
-
for (const
|
|
4879
|
-
|
|
4880
|
-
const bubble =
|
|
4881
|
-
if (
|
|
4882
|
-
const bubbleId =
|
|
6446
|
+
for (const entry of bubbles.byRowId) {
|
|
6447
|
+
{
|
|
6448
|
+
const bubble = entry.bubble;
|
|
6449
|
+
if (isInternalBubble(bubble)) continue;
|
|
6450
|
+
const bubbleId = entry.key.split(":").pop() || String(messageIndex);
|
|
4883
6451
|
const role = bubble.type === 2 ? "assistant" : "user";
|
|
4884
6452
|
let timestampMs = 0;
|
|
4885
6453
|
if (bubble.timingInfo?.clientRpcSendTime) {
|
|
@@ -4900,9 +6468,9 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4900
6468
|
parts.push({ type: "text", text, time_created: timestampMs });
|
|
4901
6469
|
}
|
|
4902
6470
|
if (bubble.toolFormerData) {
|
|
4903
|
-
const
|
|
4904
|
-
if (
|
|
4905
|
-
parts.push(
|
|
6471
|
+
const toolPart2 = this.convertToolFormerData(bubble.toolFormerData, timestampMs);
|
|
6472
|
+
if (toolPart2) {
|
|
6473
|
+
parts.push(toolPart2);
|
|
4906
6474
|
}
|
|
4907
6475
|
}
|
|
4908
6476
|
if (parts.length === 0) continue;
|
|
@@ -4910,7 +6478,7 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4910
6478
|
const tokens = { input: inputTokens, output: outputTokens };
|
|
4911
6479
|
const cost = estimateTokenCost(modelName, tokens);
|
|
4912
6480
|
messages.push({
|
|
4913
|
-
id: `cursor-${composerId}-${bubbleId}`,
|
|
6481
|
+
id: `cursor-${bubbles.composerId}-${bubbleId}`,
|
|
4914
6482
|
role,
|
|
4915
6483
|
agent: "cursor",
|
|
4916
6484
|
time_created: timestampMs,
|
|
@@ -4924,10 +6492,8 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4924
6492
|
parts
|
|
4925
6493
|
});
|
|
4926
6494
|
messageIndex++;
|
|
4927
|
-
} catch {
|
|
4928
6495
|
}
|
|
4929
6496
|
}
|
|
4930
|
-
} catch {
|
|
4931
6497
|
}
|
|
4932
6498
|
return messages;
|
|
4933
6499
|
}
|
|
@@ -4935,7 +6501,7 @@ var CursorAgent = class extends DatabaseSessionSource {
|
|
|
4935
6501
|
convertToolFormerData(toolData, timestampMs) {
|
|
4936
6502
|
if (!toolData || !toolData.name) return null;
|
|
4937
6503
|
const toolName = toolData.name;
|
|
4938
|
-
const normalizedName = toolName === "create_plan" ? "plan" :
|
|
6504
|
+
const normalizedName = toolName === "create_plan" ? "plan" : mapToolTitle3(toolName);
|
|
4939
6505
|
const state = {
|
|
4940
6506
|
status: toolData.status === "completed" ? "completed" : "running"
|
|
4941
6507
|
};
|
|
@@ -5063,7 +6629,7 @@ function narrowTimestampMs(field, value) {
|
|
|
5063
6629
|
return shaped === void 0 ? 0 : parseTimestampMs3(shaped);
|
|
5064
6630
|
}
|
|
5065
6631
|
function extractSessionIdFromFilename(filePath) {
|
|
5066
|
-
const stem =
|
|
6632
|
+
const stem = basename8(filePath, ".jsonl");
|
|
5067
6633
|
const underscore = stem.indexOf("_");
|
|
5068
6634
|
return underscore >= 0 ? stem.slice(underscore + 1) || stem : stem;
|
|
5069
6635
|
}
|
|
@@ -5119,14 +6685,14 @@ var PiAgent = class extends SingleFileSessionSource {
|
|
|
5119
6685
|
displayName = "Pi";
|
|
5120
6686
|
basePath = null;
|
|
5121
6687
|
findBasePath() {
|
|
5122
|
-
return firstExisting(
|
|
6688
|
+
return firstExisting(join12(resolvePiDataRoot(), "agent", "sessions"), "data/pi");
|
|
5123
6689
|
}
|
|
5124
6690
|
getSessionWatchPlan() {
|
|
5125
6691
|
const dataRoot = resolvePiDataRoot();
|
|
5126
6692
|
return {
|
|
5127
6693
|
status: "supported",
|
|
5128
6694
|
targets: [
|
|
5129
|
-
{ root: dataRoot, path:
|
|
6695
|
+
{ root: dataRoot, path: join12(dataRoot, "agent", "sessions") },
|
|
5130
6696
|
{ root: "data/pi", path: "data/pi" }
|
|
5131
6697
|
]
|
|
5132
6698
|
};
|
|
@@ -5147,7 +6713,7 @@ var PiAgent = class extends SingleFileSessionSource {
|
|
|
5147
6713
|
getSessionData(sessionId) {
|
|
5148
6714
|
const meta = this.sessionMetaMap.get(sessionId);
|
|
5149
6715
|
if (!meta) throw new Error(`Session not found: ${sessionId}`);
|
|
5150
|
-
if (!
|
|
6716
|
+
if (!existsSync11(meta.sourcePath)) throw new Error(`Session file missing: ${meta.sourcePath}`);
|
|
5151
6717
|
const parsed = this.parsePiFile(meta.sourcePath);
|
|
5152
6718
|
const state = this.convertEntries(parsed.pathEntries);
|
|
5153
6719
|
return {
|
|
@@ -5240,7 +6806,7 @@ var PiAgent = class extends SingleFileSessionSource {
|
|
|
5240
6806
|
const sessionId = String(header["id"] ?? extractSessionIdFromFilename(filePath)).trim();
|
|
5241
6807
|
if (!sessionId) throw new Error("missing session id");
|
|
5242
6808
|
const stat = this.sessionSourceFile(filePath).stat;
|
|
5243
|
-
const directory = String(header["cwd"] ?? "").trim() ||
|
|
6809
|
+
const directory = String(header["cwd"] ?? "").trim() || basename8(filePath, ".jsonl");
|
|
5244
6810
|
const createdAt = narrowTimestampMs("session.timestamp", header["timestamp"]) || stat.mtimeMs;
|
|
5245
6811
|
const updatedAt = pathEntries.reduce(
|
|
5246
6812
|
(max, entry) => Math.max(max, getEntryTimestamp(entry)),
|
|
@@ -5383,7 +6949,7 @@ var PiAgent = class extends SingleFileSessionSource {
|
|
|
5383
6949
|
if (type === "toolCall") {
|
|
5384
6950
|
const callId = String(item["id"] ?? "").trim();
|
|
5385
6951
|
const toolName = String(item["name"] ?? "").trim() || "tool";
|
|
5386
|
-
const
|
|
6952
|
+
const toolPart2 = {
|
|
5387
6953
|
type: "tool",
|
|
5388
6954
|
tool: toolName,
|
|
5389
6955
|
title: `Tool: ${toolName}`,
|
|
@@ -5394,7 +6960,7 @@ var PiAgent = class extends SingleFileSessionSource {
|
|
|
5394
6960
|
input: item["arguments"] ?? {}
|
|
5395
6961
|
}
|
|
5396
6962
|
};
|
|
5397
|
-
parts.push(
|
|
6963
|
+
parts.push(toolPart2);
|
|
5398
6964
|
}
|
|
5399
6965
|
}
|
|
5400
6966
|
return parts;
|
|
@@ -5490,19 +7056,19 @@ var PiAgent = class extends SingleFileSessionSource {
|
|
|
5490
7056
|
function resolveZCodeDataRoot() {
|
|
5491
7057
|
const currentPlatform = platform3();
|
|
5492
7058
|
if (currentPlatform !== "darwin" && currentPlatform !== "win32") return null;
|
|
5493
|
-
return
|
|
7059
|
+
return join13(homedir4(), ".zcode");
|
|
5494
7060
|
}
|
|
5495
7061
|
function findZCodeDbPath() {
|
|
5496
7062
|
if (!isSqliteAvailable()) return null;
|
|
5497
7063
|
const dataRoot = resolveZCodeDataRoot();
|
|
5498
7064
|
if (!dataRoot) return null;
|
|
5499
|
-
return firstExisting(
|
|
7065
|
+
return firstExisting(join13(dataRoot, "cli", "db", "db.sqlite"));
|
|
5500
7066
|
}
|
|
5501
7067
|
function getZCodeSessionWatchPlan() {
|
|
5502
7068
|
const dataRoot = resolveZCodeDataRoot();
|
|
5503
7069
|
return {
|
|
5504
7070
|
status: "supported",
|
|
5505
|
-
targets: dataRoot ? [{ root: dataRoot, path:
|
|
7071
|
+
targets: dataRoot ? [{ root: dataRoot, path: join13(dataRoot, "cli", "db", "db.sqlite") }] : []
|
|
5506
7072
|
};
|
|
5507
7073
|
}
|
|
5508
7074
|
var ZCodeAgent = class extends OpenCodeSqliteAgent {
|
|
@@ -5544,6 +7110,13 @@ registerAgent({
|
|
|
5544
7110
|
toolStrategy: "custom",
|
|
5545
7111
|
create: () => new KimiAgent()
|
|
5546
7112
|
});
|
|
7113
|
+
registerAgent({
|
|
7114
|
+
icon: "/icon/agent/kimi.svg",
|
|
7115
|
+
resolveDataRoot: resolveKimiCodeDataRoot,
|
|
7116
|
+
resumeCommandPrefix: "kimi -r",
|
|
7117
|
+
toolStrategy: "custom",
|
|
7118
|
+
create: () => new KimiCodeAgent()
|
|
7119
|
+
});
|
|
5547
7120
|
registerAgent({
|
|
5548
7121
|
icon: "/icon/agent/codex.svg",
|
|
5549
7122
|
resolveDataRoot: resolveCodexDataRoot,
|
|
@@ -5573,11 +7146,11 @@ function fallbackDisplayName(input) {
|
|
|
5573
7146
|
}
|
|
5574
7147
|
var realFs = {
|
|
5575
7148
|
exists(path2) {
|
|
5576
|
-
return
|
|
7149
|
+
return existsSync12(path2);
|
|
5577
7150
|
},
|
|
5578
7151
|
readText(path2) {
|
|
5579
7152
|
try {
|
|
5580
|
-
return
|
|
7153
|
+
return readFileSync7(path2, "utf8");
|
|
5581
7154
|
} catch {
|
|
5582
7155
|
return null;
|
|
5583
7156
|
}
|
|
@@ -5602,23 +7175,6 @@ var MANIFESTS = [
|
|
|
5602
7175
|
var PARSEABLE_MANIFESTS = ["package.json", "Cargo.toml", "pyproject.toml"];
|
|
5603
7176
|
var LOOSE_DIRS = /* @__PURE__ */ new Set(["/tmp", "/private/tmp"]);
|
|
5604
7177
|
var LOOSE_HOME_DIRS = ["Desktop", "Downloads", "Documents"];
|
|
5605
|
-
var PROJECT_IDENTITY_KINDS = /* @__PURE__ */ new Set([
|
|
5606
|
-
"git_remote",
|
|
5607
|
-
"git_common_dir",
|
|
5608
|
-
"manifest_path",
|
|
5609
|
-
"synthetic",
|
|
5610
|
-
"path",
|
|
5611
|
-
"loose"
|
|
5612
|
-
]);
|
|
5613
|
-
function isProjectIdentityKind(value) {
|
|
5614
|
-
return PROJECT_IDENTITY_KINDS.has(value);
|
|
5615
|
-
}
|
|
5616
|
-
function getProjectIdentityKey(identity) {
|
|
5617
|
-
return `${identity.kind}:${identity.key}`;
|
|
5618
|
-
}
|
|
5619
|
-
function matchesProjectIdentity(identity, expected) {
|
|
5620
|
-
return identity?.kind === expected.kind && identity.key === expected.key;
|
|
5621
|
-
}
|
|
5622
7178
|
function normalizeGitRemote(url) {
|
|
5623
7179
|
if (!url) return null;
|
|
5624
7180
|
let value = url.trim().replace(/\.git$/, "");
|
|
@@ -5765,6 +7321,7 @@ function parseManifestName(file, text) {
|
|
|
5765
7321
|
function buildProjectGroups(sessions) {
|
|
5766
7322
|
const groups = /* @__PURE__ */ new Map();
|
|
5767
7323
|
for (const session of sessions) {
|
|
7324
|
+
if (isChildSession(session)) continue;
|
|
5768
7325
|
const identity = session.project_identity;
|
|
5769
7326
|
if (!identity) continue;
|
|
5770
7327
|
const activity = session.time_updated ?? session.time_created;
|
|
@@ -5817,7 +7374,7 @@ function isPathScopeMatch(queryPath, sessionPath) {
|
|
|
5817
7374
|
return session === queryPath || session.startsWith(queryPath + "/") || queryPath.startsWith(session + "/");
|
|
5818
7375
|
}
|
|
5819
7376
|
function normalizeScopePath(path2) {
|
|
5820
|
-
return
|
|
7377
|
+
return resolve2(path2).replaceAll(sep, "/");
|
|
5821
7378
|
}
|
|
5822
7379
|
var TAG_ORDER = [
|
|
5823
7380
|
"bugfix",
|
|
@@ -6092,16 +7649,16 @@ function setSchemaEnsuredPath(path2) {
|
|
|
6092
7649
|
schemaEnsuredPath = path2;
|
|
6093
7650
|
}
|
|
6094
7651
|
function getCacheDir2() {
|
|
6095
|
-
return
|
|
7652
|
+
return join14(homedir6(), ".cache", "codesesh");
|
|
6096
7653
|
}
|
|
6097
7654
|
function getCachePath2() {
|
|
6098
|
-
return
|
|
7655
|
+
return join14(getCacheDir2(), CACHE_FILENAME);
|
|
6099
7656
|
}
|
|
6100
7657
|
function getLegacyCachePath() {
|
|
6101
|
-
return
|
|
7658
|
+
return join14(getCacheDir2(), LEGACY_CACHE_FILENAME);
|
|
6102
7659
|
}
|
|
6103
7660
|
function hasCacheStorage() {
|
|
6104
|
-
return
|
|
7661
|
+
return existsSync13(getCachePath2());
|
|
6105
7662
|
}
|
|
6106
7663
|
function likePattern(value) {
|
|
6107
7664
|
return `%${value.trim().toLowerCase().replace(/[\\%_]/g, "\\$&")}%`;
|
|
@@ -6142,6 +7699,8 @@ function prepareUpsertSession(db) {
|
|
|
6142
7699
|
title,
|
|
6143
7700
|
source_path,
|
|
6144
7701
|
directory,
|
|
7702
|
+
parent_agent_name,
|
|
7703
|
+
parent_session_id,
|
|
6145
7704
|
project_identity_kind,
|
|
6146
7705
|
project_identity_key,
|
|
6147
7706
|
project_display_name,
|
|
@@ -6160,13 +7719,15 @@ function prepareUpsertSession(db) {
|
|
|
6160
7719
|
smart_tags_json,
|
|
6161
7720
|
smart_tags_source_updated_at,
|
|
6162
7721
|
meta_json
|
|
6163
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7722
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6164
7723
|
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
6165
7724
|
sort_index = excluded.sort_index,
|
|
6166
7725
|
slug = excluded.slug,
|
|
6167
7726
|
title = excluded.title,
|
|
6168
7727
|
source_path = excluded.source_path,
|
|
6169
7728
|
directory = excluded.directory,
|
|
7729
|
+
parent_agent_name = excluded.parent_agent_name,
|
|
7730
|
+
parent_session_id = excluded.parent_session_id,
|
|
6170
7731
|
project_identity_kind = excluded.project_identity_kind,
|
|
6171
7732
|
project_identity_key = excluded.project_identity_key,
|
|
6172
7733
|
project_display_name = excluded.project_display_name,
|
|
@@ -6197,6 +7758,8 @@ function prepareUpsertIndexedSession(db) {
|
|
|
6197
7758
|
title,
|
|
6198
7759
|
source_path,
|
|
6199
7760
|
directory,
|
|
7761
|
+
parent_agent_name,
|
|
7762
|
+
parent_session_id,
|
|
6200
7763
|
project_identity_kind,
|
|
6201
7764
|
project_identity_key,
|
|
6202
7765
|
project_display_name,
|
|
@@ -6215,11 +7778,13 @@ function prepareUpsertIndexedSession(db) {
|
|
|
6215
7778
|
smart_tags_json,
|
|
6216
7779
|
smart_tags_source_updated_at,
|
|
6217
7780
|
meta_json
|
|
6218
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7781
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6219
7782
|
ON CONFLICT(agent_name, session_id) DO UPDATE SET
|
|
6220
7783
|
slug = excluded.slug,
|
|
6221
7784
|
title = excluded.title,
|
|
6222
7785
|
directory = excluded.directory,
|
|
7786
|
+
parent_agent_name = excluded.parent_agent_name,
|
|
7787
|
+
parent_session_id = excluded.parent_session_id,
|
|
6223
7788
|
project_identity_kind = excluded.project_identity_kind,
|
|
6224
7789
|
project_identity_key = excluded.project_identity_key,
|
|
6225
7790
|
project_display_name = excluded.project_display_name,
|
|
@@ -6250,6 +7815,8 @@ function upsertSessionRow(statement, agentName, session, metaJson, sortIndex, so
|
|
|
6250
7815
|
session.title,
|
|
6251
7816
|
sourcePath,
|
|
6252
7817
|
session.directory,
|
|
7818
|
+
session.parent_reference?.agentName ?? null,
|
|
7819
|
+
session.parent_reference?.sessionId ?? null,
|
|
6253
7820
|
identity.kind,
|
|
6254
7821
|
identity.key,
|
|
6255
7822
|
identity.displayName,
|
|
@@ -6327,6 +7894,12 @@ function sessionFromRow(row) {
|
|
|
6327
7894
|
displayName: String(row.project_display_name ?? "")
|
|
6328
7895
|
};
|
|
6329
7896
|
}
|
|
7897
|
+
if (row.parent_agent_name && row.parent_session_id) {
|
|
7898
|
+
session.parent_reference = {
|
|
7899
|
+
agentName: String(row.parent_agent_name),
|
|
7900
|
+
sessionId: String(row.parent_session_id)
|
|
7901
|
+
};
|
|
7902
|
+
}
|
|
6330
7903
|
if (row.time_updated != null) {
|
|
6331
7904
|
session.time_updated = Number(row.time_updated);
|
|
6332
7905
|
}
|
|
@@ -6544,7 +8117,7 @@ function buildSessionContentFromMessages(title, messages) {
|
|
|
6544
8117
|
}
|
|
6545
8118
|
return chunks.join("\n");
|
|
6546
8119
|
}
|
|
6547
|
-
var CACHE_SCHEMA_VERSION =
|
|
8120
|
+
var CACHE_SCHEMA_VERSION = 18;
|
|
6548
8121
|
function withCacheDb(fn) {
|
|
6549
8122
|
const cachePath = getCachePath2();
|
|
6550
8123
|
const db = openDb(cachePath);
|
|
@@ -6644,6 +8217,8 @@ function createSessionTables(db) {
|
|
|
6644
8217
|
title TEXT NOT NULL,
|
|
6645
8218
|
source_path TEXT,
|
|
6646
8219
|
directory TEXT NOT NULL,
|
|
8220
|
+
parent_agent_name TEXT,
|
|
8221
|
+
parent_session_id TEXT,
|
|
6647
8222
|
project_identity_kind TEXT NOT NULL,
|
|
6648
8223
|
project_identity_key TEXT NOT NULL,
|
|
6649
8224
|
project_display_name TEXT NOT NULL,
|
|
@@ -6671,6 +8246,9 @@ function createSessionTables(db) {
|
|
|
6671
8246
|
CREATE INDEX IF NOT EXISTS idx_sessions_project
|
|
6672
8247
|
ON sessions(project_identity_kind, project_identity_key, activity_time);
|
|
6673
8248
|
|
|
8249
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_parent
|
|
8250
|
+
ON sessions(parent_agent_name, parent_session_id);
|
|
8251
|
+
|
|
6674
8252
|
CREATE TABLE IF NOT EXISTS messages (
|
|
6675
8253
|
agent_name TEXT NOT NULL,
|
|
6676
8254
|
session_id TEXT NOT NULL,
|
|
@@ -6960,6 +8538,7 @@ function createProjectGroupsView(db) {
|
|
|
6960
8538
|
`);
|
|
6961
8539
|
return;
|
|
6962
8540
|
}
|
|
8541
|
+
const hasParentReference = columnExists(db, "sessions", "parent_agent_name") && columnExists(db, "sessions", "parent_session_id");
|
|
6963
8542
|
db.exec(`
|
|
6964
8543
|
CREATE VIEW IF NOT EXISTS project_groups_v AS
|
|
6965
8544
|
SELECT
|
|
@@ -6970,6 +8549,7 @@ function createProjectGroupsView(db) {
|
|
|
6970
8549
|
COUNT(*) AS session_count,
|
|
6971
8550
|
MAX(activity_time) AS last_activity
|
|
6972
8551
|
FROM sessions
|
|
8552
|
+
${hasParentReference ? "WHERE parent_agent_name IS NULL OR parent_session_id IS NULL" : ""}
|
|
6973
8553
|
GROUP BY project_identity_kind, project_identity_key;
|
|
6974
8554
|
`);
|
|
6975
8555
|
}
|
|
@@ -7367,6 +8947,19 @@ function addMessagePartsFormatVersion(db) {
|
|
|
7367
8947
|
}
|
|
7368
8948
|
db.exec("ALTER TABLE messages ADD COLUMN parts_format_version INTEGER NOT NULL DEFAULT 0");
|
|
7369
8949
|
}
|
|
8950
|
+
function addSessionParentReference(db) {
|
|
8951
|
+
if (!tableExists(db, "sessions")) return;
|
|
8952
|
+
if (!columnExists(db, "sessions", "parent_agent_name")) {
|
|
8953
|
+
db.exec("ALTER TABLE sessions ADD COLUMN parent_agent_name TEXT");
|
|
8954
|
+
}
|
|
8955
|
+
if (!columnExists(db, "sessions", "parent_session_id")) {
|
|
8956
|
+
db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
|
|
8957
|
+
}
|
|
8958
|
+
db.exec(
|
|
8959
|
+
"CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_agent_name, parent_session_id)"
|
|
8960
|
+
);
|
|
8961
|
+
recreateProjectGroupsView(db);
|
|
8962
|
+
}
|
|
7370
8963
|
var CODEX_EXEC_DECODE_MIGRATION_KEY = "codex_exec_decode_migrated_v3";
|
|
7371
8964
|
function migrateCodexExecDecode(db) {
|
|
7372
8965
|
if (!tableExists(db, "cache_meta")) return;
|
|
@@ -7381,6 +8974,30 @@ function migrateCodexExecDecode(db) {
|
|
|
7381
8974
|
"INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
|
|
7382
8975
|
).run(CODEX_EXEC_DECODE_MIGRATION_KEY);
|
|
7383
8976
|
}
|
|
8977
|
+
var OPENCODE_SUBAGENT_FOLD_KEY = "opencode_subagent_fold_v1";
|
|
8978
|
+
var SUBAGENT_TREE_KEY = "subagent_tree_v1";
|
|
8979
|
+
function migrateOpenCodeSubagentFold(db) {
|
|
8980
|
+
if (!tableExists(db, "cache_meta")) return;
|
|
8981
|
+
const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(OPENCODE_SUBAGENT_FOLD_KEY);
|
|
8982
|
+
if (done) return;
|
|
8983
|
+
if (tableExists(db, "agent_cache")) {
|
|
8984
|
+
db.prepare("DELETE FROM agent_cache WHERE agent_name IN ('zcode', 'opencode')").run();
|
|
8985
|
+
}
|
|
8986
|
+
db.prepare(
|
|
8987
|
+
"INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
|
|
8988
|
+
).run(OPENCODE_SUBAGENT_FOLD_KEY);
|
|
8989
|
+
}
|
|
8990
|
+
function migrateSubagentTree(db) {
|
|
8991
|
+
if (!tableExists(db, "cache_meta")) return;
|
|
8992
|
+
const done = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(SUBAGENT_TREE_KEY);
|
|
8993
|
+
if (done) return;
|
|
8994
|
+
if (tableExists(db, "agent_cache")) {
|
|
8995
|
+
db.prepare("DELETE FROM agent_cache WHERE agent_name IN ('codex', 'zcode', 'opencode')").run();
|
|
8996
|
+
}
|
|
8997
|
+
db.prepare(
|
|
8998
|
+
"INSERT INTO cache_meta(key, value) VALUES (?, '1') ON CONFLICT(key) DO UPDATE SET value = '1'"
|
|
8999
|
+
).run(SUBAGENT_TREE_KEY);
|
|
9000
|
+
}
|
|
7384
9001
|
function rebuildSearchIndex(db) {
|
|
7385
9002
|
if (!tableExists(db, "session_documents_fts")) {
|
|
7386
9003
|
return;
|
|
@@ -7490,6 +9107,8 @@ function ensureSchema(db, dbPath) {
|
|
|
7490
9107
|
createLatestCacheSchema(db);
|
|
7491
9108
|
setCacheSchemaVersion(db);
|
|
7492
9109
|
migrateCodexExecDecode(db);
|
|
9110
|
+
migrateOpenCodeSubagentFold(db);
|
|
9111
|
+
migrateSubagentTree(db);
|
|
7493
9112
|
return;
|
|
7494
9113
|
}
|
|
7495
9114
|
runSchemaMigrations(db, {
|
|
@@ -7521,7 +9140,13 @@ function ensureSchema(db, dbPath) {
|
|
|
7521
9140
|
invalidateSearchContentHashes(db2);
|
|
7522
9141
|
}
|
|
7523
9142
|
},
|
|
7524
|
-
{
|
|
9143
|
+
{
|
|
9144
|
+
version: 7,
|
|
9145
|
+
migrate(db2) {
|
|
9146
|
+
addSessionParentReference(db2);
|
|
9147
|
+
backfillStructuredSessions(db2);
|
|
9148
|
+
}
|
|
9149
|
+
},
|
|
7525
9150
|
{ version: 8, migrate: backfillFileActivity },
|
|
7526
9151
|
{
|
|
7527
9152
|
version: 9,
|
|
@@ -7552,7 +9177,8 @@ function ensureSchema(db, dbPath) {
|
|
|
7552
9177
|
{ version: 13, migrate: createCacheTables },
|
|
7553
9178
|
{ version: 14, migrate: addIndexedMessageCount },
|
|
7554
9179
|
{ version: 15, destructive: true, migrate: compactSessionDocuments },
|
|
7555
|
-
{ version: 17, migrate: addMessagePartsFormatVersion }
|
|
9180
|
+
{ version: 17, migrate: addMessagePartsFormatVersion },
|
|
9181
|
+
{ version: 18, migrate: addSessionParentReference }
|
|
7556
9182
|
]
|
|
7557
9183
|
});
|
|
7558
9184
|
createLatestCacheSchema(db);
|
|
@@ -7560,6 +9186,8 @@ function ensureSchema(db, dbPath) {
|
|
|
7560
9186
|
setCacheSchemaVersion(db);
|
|
7561
9187
|
}
|
|
7562
9188
|
migrateCodexExecDecode(db);
|
|
9189
|
+
migrateOpenCodeSubagentFold(db);
|
|
9190
|
+
migrateSubagentTree(db);
|
|
7563
9191
|
}
|
|
7564
9192
|
function escapeFtsTerm(value) {
|
|
7565
9193
|
return value.replaceAll('"', '""');
|
|
@@ -7690,6 +9318,7 @@ function readPendingReindexIds(db, agentName) {
|
|
|
7690
9318
|
return new Set(rows.map((row) => String(row.session_id)));
|
|
7691
9319
|
}
|
|
7692
9320
|
var SEARCH_INDEX_STATE_BATCH_SIZE = 900;
|
|
9321
|
+
var SEARCH_INDEX_COMMIT_CHUNK_SIZE = 64;
|
|
7693
9322
|
function shouldBulkSyncSearchIndex(options, changedCount) {
|
|
7694
9323
|
if (options.isBulk != null) {
|
|
7695
9324
|
return options.isBulk;
|
|
@@ -7949,6 +9578,32 @@ function syncSessionSearchIndex(agentName, sessions, loadSessionData, options =
|
|
|
7949
9578
|
sortIndex: sessionSortIndexMap.get(session.id) ?? 0
|
|
7950
9579
|
}));
|
|
7951
9580
|
let indexed = 0;
|
|
9581
|
+
if (changes.length > SEARCH_INDEX_COMMIT_CHUNK_SIZE) {
|
|
9582
|
+
runSearchIndexWrite(db, false, () => {
|
|
9583
|
+
indexed += writeSearchIndexRows(db, agentName, toDelete, []);
|
|
9584
|
+
});
|
|
9585
|
+
for (let offset = 0; offset < changes.length; offset += SEARCH_INDEX_COMMIT_CHUNK_SIZE) {
|
|
9586
|
+
const chunk = changes.slice(offset, offset + SEARCH_INDEX_COMMIT_CHUNK_SIZE);
|
|
9587
|
+
runSearchIndexWrite(db, false, () => {
|
|
9588
|
+
indexed += writeSearchIndexRows(
|
|
9589
|
+
db,
|
|
9590
|
+
agentName,
|
|
9591
|
+
[],
|
|
9592
|
+
loadSearchIndexEntries(agentName, chunk, loadSessionData)
|
|
9593
|
+
);
|
|
9594
|
+
});
|
|
9595
|
+
}
|
|
9596
|
+
return {
|
|
9597
|
+
agentName,
|
|
9598
|
+
mode: "incremental",
|
|
9599
|
+
sessions: sessions.length,
|
|
9600
|
+
changed: toUpsert.length,
|
|
9601
|
+
deleted: toDelete.length,
|
|
9602
|
+
indexed,
|
|
9603
|
+
skipped: toUpsert.length - indexed,
|
|
9604
|
+
durationMs: performance.now() - startedAt
|
|
9605
|
+
};
|
|
9606
|
+
}
|
|
7952
9607
|
const writeRows = () => {
|
|
7953
9608
|
indexed = writeSearchIndexRows(
|
|
7954
9609
|
db,
|
|
@@ -8445,55 +10100,90 @@ function buildFileActivityWhere(options) {
|
|
|
8445
10100
|
params
|
|
8446
10101
|
};
|
|
8447
10102
|
}
|
|
10103
|
+
var FILE_ACTIVITY_COLUMNS = `
|
|
10104
|
+
fa.agent_name,
|
|
10105
|
+
fa.session_id,
|
|
10106
|
+
fa.project_identity_key,
|
|
10107
|
+
fa.path,
|
|
10108
|
+
fa.kind,
|
|
10109
|
+
fa.count,
|
|
10110
|
+
fa.latest_time,
|
|
10111
|
+
s.slug,
|
|
10112
|
+
s.title,
|
|
10113
|
+
s.directory,
|
|
10114
|
+
s.project_identity_kind,
|
|
10115
|
+
s.project_display_name,
|
|
10116
|
+
s.time_created,
|
|
10117
|
+
s.time_updated,
|
|
10118
|
+
s.message_count,
|
|
10119
|
+
s.total_input_tokens,
|
|
10120
|
+
s.total_output_tokens,
|
|
10121
|
+
s.total_cache_read_tokens,
|
|
10122
|
+
s.total_cache_create_tokens,
|
|
10123
|
+
s.total_cost,
|
|
10124
|
+
s.cost_source,
|
|
10125
|
+
s.total_tokens,
|
|
10126
|
+
s.model_usage_json,
|
|
10127
|
+
s.smart_tags_json,
|
|
10128
|
+
s.smart_tags_source_updated_at
|
|
10129
|
+
`;
|
|
10130
|
+
var FILE_ACTIVITY_JOIN = `
|
|
10131
|
+
FROM session_file_activity fa
|
|
10132
|
+
JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
|
|
10133
|
+
`;
|
|
10134
|
+
var FILE_ACTIVITY_ORDER = "fa.latest_time DESC, fa.count DESC, fa.path";
|
|
8448
10135
|
function listFileActivity(options = {}) {
|
|
8449
10136
|
return queryFileActivity(options);
|
|
8450
10137
|
}
|
|
8451
|
-
function
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
const filters = buildFileActivityWhere(options);
|
|
8456
|
-
const sessionFilters = sessionSearchOptions ? buildSessionSearchFilters(sessionSearchOptions) : { where: "", params: [] };
|
|
8457
|
-
const whereClauses = [
|
|
10138
|
+
function fileActivityWhere(query) {
|
|
10139
|
+
const filters = buildFileActivityWhere(query.options);
|
|
10140
|
+
const sessionFilters = query.sessionSearchOptions ? buildSessionSearchFilters(query.sessionSearchOptions) : { where: "", params: [] };
|
|
10141
|
+
const clauses = [
|
|
8458
10142
|
filters.where.replace(/^WHERE /, ""),
|
|
8459
10143
|
sessionFilters.where.replace(/^ AND /, "")
|
|
8460
10144
|
].filter(Boolean);
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
)
|
|
10145
|
+
return {
|
|
10146
|
+
where: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
10147
|
+
params: [...filters.params, ...sessionFilters.params]
|
|
10148
|
+
};
|
|
10149
|
+
}
|
|
10150
|
+
function fileActivitySql(query, where) {
|
|
10151
|
+
if (!query.onePerSession) {
|
|
10152
|
+
return `
|
|
10153
|
+
SELECT ${FILE_ACTIVITY_COLUMNS}
|
|
10154
|
+
${FILE_ACTIVITY_JOIN}
|
|
10155
|
+
${where}
|
|
10156
|
+
ORDER BY ${FILE_ACTIVITY_ORDER}
|
|
10157
|
+
LIMIT ?
|
|
10158
|
+
`;
|
|
10159
|
+
}
|
|
10160
|
+
return `
|
|
10161
|
+
SELECT ${FILE_ACTIVITY_COLUMNS}
|
|
10162
|
+
FROM (
|
|
10163
|
+
SELECT
|
|
10164
|
+
fa.rowid AS activity_rowid,
|
|
10165
|
+
ROW_NUMBER() OVER (
|
|
10166
|
+
PARTITION BY fa.agent_name, fa.session_id
|
|
10167
|
+
ORDER BY ${FILE_ACTIVITY_ORDER}
|
|
10168
|
+
) AS session_rank
|
|
10169
|
+
${FILE_ACTIVITY_JOIN}
|
|
10170
|
+
${where}
|
|
10171
|
+
) ranked
|
|
10172
|
+
JOIN session_file_activity fa ON fa.rowid = ranked.activity_rowid
|
|
10173
|
+
JOIN sessions s ON s.agent_name = fa.agent_name AND s.session_id = fa.session_id
|
|
10174
|
+
WHERE ranked.session_rank = 1
|
|
10175
|
+
ORDER BY ${FILE_ACTIVITY_ORDER}
|
|
10176
|
+
LIMIT ?
|
|
10177
|
+
`;
|
|
10178
|
+
}
|
|
10179
|
+
function queryFileActivity(options, sessionSearchOptions, onePerSession = false) {
|
|
10180
|
+
if (!hasCacheStorage()) {
|
|
10181
|
+
return [];
|
|
10182
|
+
}
|
|
10183
|
+
const query = { options, sessionSearchOptions, onePerSession };
|
|
10184
|
+
const { where, params } = fileActivityWhere(query);
|
|
10185
|
+
const sql = fileActivitySql(query, where);
|
|
10186
|
+
const queryRows = (db) => db.prepare(sql).all(...params, options.limit ?? 50);
|
|
8497
10187
|
let rows = withCacheDbReadOnly(queryRows);
|
|
8498
10188
|
if (rows == null && options.path) {
|
|
8499
10189
|
rows = withCacheDb(queryRows);
|
|
@@ -8526,27 +10216,20 @@ function searchFileActivitySessions(query, options = {}) {
|
|
|
8526
10216
|
{
|
|
8527
10217
|
path: path2,
|
|
8528
10218
|
kind: search.options.fileKind,
|
|
8529
|
-
limit:
|
|
10219
|
+
limit: search.options.limit ?? 50
|
|
8530
10220
|
},
|
|
8531
|
-
search.options
|
|
10221
|
+
search.options,
|
|
10222
|
+
true
|
|
8532
10223
|
);
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
results.push({
|
|
8540
|
-
reference: row.reference,
|
|
8541
|
-
session: row.session,
|
|
8542
|
-
snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
|
|
8543
|
-
matchType: "file_path"
|
|
8544
|
-
});
|
|
8545
|
-
if (results.length >= (search.options.limit ?? 50)) break;
|
|
8546
|
-
}
|
|
8547
|
-
return results;
|
|
10224
|
+
return rows.map((row) => ({
|
|
10225
|
+
reference: row.reference,
|
|
10226
|
+
session: row.session,
|
|
10227
|
+
snippet: `${row.kind} ${highlightFilePath(row.path, path2)} \xB7 ${row.count} events`,
|
|
10228
|
+
matchType: "file_path"
|
|
10229
|
+
}));
|
|
8548
10230
|
}
|
|
8549
10231
|
var CACHE_INITIALIZATION_VERSION = "session-cache-v2";
|
|
10232
|
+
var FULL_SYNC_CURSOR_PREFIX = "full_sync_cursor:";
|
|
8550
10233
|
function parseCachedSessionMeta(value) {
|
|
8551
10234
|
if (!value) return null;
|
|
8552
10235
|
try {
|
|
@@ -8557,7 +10240,7 @@ function parseCachedSessionMeta(value) {
|
|
|
8557
10240
|
}
|
|
8558
10241
|
function deleteLegacyCacheFile() {
|
|
8559
10242
|
const legacyPath = getLegacyCachePath();
|
|
8560
|
-
if (!
|
|
10243
|
+
if (!existsSync14(legacyPath)) {
|
|
8561
10244
|
return;
|
|
8562
10245
|
}
|
|
8563
10246
|
try {
|
|
@@ -8584,6 +10267,8 @@ function loadCachedSessions(agentName) {
|
|
|
8584
10267
|
title,
|
|
8585
10268
|
source_path,
|
|
8586
10269
|
directory,
|
|
10270
|
+
parent_agent_name,
|
|
10271
|
+
parent_session_id,
|
|
8587
10272
|
project_identity_kind,
|
|
8588
10273
|
project_identity_key,
|
|
8589
10274
|
project_display_name,
|
|
@@ -8646,6 +10331,43 @@ function markAgentCacheInitialized(agentName, indexVersion = CACHE_INITIALIZATIO
|
|
|
8646
10331
|
).run(agentName, Date.now(), indexVersion);
|
|
8647
10332
|
});
|
|
8648
10333
|
}
|
|
10334
|
+
function markAgentFullSyncStarted(agentName) {
|
|
10335
|
+
withCacheDb((db) => {
|
|
10336
|
+
db.prepare(
|
|
10337
|
+
`
|
|
10338
|
+
UPDATE cache_initialization
|
|
10339
|
+
SET last_sync_at = 0
|
|
10340
|
+
WHERE agent_name = ?
|
|
10341
|
+
`
|
|
10342
|
+
).run(agentName);
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
function getAgentFullSyncCursor(agentName) {
|
|
10346
|
+
if (!hasCacheStorage()) return null;
|
|
10347
|
+
return withCacheDbReadOnly((db) => {
|
|
10348
|
+
const row = db.prepare("SELECT value FROM cache_meta WHERE key = ?").get(`${FULL_SYNC_CURSOR_PREFIX}${agentName}`);
|
|
10349
|
+
return row?.value || null;
|
|
10350
|
+
}) ?? null;
|
|
10351
|
+
}
|
|
10352
|
+
function markAgentFullSyncProgress(agentName, cursor) {
|
|
10353
|
+
if (!cursor) return;
|
|
10354
|
+
withCacheDb((db) => {
|
|
10355
|
+
db.prepare(
|
|
10356
|
+
`
|
|
10357
|
+
INSERT INTO cache_meta(key, value)
|
|
10358
|
+
VALUES (?, ?)
|
|
10359
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
10360
|
+
`
|
|
10361
|
+
).run(`${FULL_SYNC_CURSOR_PREFIX}${agentName}`, cursor);
|
|
10362
|
+
});
|
|
10363
|
+
}
|
|
10364
|
+
function clearAgentFullSyncCursor(agentName) {
|
|
10365
|
+
withCacheDb((db) => {
|
|
10366
|
+
db.prepare("DELETE FROM cache_meta WHERE key = ?").run(
|
|
10367
|
+
`${FULL_SYNC_CURSOR_PREFIX}${agentName}`
|
|
10368
|
+
);
|
|
10369
|
+
});
|
|
10370
|
+
}
|
|
8649
10371
|
function getAgentLastFullSyncAt(agentName) {
|
|
8650
10372
|
if (!hasCacheStorage()) {
|
|
8651
10373
|
return null;
|
|
@@ -8672,6 +10394,7 @@ function markAgentFullSyncCompleted(agentName) {
|
|
|
8672
10394
|
`
|
|
8673
10395
|
).run(Date.now(), agentName);
|
|
8674
10396
|
});
|
|
10397
|
+
clearAgentFullSyncCursor(agentName);
|
|
8675
10398
|
}
|
|
8676
10399
|
function loadCachedSessionRawEntry(agentName, sessionId) {
|
|
8677
10400
|
if (!hasCacheStorage()) {
|
|
@@ -8687,6 +10410,8 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
|
|
|
8687
10410
|
title,
|
|
8688
10411
|
source_path,
|
|
8689
10412
|
directory,
|
|
10413
|
+
parent_agent_name,
|
|
10414
|
+
parent_session_id,
|
|
8690
10415
|
project_identity_kind,
|
|
8691
10416
|
project_identity_key,
|
|
8692
10417
|
project_display_name,
|
|
@@ -8757,7 +10482,7 @@ function loadCachedSessionRawEntry(agentName, sessionId) {
|
|
|
8757
10482
|
});
|
|
8758
10483
|
}
|
|
8759
10484
|
function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
8760
|
-
withCacheDb((db) => {
|
|
10485
|
+
const persisted = withCacheDb((db) => {
|
|
8761
10486
|
const deleteSession = db.prepare(
|
|
8762
10487
|
"DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
|
|
8763
10488
|
);
|
|
@@ -8809,13 +10534,12 @@ function saveCachedSessions(agentName, sessions, meta = {}) {
|
|
|
8809
10534
|
});
|
|
8810
10535
|
write();
|
|
8811
10536
|
deleteLegacyCacheFile();
|
|
10537
|
+
return true;
|
|
8812
10538
|
});
|
|
10539
|
+
return persisted ?? false;
|
|
8813
10540
|
}
|
|
8814
10541
|
function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta = {}) {
|
|
8815
|
-
|
|
8816
|
-
return;
|
|
8817
|
-
}
|
|
8818
|
-
withCacheDb((db) => {
|
|
10542
|
+
const persisted = withCacheDb((db) => {
|
|
8819
10543
|
const deleteSession = db.prepare(
|
|
8820
10544
|
"DELETE FROM sessions WHERE agent_name = ? AND session_id = ?"
|
|
8821
10545
|
);
|
|
@@ -8861,7 +10585,9 @@ function saveCachedSessionChanges(agentName, changes, removedSessionIds, meta =
|
|
|
8861
10585
|
});
|
|
8862
10586
|
write();
|
|
8863
10587
|
deleteLegacyCacheFile();
|
|
10588
|
+
return true;
|
|
8864
10589
|
});
|
|
10590
|
+
return persisted ?? false;
|
|
8865
10591
|
}
|
|
8866
10592
|
function clearCache() {
|
|
8867
10593
|
setSchemaEnsuredPath(null);
|
|
@@ -8887,11 +10613,11 @@ function clearCache() {
|
|
|
8887
10613
|
const walPath = `${cachePath}-wal`;
|
|
8888
10614
|
const shmPath = `${cachePath}-shm`;
|
|
8889
10615
|
for (const filePath of [walPath, shmPath]) {
|
|
8890
|
-
if (!
|
|
10616
|
+
if (!existsSync14(filePath)) {
|
|
8891
10617
|
continue;
|
|
8892
10618
|
}
|
|
8893
10619
|
try {
|
|
8894
|
-
|
|
10620
|
+
rmSync2(filePath, { force: true });
|
|
8895
10621
|
} catch {
|
|
8896
10622
|
}
|
|
8897
10623
|
}
|
|
@@ -8916,6 +10642,8 @@ function sessionSignature(session) {
|
|
|
8916
10642
|
return JSON.stringify([
|
|
8917
10643
|
session.title,
|
|
8918
10644
|
session.directory,
|
|
10645
|
+
session.parent_reference?.agentName ?? null,
|
|
10646
|
+
session.parent_reference?.sessionId ?? null,
|
|
8919
10647
|
session.time_created,
|
|
8920
10648
|
session.time_updated ?? session.time_created,
|
|
8921
10649
|
session.stats.message_count,
|
|
@@ -8969,13 +10697,7 @@ function filterSessions(sessions, options) {
|
|
|
8969
10697
|
if (options.cwd) {
|
|
8970
10698
|
result = filterSessionsByProjectScope(result, options.cwd);
|
|
8971
10699
|
}
|
|
8972
|
-
|
|
8973
|
-
result = result.filter((s) => (s.time_updated ?? s.time_created) >= options.from);
|
|
8974
|
-
}
|
|
8975
|
-
if (options.to != null) {
|
|
8976
|
-
result = result.filter((s) => (s.time_updated ?? s.time_created) <= options.to);
|
|
8977
|
-
}
|
|
8978
|
-
return result;
|
|
10700
|
+
return filterSessionTreeByActivityWindow(result, options.from, options.to);
|
|
8979
10701
|
}
|
|
8980
10702
|
function saveCachedSessionDiff(agent, cachedSessions, updatedSessions, changedIds = []) {
|
|
8981
10703
|
const diff = computeSessionDiff(cachedSessions, updatedSessions, changedIds, sessionSignature);
|
|
@@ -8997,17 +10719,47 @@ function chunkSessions(items, chunkCount) {
|
|
|
8997
10719
|
});
|
|
8998
10720
|
return chunks.filter((chunk) => chunk.length > 0);
|
|
8999
10721
|
}
|
|
9000
|
-
function ensureSessionTagsSync(agent, sessions) {
|
|
10722
|
+
function ensureSessionTagsSync(agent, sessions, onProgress) {
|
|
9001
10723
|
let changed = false;
|
|
10724
|
+
let processed = 0;
|
|
10725
|
+
const total = sessions.length;
|
|
10726
|
+
const timing = {
|
|
10727
|
+
sessions: total,
|
|
10728
|
+
cacheHits: 0,
|
|
10729
|
+
staleSessions: 0,
|
|
10730
|
+
failedSessions: 0,
|
|
10731
|
+
getSessionDataCalls: 0,
|
|
10732
|
+
getSessionDataMs: 0,
|
|
10733
|
+
classifySessionTagsCalls: 0,
|
|
10734
|
+
classifySessionTagsMs: 0
|
|
10735
|
+
};
|
|
9002
10736
|
const tagged = sessions.map((session) => {
|
|
9003
10737
|
const sourceUpdatedAt = session.time_updated ?? session.time_created;
|
|
9004
10738
|
const currentTags = Array.isArray(session.smart_tags) ? session.smart_tags : null;
|
|
9005
10739
|
if (currentTags && session.smart_tags_source_updated_at === sourceUpdatedAt) {
|
|
10740
|
+
timing.cacheHits += 1;
|
|
10741
|
+
processed += 1;
|
|
10742
|
+
onProgress?.(processed, total);
|
|
9006
10743
|
return session;
|
|
9007
10744
|
}
|
|
10745
|
+
timing.staleSessions += 1;
|
|
9008
10746
|
try {
|
|
9009
|
-
|
|
9010
|
-
const
|
|
10747
|
+
timing.getSessionDataCalls += 1;
|
|
10748
|
+
const getSessionDataStartedAt = performance.now();
|
|
10749
|
+
let data;
|
|
10750
|
+
try {
|
|
10751
|
+
data = agent.getSessionData(session.id);
|
|
10752
|
+
} finally {
|
|
10753
|
+
timing.getSessionDataMs += performance.now() - getSessionDataStartedAt;
|
|
10754
|
+
}
|
|
10755
|
+
timing.classifySessionTagsCalls += 1;
|
|
10756
|
+
const classifySessionTagsStartedAt = performance.now();
|
|
10757
|
+
let tags;
|
|
10758
|
+
try {
|
|
10759
|
+
tags = classifySessionTags(data);
|
|
10760
|
+
} finally {
|
|
10761
|
+
timing.classifySessionTagsMs += performance.now() - classifySessionTagsStartedAt;
|
|
10762
|
+
}
|
|
9011
10763
|
changed = true;
|
|
9012
10764
|
return {
|
|
9013
10765
|
...session,
|
|
@@ -9015,10 +10767,14 @@ function ensureSessionTagsSync(agent, sessions) {
|
|
|
9015
10767
|
smart_tags_source_updated_at: getSmartTagSourceTimestamp(data)
|
|
9016
10768
|
};
|
|
9017
10769
|
} catch {
|
|
10770
|
+
timing.failedSessions += 1;
|
|
9018
10771
|
return session;
|
|
10772
|
+
} finally {
|
|
10773
|
+
processed += 1;
|
|
10774
|
+
onProgress?.(processed, total);
|
|
9019
10775
|
}
|
|
9020
10776
|
});
|
|
9021
|
-
return { sessions: tagged, changed };
|
|
10777
|
+
return { sessions: tagged, changed, timing };
|
|
9022
10778
|
}
|
|
9023
10779
|
async function classifySessionTagsInWorker(workerUrl, agentName, sessionIds, meta) {
|
|
9024
10780
|
return new Promise((resolveWorker, rejectWorker) => {
|
|
@@ -9130,12 +10886,13 @@ async function scanAgentSmart(agent, options, onProgress) {
|
|
|
9130
10886
|
}
|
|
9131
10887
|
agent.setSessionMetaMap(metaMap);
|
|
9132
10888
|
if (options.cacheOnly) {
|
|
10889
|
+
const visibleSessions = agent.filterCachedSessions(cached.sessions);
|
|
9133
10890
|
onProgress?.({
|
|
9134
10891
|
agent: agent.name,
|
|
9135
10892
|
phase: "cache",
|
|
9136
|
-
cachedCount:
|
|
10893
|
+
cachedCount: visibleSessions.length
|
|
9137
10894
|
});
|
|
9138
|
-
return finalizeAgentScan(agent,
|
|
10895
|
+
return finalizeAgentScan(agent, visibleSessions, {
|
|
9139
10896
|
finalization: { kind: "cache-only", cached },
|
|
9140
10897
|
options,
|
|
9141
10898
|
timing,
|
|
@@ -9211,6 +10968,7 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
|
|
|
9211
10968
|
from: options.from,
|
|
9212
10969
|
to: options.to,
|
|
9213
10970
|
fast: options.fast,
|
|
10971
|
+
includeRelatedSessions: true,
|
|
9214
10972
|
onProgress: (progress) => {
|
|
9215
10973
|
onProgress?.({
|
|
9216
10974
|
agent: agent.name,
|
|
@@ -9231,11 +10989,17 @@ async function scanAgentFull(agent, options, onProgress, timing = { total: 0 },
|
|
|
9231
10989
|
timing.tags = performance.now() - t2;
|
|
9232
10990
|
const meta = buildAgentCacheMeta(agent);
|
|
9233
10991
|
if (options.writeCache !== false) {
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
10992
|
+
const isFullWindow = options.from == null && options.to == null;
|
|
10993
|
+
const persisted = isFullWindow ? saveCachedSessions(agent.name, tagged.sessions, meta) : true;
|
|
10994
|
+
if (persisted) {
|
|
10995
|
+
if (isFullWindow) markAgentFullSyncCompleted(agent.name);
|
|
10996
|
+
markAgentCacheInitialized(agent.name);
|
|
10997
|
+
} else {
|
|
10998
|
+
getCoreDiagnostics()?.warn("cache.save_failed", {
|
|
10999
|
+
agent: agent.name,
|
|
11000
|
+
sessions: tagged.sessions.length
|
|
11001
|
+
});
|
|
9237
11002
|
}
|
|
9238
|
-
markAgentCacheInitialized(agent.name);
|
|
9239
11003
|
}
|
|
9240
11004
|
onProgress?.({ agent: agent.name, phase: "complete", newCount: tagged.sessions.length });
|
|
9241
11005
|
const filtered = filterSessions(tagged.sessions, options);
|
|
@@ -9440,16 +11204,16 @@ function getStateDir() {
|
|
|
9440
11204
|
if (process.env.CODESESH_STATE_DIR) return process.env.CODESESH_STATE_DIR;
|
|
9441
11205
|
const currentPlatform = platform4();
|
|
9442
11206
|
if (currentPlatform === "darwin") {
|
|
9443
|
-
return
|
|
11207
|
+
return join15(homedir7(), "Library", "Application Support", "codesesh");
|
|
9444
11208
|
}
|
|
9445
11209
|
if (currentPlatform === "win32") {
|
|
9446
11210
|
const appData = process.env.APPDATA ?? process.env.LOCALAPPDATA;
|
|
9447
|
-
return
|
|
11211
|
+
return join15(appData ?? join15(homedir7(), "AppData", "Roaming"), "codesesh");
|
|
9448
11212
|
}
|
|
9449
|
-
return
|
|
11213
|
+
return join15(process.env.XDG_DATA_HOME ?? join15(homedir7(), ".local", "share"), "codesesh");
|
|
9450
11214
|
}
|
|
9451
11215
|
function getStateDbPath() {
|
|
9452
|
-
return
|
|
11216
|
+
return join15(getStateDir(), STATE_DB_FILENAME);
|
|
9453
11217
|
}
|
|
9454
11218
|
function useMemoryStateStore() {
|
|
9455
11219
|
return process.env.CODESESH_STATE_STORE === MEMORY_STATE_STORE;
|
|
@@ -9866,17 +11630,6 @@ function getSessionAgentName(session) {
|
|
|
9866
11630
|
function getSessionActivityTime(session) {
|
|
9867
11631
|
return session.time_updated ?? session.time_created;
|
|
9868
11632
|
}
|
|
9869
|
-
function toLocalDateKey(ts) {
|
|
9870
|
-
const d = new Date(ts);
|
|
9871
|
-
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(
|
|
9872
|
-
d.getDate()
|
|
9873
|
-
).padStart(2, "0")}`;
|
|
9874
|
-
}
|
|
9875
|
-
function startOfLocalDay(ts) {
|
|
9876
|
-
const d = new Date(ts);
|
|
9877
|
-
d.setHours(0, 0, 0, 0);
|
|
9878
|
-
return d.getTime();
|
|
9879
|
-
}
|
|
9880
11633
|
function buildDashboard(sessions, options) {
|
|
9881
11634
|
const { byAgentNames, scope, from, to, agentInfoMap } = options;
|
|
9882
11635
|
const agentMetrics = /* @__PURE__ */ new Map();
|
|
@@ -9897,16 +11650,16 @@ function buildDashboard(sessions, options) {
|
|
|
9897
11650
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
9898
11651
|
const dailyTokenMap = /* @__PURE__ */ new Map();
|
|
9899
11652
|
if (from != null) {
|
|
9900
|
-
const
|
|
9901
|
-
const bucketDays = Math.floor((startOfLocalDay(to) - bucketStart) / 864e5) + 1;
|
|
11653
|
+
const bucketDays = countCalendarDays(from, to);
|
|
9902
11654
|
for (let i = 0; i < bucketDays; i += 1) {
|
|
9903
|
-
const
|
|
9904
|
-
const key = toLocalDateKey(ts);
|
|
11655
|
+
const key = toCalendarDayKey(addCalendarDays(from, i));
|
|
9905
11656
|
dailyMap.set(key, { date: key, sessions: 0, messages: 0 });
|
|
9906
11657
|
dailyTokenMap.set(key, { date: key, input: 0, output: 0, cache_read: 0, cache_create: 0 });
|
|
9907
11658
|
}
|
|
9908
11659
|
}
|
|
9909
|
-
|
|
11660
|
+
const visibleSessions = filterSessionTreeByActivityWindow(sessions, from, to);
|
|
11661
|
+
for (const session of visibleSessions) {
|
|
11662
|
+
if (isChildSession(session)) continue;
|
|
9910
11663
|
const agentName = getSessionAgentName(session);
|
|
9911
11664
|
if (scope.agent && agentName !== scope.agent) continue;
|
|
9912
11665
|
if (scope.projectKind || scope.projectKey) {
|
|
@@ -9916,8 +11669,6 @@ function buildDashboard(sessions, options) {
|
|
|
9916
11669
|
}
|
|
9917
11670
|
}
|
|
9918
11671
|
const activity = getSessionActivityTime(session);
|
|
9919
|
-
if (from != null && activity < from) continue;
|
|
9920
|
-
if (activity > to) continue;
|
|
9921
11672
|
const messageCount = session.stats.message_count;
|
|
9922
11673
|
const sessionTokens = getTotalTokens(session.stats);
|
|
9923
11674
|
totalSessions += 1;
|
|
@@ -9933,7 +11684,7 @@ function buildDashboard(sessions, options) {
|
|
|
9933
11684
|
metric.messages += messageCount;
|
|
9934
11685
|
metric.tokens += sessionTokens;
|
|
9935
11686
|
}
|
|
9936
|
-
const key =
|
|
11687
|
+
const key = toCalendarDayKey(activity);
|
|
9937
11688
|
let bucket = dailyMap.get(key);
|
|
9938
11689
|
if (!bucket) {
|
|
9939
11690
|
bucket = { date: key, sessions: 0, messages: 0 };
|
|
@@ -10016,18 +11767,16 @@ function buildDashboard(sessions, options) {
|
|
|
10016
11767
|
recentSessions
|
|
10017
11768
|
};
|
|
10018
11769
|
}
|
|
10019
|
-
function getProjectGroupKey(identityKind, identityKey) {
|
|
10020
|
-
return `${identityKind}:${identityKey}`;
|
|
10021
|
-
}
|
|
10022
11770
|
function emptyMetrics() {
|
|
10023
11771
|
return { messages: 0, tokens: 0, cost: 0, hasEstimatedCost: false, agentStats: /* @__PURE__ */ new Map() };
|
|
10024
11772
|
}
|
|
10025
11773
|
function attachProjectMetrics(projects, sessions) {
|
|
10026
11774
|
const metrics = /* @__PURE__ */ new Map();
|
|
10027
11775
|
for (const session of sessions) {
|
|
11776
|
+
if (isChildSession(session)) continue;
|
|
10028
11777
|
const identity = session.project_identity;
|
|
10029
11778
|
if (!identity) continue;
|
|
10030
|
-
const key =
|
|
11779
|
+
const key = getProjectIdentityKey(identity);
|
|
10031
11780
|
let current = metrics.get(key);
|
|
10032
11781
|
if (!current) {
|
|
10033
11782
|
current = emptyMetrics();
|
|
@@ -10057,7 +11806,9 @@ function attachProjectMetrics(projects, sessions) {
|
|
|
10057
11806
|
}
|
|
10058
11807
|
}
|
|
10059
11808
|
return projects.map((project) => {
|
|
10060
|
-
const metric = metrics.get(
|
|
11809
|
+
const metric = metrics.get(
|
|
11810
|
+
getProjectIdentityKey({ kind: project.identityKind, key: project.identityKey })
|
|
11811
|
+
);
|
|
10061
11812
|
return {
|
|
10062
11813
|
...project,
|
|
10063
11814
|
messages: metric?.messages ?? 0,
|
|
@@ -10194,10 +11945,20 @@ function searchIndexedSessions(query, textQuery, parsed, options) {
|
|
|
10194
11945
|
}
|
|
10195
11946
|
|
|
10196
11947
|
export {
|
|
11948
|
+
mergeSessionsUpdatedEvents,
|
|
11949
|
+
startOfCalendarDay,
|
|
11950
|
+
addCalendarDays,
|
|
11951
|
+
countCalendarDays,
|
|
11952
|
+
isProjectIdentityKind,
|
|
11953
|
+
matchesProjectIdentity,
|
|
10197
11954
|
normalizeSessionReference,
|
|
10198
11955
|
formatSessionReference,
|
|
10199
11956
|
getSessionAgentKey,
|
|
11957
|
+
sessionRoutePath,
|
|
10200
11958
|
mergeSortedSessions,
|
|
11959
|
+
isChildSession,
|
|
11960
|
+
getRootSessions,
|
|
11961
|
+
filterSessionTreeByActivityWindow,
|
|
10201
11962
|
registerAgent,
|
|
10202
11963
|
createRegisteredAgents,
|
|
10203
11964
|
getRegisteredAgents,
|
|
@@ -10206,10 +11967,14 @@ export {
|
|
|
10206
11967
|
diffSessionSources,
|
|
10207
11968
|
BaseAgent,
|
|
10208
11969
|
FileSystemSessionSource,
|
|
11970
|
+
ensurePrivateDirectory,
|
|
11971
|
+
restrictPrivateFile,
|
|
11972
|
+
restrictExistingPrivateFiles,
|
|
11973
|
+
getPricingGeneration,
|
|
11974
|
+
publishPendingPricing,
|
|
11975
|
+
hasPendingPricing,
|
|
10209
11976
|
refreshPricingCache,
|
|
10210
11977
|
perf,
|
|
10211
|
-
isProjectIdentityKind,
|
|
10212
|
-
matchesProjectIdentity,
|
|
10213
11978
|
createProjectScopeMatcher,
|
|
10214
11979
|
matchesProjectScope,
|
|
10215
11980
|
getSmartTagSourceTimestamp,
|
|
@@ -10222,6 +11987,9 @@ export {
|
|
|
10222
11987
|
loadCachedSessions,
|
|
10223
11988
|
isAgentCacheInitialized,
|
|
10224
11989
|
markAgentCacheInitialized,
|
|
11990
|
+
markAgentFullSyncStarted,
|
|
11991
|
+
getAgentFullSyncCursor,
|
|
11992
|
+
markAgentFullSyncProgress,
|
|
10225
11993
|
getAgentLastFullSyncAt,
|
|
10226
11994
|
markAgentFullSyncCompleted,
|
|
10227
11995
|
saveCachedSessions,
|
|
@@ -10246,10 +12014,9 @@ export {
|
|
|
10246
12014
|
upsertSessionAlias,
|
|
10247
12015
|
deleteSessionAlias,
|
|
10248
12016
|
getSessionActivityTime,
|
|
10249
|
-
startOfLocalDay,
|
|
10250
12017
|
buildDashboard,
|
|
10251
12018
|
attachProjectMetrics,
|
|
10252
12019
|
executeSessionSearch,
|
|
10253
12020
|
filterSessionSearchCandidates
|
|
10254
12021
|
};
|
|
10255
|
-
//# sourceMappingURL=chunk-
|
|
12022
|
+
//# sourceMappingURL=chunk-G2BTNW3C.js.map
|