chat-recall 0.7.18 → 0.7.19
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 +2 -2
- package/dist/cli.js +873 -248
- package/dist/cli.js.map +1 -1
- package/dist/mcp-relay.js +1 -1
- package/dist/mcp.js +799 -179
- package/dist/scan-worker.js +615 -31
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +24 -10
- package/dist/sync-client.js.map +1 -1
- package/dist/sync-ledger.d.ts +6 -0
- package/dist/sync-ledger.d.ts.map +1 -1
- package/dist/sync-ledger.js +3 -0
- package/dist/sync-ledger.js.map +1 -1
- package/dist/watch.js +801 -170
- package/package.json +1 -1
package/dist/scan-worker.js
CHANGED
|
@@ -4524,6 +4524,7 @@ function defaultSourcesEnabled() {
|
|
|
4524
4524
|
commands: true
|
|
4525
4525
|
},
|
|
4526
4526
|
cursor: { sessions: true, skills: true, agents: true, commands: true },
|
|
4527
|
+
hermes: { sessions: true },
|
|
4527
4528
|
shared: { skills: true },
|
|
4528
4529
|
common: { mcps: true, agentMd: true }
|
|
4529
4530
|
};
|
|
@@ -4594,6 +4595,7 @@ function mergeSources(base, partial) {
|
|
|
4594
4595
|
codex: { ...base.enabled.codex, ...partial.enabled?.codex ?? {} },
|
|
4595
4596
|
agy: { ...base.enabled.agy, ...inherited, ...partial.enabled?.agy ?? {} },
|
|
4596
4597
|
cursor: { ...base.enabled.cursor, ...partial.enabled?.cursor ?? {} },
|
|
4598
|
+
hermes: { ...base.enabled.hermes, ...partial.enabled?.hermes ?? {} },
|
|
4597
4599
|
shared: { ...base.enabled.shared, ...partial.enabled?.shared ?? {} },
|
|
4598
4600
|
common: { ...base.enabled.common, ...partial.enabled?.common ?? {} }
|
|
4599
4601
|
};
|
|
@@ -4605,6 +4607,7 @@ function mergeSources(base, partial) {
|
|
|
4605
4607
|
agyHome: partial.agyHome ?? base.agyHome,
|
|
4606
4608
|
cursorHome: partial.cursorHome ?? base.cursorHome,
|
|
4607
4609
|
cursorIdeHome: partial.cursorIdeHome ?? base.cursorIdeHome,
|
|
4610
|
+
hermesHome: partial.hermesHome ?? base.hermesHome,
|
|
4608
4611
|
extraClaudeHomes: partial.extraClaudeHomes ?? base.extraClaudeHomes,
|
|
4609
4612
|
approvedHomes: partial.approvedHomes ?? base.approvedHomes,
|
|
4610
4613
|
declinedHomes: partial.declinedHomes ?? base.declinedHomes,
|
|
@@ -5284,6 +5287,8 @@ function declaredOrigin(value) {
|
|
|
5284
5287
|
function userRecordOrigin(obj, text) {
|
|
5285
5288
|
if (obj.isSidechain === true)
|
|
5286
5289
|
return "subagent";
|
|
5290
|
+
if (obj.isCompactSummary === true)
|
|
5291
|
+
return "compact-summary";
|
|
5287
5292
|
const declared = declaredOrigin(obj.origin);
|
|
5288
5293
|
if (declared && declared !== "human")
|
|
5289
5294
|
return declared;
|
|
@@ -5526,6 +5531,13 @@ function grandfatherLegacyHomes() {
|
|
|
5526
5531
|
}
|
|
5527
5532
|
return { seeded };
|
|
5528
5533
|
}
|
|
5534
|
+
function defaultHermesHome() {
|
|
5535
|
+
if (process.platform === "win32") {
|
|
5536
|
+
const local = process.env.LOCALAPPDATA?.trim() || join3(homedir2(), "AppData", "Local");
|
|
5537
|
+
return join3(local, "hermes");
|
|
5538
|
+
}
|
|
5539
|
+
return join3(homedir2(), ".hermes");
|
|
5540
|
+
}
|
|
5529
5541
|
function primaryHomes() {
|
|
5530
5542
|
const home = homedir2();
|
|
5531
5543
|
const env = process.env;
|
|
@@ -5542,7 +5554,8 @@ function primaryHomes() {
|
|
|
5542
5554
|
env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
|
|
5543
5555
|
env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
|
|
5544
5556
|
env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
|
|
5545
|
-
dirOf(opencodeDb)
|
|
5557
|
+
dirOf(opencodeDb),
|
|
5558
|
+
env.CHAT_RECALL_HERMES_HOME || s?.hermesHome || env.HERMES_HOME?.trim() || defaultHermesHome()
|
|
5546
5559
|
];
|
|
5547
5560
|
return candidates.map(normalizeHomePath);
|
|
5548
5561
|
}
|
|
@@ -5621,6 +5634,9 @@ function geminiHomeDir() {
|
|
|
5621
5634
|
function opencodeDbPath() {
|
|
5622
5635
|
return process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath || join4(homedir3(), ".local", "share", "opencode", "opencode.db");
|
|
5623
5636
|
}
|
|
5637
|
+
function hermesHomeDir() {
|
|
5638
|
+
return process.env.CHAT_RECALL_HERMES_HOME || sources().hermesHome || process.env.HERMES_HOME?.trim() || defaultHermesHome();
|
|
5639
|
+
}
|
|
5624
5640
|
function codexHomeDir() {
|
|
5625
5641
|
return process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome || join4(homedir3(), ".codex");
|
|
5626
5642
|
}
|
|
@@ -5726,6 +5742,22 @@ function opencodeDbPaths(opts = {}) {
|
|
|
5726
5742
|
dbs.push(primary);
|
|
5727
5743
|
return applyExclusions(dbs, opts.includeExcluded);
|
|
5728
5744
|
}
|
|
5745
|
+
function hermesDbPaths(opts = {}) {
|
|
5746
|
+
const root = hermesHomeDir();
|
|
5747
|
+
const dbs = [join4(root, "state.db")];
|
|
5748
|
+
try {
|
|
5749
|
+
const profiles = join4(root, "profiles");
|
|
5750
|
+
for (const entry of readdirSync2(profiles, { withFileTypes: true })) {
|
|
5751
|
+
if (!entry.isDirectory())
|
|
5752
|
+
continue;
|
|
5753
|
+
const p = join4(profiles, entry.name, "state.db");
|
|
5754
|
+
if (existsSync3(p))
|
|
5755
|
+
dbs.push(p);
|
|
5756
|
+
}
|
|
5757
|
+
} catch {
|
|
5758
|
+
}
|
|
5759
|
+
return applyExclusions(dbs, opts.includeExcluded);
|
|
5760
|
+
}
|
|
5729
5761
|
var sources, _sourceExclusionFilter;
|
|
5730
5762
|
var init_tool_paths = __esm({
|
|
5731
5763
|
"../engine/dist/core/tool-paths.js"() {
|
|
@@ -7003,6 +7035,71 @@ function readTailFromOffset(path, offset, maxBytes = TAIL_READ_MAX_BYTES) {
|
|
|
7003
7035
|
}
|
|
7004
7036
|
}
|
|
7005
7037
|
}
|
|
7038
|
+
function readSourcesFromOffset(paths, offset, maxBytes = TAIL_READ_MAX_BYTES) {
|
|
7039
|
+
let start = 0;
|
|
7040
|
+
let linesBefore = 0;
|
|
7041
|
+
for (let i = 0; i < paths.length; i++) {
|
|
7042
|
+
const path = paths[i];
|
|
7043
|
+
let size = 0;
|
|
7044
|
+
try {
|
|
7045
|
+
size = statSync3(path).size;
|
|
7046
|
+
} catch {
|
|
7047
|
+
size = 0;
|
|
7048
|
+
}
|
|
7049
|
+
const last = i === paths.length - 1;
|
|
7050
|
+
if (!last && offset >= start + size) {
|
|
7051
|
+
const lines = linesBeforeOffset(path, size);
|
|
7052
|
+
if (lines === void 0)
|
|
7053
|
+
return { text: "", newOffset: offset };
|
|
7054
|
+
linesBefore += lines + (size > 0 && !endsWithNewline(path, size) ? 1 : 0);
|
|
7055
|
+
start += size;
|
|
7056
|
+
continue;
|
|
7057
|
+
}
|
|
7058
|
+
const local = offset - start;
|
|
7059
|
+
const read = readTailFromOffset(path, local, maxBytes);
|
|
7060
|
+
const shift = (r) => ({
|
|
7061
|
+
...r,
|
|
7062
|
+
newOffset: start + r.newOffset,
|
|
7063
|
+
...r.baseLine !== void 0 ? { baseLine: linesBefore + r.baseLine } : {}
|
|
7064
|
+
});
|
|
7065
|
+
if (last || read.newOffset > local || local >= size)
|
|
7066
|
+
return shift(read);
|
|
7067
|
+
if (size - local > maxBytes)
|
|
7068
|
+
return { text: "", newOffset: start + size, skippedBytes: size - local };
|
|
7069
|
+
let fd = null;
|
|
7070
|
+
try {
|
|
7071
|
+
fd = openSync(path, "r");
|
|
7072
|
+
const text = readAt(fd, local, size - local).toString("utf-8");
|
|
7073
|
+
return shift({ text, newOffset: size, baseLine: linesBeforeOffset(path, local) });
|
|
7074
|
+
} catch {
|
|
7075
|
+
return { text: "", newOffset: offset };
|
|
7076
|
+
} finally {
|
|
7077
|
+
if (fd !== null) {
|
|
7078
|
+
try {
|
|
7079
|
+
closeSync(fd);
|
|
7080
|
+
} catch {
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
}
|
|
7085
|
+
return { text: "", newOffset: offset };
|
|
7086
|
+
}
|
|
7087
|
+
function endsWithNewline(path, size) {
|
|
7088
|
+
let fd = null;
|
|
7089
|
+
try {
|
|
7090
|
+
fd = openSync(path, "r");
|
|
7091
|
+
return readAt(fd, size - 1, 1)[0] === 10;
|
|
7092
|
+
} catch {
|
|
7093
|
+
return true;
|
|
7094
|
+
} finally {
|
|
7095
|
+
if (fd !== null) {
|
|
7096
|
+
try {
|
|
7097
|
+
closeSync(fd);
|
|
7098
|
+
} catch {
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
7006
7103
|
function findNewline(fd, from, size) {
|
|
7007
7104
|
const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
|
|
7008
7105
|
let pos = from;
|
|
@@ -9481,6 +9578,473 @@ var init_cursor = __esm({
|
|
|
9481
9578
|
}
|
|
9482
9579
|
});
|
|
9483
9580
|
|
|
9581
|
+
// ../engine/dist/core/backends/hermes.js
|
|
9582
|
+
import { existsSync as existsSync10 } from "fs";
|
|
9583
|
+
function firstUserPrompt(db, sessionId) {
|
|
9584
|
+
try {
|
|
9585
|
+
const row = db.prepare(`
|
|
9586
|
+
SELECT substr(content, 1, 400) AS text FROM messages
|
|
9587
|
+
WHERE session_id = ? AND role = 'user' AND active = 1 AND _compressed_summary = 0
|
|
9588
|
+
AND (display_kind IS NULL OR display_kind = 'steer')
|
|
9589
|
+
AND length(trim(content)) > 0
|
|
9590
|
+
ORDER BY id LIMIT 1
|
|
9591
|
+
`).get(sessionId);
|
|
9592
|
+
return row?.text ? flatString(row.text.slice(0, 200)) : "";
|
|
9593
|
+
} catch {
|
|
9594
|
+
return "";
|
|
9595
|
+
}
|
|
9596
|
+
}
|
|
9597
|
+
function messageRowsToEvents(rows) {
|
|
9598
|
+
const events = [];
|
|
9599
|
+
let line = 0;
|
|
9600
|
+
for (const r of rows) {
|
|
9601
|
+
line++;
|
|
9602
|
+
if (r.active === 0)
|
|
9603
|
+
continue;
|
|
9604
|
+
if (r.display_kind === "failed_turn" || r.display_kind === "hidden")
|
|
9605
|
+
continue;
|
|
9606
|
+
const ts = Math.round((r.timestamp || 0) * 1e3);
|
|
9607
|
+
const tsIso = ts ? new Date(ts).toISOString() : void 0;
|
|
9608
|
+
const text = (r.content ?? "").trim();
|
|
9609
|
+
if (r._compressed_summary === 1) {
|
|
9610
|
+
if (text)
|
|
9611
|
+
events.push({ kind: "summary", ts, tsIso, line, text });
|
|
9612
|
+
continue;
|
|
9613
|
+
}
|
|
9614
|
+
if (r.role === "user") {
|
|
9615
|
+
if (!text)
|
|
9616
|
+
continue;
|
|
9617
|
+
events.push({
|
|
9618
|
+
kind: "user",
|
|
9619
|
+
ts,
|
|
9620
|
+
tsIso,
|
|
9621
|
+
line,
|
|
9622
|
+
text,
|
|
9623
|
+
...r.display_kind === "process_complete" ? { origin: "task-notification" } : {}
|
|
9624
|
+
});
|
|
9625
|
+
continue;
|
|
9626
|
+
}
|
|
9627
|
+
if (r.role === "assistant") {
|
|
9628
|
+
if (text)
|
|
9629
|
+
events.push({ kind: "assistant_text", ts, tsIso, line, text });
|
|
9630
|
+
for (const call of parseToolCalls(r.tool_calls)) {
|
|
9631
|
+
const toolName = call.function?.name ?? "";
|
|
9632
|
+
const toolInput = parseArguments(call.function?.arguments);
|
|
9633
|
+
const command = toolName === "terminal" && typeof toolInput?.command === "string" ? toolInput.command : void 0;
|
|
9634
|
+
events.push({
|
|
9635
|
+
kind: "tool_use",
|
|
9636
|
+
ts,
|
|
9637
|
+
tsIso,
|
|
9638
|
+
line,
|
|
9639
|
+
toolName,
|
|
9640
|
+
toolUseId: call.id || call.call_id || `${r.id}:${toolName}`,
|
|
9641
|
+
toolInput,
|
|
9642
|
+
...command ? { command } : {}
|
|
9643
|
+
});
|
|
9644
|
+
}
|
|
9645
|
+
continue;
|
|
9646
|
+
}
|
|
9647
|
+
if (r.role === "tool") {
|
|
9648
|
+
const body = r.content ?? "";
|
|
9649
|
+
const { exitCode, isError } = toolResultStatus(body);
|
|
9650
|
+
events.push({
|
|
9651
|
+
kind: "tool_result",
|
|
9652
|
+
ts,
|
|
9653
|
+
tsIso,
|
|
9654
|
+
line,
|
|
9655
|
+
toolUseId: r.tool_call_id ?? void 0,
|
|
9656
|
+
toolName: r.tool_name ?? void 0,
|
|
9657
|
+
resultBody: body,
|
|
9658
|
+
resultIsError: isError,
|
|
9659
|
+
...exitCode !== void 0 ? { resultExitCode: exitCode } : {},
|
|
9660
|
+
resultBytes: Buffer.byteLength(body)
|
|
9661
|
+
});
|
|
9662
|
+
}
|
|
9663
|
+
}
|
|
9664
|
+
return events;
|
|
9665
|
+
}
|
|
9666
|
+
function parseToolCalls(json) {
|
|
9667
|
+
if (!json)
|
|
9668
|
+
return [];
|
|
9669
|
+
try {
|
|
9670
|
+
const calls = JSON.parse(json);
|
|
9671
|
+
return Array.isArray(calls) ? calls : [];
|
|
9672
|
+
} catch {
|
|
9673
|
+
return [];
|
|
9674
|
+
}
|
|
9675
|
+
}
|
|
9676
|
+
function parseArguments(args) {
|
|
9677
|
+
if (typeof args !== "string")
|
|
9678
|
+
return args ?? {};
|
|
9679
|
+
try {
|
|
9680
|
+
return JSON.parse(args);
|
|
9681
|
+
} catch {
|
|
9682
|
+
return { raw: args };
|
|
9683
|
+
}
|
|
9684
|
+
}
|
|
9685
|
+
function toolResultStatus(body) {
|
|
9686
|
+
if (!body.startsWith("{"))
|
|
9687
|
+
return { isError: false };
|
|
9688
|
+
try {
|
|
9689
|
+
const r = JSON.parse(body);
|
|
9690
|
+
const exitCode = typeof r.exit_code === "number" ? r.exit_code : void 0;
|
|
9691
|
+
const hasError = r.error !== void 0 && r.error !== null && r.error !== "";
|
|
9692
|
+
return { exitCode, isError: hasError || exitCode !== void 0 && exitCode !== 0 };
|
|
9693
|
+
} catch {
|
|
9694
|
+
return { isError: false };
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
var PREFIX5, MESSAGE_COLUMNS, ARCHIVED_SESSION_COLUMNS, SESSION_MTIME_SQL, HermesBackend, hermesBackend;
|
|
9698
|
+
var init_hermes = __esm({
|
|
9699
|
+
"../engine/dist/core/backends/hermes.js"() {
|
|
9700
|
+
"use strict";
|
|
9701
|
+
init_sqlite_reader();
|
|
9702
|
+
init_tool_paths();
|
|
9703
|
+
init_session_outcome();
|
|
9704
|
+
init_session_git();
|
|
9705
|
+
init_generic_engine();
|
|
9706
|
+
init_flat_string();
|
|
9707
|
+
PREFIX5 = "hermes_";
|
|
9708
|
+
MESSAGE_COLUMNS = "id, role, content, tool_calls, tool_call_id, tool_name, timestamp, display_kind, active, _compressed_summary";
|
|
9709
|
+
ARCHIVED_SESSION_COLUMNS = [
|
|
9710
|
+
"id",
|
|
9711
|
+
"source",
|
|
9712
|
+
"title",
|
|
9713
|
+
"model",
|
|
9714
|
+
"cwd",
|
|
9715
|
+
"git_branch",
|
|
9716
|
+
"git_repo_root",
|
|
9717
|
+
"parent_session_id",
|
|
9718
|
+
"started_at",
|
|
9719
|
+
"ended_at",
|
|
9720
|
+
"end_reason",
|
|
9721
|
+
"message_count",
|
|
9722
|
+
"tool_call_count",
|
|
9723
|
+
"input_tokens",
|
|
9724
|
+
"output_tokens",
|
|
9725
|
+
"cache_read_tokens",
|
|
9726
|
+
"cache_write_tokens",
|
|
9727
|
+
"reasoning_tokens",
|
|
9728
|
+
"estimated_cost_usd",
|
|
9729
|
+
"actual_cost_usd"
|
|
9730
|
+
];
|
|
9731
|
+
SESSION_MTIME_SQL = `MAX(
|
|
9732
|
+
s.started_at,
|
|
9733
|
+
COALESCE(s.ended_at, 0),
|
|
9734
|
+
COALESCE(s.last_activity_at, 0),
|
|
9735
|
+
COALESCE((SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id), 0)
|
|
9736
|
+
)`;
|
|
9737
|
+
HermesBackend = class {
|
|
9738
|
+
id = "hermes";
|
|
9739
|
+
idPrefix = PREFIX5;
|
|
9740
|
+
displayName = "Hermes";
|
|
9741
|
+
homeDir() {
|
|
9742
|
+
return hermesHomeDir();
|
|
9743
|
+
}
|
|
9744
|
+
/** Every Hermes database on this machine, the root profile first. */
|
|
9745
|
+
dbPaths() {
|
|
9746
|
+
return hermesDbPaths();
|
|
9747
|
+
}
|
|
9748
|
+
isAvailable() {
|
|
9749
|
+
return this.dbPaths().some((p) => existsSync10(p));
|
|
9750
|
+
}
|
|
9751
|
+
/** The database that holds this session. A session id is unique within a
|
|
9752
|
+
* database, and each named profile has its own. */
|
|
9753
|
+
dbPathFor(rawId) {
|
|
9754
|
+
const cached = this._dbForSession.get(rawId);
|
|
9755
|
+
if (cached)
|
|
9756
|
+
return cached;
|
|
9757
|
+
for (const path of this.dbPaths()) {
|
|
9758
|
+
const db = openSqliteReadonly(path);
|
|
9759
|
+
if (!db)
|
|
9760
|
+
continue;
|
|
9761
|
+
try {
|
|
9762
|
+
if (db.prepare("SELECT 1 AS ok FROM sessions WHERE id = ?").get(rawId)) {
|
|
9763
|
+
this._dbForSession.set(rawId, path);
|
|
9764
|
+
return path;
|
|
9765
|
+
}
|
|
9766
|
+
} catch {
|
|
9767
|
+
} finally {
|
|
9768
|
+
db.close();
|
|
9769
|
+
}
|
|
9770
|
+
}
|
|
9771
|
+
return null;
|
|
9772
|
+
}
|
|
9773
|
+
_dbForSession = /* @__PURE__ */ new Map();
|
|
9774
|
+
/** Tests, and anything that adds a profile mid-process. */
|
|
9775
|
+
_clearDbRouting() {
|
|
9776
|
+
this._dbForSession.clear();
|
|
9777
|
+
}
|
|
9778
|
+
// ── ID handling ────────────────────────────────────────────────
|
|
9779
|
+
matchesId(id) {
|
|
9780
|
+
return id.startsWith(PREFIX5);
|
|
9781
|
+
}
|
|
9782
|
+
toRawId(id) {
|
|
9783
|
+
return id.startsWith(PREFIX5) ? id.slice(PREFIX5.length) : id;
|
|
9784
|
+
}
|
|
9785
|
+
toPrefixedId(rawId) {
|
|
9786
|
+
return rawId.startsWith(PREFIX5) ? rawId : PREFIX5 + rawId;
|
|
9787
|
+
}
|
|
9788
|
+
// ── Location ───────────────────────────────────────────────────
|
|
9789
|
+
findSession(id) {
|
|
9790
|
+
const rawId = this.toRawId(id);
|
|
9791
|
+
const path = this.dbPathFor(rawId);
|
|
9792
|
+
if (!path)
|
|
9793
|
+
return null;
|
|
9794
|
+
const db = openSqliteReadonly(path);
|
|
9795
|
+
if (!db)
|
|
9796
|
+
return null;
|
|
9797
|
+
try {
|
|
9798
|
+
const row = db.prepare(`
|
|
9799
|
+
SELECT s.cwd, s.git_repo_root, ${SESSION_MTIME_SQL} AS mtime
|
|
9800
|
+
FROM sessions s WHERE s.id = ?
|
|
9801
|
+
`).get(rawId);
|
|
9802
|
+
if (!row)
|
|
9803
|
+
return null;
|
|
9804
|
+
return {
|
|
9805
|
+
path,
|
|
9806
|
+
format: "sqlite",
|
|
9807
|
+
projectDir: "",
|
|
9808
|
+
projectPath: row.git_repo_root || row.cwd || "",
|
|
9809
|
+
mtime: Math.round((row.mtime || 0) * 1e3)
|
|
9810
|
+
};
|
|
9811
|
+
} catch {
|
|
9812
|
+
return null;
|
|
9813
|
+
} finally {
|
|
9814
|
+
db.close();
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
listSessions(opts = {}) {
|
|
9818
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9819
|
+
for (const dbPath of this.dbPaths()) {
|
|
9820
|
+
if (!existsSync10(dbPath))
|
|
9821
|
+
continue;
|
|
9822
|
+
for (const ref of this.listSessionsInDb(dbPath, opts)) {
|
|
9823
|
+
const prior = byId.get(ref.rawId);
|
|
9824
|
+
if (!prior || ref.mtime > prior.mtime)
|
|
9825
|
+
byId.set(ref.rawId, ref);
|
|
9826
|
+
}
|
|
9827
|
+
}
|
|
9828
|
+
const all = [...byId.values()].sort((a, b) => b.mtime - a.mtime);
|
|
9829
|
+
return opts.limit ? all.slice(0, opts.limit) : all;
|
|
9830
|
+
}
|
|
9831
|
+
listSessionsInDb(dbPath, opts) {
|
|
9832
|
+
const cutoffSec = (opts.sinceMs ?? 0) / 1e3;
|
|
9833
|
+
const filter = opts.projectFilter?.toLowerCase();
|
|
9834
|
+
const db = openSqliteReadonly(dbPath);
|
|
9835
|
+
if (!db)
|
|
9836
|
+
return [];
|
|
9837
|
+
try {
|
|
9838
|
+
const rows = db.prepare(`
|
|
9839
|
+
SELECT * FROM (
|
|
9840
|
+
SELECT s.id, s.cwd, s.git_repo_root, s.started_at, s.message_count,
|
|
9841
|
+
${SESSION_MTIME_SQL} AS mtime
|
|
9842
|
+
FROM sessions s
|
|
9843
|
+
) WHERE mtime >= ? ORDER BY mtime DESC
|
|
9844
|
+
`).all(cutoffSec);
|
|
9845
|
+
const out = [];
|
|
9846
|
+
for (const row of rows) {
|
|
9847
|
+
const projectPath = row.git_repo_root || row.cwd || "";
|
|
9848
|
+
if (filter && !projectPath.toLowerCase().includes(filter))
|
|
9849
|
+
continue;
|
|
9850
|
+
const mtime = Math.round(row.mtime * 1e3);
|
|
9851
|
+
this._dbForSession.set(row.id, dbPath);
|
|
9852
|
+
out.push({
|
|
9853
|
+
toolId: "hermes",
|
|
9854
|
+
rawId: row.id,
|
|
9855
|
+
prefixedId: this.toPrefixedId(row.id),
|
|
9856
|
+
projectPath,
|
|
9857
|
+
projectDir: "",
|
|
9858
|
+
fullPath: dbPath,
|
|
9859
|
+
created: new Date(Math.round(row.started_at * 1e3)).toISOString(),
|
|
9860
|
+
modified: new Date(mtime).toISOString(),
|
|
9861
|
+
mtime,
|
|
9862
|
+
firstPrompt: opts.previews === false ? "" : firstUserPrompt(db, row.id),
|
|
9863
|
+
messageCount: opts.previews === false ? 0 : row.message_count ?? 0
|
|
9864
|
+
});
|
|
9865
|
+
if (opts.limit && out.length >= opts.limit)
|
|
9866
|
+
break;
|
|
9867
|
+
}
|
|
9868
|
+
return out;
|
|
9869
|
+
} catch {
|
|
9870
|
+
return [];
|
|
9871
|
+
} finally {
|
|
9872
|
+
db.close();
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
/** Hermes writes a title for each session (a model-written one, or the
|
|
9876
|
+
* first prompt for a one-shot run). */
|
|
9877
|
+
getNativeTitle(rawId) {
|
|
9878
|
+
const path = this.dbPathFor(this.toRawId(rawId));
|
|
9879
|
+
if (!path)
|
|
9880
|
+
return null;
|
|
9881
|
+
const db = openSqliteReadonly(path);
|
|
9882
|
+
if (!db)
|
|
9883
|
+
return null;
|
|
9884
|
+
try {
|
|
9885
|
+
const row = db.prepare("SELECT title FROM sessions WHERE id = ?").get(this.toRawId(rawId));
|
|
9886
|
+
const t = row?.title?.trim();
|
|
9887
|
+
return t ? t.slice(0, 200) : null;
|
|
9888
|
+
} catch {
|
|
9889
|
+
return null;
|
|
9890
|
+
} finally {
|
|
9891
|
+
db.close();
|
|
9892
|
+
}
|
|
9893
|
+
}
|
|
9894
|
+
// ── Generic-engine inputs ───────────────────────────────────────
|
|
9895
|
+
fileToolMap = {
|
|
9896
|
+
write_file: "write",
|
|
9897
|
+
patch: "edit",
|
|
9898
|
+
read_file: "read"
|
|
9899
|
+
};
|
|
9900
|
+
/**
|
|
9901
|
+
* `write_file` carries `{ path, content }`. `patch` carries
|
|
9902
|
+
* `{ mode: 'replace', path, old_string, new_string }`; its other mode,
|
|
9903
|
+
* `patch`, is a multi-file V4A patch with no single before and after.
|
|
9904
|
+
*/
|
|
9905
|
+
extractEditDelta(toolName, input) {
|
|
9906
|
+
if (input == null || typeof input !== "object")
|
|
9907
|
+
return null;
|
|
9908
|
+
const inp = input;
|
|
9909
|
+
if (toolName === "write_file") {
|
|
9910
|
+
return { before: "", after: typeof inp.content === "string" ? inp.content : null };
|
|
9911
|
+
}
|
|
9912
|
+
if (toolName === "patch" && (inp.mode === void 0 || inp.mode === "replace")) {
|
|
9913
|
+
const before = typeof inp.old_string === "string" ? inp.old_string : null;
|
|
9914
|
+
const after = typeof inp.new_string === "string" ? inp.new_string : null;
|
|
9915
|
+
if (before === null && after === null)
|
|
9916
|
+
return null;
|
|
9917
|
+
return { before, after };
|
|
9918
|
+
}
|
|
9919
|
+
return null;
|
|
9920
|
+
}
|
|
9921
|
+
readEvents(rawId) {
|
|
9922
|
+
const id = this.toRawId(rawId);
|
|
9923
|
+
const path = this.dbPathFor(id);
|
|
9924
|
+
if (!path)
|
|
9925
|
+
return [];
|
|
9926
|
+
const db = openSqliteReadonly(path);
|
|
9927
|
+
if (!db)
|
|
9928
|
+
return [];
|
|
9929
|
+
try {
|
|
9930
|
+
const rows = db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM messages WHERE session_id = ? ORDER BY id`).all(id);
|
|
9931
|
+
return messageRowsToEvents(rows);
|
|
9932
|
+
} catch {
|
|
9933
|
+
return [];
|
|
9934
|
+
} finally {
|
|
9935
|
+
db.close();
|
|
9936
|
+
}
|
|
9937
|
+
}
|
|
9938
|
+
/**
|
|
9939
|
+
* Events from the dump `exportRawSession` writes, for a session rebuilt from
|
|
9940
|
+
* the server's archive. The archive joins the lines of every dump it got, so
|
|
9941
|
+
* a row that changed (a rewind sets `active` to 0) is there more than once.
|
|
9942
|
+
* The last copy of each row id is the newest.
|
|
9943
|
+
*/
|
|
9944
|
+
readEventsFromText(text) {
|
|
9945
|
+
const rows = /* @__PURE__ */ new Map();
|
|
9946
|
+
for (const line of text.split("\n")) {
|
|
9947
|
+
if (!line.trim())
|
|
9948
|
+
continue;
|
|
9949
|
+
try {
|
|
9950
|
+
const rec = JSON.parse(line);
|
|
9951
|
+
if (rec.kind === "message" && rec.row) {
|
|
9952
|
+
rows.delete(rec.row.id);
|
|
9953
|
+
rows.set(rec.row.id, rec.row);
|
|
9954
|
+
}
|
|
9955
|
+
} catch {
|
|
9956
|
+
}
|
|
9957
|
+
}
|
|
9958
|
+
return messageRowsToEvents([...rows.values()].sort((a, b) => a.id - b.id));
|
|
9959
|
+
}
|
|
9960
|
+
// ── Per-session operations — all delegate to the generic engine ─
|
|
9961
|
+
extractTurns(id, opts = {}) {
|
|
9962
|
+
return extractTurnsFromEvents(this.toPrefixedId(id), this.readEvents(id), opts);
|
|
9963
|
+
}
|
|
9964
|
+
liveScanEdits(id) {
|
|
9965
|
+
const located = this.findSession(id);
|
|
9966
|
+
if (!located) {
|
|
9967
|
+
return { found: false, projectPath: "", projectDir: "", edits: [], fileMtime: 0, tool: "hermes" };
|
|
9968
|
+
}
|
|
9969
|
+
return liveScanEditsFromEvents(this.readEvents(id), this.fileToolMap, {
|
|
9970
|
+
sessionId: this.toPrefixedId(id),
|
|
9971
|
+
tool: "hermes",
|
|
9972
|
+
projectPath: located.projectPath,
|
|
9973
|
+
projectDir: located.projectDir,
|
|
9974
|
+
fileMtime: located.mtime,
|
|
9975
|
+
found: true
|
|
9976
|
+
});
|
|
9977
|
+
}
|
|
9978
|
+
replay(id) {
|
|
9979
|
+
const located = this.findSession(id);
|
|
9980
|
+
if (!located) {
|
|
9981
|
+
return { sessionId: this.toPrefixedId(id), found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
|
|
9982
|
+
}
|
|
9983
|
+
return replayFromEvents(this.toPrefixedId(id), this.readEvents(id), this.fileToolMap, this.extractEditDelta.bind(this), {
|
|
9984
|
+
projectPath: located.projectPath,
|
|
9985
|
+
found: true
|
|
9986
|
+
});
|
|
9987
|
+
}
|
|
9988
|
+
computeOutcome(id, opts) {
|
|
9989
|
+
return computeOutcome(this.toPrefixedId(id), opts);
|
|
9990
|
+
}
|
|
9991
|
+
/** File edits since `sinceMs` across the sessions active since then. */
|
|
9992
|
+
collectRecentEdits(opts) {
|
|
9993
|
+
const refs = this.listSessions({ sinceMs: opts.sinceMs, projectFilter: opts.projectFilter, previews: false });
|
|
9994
|
+
const edits = [];
|
|
9995
|
+
for (const ref of opts.limitSessions ? refs.slice(0, opts.limitSessions) : refs) {
|
|
9996
|
+
for (const e of this.liveScanEdits(ref.rawId).edits) {
|
|
9997
|
+
if (e.ts >= opts.sinceMs)
|
|
9998
|
+
edits.push(e);
|
|
9999
|
+
}
|
|
10000
|
+
}
|
|
10001
|
+
return edits.sort((a, b) => b.ts - a.ts);
|
|
10002
|
+
}
|
|
10003
|
+
getCommits(id, files, startMs, endMs, bufferMinutes) {
|
|
10004
|
+
return getSessionCommits(this.toPrefixedId(id), files, startMs, endMs, bufferMinutes);
|
|
10005
|
+
}
|
|
10006
|
+
/**
|
|
10007
|
+
* The session row and its messages as JSONL, one row per line in id order.
|
|
10008
|
+
* Hermes rewrites and compacts its database, so the archive keeps the rows
|
|
10009
|
+
* as they were synced.
|
|
10010
|
+
*/
|
|
10011
|
+
exportRawSession(id) {
|
|
10012
|
+
const rawId = this.toRawId(id);
|
|
10013
|
+
const path = this.dbPathFor(rawId);
|
|
10014
|
+
if (!path)
|
|
10015
|
+
return null;
|
|
10016
|
+
const db = openSqliteReadonly(path);
|
|
10017
|
+
if (!db)
|
|
10018
|
+
return null;
|
|
10019
|
+
try {
|
|
10020
|
+
const have = new Set(db.prepare(`SELECT name FROM pragma_table_info('sessions')`).all().map((c) => c.name));
|
|
10021
|
+
const cols = ARCHIVED_SESSION_COLUMNS.filter((c) => have.has(c)).map((c) => `s.${c}`);
|
|
10022
|
+
const session = db.prepare(`
|
|
10023
|
+
SELECT ${[...cols, `${SESSION_MTIME_SQL} AS mtime`].join(", ")}
|
|
10024
|
+
FROM sessions s WHERE s.id = ?
|
|
10025
|
+
`).get(rawId);
|
|
10026
|
+
if (!session)
|
|
10027
|
+
return null;
|
|
10028
|
+
const rows = db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM messages WHERE session_id = ? ORDER BY id`).all(rawId);
|
|
10029
|
+
const lines = [JSON.stringify({ kind: "session", row: session })];
|
|
10030
|
+
for (const r of rows)
|
|
10031
|
+
lines.push(JSON.stringify({ kind: "message", row: r }));
|
|
10032
|
+
return {
|
|
10033
|
+
tool: "hermes",
|
|
10034
|
+
mtime: Math.round(Number(session.mtime || 0) * 1e3),
|
|
10035
|
+
files: [{ name: `${rawId}.dump.jsonl`, bytes: Buffer.from(lines.join("\n") + "\n", "utf-8") }]
|
|
10036
|
+
};
|
|
10037
|
+
} catch {
|
|
10038
|
+
return null;
|
|
10039
|
+
} finally {
|
|
10040
|
+
db.close();
|
|
10041
|
+
}
|
|
10042
|
+
}
|
|
10043
|
+
};
|
|
10044
|
+
hermesBackend = new HermesBackend();
|
|
10045
|
+
}
|
|
10046
|
+
});
|
|
10047
|
+
|
|
9484
10048
|
// ../engine/dist/core/backends/index.js
|
|
9485
10049
|
function bootstrapBackends() {
|
|
9486
10050
|
registerBackend(claudeBackend);
|
|
@@ -9488,6 +10052,7 @@ function bootstrapBackends() {
|
|
|
9488
10052
|
registerBackend(codexBackend);
|
|
9489
10053
|
registerBackend(agyBackend);
|
|
9490
10054
|
registerBackend(cursorBackend);
|
|
10055
|
+
registerBackend(hermesBackend);
|
|
9491
10056
|
}
|
|
9492
10057
|
var init_backends = __esm({
|
|
9493
10058
|
"../engine/dist/core/backends/index.js"() {
|
|
@@ -9498,6 +10063,7 @@ var init_backends = __esm({
|
|
|
9498
10063
|
init_codex2();
|
|
9499
10064
|
init_agy();
|
|
9500
10065
|
init_cursor();
|
|
10066
|
+
init_hermes();
|
|
9501
10067
|
init_tool_backend();
|
|
9502
10068
|
_setRegistryBootstrapper(bootstrapBackends);
|
|
9503
10069
|
}
|
|
@@ -9710,7 +10276,7 @@ var init_session_outcome = __esm({
|
|
|
9710
10276
|
});
|
|
9711
10277
|
|
|
9712
10278
|
// ../engine/dist/core/first-prompt.js
|
|
9713
|
-
import { createReadStream, existsSync as
|
|
10279
|
+
import { createReadStream, existsSync as existsSync11, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
|
|
9714
10280
|
import { StringDecoder } from "string_decoder";
|
|
9715
10281
|
function stripBanners(s) {
|
|
9716
10282
|
let out = s;
|
|
@@ -9879,7 +10445,7 @@ var init_project_dir_name = __esm({
|
|
|
9879
10445
|
});
|
|
9880
10446
|
|
|
9881
10447
|
// ../engine/dist/core/backends/claude.js
|
|
9882
|
-
import { existsSync as
|
|
10448
|
+
import { existsSync as existsSync12, readdirSync as readdirSync8, readFileSync as readFileSync7, statSync as statSync9, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
|
|
9883
10449
|
import { join as join11, basename as basename6 } from "path";
|
|
9884
10450
|
function stripReminders(text) {
|
|
9885
10451
|
return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
@@ -9949,7 +10515,7 @@ var init_claude2 = __esm({
|
|
|
9949
10515
|
return join11(this.homeDir(), "history.jsonl");
|
|
9950
10516
|
}
|
|
9951
10517
|
isAvailable() {
|
|
9952
|
-
return
|
|
10518
|
+
return existsSync12(this.projectsDir());
|
|
9953
10519
|
}
|
|
9954
10520
|
// ── ID handling ────────────────────────────────────────────────
|
|
9955
10521
|
/**
|
|
@@ -10055,7 +10621,7 @@ var init_claude2 = __esm({
|
|
|
10055
10621
|
prior.modified = ref.modified;
|
|
10056
10622
|
}
|
|
10057
10623
|
};
|
|
10058
|
-
if (!
|
|
10624
|
+
if (!existsSync12(root))
|
|
10059
10625
|
return;
|
|
10060
10626
|
for (const proj of readdirSync8(root, { withFileTypes: true })) {
|
|
10061
10627
|
if (!proj.isDirectory())
|
|
@@ -10066,12 +10632,12 @@ var init_claude2 = __esm({
|
|
|
10066
10632
|
const projectPath = resolveProjectDirName(proj.name);
|
|
10067
10633
|
const indexPath = join11(projPath, "sessions-index.json");
|
|
10068
10634
|
const indexed = /* @__PURE__ */ new Set();
|
|
10069
|
-
if (
|
|
10635
|
+
if (existsSync12(indexPath)) {
|
|
10070
10636
|
try {
|
|
10071
10637
|
const indexData = JSON.parse(readFileSync7(indexPath, "utf-8"));
|
|
10072
10638
|
const entries = indexData.entries || [];
|
|
10073
10639
|
for (const entry of entries) {
|
|
10074
|
-
if (!entry.fullPath || !
|
|
10640
|
+
if (!entry.fullPath || !existsSync12(entry.fullPath))
|
|
10075
10641
|
continue;
|
|
10076
10642
|
indexed.add(entry.sessionId);
|
|
10077
10643
|
add({
|
|
@@ -10396,7 +10962,7 @@ var init_claude2 = __esm({
|
|
|
10396
10962
|
collectRecentEdits(opts) {
|
|
10397
10963
|
const candidates = [];
|
|
10398
10964
|
for (const root of claudeProjectDirs()) {
|
|
10399
|
-
if (!
|
|
10965
|
+
if (!existsSync12(root))
|
|
10400
10966
|
continue;
|
|
10401
10967
|
for (const projEntry of readdirSync8(root, { withFileTypes: true })) {
|
|
10402
10968
|
if (!projEntry.isDirectory())
|
|
@@ -10418,7 +10984,7 @@ var init_claude2 = __esm({
|
|
|
10418
10984
|
const st = statSync9(fp);
|
|
10419
10985
|
let mtime = st.mtimeMs;
|
|
10420
10986
|
const subDir = join11(projPath, f.slice(0, -6), "subagents");
|
|
10421
|
-
if (
|
|
10987
|
+
if (existsSync12(subDir)) {
|
|
10422
10988
|
for (const sub of readdirSync8(subDir)) {
|
|
10423
10989
|
try {
|
|
10424
10990
|
const subSt = statSync9(join11(subDir, sub));
|
|
@@ -10495,27 +11061,45 @@ var init_claude2 = __esm({
|
|
|
10495
11061
|
/**
|
|
10496
11062
|
* True when this session exists in MORE THAN ONE home.
|
|
10497
11063
|
*
|
|
10498
|
-
* Such a session cannot be tail-appended: `fileSize()` is the SUM across
|
|
10499
|
-
* copies while a byte offset addresses ONE file, so the two disagree by
|
|
10500
|
-
* construction and the "tail" is either empty or the wrong bytes. The caller
|
|
10501
|
-
* uses this to force a FULL sync, which ships the unioned container and is
|
|
10502
|
-
* correct regardless of how the halves are arranged.
|
|
10503
|
-
*
|
|
10504
11064
|
* Measured on ec05f266: ~/.claude 1715 records, ~/.claude-work 205 records,
|
|
10505
|
-
* zero overlap.
|
|
10506
|
-
*
|
|
11065
|
+
* zero overlap. `fileSize()` is the sum across the copies, and
|
|
11066
|
+
* `readFromOffset()` reads them joined end to end, primary first.
|
|
10507
11067
|
*/
|
|
10508
11068
|
spansMultipleSources(prefixedId) {
|
|
10509
11069
|
return findSessionFiles(this.toRawId(prefixedId)).length > 1;
|
|
10510
11070
|
}
|
|
11071
|
+
/**
|
|
11072
|
+
* The copies before the last one, each with its size. Claude Code writes to
|
|
11073
|
+
* the home of the profile that runs the session, so the live copy can grow,
|
|
11074
|
+
* appear, vanish or merge back into the primary. Each of those moves bytes
|
|
11075
|
+
* under an offset into the joined copies; this key changes with all of them.
|
|
11076
|
+
*/
|
|
11077
|
+
sourceLayout(prefixedId) {
|
|
11078
|
+
const copies = findSessionFiles(this.toRawId(prefixedId));
|
|
11079
|
+
if (copies.length < 2)
|
|
11080
|
+
return "";
|
|
11081
|
+
return copies.slice(0, -1).map((c) => {
|
|
11082
|
+
let size = -1;
|
|
11083
|
+
try {
|
|
11084
|
+
size = statSync9(c.path).size;
|
|
11085
|
+
} catch {
|
|
11086
|
+
}
|
|
11087
|
+
return `${c.path}:${size}`;
|
|
11088
|
+
}).join("|");
|
|
11089
|
+
}
|
|
11090
|
+
/**
|
|
11091
|
+
* Session 8de13d0a had 31 MB in ~/.claude and 2 MB in ~/.claude-work. Over
|
|
11092
|
+
* the 24 MB build ceiling a sync ships the transcript in chunks read here,
|
|
11093
|
+
* and this returned no text for a session in two homes. Each sync shipped
|
|
11094
|
+
* nothing, and the server stayed three hours behind.
|
|
11095
|
+
*/
|
|
10511
11096
|
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
10512
|
-
const
|
|
10513
|
-
const copies = findSessionFiles(rawId);
|
|
11097
|
+
const copies = findSessionFiles(this.toRawId(prefixedId));
|
|
10514
11098
|
if (copies.length === 0)
|
|
10515
11099
|
return { text: "", newOffset: offset };
|
|
10516
|
-
if (copies.length
|
|
10517
|
-
return
|
|
10518
|
-
return
|
|
11100
|
+
if (copies.length === 1)
|
|
11101
|
+
return readTailFromOffset(copies[0].path, offset, maxBytes);
|
|
11102
|
+
return readSourcesFromOffset(copies.map((c) => c.path), offset, maxBytes);
|
|
10519
11103
|
}
|
|
10520
11104
|
};
|
|
10521
11105
|
claudeBackend = new ClaudeBackend();
|
|
@@ -10530,11 +11114,11 @@ var init_model_pricing = __esm({
|
|
|
10530
11114
|
});
|
|
10531
11115
|
|
|
10532
11116
|
// ../engine/dist/parsers/session.js
|
|
10533
|
-
import { createReadStream as createReadStream2, existsSync as
|
|
11117
|
+
import { createReadStream as createReadStream2, existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync8, statSync as statSync10, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
|
|
10534
11118
|
import { join as join12, basename as basename7 } from "path";
|
|
10535
11119
|
function hasSubagentsDir(sessionPath) {
|
|
10536
11120
|
const dir = sessionPath.slice(0, -6);
|
|
10537
|
-
return
|
|
11121
|
+
return existsSync13(join12(dir, "subagents"));
|
|
10538
11122
|
}
|
|
10539
11123
|
var init_session = __esm({
|
|
10540
11124
|
"../engine/dist/parsers/session.js"() {
|
|
@@ -10633,7 +11217,7 @@ __export(live_session_scan_exports, {
|
|
|
10633
11217
|
withSessionReadCache: () => withSessionReadCache,
|
|
10634
11218
|
withSessionScanScope: () => withSessionScanScope
|
|
10635
11219
|
});
|
|
10636
|
-
import { existsSync as
|
|
11220
|
+
import { existsSync as existsSync14, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
|
|
10637
11221
|
import { join as join13, basename as basename8 } from "path";
|
|
10638
11222
|
import { createHash as createHash2 } from "crypto";
|
|
10639
11223
|
function lazyGeminiProjectsJson() {
|
|
@@ -10694,7 +11278,7 @@ function sessionFileIndex() {
|
|
|
10694
11278
|
return p;
|
|
10695
11279
|
};
|
|
10696
11280
|
for (const root of roots) {
|
|
10697
|
-
if (seenRoots.has(root) || !
|
|
11281
|
+
if (seenRoots.has(root) || !existsSync14(root))
|
|
10698
11282
|
continue;
|
|
10699
11283
|
seenRoots.add(root);
|
|
10700
11284
|
dirs.set(root, dirMtime(root));
|
|
@@ -10820,7 +11404,7 @@ function resolveSessionContentPaths(sessionFile) {
|
|
|
10820
11404
|
if (!hasSubagentsDir(sessionFile))
|
|
10821
11405
|
return [sessionFile];
|
|
10822
11406
|
const subDir = join13(sessionFile.slice(0, -6), "subagents");
|
|
10823
|
-
if (!
|
|
11407
|
+
if (!existsSync14(subDir))
|
|
10824
11408
|
return [sessionFile];
|
|
10825
11409
|
const subPaths = readdirSync10(subDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join13(subDir, f));
|
|
10826
11410
|
return [sessionFile, ...subPaths];
|
|
@@ -10837,7 +11421,7 @@ function loadGeminiProjectMap() {
|
|
|
10837
11421
|
return geminiProjectMapCache;
|
|
10838
11422
|
const map = /* @__PURE__ */ new Map();
|
|
10839
11423
|
const projectsPath = lazyGeminiProjectsJson();
|
|
10840
|
-
if (
|
|
11424
|
+
if (existsSync14(projectsPath)) {
|
|
10841
11425
|
try {
|
|
10842
11426
|
const data = JSON.parse(readFileSync9(projectsPath, "utf-8"));
|
|
10843
11427
|
for (const path of Object.keys(data.projects || {})) {
|
|
@@ -10873,7 +11457,7 @@ function geminiIndex() {
|
|
|
10873
11457
|
const projMap = loadGeminiProjectMap();
|
|
10874
11458
|
const isGeminiChat = (f) => f.startsWith("session-") && (f.endsWith(".json") || f.endsWith(".jsonl"));
|
|
10875
11459
|
for (const tmpRoot of geminiTmpDirs()) {
|
|
10876
|
-
if (!
|
|
11460
|
+
if (!existsSync14(tmpRoot))
|
|
10877
11461
|
continue;
|
|
10878
11462
|
let projs;
|
|
10879
11463
|
try {
|
|
@@ -10885,7 +11469,7 @@ function geminiIndex() {
|
|
|
10885
11469
|
if (!proj.isDirectory())
|
|
10886
11470
|
continue;
|
|
10887
11471
|
const chats = join13(tmpRoot, proj.name, "chats");
|
|
10888
|
-
if (!
|
|
11472
|
+
if (!existsSync14(chats))
|
|
10889
11473
|
continue;
|
|
10890
11474
|
let files;
|
|
10891
11475
|
try {
|
|
@@ -11006,7 +11590,7 @@ function findCodexSessionFile(sessionId) {
|
|
|
11006
11590
|
return null;
|
|
11007
11591
|
}
|
|
11008
11592
|
function findCodexSessionFileIn(root, sessionId) {
|
|
11009
|
-
if (!
|
|
11593
|
+
if (!existsSync14(root))
|
|
11010
11594
|
return null;
|
|
11011
11595
|
for (const year of readdirSync10(root, { withFileTypes: true })) {
|
|
11012
11596
|
if (!year.isDirectory())
|