@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.cjs
CHANGED
|
@@ -2725,11 +2725,11 @@ var import_node_ws = require("@hono/node-ws");
|
|
|
2725
2725
|
var import_client = require("@temporalio/client");
|
|
2726
2726
|
var import_scanner3 = require("@threadbase-sh/scanner");
|
|
2727
2727
|
var import_events = require("events");
|
|
2728
|
-
var
|
|
2728
|
+
var import_fs18 = require("fs");
|
|
2729
2729
|
var import_promises7 = require("fs/promises");
|
|
2730
2730
|
var import_http = require("http");
|
|
2731
|
-
var
|
|
2732
|
-
var
|
|
2731
|
+
var import_os9 = require("os");
|
|
2732
|
+
var import_path18 = require("path");
|
|
2733
2733
|
var import_readline = require("readline");
|
|
2734
2734
|
|
|
2735
2735
|
// node_modules/nanoid/index.js
|
|
@@ -5456,6 +5456,14 @@ function seal(plaintext, recipientPublicKeyBase64) {
|
|
|
5456
5456
|
};
|
|
5457
5457
|
}
|
|
5458
5458
|
|
|
5459
|
+
// src/services/cache/cacheMetadata.ts
|
|
5460
|
+
function getCacheMetadata(repo, key) {
|
|
5461
|
+
return repo.getCacheMetadata(key);
|
|
5462
|
+
}
|
|
5463
|
+
function setCacheMetadata(repo, key, value) {
|
|
5464
|
+
repo.setCacheMetadata(key, value);
|
|
5465
|
+
}
|
|
5466
|
+
|
|
5459
5467
|
// src/services/cache-integrity/cacheIntegrityMonitor.ts
|
|
5460
5468
|
var import_crypto8 = require("crypto");
|
|
5461
5469
|
var import_fs13 = require("fs");
|
|
@@ -5996,6 +6004,140 @@ function pruneAgentConversations(cache) {
|
|
|
5996
6004
|
return { scanned: rows.length, pruned, missing };
|
|
5997
6005
|
}
|
|
5998
6006
|
|
|
6007
|
+
// src/utils/dates.ts
|
|
6008
|
+
var import_date_fns = require("date-fns");
|
|
6009
|
+
function parseIsoDateOrNull(value) {
|
|
6010
|
+
if (!value) return null;
|
|
6011
|
+
const parsed = (0, import_date_fns.parseISO)(value);
|
|
6012
|
+
return (0, import_date_fns.isValid)(parsed) ? parsed : null;
|
|
6013
|
+
}
|
|
6014
|
+
function compareIsoDesc(a, b) {
|
|
6015
|
+
const dateA = parseIsoDateOrNull(a);
|
|
6016
|
+
const dateB = parseIsoDateOrNull(b);
|
|
6017
|
+
if (!dateA && !dateB) return 0;
|
|
6018
|
+
if (!dateA) return 1;
|
|
6019
|
+
if (!dateB) return -1;
|
|
6020
|
+
return (0, import_date_fns.compareDesc)(dateA, dateB);
|
|
6021
|
+
}
|
|
6022
|
+
|
|
6023
|
+
// src/services/projects/ensureProjectsForConversations.ts
|
|
6024
|
+
function ensureProjectsForConversations(repo, conversations) {
|
|
6025
|
+
const conversationsByPath = /* @__PURE__ */ new Map();
|
|
6026
|
+
for (const conversation of conversations) {
|
|
6027
|
+
if (!conversation.projectPath) continue;
|
|
6028
|
+
const canonical = canonicalizeProjectPath(conversation.projectPath);
|
|
6029
|
+
if (!canonical) continue;
|
|
6030
|
+
const existing = conversationsByPath.get(canonical) ?? [];
|
|
6031
|
+
existing.push(conversation);
|
|
6032
|
+
conversationsByPath.set(canonical, existing);
|
|
6033
|
+
}
|
|
6034
|
+
const pathToProjectId = /* @__PURE__ */ new Map();
|
|
6035
|
+
for (const [path, projectConversations] of conversationsByPath) {
|
|
6036
|
+
const latest = pickLatestConversation(projectConversations);
|
|
6037
|
+
const project = repo.upsertProjectByPath(path, {
|
|
6038
|
+
lastConversationId: latest?.id ?? null,
|
|
6039
|
+
lastConversationCreatedAt: latest?.createdAt ?? null,
|
|
6040
|
+
latestMessageAt: latest?.latestMessageAt ?? null
|
|
6041
|
+
});
|
|
6042
|
+
pathToProjectId.set(path, project.id);
|
|
6043
|
+
}
|
|
6044
|
+
return pathToProjectId;
|
|
6045
|
+
}
|
|
6046
|
+
function pickLatestConversation(conversations) {
|
|
6047
|
+
if (conversations.length === 0) return void 0;
|
|
6048
|
+
return [...conversations].sort((a, b) => {
|
|
6049
|
+
const cmp = compareIsoDesc(a.latestMessageAt ?? null, b.latestMessageAt ?? null);
|
|
6050
|
+
if (cmp !== 0) return cmp;
|
|
6051
|
+
return compareIsoDesc(a.createdAt ?? null, b.createdAt ?? null);
|
|
6052
|
+
})[0];
|
|
6053
|
+
}
|
|
6054
|
+
|
|
6055
|
+
// src/services/conversations/refreshConversationCache.ts
|
|
6056
|
+
function refreshConversationCache(deps) {
|
|
6057
|
+
const { projectsRepo, conversationsRepo, cacheMetadataRepo } = deps;
|
|
6058
|
+
const conversations = conversationsRepo.listConversationsForProjectBackfill();
|
|
6059
|
+
const pathToProjectId = ensureProjectsForConversations(
|
|
6060
|
+
projectsRepo,
|
|
6061
|
+
conversations.map((c) => ({
|
|
6062
|
+
id: c.id,
|
|
6063
|
+
projectPath: c.projectPath,
|
|
6064
|
+
latestMessageAt: c.lastActivity ?? null,
|
|
6065
|
+
createdAt: c.lastActivity ?? null
|
|
6066
|
+
}))
|
|
6067
|
+
);
|
|
6068
|
+
let conversationsBackfilled = 0;
|
|
6069
|
+
for (const conversation of conversations) {
|
|
6070
|
+
if (!conversation.projectPath) continue;
|
|
6071
|
+
if (conversation.projectId) continue;
|
|
6072
|
+
const projectId = pathToProjectId.get(canonicalizeProjectPath(conversation.projectPath));
|
|
6073
|
+
if (!projectId) continue;
|
|
6074
|
+
conversationsRepo.updateConversationProjectId({
|
|
6075
|
+
conversationId: conversation.id,
|
|
6076
|
+
projectId
|
|
6077
|
+
});
|
|
6078
|
+
conversationsBackfilled += 1;
|
|
6079
|
+
}
|
|
6080
|
+
const latest = conversationsRepo.getLatestConversation();
|
|
6081
|
+
if (latest) {
|
|
6082
|
+
setCacheMetadata(cacheMetadataRepo, "last_conversation_id", latest.id);
|
|
6083
|
+
if (latest.lastActivity) {
|
|
6084
|
+
setCacheMetadata(cacheMetadataRepo, "last_conversation_created_at", latest.lastActivity);
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
setCacheMetadata(cacheMetadataRepo, "conversations_last_indexed_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
6088
|
+
return {
|
|
6089
|
+
projectsTouched: pathToProjectId.size,
|
|
6090
|
+
conversationsBackfilled,
|
|
6091
|
+
latestConversationId: latest?.id ?? null
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
// src/services/conversations/shouldRefreshProjectsFromHdd.ts
|
|
6096
|
+
var import_fs16 = require("fs");
|
|
6097
|
+
var import_os8 = require("os");
|
|
6098
|
+
var import_path16 = require("path");
|
|
6099
|
+
var DEFAULT_PROJECTS_DIR = (0, import_path16.join)((0, import_os8.homedir)(), ".claude", "projects");
|
|
6100
|
+
function maxProjectsTreeMtimeMs(projectsDir) {
|
|
6101
|
+
let maxMs;
|
|
6102
|
+
try {
|
|
6103
|
+
maxMs = (0, import_fs16.statSync)(projectsDir).mtimeMs;
|
|
6104
|
+
} catch {
|
|
6105
|
+
return null;
|
|
6106
|
+
}
|
|
6107
|
+
try {
|
|
6108
|
+
for (const ent of (0, import_fs16.readdirSync)(projectsDir, { withFileTypes: true })) {
|
|
6109
|
+
if (!ent.isDirectory()) continue;
|
|
6110
|
+
try {
|
|
6111
|
+
const childMs = (0, import_fs16.statSync)((0, import_path16.join)(projectsDir, ent.name)).mtimeMs;
|
|
6112
|
+
if (childMs > maxMs) maxMs = childMs;
|
|
6113
|
+
} catch {
|
|
6114
|
+
}
|
|
6115
|
+
}
|
|
6116
|
+
} catch {
|
|
6117
|
+
}
|
|
6118
|
+
return maxMs;
|
|
6119
|
+
}
|
|
6120
|
+
function shouldRefreshProjectsFromHdd(conversationsRepo, cacheMetadataRepo, opts = {}) {
|
|
6121
|
+
if (conversationsRepo.hasOrphanRows()) return true;
|
|
6122
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6123
|
+
if (opts.projectsDirs) {
|
|
6124
|
+
for (const d of opts.projectsDirs) dirs.add(d);
|
|
6125
|
+
}
|
|
6126
|
+
dirs.add(opts.projectsDir ?? DEFAULT_PROJECTS_DIR);
|
|
6127
|
+
let newestMs = null;
|
|
6128
|
+
for (const dir of dirs) {
|
|
6129
|
+
const ms = maxProjectsTreeMtimeMs(dir);
|
|
6130
|
+
if (ms === null) continue;
|
|
6131
|
+
if (newestMs === null || ms > newestMs) newestMs = ms;
|
|
6132
|
+
}
|
|
6133
|
+
if (newestMs === null) return false;
|
|
6134
|
+
const lastIndexedIso = getCacheMetadata(cacheMetadataRepo, "conversations_last_indexed_at");
|
|
6135
|
+
if (!lastIndexedIso) return true;
|
|
6136
|
+
const lastIndexedMs = Date.parse(lastIndexedIso);
|
|
6137
|
+
if (Number.isNaN(lastIndexedMs)) return true;
|
|
6138
|
+
return newestMs > lastIndexedMs;
|
|
6139
|
+
}
|
|
6140
|
+
|
|
5999
6141
|
// src/services/projectChats/deriveProjectChatTitle.ts
|
|
6000
6142
|
function deriveProjectChatTitle(input) {
|
|
6001
6143
|
const trimmed = input.title?.trim();
|
|
@@ -6157,7 +6299,7 @@ function resolveAnswer(pending, body) {
|
|
|
6157
6299
|
}
|
|
6158
6300
|
|
|
6159
6301
|
// src/services/sessions/conversationBusy.ts
|
|
6160
|
-
var
|
|
6302
|
+
var import_fs17 = require("fs");
|
|
6161
6303
|
var RESUME_BUSY_WINDOW_MS = 12e4;
|
|
6162
6304
|
function resolveResumeBusyWindowMs(env = process.env) {
|
|
6163
6305
|
const raw = env.THREADBASE_RESUME_BUSY_WINDOW_MS;
|
|
@@ -6174,7 +6316,7 @@ function conversationBusy(input) {
|
|
|
6174
6316
|
let lastActivityMs = null;
|
|
6175
6317
|
if (input.jsonlPath) {
|
|
6176
6318
|
try {
|
|
6177
|
-
const mtimeMs = (0,
|
|
6319
|
+
const mtimeMs = (0, import_fs17.statSync)(input.jsonlPath).mtimeMs;
|
|
6178
6320
|
const age = now - mtimeMs;
|
|
6179
6321
|
lastActivityMs = Math.max(0, age);
|
|
6180
6322
|
const isSelfEcho = input.selfPtyEndedAt != null && mtimeMs <= input.selfPtyEndedAt + SELF_ACTIVITY_SKEW_MS;
|
|
@@ -6402,7 +6544,7 @@ function discoveredToResponse(d, conversationId) {
|
|
|
6402
6544
|
var import_crypto9 = require("crypto");
|
|
6403
6545
|
var import_promises6 = require("fs/promises");
|
|
6404
6546
|
var import_heic_convert = __toESM(require("heic-convert"), 1);
|
|
6405
|
-
var
|
|
6547
|
+
var import_path17 = require("path");
|
|
6406
6548
|
var UPLOAD_DIR_NAME = ".threadbase-uploads";
|
|
6407
6549
|
var MAX_BYTES = 25 * 1024 * 1024;
|
|
6408
6550
|
var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
|
|
@@ -6435,9 +6577,9 @@ async function saveUploadFile(input) {
|
|
|
6435
6577
|
}
|
|
6436
6578
|
const id = `up_${(0, import_crypto9.randomBytes)(8).toString("hex")}`;
|
|
6437
6579
|
const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
|
|
6438
|
-
const dir = (0,
|
|
6580
|
+
const dir = (0, import_path17.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
|
|
6439
6581
|
await (0, import_promises6.mkdir)(dir, { recursive: true });
|
|
6440
|
-
const filePath = (0,
|
|
6582
|
+
const filePath = (0, import_path17.join)(dir, `${Date.now()}-${id}-${safeName}`);
|
|
6441
6583
|
await (0, import_promises6.writeFile)(filePath, buffer);
|
|
6442
6584
|
return {
|
|
6443
6585
|
id,
|
|
@@ -6547,14 +6689,6 @@ function computeConversationEtag({
|
|
|
6547
6689
|
return `"${digest}"`;
|
|
6548
6690
|
}
|
|
6549
6691
|
|
|
6550
|
-
// src/utils/dates.ts
|
|
6551
|
-
var import_date_fns = require("date-fns");
|
|
6552
|
-
function parseIsoDateOrNull(value) {
|
|
6553
|
-
if (!value) return null;
|
|
6554
|
-
const parsed = (0, import_date_fns.parseISO)(value);
|
|
6555
|
-
return (0, import_date_fns.isValid)(parsed) ? parsed : null;
|
|
6556
|
-
}
|
|
6557
|
-
|
|
6558
6692
|
// src/utils/isScannedSnapshotStale.ts
|
|
6559
6693
|
var STALENESS_TOLERANCE_MS = 1e3;
|
|
6560
6694
|
function isScannedSnapshotStale(snapshotTimestamp, fileMtimeMs) {
|
|
@@ -6855,13 +6989,13 @@ var StreamerServer = class {
|
|
|
6855
6989
|
this.disableDb = config.disableDb ?? false;
|
|
6856
6990
|
this.scannerPersistenceDisabled = config.scannerPersistent === false;
|
|
6857
6991
|
this.scanProfiles = config.scanProfiles;
|
|
6858
|
-
this.codexRoots = config.codexRoots ?? [(0,
|
|
6992
|
+
this.codexRoots = config.codexRoots ?? [(0, import_path18.join)((0, import_os9.homedir)(), ".codex", "sessions")];
|
|
6859
6993
|
this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
6860
6994
|
this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
6861
6995
|
this.defaultPermissionMode = config.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
|
|
6862
6996
|
this.defaultModel = config.defaultModel ?? "sonnet";
|
|
6863
6997
|
this.defaultEffort = config.defaultEffort ?? "low";
|
|
6864
|
-
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0,
|
|
6998
|
+
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path18.join)((0, import_os9.homedir)(), ".threadbase", "cache");
|
|
6865
6999
|
this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
|
|
6866
7000
|
this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
|
|
6867
7001
|
this.markScannerStaleDebounced = debounce(() => {
|
|
@@ -6902,7 +7036,7 @@ var StreamerServer = class {
|
|
|
6902
7036
|
const seqs = cache.extendMessageIndex(
|
|
6903
7037
|
filePath,
|
|
6904
7038
|
spans,
|
|
6905
|
-
(0,
|
|
7039
|
+
(0, import_fs18.statSync)(filePath),
|
|
6906
7040
|
readFrom,
|
|
6907
7041
|
endOffset
|
|
6908
7042
|
);
|
|
@@ -7068,7 +7202,7 @@ var StreamerServer = class {
|
|
|
7068
7202
|
temporalClient,
|
|
7069
7203
|
taskQueue: agentConfig.temporal.taskQueue
|
|
7070
7204
|
});
|
|
7071
|
-
const conversationsBaseDir = agentConfig.conversationsDir || (0,
|
|
7205
|
+
const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path18.join)((0, import_path18.dirname)(this.cacheDir), "conversations");
|
|
7072
7206
|
conversationWriter = createConversationWriter({
|
|
7073
7207
|
baseDir: conversationsBaseDir
|
|
7074
7208
|
});
|
|
@@ -7391,7 +7525,7 @@ var StreamerServer = class {
|
|
|
7391
7525
|
});
|
|
7392
7526
|
try {
|
|
7393
7527
|
this.cache = ConversationCache.open(
|
|
7394
|
-
(0,
|
|
7528
|
+
(0, import_path18.join)(this.cacheDir, "cache.db"),
|
|
7395
7529
|
this.tailSize,
|
|
7396
7530
|
void 0,
|
|
7397
7531
|
{
|
|
@@ -7436,7 +7570,7 @@ var StreamerServer = class {
|
|
|
7436
7570
|
this.fileWatcher.watchDirectory(dir);
|
|
7437
7571
|
}
|
|
7438
7572
|
for (const dir of this.codexRoots) {
|
|
7439
|
-
if (!(0,
|
|
7573
|
+
if (!(0, import_fs18.existsSync)(dir)) continue;
|
|
7440
7574
|
this.fileWatcher.watchDirectory(dir);
|
|
7441
7575
|
}
|
|
7442
7576
|
} catch (err) {
|
|
@@ -7517,6 +7651,20 @@ var StreamerServer = class {
|
|
|
7517
7651
|
count: pruned.length,
|
|
7518
7652
|
event: "cache.prune_ghosts"
|
|
7519
7653
|
});
|
|
7654
|
+
if (this.projectsRepo && this.conversationsRepo && this.cacheMetadataRepo) {
|
|
7655
|
+
refreshConversationCache({
|
|
7656
|
+
cache: this.cache,
|
|
7657
|
+
projectsRepo: this.projectsRepo,
|
|
7658
|
+
conversationsRepo: this.conversationsRepo,
|
|
7659
|
+
cacheMetadataRepo: this.cacheMetadataRepo
|
|
7660
|
+
});
|
|
7661
|
+
} else if (this.cacheMetadataRepo) {
|
|
7662
|
+
setCacheMetadata(
|
|
7663
|
+
this.cacheMetadataRepo,
|
|
7664
|
+
"conversations_last_indexed_at",
|
|
7665
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
7666
|
+
);
|
|
7667
|
+
}
|
|
7520
7668
|
}
|
|
7521
7669
|
}).catch((err) => {
|
|
7522
7670
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -7756,6 +7904,56 @@ var StreamerServer = class {
|
|
|
7756
7904
|
checkSessionInputRateLimit(sessionId) {
|
|
7757
7905
|
return this.checkRateLimit(this.sessionInputAttempts, sessionId, 500, 6e4);
|
|
7758
7906
|
}
|
|
7907
|
+
/** Project roots watched for conversation JSONLs (profiles or ~/.claude/projects). */
|
|
7908
|
+
projectsDirsForFreshnessCheck() {
|
|
7909
|
+
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
7910
|
+
return this.scanProfiles.filter((p) => p.enabled).map((p) => (0, import_path18.join)(p.configDir, "projects"));
|
|
7911
|
+
}
|
|
7912
|
+
return [(0, import_path18.join)((0, import_os9.homedir)(), ".claude", "projects")];
|
|
7913
|
+
}
|
|
7914
|
+
/**
|
|
7915
|
+
* Full-glob scan + cache upsert/delete reconcile. Used by ?refresh=1 and by
|
|
7916
|
+
* the automatic freshness path when the directory watcher marked the scanner
|
|
7917
|
+
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
7918
|
+
*/
|
|
7919
|
+
async reconcileConversationsCacheFromDisk() {
|
|
7920
|
+
if (!this.cache) return;
|
|
7921
|
+
const scanner = await this.rescanForRefresh();
|
|
7922
|
+
const metas = [...scanner.getMetadataCache().values()];
|
|
7923
|
+
try {
|
|
7924
|
+
this.cache.upsertFromScannerMeta(metas);
|
|
7925
|
+
if (!this.cacheMonitor?.pending) {
|
|
7926
|
+
this.cache.reconcileDeletions(canonicalLivePathSet(metas));
|
|
7927
|
+
}
|
|
7928
|
+
if (this.projectsRepo && this.conversationsRepo && this.cacheMetadataRepo) {
|
|
7929
|
+
refreshConversationCache({
|
|
7930
|
+
cache: this.cache,
|
|
7931
|
+
projectsRepo: this.projectsRepo,
|
|
7932
|
+
conversationsRepo: this.conversationsRepo,
|
|
7933
|
+
cacheMetadataRepo: this.cacheMetadataRepo
|
|
7934
|
+
});
|
|
7935
|
+
} else if (this.cacheMetadataRepo) {
|
|
7936
|
+
setCacheMetadata(
|
|
7937
|
+
this.cacheMetadataRepo,
|
|
7938
|
+
"conversations_last_indexed_at",
|
|
7939
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
7940
|
+
);
|
|
7941
|
+
}
|
|
7942
|
+
} catch (err) {
|
|
7943
|
+
this.log.warn(
|
|
7944
|
+
`refresh reconcile failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
7945
|
+
{ event: "conversations.reconcile_failed" }
|
|
7946
|
+
);
|
|
7947
|
+
}
|
|
7948
|
+
}
|
|
7949
|
+
shouldAutoReconcileConversationList() {
|
|
7950
|
+
if (!this.cache) return false;
|
|
7951
|
+
if (this.scannerStale) return true;
|
|
7952
|
+
if (!this.conversationsRepo || !this.cacheMetadataRepo) return false;
|
|
7953
|
+
return shouldRefreshProjectsFromHdd(this.conversationsRepo, this.cacheMetadataRepo, {
|
|
7954
|
+
projectsDirs: this.projectsDirsForFreshnessCheck()
|
|
7955
|
+
});
|
|
7956
|
+
}
|
|
7759
7957
|
async handleListConversations(url, res) {
|
|
7760
7958
|
if (this.rejectIfWarmingUp(res)) return;
|
|
7761
7959
|
const limit = intParam(url, "limit", 50);
|
|
@@ -7764,19 +7962,14 @@ var StreamerServer = class {
|
|
|
7764
7962
|
const project = url.searchParams.get("project") ?? void 0;
|
|
7765
7963
|
const providerFilter = url.searchParams.get("provider") ?? void 0;
|
|
7766
7964
|
const bustCache = url.searchParams.get("refresh") === "1";
|
|
7767
|
-
if (
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
if (!this.cacheMonitor?.pending) {
|
|
7773
|
-
this.cache.reconcileDeletions(canonicalLivePathSet(metas2));
|
|
7774
|
-
}
|
|
7775
|
-
} catch (err) {
|
|
7776
|
-
this.log.warn(
|
|
7777
|
-
`refresh reconcile failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
7778
|
-
{ event: "conversations.reconcile_failed" }
|
|
7965
|
+
if (this.cache && (bustCache || this.shouldAutoReconcileConversationList())) {
|
|
7966
|
+
if (bustCache) {
|
|
7967
|
+
await this.withWarmup(
|
|
7968
|
+
"conversation_refresh",
|
|
7969
|
+
() => this.reconcileConversationsCacheFromDisk()
|
|
7779
7970
|
);
|
|
7971
|
+
} else {
|
|
7972
|
+
await this.reconcileConversationsCacheFromDisk();
|
|
7780
7973
|
}
|
|
7781
7974
|
}
|
|
7782
7975
|
if (this.cache) {
|
|
@@ -8038,22 +8231,22 @@ var StreamerServer = class {
|
|
|
8038
8231
|
*/
|
|
8039
8232
|
projectsDirs() {
|
|
8040
8233
|
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
8041
|
-
return this.scanProfiles.filter((p) => p.enabled).map((p) => (0,
|
|
8234
|
+
return this.scanProfiles.filter((p) => p.enabled).map((p) => (0, import_path18.join)(p.configDir, "projects"));
|
|
8042
8235
|
}
|
|
8043
|
-
return [(0,
|
|
8236
|
+
return [(0, import_path18.join)((0, import_os9.homedir)(), ".claude", "projects")];
|
|
8044
8237
|
}
|
|
8045
8238
|
findJsonlPath(uuid) {
|
|
8046
8239
|
const filename = `${uuid}.jsonl`;
|
|
8047
8240
|
for (const projectsDir of this.projectsDirs()) {
|
|
8048
|
-
if (!(0,
|
|
8049
|
-
for (const dir of (0,
|
|
8050
|
-
const fp = (0,
|
|
8051
|
-
if ((0,
|
|
8052
|
-
const projectDir = (0,
|
|
8241
|
+
if (!(0, import_fs18.existsSync)(projectsDir)) continue;
|
|
8242
|
+
for (const dir of (0, import_fs18.readdirSync)(projectsDir)) {
|
|
8243
|
+
const fp = (0, import_path18.join)(projectsDir, dir, filename);
|
|
8244
|
+
if ((0, import_fs18.existsSync)(fp)) return fp;
|
|
8245
|
+
const projectDir = (0, import_path18.join)(projectsDir, dir);
|
|
8053
8246
|
try {
|
|
8054
|
-
for (const sub of (0,
|
|
8055
|
-
const subagentPath = (0,
|
|
8056
|
-
if ((0,
|
|
8247
|
+
for (const sub of (0, import_fs18.readdirSync)(projectDir)) {
|
|
8248
|
+
const subagentPath = (0, import_path18.join)(projectDir, sub, "subagents", filename);
|
|
8249
|
+
if ((0, import_fs18.existsSync)(subagentPath)) return subagentPath;
|
|
8057
8250
|
}
|
|
8058
8251
|
} catch {
|
|
8059
8252
|
}
|
|
@@ -8063,7 +8256,7 @@ var StreamerServer = class {
|
|
|
8063
8256
|
}
|
|
8064
8257
|
async readCwdFromJsonl(filePath) {
|
|
8065
8258
|
return new Promise((resolve2) => {
|
|
8066
|
-
const rl = (0, import_readline.createInterface)({ input: (0,
|
|
8259
|
+
const rl = (0, import_readline.createInterface)({ input: (0, import_fs18.createReadStream)(filePath), crlfDelay: Infinity });
|
|
8067
8260
|
let found = false;
|
|
8068
8261
|
rl.on("line", (line) => {
|
|
8069
8262
|
if (found) return;
|
|
@@ -8153,7 +8346,7 @@ var StreamerServer = class {
|
|
|
8153
8346
|
if (this.isManagedTailPath(key)) return;
|
|
8154
8347
|
let mtimeMs;
|
|
8155
8348
|
try {
|
|
8156
|
-
mtimeMs = (0,
|
|
8349
|
+
mtimeMs = (0, import_fs18.statSync)(filePath).mtimeMs;
|
|
8157
8350
|
} catch {
|
|
8158
8351
|
return;
|
|
8159
8352
|
}
|
|
@@ -8335,7 +8528,7 @@ var StreamerServer = class {
|
|
|
8335
8528
|
if (!conv.filePath) return false;
|
|
8336
8529
|
let mtimeMs = null;
|
|
8337
8530
|
try {
|
|
8338
|
-
mtimeMs = (0,
|
|
8531
|
+
mtimeMs = (0, import_fs18.statSync)(conv.filePath).mtimeMs;
|
|
8339
8532
|
} catch {
|
|
8340
8533
|
return false;
|
|
8341
8534
|
}
|
|
@@ -8708,7 +8901,7 @@ var StreamerServer = class {
|
|
|
8708
8901
|
if (this.rejectIfWarmingUp(res)) return;
|
|
8709
8902
|
const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
|
|
8710
8903
|
if (session) {
|
|
8711
|
-
if (!(0,
|
|
8904
|
+
if (!(0, import_fs18.existsSync)(session.projectPath)) {
|
|
8712
8905
|
session.failureReason = `Project directory not found: ${session.projectPath}`;
|
|
8713
8906
|
}
|
|
8714
8907
|
if (this.ptyManager.hasSession(sessionId)) {
|
|
@@ -9179,7 +9372,7 @@ var StreamerServer = class {
|
|
|
9179
9372
|
const jsonlCwd = jsonlPath ? await this.readCwdFromJsonl(jsonlPath) : null;
|
|
9180
9373
|
if (jsonlCwd) {
|
|
9181
9374
|
projectPath = jsonlCwd;
|
|
9182
|
-
projectName = projectName || (0,
|
|
9375
|
+
projectName = projectName || (0, import_path18.basename)(jsonlCwd);
|
|
9183
9376
|
}
|
|
9184
9377
|
}
|
|
9185
9378
|
if (!projectPath) {
|
|
@@ -9255,7 +9448,7 @@ var StreamerServer = class {
|
|
|
9255
9448
|
sessionStore: this.sessionStore,
|
|
9256
9449
|
// biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
|
|
9257
9450
|
agentClient: this.agentClient,
|
|
9258
|
-
conversationsDir: this.cacheDir ? (0,
|
|
9451
|
+
conversationsDir: this.cacheDir ? (0, import_path18.join)((0, import_path18.dirname)(this.cacheDir), "conversations") : "",
|
|
9259
9452
|
agentConfig: this.agentConfig
|
|
9260
9453
|
});
|
|
9261
9454
|
json(res, result.status, result.body);
|
|
@@ -9399,7 +9592,7 @@ var StreamerServer = class {
|
|
|
9399
9592
|
// file isn't slurped in full.
|
|
9400
9593
|
readFirstLineSessionId(filePath) {
|
|
9401
9594
|
try {
|
|
9402
|
-
const content = (0,
|
|
9595
|
+
const content = (0, import_fs18.readFileSync)(filePath, "utf8");
|
|
9403
9596
|
const nl = content.indexOf("\n");
|
|
9404
9597
|
const firstLine = nl === -1 ? content : content.slice(0, nl);
|
|
9405
9598
|
if (!firstLine.trim()) return null;
|
|
@@ -9414,9 +9607,9 @@ var StreamerServer = class {
|
|
|
9414
9607
|
// was passed to Claude via --session-id so the filename matches from the start.
|
|
9415
9608
|
watchForJsonl(sessionId, projectPath) {
|
|
9416
9609
|
const encoded = projectPath.replace(/[/\\:.]/g, "-");
|
|
9417
|
-
const projectsDir = (0,
|
|
9610
|
+
const projectsDir = (0, import_path18.join)((0, import_os9.homedir)(), ".claude", "projects", encoded);
|
|
9418
9611
|
const expectedFile = `${sessionId}.jsonl`;
|
|
9419
|
-
const filePath = (0,
|
|
9612
|
+
const filePath = (0, import_path18.join)(projectsDir, expectedFile);
|
|
9420
9613
|
const deadline = Date.now() + 12e4;
|
|
9421
9614
|
let watcher = null;
|
|
9422
9615
|
const cleanup = () => {
|
|
@@ -9434,14 +9627,14 @@ var StreamerServer = class {
|
|
|
9434
9627
|
cleanup();
|
|
9435
9628
|
return;
|
|
9436
9629
|
}
|
|
9437
|
-
let resolvedFilePath = (0,
|
|
9438
|
-
if (!resolvedFilePath && (0,
|
|
9630
|
+
let resolvedFilePath = (0, import_fs18.existsSync)(filePath) ? filePath : null;
|
|
9631
|
+
if (!resolvedFilePath && (0, import_fs18.existsSync)(projectsDir)) {
|
|
9439
9632
|
try {
|
|
9440
9633
|
const now = Date.now();
|
|
9441
|
-
const match = (0,
|
|
9442
|
-
({ f }) => (0,
|
|
9634
|
+
const match = (0, import_fs18.readdirSync)(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: (0, import_fs18.statSync)((0, import_path18.join)(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
|
|
9635
|
+
({ f }) => (0, import_path18.basename)(f, ".jsonl") === sessionId || this.readFirstLineSessionId((0, import_path18.join)(projectsDir, f)) === sessionId
|
|
9443
9636
|
).sort((a, b) => b.mtime - a.mtime)[0];
|
|
9444
|
-
if (match) resolvedFilePath = (0,
|
|
9637
|
+
if (match) resolvedFilePath = (0, import_path18.join)(projectsDir, match.f);
|
|
9445
9638
|
} catch {
|
|
9446
9639
|
}
|
|
9447
9640
|
}
|
|
@@ -9449,7 +9642,7 @@ var StreamerServer = class {
|
|
|
9449
9642
|
cleanup();
|
|
9450
9643
|
this.sessionFileMap.set(sessionId, resolvedFilePath);
|
|
9451
9644
|
try {
|
|
9452
|
-
const existing = (0,
|
|
9645
|
+
const existing = (0, import_fs18.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
|
|
9453
9646
|
if (existing.length > 0) {
|
|
9454
9647
|
this.broadcastConversationLines(sessionId, existing);
|
|
9455
9648
|
}
|
|
@@ -9473,7 +9666,7 @@ var StreamerServer = class {
|
|
|
9473
9666
|
if (this.sessionFileMap.has(sessionId)) return;
|
|
9474
9667
|
try {
|
|
9475
9668
|
require("fs").mkdirSync(projectsDir, { recursive: true });
|
|
9476
|
-
watcher = (0,
|
|
9669
|
+
watcher = (0, import_fs18.watch)(projectsDir, tryWire);
|
|
9477
9670
|
watcher.on("error", cleanup);
|
|
9478
9671
|
} catch {
|
|
9479
9672
|
}
|
|
@@ -9489,7 +9682,7 @@ var StreamerServer = class {
|
|
|
9489
9682
|
watchForCodexRollout(sessionId, projectPath) {
|
|
9490
9683
|
const deadline = Date.now() + 12e4;
|
|
9491
9684
|
const now = /* @__PURE__ */ new Date();
|
|
9492
|
-
const dateDir = (0,
|
|
9685
|
+
const dateDir = (0, import_path18.join)(
|
|
9493
9686
|
String(now.getFullYear()),
|
|
9494
9687
|
String(now.getMonth() + 1).padStart(2, "0"),
|
|
9495
9688
|
String(now.getDate()).padStart(2, "0")
|
|
@@ -9502,7 +9695,7 @@ var StreamerServer = class {
|
|
|
9502
9695
|
};
|
|
9503
9696
|
const matchesProjectPath = (candidatePath) => {
|
|
9504
9697
|
try {
|
|
9505
|
-
const firstLine = (0,
|
|
9698
|
+
const firstLine = (0, import_fs18.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
|
|
9506
9699
|
if (!firstLine) return null;
|
|
9507
9700
|
const parsed = JSON.parse(firstLine);
|
|
9508
9701
|
if (parsed?.type !== "session_meta") return null;
|
|
@@ -9530,18 +9723,18 @@ var StreamerServer = class {
|
|
|
9530
9723
|
this.sessionStore.listManaged().filter((s) => s.id !== sessionId && s.boundConversationId != null).map((s) => s.boundConversationId)
|
|
9531
9724
|
);
|
|
9532
9725
|
for (const root of this.codexRoots) {
|
|
9533
|
-
const sessionsDir = (0,
|
|
9534
|
-
if (!(0,
|
|
9726
|
+
const sessionsDir = (0, import_path18.join)(root, dateDir);
|
|
9727
|
+
if (!(0, import_fs18.existsSync)(sessionsDir)) continue;
|
|
9535
9728
|
let candidateFiles;
|
|
9536
9729
|
try {
|
|
9537
|
-
candidateFiles = (0,
|
|
9730
|
+
candidateFiles = (0, import_fs18.readdirSync)(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
9538
9731
|
} catch {
|
|
9539
9732
|
continue;
|
|
9540
9733
|
}
|
|
9541
9734
|
const nowMs = Date.now();
|
|
9542
|
-
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: (0,
|
|
9735
|
+
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: (0, import_fs18.statSync)((0, import_path18.join)(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
|
|
9543
9736
|
for (const { f } of recentCandidates) {
|
|
9544
|
-
const candidatePath = (0,
|
|
9737
|
+
const candidatePath = (0, import_path18.join)(sessionsDir, f);
|
|
9545
9738
|
const match = matchesProjectPath(candidatePath);
|
|
9546
9739
|
if (!match) continue;
|
|
9547
9740
|
if (boundElsewhere.has(match.id)) continue;
|
|
@@ -9551,7 +9744,7 @@ var StreamerServer = class {
|
|
|
9551
9744
|
this.sessionFileMap.set(sessionId, candidatePath);
|
|
9552
9745
|
this.fileWatcher.watch(candidatePath);
|
|
9553
9746
|
try {
|
|
9554
|
-
const existing = (0,
|
|
9747
|
+
const existing = (0, import_fs18.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
|
|
9555
9748
|
if (existing.length > 0) {
|
|
9556
9749
|
this.broadcastConversationLines(sessionId, existing);
|
|
9557
9750
|
}
|
|
@@ -9685,7 +9878,7 @@ async function waitForProcessExit(pid, timeoutMs, pollMs = ADOPT_KILL_POLL_MS) {
|
|
|
9685
9878
|
}
|
|
9686
9879
|
function classifyResumability(cwd) {
|
|
9687
9880
|
if (!cwd) return { resumable: true };
|
|
9688
|
-
if ((0,
|
|
9881
|
+
if ((0, import_fs18.existsSync)(cwd)) return { resumable: true };
|
|
9689
9882
|
const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
|
|
9690
9883
|
return {
|
|
9691
9884
|
resumable: false,
|