@threadbase-sh/streamer 1.36.1 → 1.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +555 -354
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +262 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +247 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1285,6 +1285,15 @@ declare class StreamerServer {
|
|
|
1285
1285
|
private checkExchangeRateLimit;
|
|
1286
1286
|
private checkSessionStartRateLimit;
|
|
1287
1287
|
private checkSessionInputRateLimit;
|
|
1288
|
+
/** Project roots watched for conversation JSONLs (profiles or ~/.claude/projects). */
|
|
1289
|
+
private projectsDirsForFreshnessCheck;
|
|
1290
|
+
/**
|
|
1291
|
+
* Full-glob scan + cache upsert/delete reconcile. Used by ?refresh=1 and by
|
|
1292
|
+
* the automatic freshness path when the directory watcher marked the scanner
|
|
1293
|
+
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
1294
|
+
*/
|
|
1295
|
+
private reconcileConversationsCacheFromDisk;
|
|
1296
|
+
private shouldAutoReconcileConversationList;
|
|
1288
1297
|
private handleListConversations;
|
|
1289
1298
|
private handleConversationsCount;
|
|
1290
1299
|
private refreshCountInBackground;
|
package/dist/index.d.ts
CHANGED
|
@@ -1285,6 +1285,15 @@ declare class StreamerServer {
|
|
|
1285
1285
|
private checkExchangeRateLimit;
|
|
1286
1286
|
private checkSessionStartRateLimit;
|
|
1287
1287
|
private checkSessionInputRateLimit;
|
|
1288
|
+
/** Project roots watched for conversation JSONLs (profiles or ~/.claude/projects). */
|
|
1289
|
+
private projectsDirsForFreshnessCheck;
|
|
1290
|
+
/**
|
|
1291
|
+
* Full-glob scan + cache upsert/delete reconcile. Used by ?refresh=1 and by
|
|
1292
|
+
* the automatic freshness path when the directory watcher marked the scanner
|
|
1293
|
+
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
1294
|
+
*/
|
|
1295
|
+
private reconcileConversationsCacheFromDisk;
|
|
1296
|
+
private shouldAutoReconcileConversationList;
|
|
1288
1297
|
private handleListConversations;
|
|
1289
1298
|
private handleConversationsCount;
|
|
1290
1299
|
private refreshCountInBackground;
|
package/dist/index.js
CHANGED
|
@@ -2684,14 +2684,14 @@ import {
|
|
|
2684
2684
|
createReadStream,
|
|
2685
2685
|
existsSync as existsSync10,
|
|
2686
2686
|
watch as fsWatch,
|
|
2687
|
-
readdirSync as
|
|
2687
|
+
readdirSync as readdirSync6,
|
|
2688
2688
|
readFileSync as readFileSync8,
|
|
2689
|
-
statSync as
|
|
2689
|
+
statSync as statSync9
|
|
2690
2690
|
} from "fs";
|
|
2691
2691
|
import { realpath as realpath2 } from "fs/promises";
|
|
2692
2692
|
import { createServer } from "http";
|
|
2693
|
-
import { homedir as
|
|
2694
|
-
import { basename as basename5, dirname as dirname9, join as
|
|
2693
|
+
import { homedir as homedir9 } from "os";
|
|
2694
|
+
import { basename as basename5, dirname as dirname9, join as join18 } from "path";
|
|
2695
2695
|
import { createInterface } from "readline";
|
|
2696
2696
|
|
|
2697
2697
|
// node_modules/nanoid/index.js
|
|
@@ -5420,6 +5420,14 @@ function seal(plaintext, recipientPublicKeyBase64) {
|
|
|
5420
5420
|
};
|
|
5421
5421
|
}
|
|
5422
5422
|
|
|
5423
|
+
// src/services/cache/cacheMetadata.ts
|
|
5424
|
+
function getCacheMetadata(repo, key) {
|
|
5425
|
+
return repo.getCacheMetadata(key);
|
|
5426
|
+
}
|
|
5427
|
+
function setCacheMetadata(repo, key, value) {
|
|
5428
|
+
repo.setCacheMetadata(key, value);
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5423
5431
|
// src/services/cache-integrity/cacheIntegrityMonitor.ts
|
|
5424
5432
|
import { createHash as createHash2 } from "crypto";
|
|
5425
5433
|
import { existsSync as existsSync8 } from "fs";
|
|
@@ -5960,6 +5968,140 @@ function pruneAgentConversations(cache) {
|
|
|
5960
5968
|
return { scanned: rows.length, pruned, missing };
|
|
5961
5969
|
}
|
|
5962
5970
|
|
|
5971
|
+
// src/utils/dates.ts
|
|
5972
|
+
import { compareDesc, isValid, parseISO } from "date-fns";
|
|
5973
|
+
function parseIsoDateOrNull(value) {
|
|
5974
|
+
if (!value) return null;
|
|
5975
|
+
const parsed = parseISO(value);
|
|
5976
|
+
return isValid(parsed) ? parsed : null;
|
|
5977
|
+
}
|
|
5978
|
+
function compareIsoDesc(a, b) {
|
|
5979
|
+
const dateA = parseIsoDateOrNull(a);
|
|
5980
|
+
const dateB = parseIsoDateOrNull(b);
|
|
5981
|
+
if (!dateA && !dateB) return 0;
|
|
5982
|
+
if (!dateA) return 1;
|
|
5983
|
+
if (!dateB) return -1;
|
|
5984
|
+
return compareDesc(dateA, dateB);
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5987
|
+
// src/services/projects/ensureProjectsForConversations.ts
|
|
5988
|
+
function ensureProjectsForConversations(repo, conversations) {
|
|
5989
|
+
const conversationsByPath = /* @__PURE__ */ new Map();
|
|
5990
|
+
for (const conversation of conversations) {
|
|
5991
|
+
if (!conversation.projectPath) continue;
|
|
5992
|
+
const canonical = canonicalizeProjectPath(conversation.projectPath);
|
|
5993
|
+
if (!canonical) continue;
|
|
5994
|
+
const existing = conversationsByPath.get(canonical) ?? [];
|
|
5995
|
+
existing.push(conversation);
|
|
5996
|
+
conversationsByPath.set(canonical, existing);
|
|
5997
|
+
}
|
|
5998
|
+
const pathToProjectId = /* @__PURE__ */ new Map();
|
|
5999
|
+
for (const [path, projectConversations] of conversationsByPath) {
|
|
6000
|
+
const latest = pickLatestConversation(projectConversations);
|
|
6001
|
+
const project = repo.upsertProjectByPath(path, {
|
|
6002
|
+
lastConversationId: latest?.id ?? null,
|
|
6003
|
+
lastConversationCreatedAt: latest?.createdAt ?? null,
|
|
6004
|
+
latestMessageAt: latest?.latestMessageAt ?? null
|
|
6005
|
+
});
|
|
6006
|
+
pathToProjectId.set(path, project.id);
|
|
6007
|
+
}
|
|
6008
|
+
return pathToProjectId;
|
|
6009
|
+
}
|
|
6010
|
+
function pickLatestConversation(conversations) {
|
|
6011
|
+
if (conversations.length === 0) return void 0;
|
|
6012
|
+
return [...conversations].sort((a, b) => {
|
|
6013
|
+
const cmp = compareIsoDesc(a.latestMessageAt ?? null, b.latestMessageAt ?? null);
|
|
6014
|
+
if (cmp !== 0) return cmp;
|
|
6015
|
+
return compareIsoDesc(a.createdAt ?? null, b.createdAt ?? null);
|
|
6016
|
+
})[0];
|
|
6017
|
+
}
|
|
6018
|
+
|
|
6019
|
+
// src/services/conversations/refreshConversationCache.ts
|
|
6020
|
+
function refreshConversationCache(deps) {
|
|
6021
|
+
const { projectsRepo, conversationsRepo, cacheMetadataRepo } = deps;
|
|
6022
|
+
const conversations = conversationsRepo.listConversationsForProjectBackfill();
|
|
6023
|
+
const pathToProjectId = ensureProjectsForConversations(
|
|
6024
|
+
projectsRepo,
|
|
6025
|
+
conversations.map((c) => ({
|
|
6026
|
+
id: c.id,
|
|
6027
|
+
projectPath: c.projectPath,
|
|
6028
|
+
latestMessageAt: c.lastActivity ?? null,
|
|
6029
|
+
createdAt: c.lastActivity ?? null
|
|
6030
|
+
}))
|
|
6031
|
+
);
|
|
6032
|
+
let conversationsBackfilled = 0;
|
|
6033
|
+
for (const conversation of conversations) {
|
|
6034
|
+
if (!conversation.projectPath) continue;
|
|
6035
|
+
if (conversation.projectId) continue;
|
|
6036
|
+
const projectId = pathToProjectId.get(canonicalizeProjectPath(conversation.projectPath));
|
|
6037
|
+
if (!projectId) continue;
|
|
6038
|
+
conversationsRepo.updateConversationProjectId({
|
|
6039
|
+
conversationId: conversation.id,
|
|
6040
|
+
projectId
|
|
6041
|
+
});
|
|
6042
|
+
conversationsBackfilled += 1;
|
|
6043
|
+
}
|
|
6044
|
+
const latest = conversationsRepo.getLatestConversation();
|
|
6045
|
+
if (latest) {
|
|
6046
|
+
setCacheMetadata(cacheMetadataRepo, "last_conversation_id", latest.id);
|
|
6047
|
+
if (latest.lastActivity) {
|
|
6048
|
+
setCacheMetadata(cacheMetadataRepo, "last_conversation_created_at", latest.lastActivity);
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
setCacheMetadata(cacheMetadataRepo, "conversations_last_indexed_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
6052
|
+
return {
|
|
6053
|
+
projectsTouched: pathToProjectId.size,
|
|
6054
|
+
conversationsBackfilled,
|
|
6055
|
+
latestConversationId: latest?.id ?? null
|
|
6056
|
+
};
|
|
6057
|
+
}
|
|
6058
|
+
|
|
6059
|
+
// src/services/conversations/shouldRefreshProjectsFromHdd.ts
|
|
6060
|
+
import { readdirSync as readdirSync5, statSync as statSync7 } from "fs";
|
|
6061
|
+
import { homedir as homedir8 } from "os";
|
|
6062
|
+
import { join as join16 } from "path";
|
|
6063
|
+
var DEFAULT_PROJECTS_DIR = join16(homedir8(), ".claude", "projects");
|
|
6064
|
+
function maxProjectsTreeMtimeMs(projectsDir) {
|
|
6065
|
+
let maxMs;
|
|
6066
|
+
try {
|
|
6067
|
+
maxMs = statSync7(projectsDir).mtimeMs;
|
|
6068
|
+
} catch {
|
|
6069
|
+
return null;
|
|
6070
|
+
}
|
|
6071
|
+
try {
|
|
6072
|
+
for (const ent of readdirSync5(projectsDir, { withFileTypes: true })) {
|
|
6073
|
+
if (!ent.isDirectory()) continue;
|
|
6074
|
+
try {
|
|
6075
|
+
const childMs = statSync7(join16(projectsDir, ent.name)).mtimeMs;
|
|
6076
|
+
if (childMs > maxMs) maxMs = childMs;
|
|
6077
|
+
} catch {
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
} catch {
|
|
6081
|
+
}
|
|
6082
|
+
return maxMs;
|
|
6083
|
+
}
|
|
6084
|
+
function shouldRefreshProjectsFromHdd(conversationsRepo, cacheMetadataRepo, opts = {}) {
|
|
6085
|
+
if (conversationsRepo.hasOrphanRows()) return true;
|
|
6086
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6087
|
+
if (opts.projectsDirs) {
|
|
6088
|
+
for (const d of opts.projectsDirs) dirs.add(d);
|
|
6089
|
+
}
|
|
6090
|
+
dirs.add(opts.projectsDir ?? DEFAULT_PROJECTS_DIR);
|
|
6091
|
+
let newestMs = null;
|
|
6092
|
+
for (const dir of dirs) {
|
|
6093
|
+
const ms = maxProjectsTreeMtimeMs(dir);
|
|
6094
|
+
if (ms === null) continue;
|
|
6095
|
+
if (newestMs === null || ms > newestMs) newestMs = ms;
|
|
6096
|
+
}
|
|
6097
|
+
if (newestMs === null) return false;
|
|
6098
|
+
const lastIndexedIso = getCacheMetadata(cacheMetadataRepo, "conversations_last_indexed_at");
|
|
6099
|
+
if (!lastIndexedIso) return true;
|
|
6100
|
+
const lastIndexedMs = Date.parse(lastIndexedIso);
|
|
6101
|
+
if (Number.isNaN(lastIndexedMs)) return true;
|
|
6102
|
+
return newestMs > lastIndexedMs;
|
|
6103
|
+
}
|
|
6104
|
+
|
|
5963
6105
|
// src/services/projectChats/deriveProjectChatTitle.ts
|
|
5964
6106
|
function deriveProjectChatTitle(input) {
|
|
5965
6107
|
const trimmed = input.title?.trim();
|
|
@@ -6121,7 +6263,7 @@ function resolveAnswer(pending, body) {
|
|
|
6121
6263
|
}
|
|
6122
6264
|
|
|
6123
6265
|
// src/services/sessions/conversationBusy.ts
|
|
6124
|
-
import { statSync as
|
|
6266
|
+
import { statSync as statSync8 } from "fs";
|
|
6125
6267
|
var RESUME_BUSY_WINDOW_MS = 12e4;
|
|
6126
6268
|
function resolveResumeBusyWindowMs(env = process.env) {
|
|
6127
6269
|
const raw = env.THREADBASE_RESUME_BUSY_WINDOW_MS;
|
|
@@ -6138,7 +6280,7 @@ function conversationBusy(input) {
|
|
|
6138
6280
|
let lastActivityMs = null;
|
|
6139
6281
|
if (input.jsonlPath) {
|
|
6140
6282
|
try {
|
|
6141
|
-
const mtimeMs =
|
|
6283
|
+
const mtimeMs = statSync8(input.jsonlPath).mtimeMs;
|
|
6142
6284
|
const age = now - mtimeMs;
|
|
6143
6285
|
lastActivityMs = Math.max(0, age);
|
|
6144
6286
|
const isSelfEcho = input.selfPtyEndedAt != null && mtimeMs <= input.selfPtyEndedAt + SELF_ACTIVITY_SKEW_MS;
|
|
@@ -6366,7 +6508,7 @@ function discoveredToResponse(d, conversationId) {
|
|
|
6366
6508
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
6367
6509
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
6368
6510
|
import heicConvert from "heic-convert";
|
|
6369
|
-
import { join as
|
|
6511
|
+
import { join as join17 } from "path";
|
|
6370
6512
|
var UPLOAD_DIR_NAME = ".threadbase-uploads";
|
|
6371
6513
|
var MAX_BYTES = 25 * 1024 * 1024;
|
|
6372
6514
|
var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
|
|
@@ -6399,9 +6541,9 @@ async function saveUploadFile(input) {
|
|
|
6399
6541
|
}
|
|
6400
6542
|
const id = `up_${randomBytes3(8).toString("hex")}`;
|
|
6401
6543
|
const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
|
|
6402
|
-
const dir =
|
|
6544
|
+
const dir = join17(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
|
|
6403
6545
|
await mkdir3(dir, { recursive: true });
|
|
6404
|
-
const filePath =
|
|
6546
|
+
const filePath = join17(dir, `${Date.now()}-${id}-${safeName}`);
|
|
6405
6547
|
await writeFile(filePath, buffer);
|
|
6406
6548
|
return {
|
|
6407
6549
|
id,
|
|
@@ -6511,14 +6653,6 @@ function computeConversationEtag({
|
|
|
6511
6653
|
return `"${digest}"`;
|
|
6512
6654
|
}
|
|
6513
6655
|
|
|
6514
|
-
// src/utils/dates.ts
|
|
6515
|
-
import { compareDesc, isValid, parseISO } from "date-fns";
|
|
6516
|
-
function parseIsoDateOrNull(value) {
|
|
6517
|
-
if (!value) return null;
|
|
6518
|
-
const parsed = parseISO(value);
|
|
6519
|
-
return isValid(parsed) ? parsed : null;
|
|
6520
|
-
}
|
|
6521
|
-
|
|
6522
6656
|
// src/utils/isScannedSnapshotStale.ts
|
|
6523
6657
|
var STALENESS_TOLERANCE_MS = 1e3;
|
|
6524
6658
|
function isScannedSnapshotStale(snapshotTimestamp, fileMtimeMs) {
|
|
@@ -6819,13 +6953,13 @@ var StreamerServer = class {
|
|
|
6819
6953
|
this.disableDb = config.disableDb ?? false;
|
|
6820
6954
|
this.scannerPersistenceDisabled = config.scannerPersistent === false;
|
|
6821
6955
|
this.scanProfiles = config.scanProfiles;
|
|
6822
|
-
this.codexRoots = config.codexRoots ?? [
|
|
6956
|
+
this.codexRoots = config.codexRoots ?? [join18(homedir9(), ".codex", "sessions")];
|
|
6823
6957
|
this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
6824
6958
|
this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
6825
6959
|
this.defaultPermissionMode = config.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
|
|
6826
6960
|
this.defaultModel = config.defaultModel ?? "sonnet";
|
|
6827
6961
|
this.defaultEffort = config.defaultEffort ?? "low";
|
|
6828
|
-
this.cacheDir = config.cacheDir ?? loadCacheDir() ??
|
|
6962
|
+
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? join18(homedir9(), ".threadbase", "cache");
|
|
6829
6963
|
this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
|
|
6830
6964
|
this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
|
|
6831
6965
|
this.markScannerStaleDebounced = debounce(() => {
|
|
@@ -6866,7 +7000,7 @@ var StreamerServer = class {
|
|
|
6866
7000
|
const seqs = cache.extendMessageIndex(
|
|
6867
7001
|
filePath,
|
|
6868
7002
|
spans,
|
|
6869
|
-
|
|
7003
|
+
statSync9(filePath),
|
|
6870
7004
|
readFrom,
|
|
6871
7005
|
endOffset
|
|
6872
7006
|
);
|
|
@@ -7032,7 +7166,7 @@ var StreamerServer = class {
|
|
|
7032
7166
|
temporalClient,
|
|
7033
7167
|
taskQueue: agentConfig.temporal.taskQueue
|
|
7034
7168
|
});
|
|
7035
|
-
const conversationsBaseDir = agentConfig.conversationsDir ||
|
|
7169
|
+
const conversationsBaseDir = agentConfig.conversationsDir || join18(dirname9(this.cacheDir), "conversations");
|
|
7036
7170
|
conversationWriter = createConversationWriter({
|
|
7037
7171
|
baseDir: conversationsBaseDir
|
|
7038
7172
|
});
|
|
@@ -7355,7 +7489,7 @@ var StreamerServer = class {
|
|
|
7355
7489
|
});
|
|
7356
7490
|
try {
|
|
7357
7491
|
this.cache = ConversationCache.open(
|
|
7358
|
-
|
|
7492
|
+
join18(this.cacheDir, "cache.db"),
|
|
7359
7493
|
this.tailSize,
|
|
7360
7494
|
void 0,
|
|
7361
7495
|
{
|
|
@@ -7481,6 +7615,20 @@ var StreamerServer = class {
|
|
|
7481
7615
|
count: pruned.length,
|
|
7482
7616
|
event: "cache.prune_ghosts"
|
|
7483
7617
|
});
|
|
7618
|
+
if (this.projectsRepo && this.conversationsRepo && this.cacheMetadataRepo) {
|
|
7619
|
+
refreshConversationCache({
|
|
7620
|
+
cache: this.cache,
|
|
7621
|
+
projectsRepo: this.projectsRepo,
|
|
7622
|
+
conversationsRepo: this.conversationsRepo,
|
|
7623
|
+
cacheMetadataRepo: this.cacheMetadataRepo
|
|
7624
|
+
});
|
|
7625
|
+
} else if (this.cacheMetadataRepo) {
|
|
7626
|
+
setCacheMetadata(
|
|
7627
|
+
this.cacheMetadataRepo,
|
|
7628
|
+
"conversations_last_indexed_at",
|
|
7629
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
7630
|
+
);
|
|
7631
|
+
}
|
|
7484
7632
|
}
|
|
7485
7633
|
}).catch((err) => {
|
|
7486
7634
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -7720,6 +7868,56 @@ var StreamerServer = class {
|
|
|
7720
7868
|
checkSessionInputRateLimit(sessionId) {
|
|
7721
7869
|
return this.checkRateLimit(this.sessionInputAttempts, sessionId, 500, 6e4);
|
|
7722
7870
|
}
|
|
7871
|
+
/** Project roots watched for conversation JSONLs (profiles or ~/.claude/projects). */
|
|
7872
|
+
projectsDirsForFreshnessCheck() {
|
|
7873
|
+
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
7874
|
+
return this.scanProfiles.filter((p) => p.enabled).map((p) => join18(p.configDir, "projects"));
|
|
7875
|
+
}
|
|
7876
|
+
return [join18(homedir9(), ".claude", "projects")];
|
|
7877
|
+
}
|
|
7878
|
+
/**
|
|
7879
|
+
* Full-glob scan + cache upsert/delete reconcile. Used by ?refresh=1 and by
|
|
7880
|
+
* the automatic freshness path when the directory watcher marked the scanner
|
|
7881
|
+
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
7882
|
+
*/
|
|
7883
|
+
async reconcileConversationsCacheFromDisk() {
|
|
7884
|
+
if (!this.cache) return;
|
|
7885
|
+
const scanner = await this.rescanForRefresh();
|
|
7886
|
+
const metas = [...scanner.getMetadataCache().values()];
|
|
7887
|
+
try {
|
|
7888
|
+
this.cache.upsertFromScannerMeta(metas);
|
|
7889
|
+
if (!this.cacheMonitor?.pending) {
|
|
7890
|
+
this.cache.reconcileDeletions(canonicalLivePathSet(metas));
|
|
7891
|
+
}
|
|
7892
|
+
if (this.projectsRepo && this.conversationsRepo && this.cacheMetadataRepo) {
|
|
7893
|
+
refreshConversationCache({
|
|
7894
|
+
cache: this.cache,
|
|
7895
|
+
projectsRepo: this.projectsRepo,
|
|
7896
|
+
conversationsRepo: this.conversationsRepo,
|
|
7897
|
+
cacheMetadataRepo: this.cacheMetadataRepo
|
|
7898
|
+
});
|
|
7899
|
+
} else if (this.cacheMetadataRepo) {
|
|
7900
|
+
setCacheMetadata(
|
|
7901
|
+
this.cacheMetadataRepo,
|
|
7902
|
+
"conversations_last_indexed_at",
|
|
7903
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
7904
|
+
);
|
|
7905
|
+
}
|
|
7906
|
+
} catch (err) {
|
|
7907
|
+
this.log.warn(
|
|
7908
|
+
`refresh reconcile failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
7909
|
+
{ event: "conversations.reconcile_failed" }
|
|
7910
|
+
);
|
|
7911
|
+
}
|
|
7912
|
+
}
|
|
7913
|
+
shouldAutoReconcileConversationList() {
|
|
7914
|
+
if (!this.cache) return false;
|
|
7915
|
+
if (this.scannerStale) return true;
|
|
7916
|
+
if (!this.conversationsRepo || !this.cacheMetadataRepo) return false;
|
|
7917
|
+
return shouldRefreshProjectsFromHdd(this.conversationsRepo, this.cacheMetadataRepo, {
|
|
7918
|
+
projectsDirs: this.projectsDirsForFreshnessCheck()
|
|
7919
|
+
});
|
|
7920
|
+
}
|
|
7723
7921
|
async handleListConversations(url, res) {
|
|
7724
7922
|
if (this.rejectIfWarmingUp(res)) return;
|
|
7725
7923
|
const limit = intParam(url, "limit", 50);
|
|
@@ -7728,19 +7926,14 @@ var StreamerServer = class {
|
|
|
7728
7926
|
const project = url.searchParams.get("project") ?? void 0;
|
|
7729
7927
|
const providerFilter = url.searchParams.get("provider") ?? void 0;
|
|
7730
7928
|
const bustCache = url.searchParams.get("refresh") === "1";
|
|
7731
|
-
if (
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
if (!this.cacheMonitor?.pending) {
|
|
7737
|
-
this.cache.reconcileDeletions(canonicalLivePathSet(metas2));
|
|
7738
|
-
}
|
|
7739
|
-
} catch (err) {
|
|
7740
|
-
this.log.warn(
|
|
7741
|
-
`refresh reconcile failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
7742
|
-
{ event: "conversations.reconcile_failed" }
|
|
7929
|
+
if (this.cache && (bustCache || this.shouldAutoReconcileConversationList())) {
|
|
7930
|
+
if (bustCache) {
|
|
7931
|
+
await this.withWarmup(
|
|
7932
|
+
"conversation_refresh",
|
|
7933
|
+
() => this.reconcileConversationsCacheFromDisk()
|
|
7743
7934
|
);
|
|
7935
|
+
} else {
|
|
7936
|
+
await this.reconcileConversationsCacheFromDisk();
|
|
7744
7937
|
}
|
|
7745
7938
|
}
|
|
7746
7939
|
if (this.cache) {
|
|
@@ -8002,21 +8195,21 @@ var StreamerServer = class {
|
|
|
8002
8195
|
*/
|
|
8003
8196
|
projectsDirs() {
|
|
8004
8197
|
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
8005
|
-
return this.scanProfiles.filter((p) => p.enabled).map((p) =>
|
|
8198
|
+
return this.scanProfiles.filter((p) => p.enabled).map((p) => join18(p.configDir, "projects"));
|
|
8006
8199
|
}
|
|
8007
|
-
return [
|
|
8200
|
+
return [join18(homedir9(), ".claude", "projects")];
|
|
8008
8201
|
}
|
|
8009
8202
|
findJsonlPath(uuid) {
|
|
8010
8203
|
const filename = `${uuid}.jsonl`;
|
|
8011
8204
|
for (const projectsDir of this.projectsDirs()) {
|
|
8012
8205
|
if (!existsSync10(projectsDir)) continue;
|
|
8013
|
-
for (const dir of
|
|
8014
|
-
const fp =
|
|
8206
|
+
for (const dir of readdirSync6(projectsDir)) {
|
|
8207
|
+
const fp = join18(projectsDir, dir, filename);
|
|
8015
8208
|
if (existsSync10(fp)) return fp;
|
|
8016
|
-
const projectDir =
|
|
8209
|
+
const projectDir = join18(projectsDir, dir);
|
|
8017
8210
|
try {
|
|
8018
|
-
for (const sub of
|
|
8019
|
-
const subagentPath =
|
|
8211
|
+
for (const sub of readdirSync6(projectDir)) {
|
|
8212
|
+
const subagentPath = join18(projectDir, sub, "subagents", filename);
|
|
8020
8213
|
if (existsSync10(subagentPath)) return subagentPath;
|
|
8021
8214
|
}
|
|
8022
8215
|
} catch {
|
|
@@ -8117,7 +8310,7 @@ var StreamerServer = class {
|
|
|
8117
8310
|
if (this.isManagedTailPath(key)) return;
|
|
8118
8311
|
let mtimeMs;
|
|
8119
8312
|
try {
|
|
8120
|
-
mtimeMs =
|
|
8313
|
+
mtimeMs = statSync9(filePath).mtimeMs;
|
|
8121
8314
|
} catch {
|
|
8122
8315
|
return;
|
|
8123
8316
|
}
|
|
@@ -8299,7 +8492,7 @@ var StreamerServer = class {
|
|
|
8299
8492
|
if (!conv.filePath) return false;
|
|
8300
8493
|
let mtimeMs = null;
|
|
8301
8494
|
try {
|
|
8302
|
-
mtimeMs =
|
|
8495
|
+
mtimeMs = statSync9(conv.filePath).mtimeMs;
|
|
8303
8496
|
} catch {
|
|
8304
8497
|
return false;
|
|
8305
8498
|
}
|
|
@@ -9219,7 +9412,7 @@ var StreamerServer = class {
|
|
|
9219
9412
|
sessionStore: this.sessionStore,
|
|
9220
9413
|
// biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
|
|
9221
9414
|
agentClient: this.agentClient,
|
|
9222
|
-
conversationsDir: this.cacheDir ?
|
|
9415
|
+
conversationsDir: this.cacheDir ? join18(dirname9(this.cacheDir), "conversations") : "",
|
|
9223
9416
|
agentConfig: this.agentConfig
|
|
9224
9417
|
});
|
|
9225
9418
|
json(res, result.status, result.body);
|
|
@@ -9378,9 +9571,9 @@ var StreamerServer = class {
|
|
|
9378
9571
|
// was passed to Claude via --session-id so the filename matches from the start.
|
|
9379
9572
|
watchForJsonl(sessionId, projectPath) {
|
|
9380
9573
|
const encoded = projectPath.replace(/[/\\:.]/g, "-");
|
|
9381
|
-
const projectsDir =
|
|
9574
|
+
const projectsDir = join18(homedir9(), ".claude", "projects", encoded);
|
|
9382
9575
|
const expectedFile = `${sessionId}.jsonl`;
|
|
9383
|
-
const filePath =
|
|
9576
|
+
const filePath = join18(projectsDir, expectedFile);
|
|
9384
9577
|
const deadline = Date.now() + 12e4;
|
|
9385
9578
|
let watcher = null;
|
|
9386
9579
|
const cleanup = () => {
|
|
@@ -9402,10 +9595,10 @@ var StreamerServer = class {
|
|
|
9402
9595
|
if (!resolvedFilePath && existsSync10(projectsDir)) {
|
|
9403
9596
|
try {
|
|
9404
9597
|
const now = Date.now();
|
|
9405
|
-
const match =
|
|
9406
|
-
({ f }) => basename5(f, ".jsonl") === sessionId || this.readFirstLineSessionId(
|
|
9598
|
+
const match = readdirSync6(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync9(join18(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
|
|
9599
|
+
({ f }) => basename5(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join18(projectsDir, f)) === sessionId
|
|
9407
9600
|
).sort((a, b) => b.mtime - a.mtime)[0];
|
|
9408
|
-
if (match) resolvedFilePath =
|
|
9601
|
+
if (match) resolvedFilePath = join18(projectsDir, match.f);
|
|
9409
9602
|
} catch {
|
|
9410
9603
|
}
|
|
9411
9604
|
}
|
|
@@ -9453,7 +9646,7 @@ var StreamerServer = class {
|
|
|
9453
9646
|
watchForCodexRollout(sessionId, projectPath) {
|
|
9454
9647
|
const deadline = Date.now() + 12e4;
|
|
9455
9648
|
const now = /* @__PURE__ */ new Date();
|
|
9456
|
-
const dateDir =
|
|
9649
|
+
const dateDir = join18(
|
|
9457
9650
|
String(now.getFullYear()),
|
|
9458
9651
|
String(now.getMonth() + 1).padStart(2, "0"),
|
|
9459
9652
|
String(now.getDate()).padStart(2, "0")
|
|
@@ -9494,18 +9687,18 @@ var StreamerServer = class {
|
|
|
9494
9687
|
this.sessionStore.listManaged().filter((s) => s.id !== sessionId && s.boundConversationId != null).map((s) => s.boundConversationId)
|
|
9495
9688
|
);
|
|
9496
9689
|
for (const root of this.codexRoots) {
|
|
9497
|
-
const sessionsDir =
|
|
9690
|
+
const sessionsDir = join18(root, dateDir);
|
|
9498
9691
|
if (!existsSync10(sessionsDir)) continue;
|
|
9499
9692
|
let candidateFiles;
|
|
9500
9693
|
try {
|
|
9501
|
-
candidateFiles =
|
|
9694
|
+
candidateFiles = readdirSync6(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
9502
9695
|
} catch {
|
|
9503
9696
|
continue;
|
|
9504
9697
|
}
|
|
9505
9698
|
const nowMs = Date.now();
|
|
9506
|
-
const recentCandidates = candidateFiles.map((f) => ({ f, mtime:
|
|
9699
|
+
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: statSync9(join18(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
|
|
9507
9700
|
for (const { f } of recentCandidates) {
|
|
9508
|
-
const candidatePath =
|
|
9701
|
+
const candidatePath = join18(sessionsDir, f);
|
|
9509
9702
|
const match = matchesProjectPath(candidatePath);
|
|
9510
9703
|
if (!match) continue;
|
|
9511
9704
|
if (boundElsewhere.has(match.id)) continue;
|