@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/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 import_fs17 = require("fs");
2728
+ var import_fs18 = require("fs");
2729
2729
  var import_promises7 = require("fs/promises");
2730
2730
  var import_http = require("http");
2731
- var import_os8 = require("os");
2732
- var import_path17 = require("path");
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 import_fs16 = require("fs");
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, import_fs16.statSync)(input.jsonlPath).mtimeMs;
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 import_path16 = require("path");
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, import_path16.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
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, import_path16.join)(dir, `${Date.now()}-${id}-${safeName}`);
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, import_path17.join)((0, import_os8.homedir)(), ".codex", "sessions")];
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, import_path17.join)((0, import_os8.homedir)(), ".threadbase", "cache");
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, import_fs17.statSync)(filePath),
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, import_path17.join)((0, import_path17.dirname)(this.cacheDir), "conversations");
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, import_path17.join)(this.cacheDir, "cache.db"),
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, import_fs17.existsSync)(dir)) continue;
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 (bustCache && this.cache) {
7768
- const scanner2 = await this.withWarmup("conversation_refresh", () => this.rescanForRefresh());
7769
- const metas2 = [...scanner2.getMetadataCache().values()];
7770
- try {
7771
- this.cache.upsertFromScannerMeta(metas2);
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, import_path17.join)(p.configDir, "projects"));
8234
+ return this.scanProfiles.filter((p) => p.enabled).map((p) => (0, import_path18.join)(p.configDir, "projects"));
8042
8235
  }
8043
- return [(0, import_path17.join)((0, import_os8.homedir)(), ".claude", "projects")];
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, import_fs17.existsSync)(projectsDir)) continue;
8049
- for (const dir of (0, import_fs17.readdirSync)(projectsDir)) {
8050
- const fp = (0, import_path17.join)(projectsDir, dir, filename);
8051
- if ((0, import_fs17.existsSync)(fp)) return fp;
8052
- const projectDir = (0, import_path17.join)(projectsDir, dir);
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, import_fs17.readdirSync)(projectDir)) {
8055
- const subagentPath = (0, import_path17.join)(projectDir, sub, "subagents", filename);
8056
- if ((0, import_fs17.existsSync)(subagentPath)) return subagentPath;
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, import_fs17.createReadStream)(filePath), crlfDelay: Infinity });
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, import_fs17.statSync)(filePath).mtimeMs;
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, import_fs17.statSync)(conv.filePath).mtimeMs;
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, import_fs17.existsSync)(session.projectPath)) {
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, import_path17.basename)(jsonlCwd);
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, import_path17.join)((0, import_path17.dirname)(this.cacheDir), "conversations") : "",
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, import_fs17.readFileSync)(filePath, "utf8");
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, import_path17.join)((0, import_os8.homedir)(), ".claude", "projects", encoded);
9610
+ const projectsDir = (0, import_path18.join)((0, import_os9.homedir)(), ".claude", "projects", encoded);
9418
9611
  const expectedFile = `${sessionId}.jsonl`;
9419
- const filePath = (0, import_path17.join)(projectsDir, expectedFile);
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, import_fs17.existsSync)(filePath) ? filePath : null;
9438
- if (!resolvedFilePath && (0, import_fs17.existsSync)(projectsDir)) {
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, import_fs17.readdirSync)(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: (0, import_fs17.statSync)((0, import_path17.join)(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
9442
- ({ f }) => (0, import_path17.basename)(f, ".jsonl") === sessionId || this.readFirstLineSessionId((0, import_path17.join)(projectsDir, f)) === sessionId
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, import_path17.join)(projectsDir, match.f);
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, import_fs17.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
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, import_fs17.watch)(projectsDir, tryWire);
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, import_path17.join)(
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, import_fs17.readFileSync)(candidatePath, "utf8").split("\n", 1)[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, import_path17.join)(root, dateDir);
9534
- if (!(0, import_fs17.existsSync)(sessionsDir)) continue;
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, import_fs17.readdirSync)(sessionsDir).filter((f) => f.endsWith(".jsonl"));
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, import_fs17.statSync)((0, import_path17.join)(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
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, import_path17.join)(sessionsDir, f);
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, import_fs17.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
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, import_fs17.existsSync)(cwd)) return { resumable: true };
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,