@threadbase-sh/streamer 1.54.2 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3982,13 +3982,13 @@ async function readGitBranch(dir) {
3982
3982
  // src/server.ts
3983
3983
  var import_node_ws = require("@hono/node-ws");
3984
3984
  var import_client = require("@temporalio/client");
3985
- var import_crypto11 = require("crypto");
3985
+ var import_crypto12 = require("crypto");
3986
3986
  var import_events = require("events");
3987
- var import_fs27 = require("fs");
3987
+ var import_fs28 = require("fs");
3988
3988
  var import_promises7 = require("fs/promises");
3989
3989
  var import_http = require("http");
3990
- var import_os13 = require("os");
3991
- var import_path23 = require("path");
3990
+ var import_os14 = require("os");
3991
+ var import_path24 = require("path");
3992
3992
 
3993
3993
  // src/api/app.ts
3994
3994
  var import_hono18 = require("hono");
@@ -5160,7 +5160,7 @@ function createLogsRoutes() {
5160
5160
  var import_node_child_process = require("child_process");
5161
5161
  var import_node_crypto3 = require("crypto");
5162
5162
  var import_hono11 = require("hono");
5163
- var import_os6 = require("os");
5163
+ var import_os7 = require("os");
5164
5164
 
5165
5165
  // src/config/update-config.ts
5166
5166
  var import_node_fs4 = require("fs");
@@ -5452,6 +5452,66 @@ var PushRepository = class {
5452
5452
  }
5453
5453
  };
5454
5454
 
5455
+ // src/server-identity.ts
5456
+ var import_crypto5 = require("crypto");
5457
+ var import_fs8 = require("fs");
5458
+ var import_os6 = require("os");
5459
+ var import_path9 = require("path");
5460
+ var IDENTITY_FILE_VERSION = 1;
5461
+ function serverIdentityKeyPath() {
5462
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path9.join)((0, import_os6.homedir)(), ".threadbase");
5463
+ return (0, import_path9.join)(dir, "keys", "server-identity.key");
5464
+ }
5465
+ function loadOrCreateServerIdentity() {
5466
+ const path = serverIdentityKeyPath();
5467
+ let raw;
5468
+ try {
5469
+ raw = (0, import_fs8.readFileSync)(path, "utf-8");
5470
+ } catch (err) {
5471
+ if (err.code !== "ENOENT") throw err;
5472
+ return generateIdentity(path);
5473
+ }
5474
+ let privateKey;
5475
+ try {
5476
+ privateKey = (0, import_crypto5.createPrivateKey)({ key: JSON.parse(raw).key, format: "jwk" });
5477
+ } catch {
5478
+ throw new Error(
5479
+ `Server identity key at ${path} could not be read. Refusing to generate a new one \u2014 that would invalidate every paired device. Repair or delete the file deliberately.`
5480
+ );
5481
+ }
5482
+ return { publicKey: publicKeyOf(privateKey), privateKey };
5483
+ }
5484
+ function serverIdentityPublicKey() {
5485
+ return loadOrCreateServerIdentity().publicKey;
5486
+ }
5487
+ function generateIdentity(path) {
5488
+ const { privateKey } = (0, import_crypto5.generateKeyPairSync)("x25519");
5489
+ const file = {
5490
+ v: IDENTITY_FILE_VERSION,
5491
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5492
+ key: privateKey.export({ format: "jwk" })
5493
+ };
5494
+ (0, import_fs8.mkdirSync)((0, import_path9.dirname)(path), { recursive: true, mode: 448 });
5495
+ const tmp = `${path}.tmp`;
5496
+ (0, import_fs8.writeFileSync)(tmp, `${JSON.stringify(file)}
5497
+ `, { encoding: "utf-8", mode: 384 });
5498
+ (0, import_fs8.chmodSync)(tmp, 384);
5499
+ (0, import_fs8.renameSync)(tmp, path);
5500
+ const publicKey = publicKeyOf(privateKey);
5501
+ getLogger("identity").info(`Generated server identity key ${publicKey}`, {
5502
+ event: "identity.key_generated",
5503
+ path
5504
+ });
5505
+ return { publicKey, privateKey };
5506
+ }
5507
+ function publicKeyOf(privateKey) {
5508
+ const jwk = (0, import_crypto5.createPublicKey)(privateKey).export({ format: "jwk" });
5509
+ if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
5510
+ throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
5511
+ }
5512
+ return jwk.x;
5513
+ }
5514
+
5455
5515
  // src/services/push/apnsClient.ts
5456
5516
  var import_node_crypto2 = require("crypto");
5457
5517
  var import_node_http2 = require("http2");
@@ -5660,6 +5720,21 @@ function describePushCapability(liveActivityEnabled, env = process.env) {
5660
5720
  liveActivityReason: describeMissingApnsCredentials(env) ?? "APNs credentials are set but the push token store is unavailable, so Live Activity push is disabled."
5661
5721
  };
5662
5722
  }
5723
+ var identityKeyFailureLogged = false;
5724
+ function describeServerIdentityKey() {
5725
+ try {
5726
+ return serverIdentityPublicKey();
5727
+ } catch (err) {
5728
+ if (!identityKeyFailureLogged) {
5729
+ identityKeyFailureLogged = true;
5730
+ getLogger("identity").error(
5731
+ `Server identity key unavailable, so /api/info will omit it: ${err instanceof Error ? err.message : String(err)}`,
5732
+ { event: "identity.unavailable" }
5733
+ );
5734
+ }
5735
+ return void 0;
5736
+ }
5737
+ }
5663
5738
  var clientLog = getLogger("client");
5664
5739
  var createMiscRoutes = (deps) => {
5665
5740
  const app = new import_hono11.Hono();
@@ -5667,7 +5742,7 @@ var createMiscRoutes = (deps) => {
5667
5742
  const ptyIds = deps.ptyAttachedIds();
5668
5743
  return c.json({
5669
5744
  version: getVersion(),
5670
- machineName: (0, import_os6.hostname)(),
5745
+ machineName: (0, import_os7.hostname)(),
5671
5746
  platform: process.platform,
5672
5747
  activeSessions: deps.sessionStore.list(ptyIds).filter((s) => s.status === "running").length,
5673
5748
  publicUrl: deps.publicUrl,
@@ -5693,7 +5768,13 @@ var createMiscRoutes = (deps) => {
5693
5768
  // actually send a push, so mobile can hide an affordance instead of
5694
5769
  // registering tokens nothing will ever send to. Absent on older servers,
5695
5770
  // which a client should read as "unknown", not "unavailable".
5696
- push: describePushCapability(deps.liveActivityPushEnabled())
5771
+ push: describePushCapability(deps.liveActivityPushEnabled()),
5772
+ // This server's long-term X25519 public key, base64url. The same value the
5773
+ // pair QR carries as `spk`, served here so an already-paired client can
5774
+ // learn it without re-scanning. Additive: absent means a server with no
5775
+ // readable identity key, which a client must read as "cannot verify this
5776
+ // server" — never as a reason to fail the rest of this response.
5777
+ serverIdentityKey: describeServerIdentityKey()
5697
5778
  });
5698
5779
  });
5699
5780
  app.get("/api/profiles", (c) => c.json([]));
@@ -6204,16 +6285,16 @@ var createHonoApp = (deps, upgradeWebSocket) => {
6204
6285
 
6205
6286
  // src/api/handlers/conversations.handlers.ts
6206
6287
  var import_scanner3 = require("@threadbase-sh/scanner");
6207
- var import_fs12 = require("fs");
6208
- var import_path12 = require("path");
6288
+ var import_fs13 = require("fs");
6289
+ var import_path13 = require("path");
6209
6290
  var import_readline = require("readline");
6210
6291
 
6211
6292
  // src/conversation-cache.ts
6212
6293
  var import_scanner2 = require("@threadbase-sh/scanner");
6213
6294
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
6214
- var import_fs10 = require("fs");
6295
+ var import_fs11 = require("fs");
6215
6296
  var import_promises2 = require("fs/promises");
6216
- var import_path11 = require("path");
6297
+ var import_path12 = require("path");
6217
6298
  var import_promises3 = require("timers/promises");
6218
6299
 
6219
6300
  // src/db/query-timing.ts
@@ -6285,18 +6366,18 @@ function labelStatements(statements) {
6285
6366
  }
6286
6367
 
6287
6368
  // src/db/sqlite-migrate.ts
6288
- var import_fs8 = require("fs");
6289
- var import_path9 = require("path");
6369
+ var import_fs9 = require("fs");
6370
+ var import_path10 = require("path");
6290
6371
  var import_url2 = require("url");
6291
6372
  var import_meta2 = {};
6292
6373
  function getMigrationsDir2() {
6293
6374
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
6294
- return (0, import_path9.dirname)((0, import_url2.fileURLToPath)(import_meta2.url));
6375
+ return (0, import_path10.dirname)((0, import_url2.fileURLToPath)(import_meta2.url));
6295
6376
  }
6296
6377
  return __dirname;
6297
6378
  }
6298
6379
  function resolveMigrationsDir(name = "migrations") {
6299
- return (0, import_path9.join)(getMigrationsDir2(), name);
6380
+ return (0, import_path10.join)(getMigrationsDir2(), name);
6300
6381
  }
6301
6382
  var SCHEMA_MIGRATIONS_SQL = `
6302
6383
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -6307,7 +6388,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
6307
6388
  function runSqliteMigrations(db, migrationsDir) {
6308
6389
  db.exec(SCHEMA_MIGRATIONS_SQL);
6309
6390
  const dir = migrationsDir ?? resolveMigrationsDir();
6310
- const files = (0, import_fs8.readdirSync)(dir).filter((f) => f.endsWith(".sql")).sort();
6391
+ const files = (0, import_fs9.readdirSync)(dir).filter((f) => f.endsWith(".sql")).sort();
6311
6392
  const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
6312
6393
  const appliedSet = new Set(appliedRows.map((r) => r.id));
6313
6394
  const recordApplied = db.prepare("INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)");
@@ -6318,7 +6399,7 @@ function runSqliteMigrations(db, migrationsDir) {
6318
6399
  skipped.push(file);
6319
6400
  continue;
6320
6401
  }
6321
- const sql = (0, import_fs8.readFileSync)((0, import_path9.join)(dir, file), "utf-8");
6402
+ const sql = (0, import_fs9.readFileSync)((0, import_path10.join)(dir, file), "utf-8");
6322
6403
  const tx = db.transaction(() => {
6323
6404
  db.exec(sql);
6324
6405
  recordApplied.run(file, (/* @__PURE__ */ new Date()).toISOString());
@@ -6330,7 +6411,7 @@ function runSqliteMigrations(db, migrationsDir) {
6330
6411
  }
6331
6412
 
6332
6413
  // src/services/conversations/isAgentConversation.ts
6333
- var import_fs9 = require("fs");
6414
+ var import_fs10 = require("fs");
6334
6415
  var DEFAULT_AGENT_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-cli", "claude-vscode"]);
6335
6416
  var CHUNK_BYTES = 64 * 1024;
6336
6417
  var ENTRYPOINT_PROBE = `"entrypoint":`;
@@ -6352,12 +6433,12 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
6352
6433
  if (cached3 !== void 0) return cached3;
6353
6434
  let fd;
6354
6435
  try {
6355
- fd = (0, import_fs9.openSync)(filePath, "r");
6436
+ fd = (0, import_fs10.openSync)(filePath, "r");
6356
6437
  } catch {
6357
6438
  return false;
6358
6439
  }
6359
6440
  try {
6360
- const fileSize = (0, import_fs9.statSync)(filePath).size;
6441
+ const fileSize = (0, import_fs10.statSync)(filePath).size;
6361
6442
  if (fileSize === 0) {
6362
6443
  fileDecisionCache.set(key, false);
6363
6444
  return false;
@@ -6368,7 +6449,7 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
6368
6449
  let carry = "";
6369
6450
  while (offset < fileSize) {
6370
6451
  const toRead = Math.min(CHUNK_BYTES, fileSize - offset);
6371
- const got = (0, import_fs9.readSync)(fd, buf, 0, toRead, offset);
6452
+ const got = (0, import_fs10.readSync)(fd, buf, 0, toRead, offset);
6372
6453
  if (got <= 0) break;
6373
6454
  const chunk = carry + buf.toString("utf8", 0, got);
6374
6455
  for (const marker of markers) {
@@ -6389,7 +6470,7 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
6389
6470
  } catch {
6390
6471
  return false;
6391
6472
  } finally {
6392
- (0, import_fs9.closeSync)(fd);
6473
+ (0, import_fs10.closeSync)(fd);
6393
6474
  }
6394
6475
  }
6395
6476
  function parseAgentEntrypointsEnv(raw) {
@@ -6399,12 +6480,12 @@ function parseAgentEntrypointsEnv(raw) {
6399
6480
  }
6400
6481
 
6401
6482
  // src/utils/canonicalizeFilePath.ts
6402
- var import_path10 = require("path");
6483
+ var import_path11 = require("path");
6403
6484
  function canonicalizeFilePath(filePath) {
6404
6485
  return filePath.trim().replace(/\\/g, "/");
6405
6486
  }
6406
6487
  function toNativeFilePath(filePath) {
6407
- return (0, import_path10.normalize)(filePath.trim());
6488
+ return (0, import_path11.normalize)(filePath.trim());
6408
6489
  }
6409
6490
  function canonicalLivePathSet(metas) {
6410
6491
  const live = /* @__PURE__ */ new Set();
@@ -6424,11 +6505,11 @@ function joinStatCacheByNativePath(metas, canonicalStats) {
6424
6505
  }
6425
6506
 
6426
6507
  // src/utils/fileIdentity.ts
6427
- var import_crypto5 = require("crypto");
6508
+ var import_crypto6 = require("crypto");
6428
6509
  function fileIdentity(stat3, headBytes) {
6429
6510
  if (stat3.ino && stat3.ino > 0) return `inode:${stat3.dev}:${stat3.ino}`;
6430
6511
  const head = headBytes ?? Buffer.alloc(0);
6431
- return `fp:${(0, import_crypto5.createHash)("sha1").update(head).digest("hex")}`;
6512
+ return `fp:${(0, import_crypto6.createHash)("sha1").update(head).digest("hex")}`;
6432
6513
  }
6433
6514
  function splitCompleteLines(buf, baseOffset) {
6434
6515
  const spans = [];
@@ -6995,7 +7076,7 @@ var ConversationCache = class _ConversationCache {
6995
7076
  if (!fileState) return null;
6996
7077
  let stat3;
6997
7078
  try {
6998
- stat3 = (0, import_fs10.statSync)(filePath);
7079
+ stat3 = (0, import_fs11.statSync)(filePath);
6999
7080
  } catch {
7000
7081
  return null;
7001
7082
  }
@@ -7016,17 +7097,17 @@ var ConversationCache = class _ConversationCache {
7016
7097
  );
7017
7098
  if (rows.length === 0) return { messages: [], total, fromIndex: from };
7018
7099
  const messages = [];
7019
- const fd = (0, import_fs10.openSync)(filePath, "r");
7100
+ const fd = (0, import_fs11.openSync)(filePath, "r");
7020
7101
  try {
7021
7102
  const state = (0, import_scanner2.createJsonlParseState)();
7022
7103
  for (const row of rows) {
7023
7104
  const buf = Buffer.alloc(row.byte_length);
7024
- (0, import_fs10.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
7105
+ (0, import_fs11.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
7025
7106
  const msg = (0, import_scanner2.parseJsonlLine)(buf.toString("utf-8"), state);
7026
7107
  if (msg) messages.push(msg);
7027
7108
  }
7028
7109
  } finally {
7029
- (0, import_fs10.closeSync)(fd);
7110
+ (0, import_fs11.closeSync)(fd);
7030
7111
  }
7031
7112
  return { messages, total, fromIndex: from };
7032
7113
  }
@@ -7054,14 +7135,14 @@ var ConversationCache = class _ConversationCache {
7054
7135
  isAgentFileCached(filePath) {
7055
7136
  let s;
7056
7137
  try {
7057
- s = (0, import_fs10.statSync)(filePath);
7138
+ s = (0, import_fs11.statSync)(filePath);
7058
7139
  } catch {
7059
7140
  return false;
7060
7141
  }
7061
7142
  return this.classifyAgentFile(filePath, s.mtimeMs, s.size);
7062
7143
  }
7063
7144
  static open(dbPath, tailSize = 10, migrationsDir, options) {
7064
- (0, import_fs10.mkdirSync)((0, import_path11.dirname)(dbPath), { recursive: true });
7145
+ (0, import_fs11.mkdirSync)((0, import_path12.dirname)(dbPath), { recursive: true });
7065
7146
  const db = instrumentDatabase(new import_better_sqlite3.default(dbPath));
7066
7147
  db.pragma("journal_mode = WAL");
7067
7148
  db.pragma("foreign_keys = ON");
@@ -7265,7 +7346,7 @@ var ConversationCache = class _ConversationCache {
7265
7346
  let mtimeMs = null;
7266
7347
  let fileSize = null;
7267
7348
  try {
7268
- const s = (0, import_fs10.statSync)(m.filePath);
7349
+ const s = (0, import_fs11.statSync)(m.filePath);
7269
7350
  mtimeMs = s.mtimeMs;
7270
7351
  fileSize = s.size;
7271
7352
  } catch {
@@ -7325,8 +7406,8 @@ var ConversationCache = class _ConversationCache {
7325
7406
  let fileSize;
7326
7407
  let fd;
7327
7408
  try {
7328
- fileSize = (0, import_fs10.statSync)(filePath).size;
7329
- fd = (0, import_fs10.openSync)(filePath, "r");
7409
+ fileSize = (0, import_fs11.statSync)(filePath).size;
7410
+ fd = (0, import_fs11.openSync)(filePath, "r");
7330
7411
  } catch {
7331
7412
  return false;
7332
7413
  }
@@ -7339,7 +7420,7 @@ var ConversationCache = class _ConversationCache {
7339
7420
  while (pos > 0 && lines.length < this.tailSize * 4) {
7340
7421
  const toRead = Math.min(CHUNK, pos);
7341
7422
  pos -= toRead;
7342
- (0, import_fs10.readSync)(fd, buf, 0, toRead, pos);
7423
+ (0, import_fs11.readSync)(fd, buf, 0, toRead, pos);
7343
7424
  const chunk = buf.subarray(0, toRead).toString("utf8");
7344
7425
  const combined = chunk + partial;
7345
7426
  const parts = combined.split("\n");
@@ -7350,7 +7431,7 @@ var ConversationCache = class _ConversationCache {
7350
7431
  }
7351
7432
  if (partial) lines.push(partial);
7352
7433
  } finally {
7353
- (0, import_fs10.closeSync)(fd);
7434
+ (0, import_fs11.closeSync)(fd);
7354
7435
  }
7355
7436
  const msgs = [];
7356
7437
  for (let i = 0; i < lines.length && msgs.length < this.tailSize; i++) {
@@ -7563,7 +7644,7 @@ var ConversationCache = class _ConversationCache {
7563
7644
  * `handleGetConversation` can still serve the cached tail even when the
7564
7645
  * JSONL has been deleted.
7565
7646
  */
7566
- pruneGhostFiles(exists = import_fs10.existsSync) {
7647
+ pruneGhostFiles(exists = import_fs11.existsSync) {
7567
7648
  const rows = this.stmts.allFilePaths.all();
7568
7649
  const ghosts = [];
7569
7650
  const prune = this.db.transaction((ids) => {
@@ -7618,7 +7699,7 @@ var ConversationCache = class _ConversationCache {
7618
7699
  * Returns the removed IDs.
7619
7700
  */
7620
7701
  reconcileDeletions(livePaths, opts) {
7621
- const exists = opts?.exists ?? import_fs10.existsSync;
7702
+ const exists = opts?.exists ?? import_fs11.existsSync;
7622
7703
  const rows = this.stmts.allFilePaths.all();
7623
7704
  const removed = [];
7624
7705
  const drop = this.db.transaction((ids) => {
@@ -7653,7 +7734,7 @@ var ConversationCache = class _ConversationCache {
7653
7734
  * reports drift for the CacheIntegrityMonitor to classify. `tailed` flags
7654
7735
  * rows that still have cached history (which pruneGhostFiles would keep).
7655
7736
  */
7656
- listMissingFiles(exists = import_fs10.existsSync) {
7737
+ listMissingFiles(exists = import_fs11.existsSync) {
7657
7738
  const rows = this.stmts.allFilePathsWithTitle.all();
7658
7739
  const missing = [];
7659
7740
  for (const row of rows) {
@@ -7994,10 +8075,10 @@ function createScanProgressThrottle() {
7994
8075
  }
7995
8076
 
7996
8077
  // src/api/handlers/http-helpers.ts
7997
- var import_fs11 = require("fs");
8078
+ var import_fs12 = require("fs");
7998
8079
  function classifyResumability(cwd) {
7999
8080
  if (!cwd) return { resumable: true };
8000
- if ((0, import_fs11.existsSync)(cwd)) return { resumable: true };
8081
+ if ((0, import_fs12.existsSync)(cwd)) return { resumable: true };
8001
8082
  const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
8002
8083
  return {
8003
8084
  resumable: false,
@@ -8352,15 +8433,15 @@ var ConversationHandlers = class {
8352
8433
  findJsonlPath(uuid) {
8353
8434
  const filename = `${uuid}.jsonl`;
8354
8435
  for (const projectsDir of this.scannerManager.projectsDirs()) {
8355
- if (!(0, import_fs12.existsSync)(projectsDir)) continue;
8356
- for (const dir of (0, import_fs12.readdirSync)(projectsDir)) {
8357
- const fp = (0, import_path12.join)(projectsDir, dir, filename);
8358
- if ((0, import_fs12.existsSync)(fp)) return fp;
8359
- const projectDir = (0, import_path12.join)(projectsDir, dir);
8436
+ if (!(0, import_fs13.existsSync)(projectsDir)) continue;
8437
+ for (const dir of (0, import_fs13.readdirSync)(projectsDir)) {
8438
+ const fp = (0, import_path13.join)(projectsDir, dir, filename);
8439
+ if ((0, import_fs13.existsSync)(fp)) return fp;
8440
+ const projectDir = (0, import_path13.join)(projectsDir, dir);
8360
8441
  try {
8361
- for (const sub of (0, import_fs12.readdirSync)(projectDir)) {
8362
- const subagentPath = (0, import_path12.join)(projectDir, sub, "subagents", filename);
8363
- if ((0, import_fs12.existsSync)(subagentPath)) return subagentPath;
8442
+ for (const sub of (0, import_fs13.readdirSync)(projectDir)) {
8443
+ const subagentPath = (0, import_path13.join)(projectDir, sub, "subagents", filename);
8444
+ if ((0, import_fs13.existsSync)(subagentPath)) return subagentPath;
8364
8445
  }
8365
8446
  } catch {
8366
8447
  }
@@ -8370,7 +8451,7 @@ var ConversationHandlers = class {
8370
8451
  }
8371
8452
  async readCwdFromJsonl(filePath) {
8372
8453
  return new Promise((resolve2) => {
8373
- const rl = (0, import_readline.createInterface)({ input: (0, import_fs12.createReadStream)(filePath), crlfDelay: Infinity });
8454
+ const rl = (0, import_readline.createInterface)({ input: (0, import_fs13.createReadStream)(filePath), crlfDelay: Infinity });
8374
8455
  let found = false;
8375
8456
  rl.on("line", (line) => {
8376
8457
  if (found) return;
@@ -8833,11 +8914,11 @@ var ConversationHandlers = class {
8833
8914
  };
8834
8915
 
8835
8916
  // src/api/handlers/sessions.handlers.ts
8836
- var import_fs14 = require("fs");
8837
- var import_path15 = require("path");
8917
+ var import_fs15 = require("fs");
8918
+ var import_path16 = require("path");
8838
8919
 
8839
8920
  // node_modules/nanoid/index.js
8840
- var import_crypto6 = __toESM(require("crypto"), 1);
8921
+ var import_crypto7 = __toESM(require("crypto"), 1);
8841
8922
 
8842
8923
  // node_modules/nanoid/url-alphabet/index.js
8843
8924
  var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
@@ -8851,10 +8932,10 @@ var fillPool = (bytes) => {
8851
8932
  try {
8852
8933
  if (!pool || pool.length < bytes) {
8853
8934
  pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
8854
- import_crypto6.default.randomFillSync(pool);
8935
+ import_crypto7.default.randomFillSync(pool);
8855
8936
  poolOffset = 0;
8856
8937
  } else if (poolOffset + bytes > pool.length) {
8857
- import_crypto6.default.randomFillSync(pool);
8938
+ import_crypto7.default.randomFillSync(pool);
8858
8939
  poolOffset = 0;
8859
8940
  }
8860
8941
  } catch (e) {
@@ -9093,7 +9174,7 @@ async function handleStartAgentSession(body, deps) {
9093
9174
 
9094
9175
  // src/browse.ts
9095
9176
  var import_promises4 = require("fs/promises");
9096
- var import_path13 = require("path");
9177
+ var import_path14 = require("path");
9097
9178
  var BrowsePathNotFoundError = class extends Error {
9098
9179
  constructor(message) {
9099
9180
  super(message);
@@ -9101,15 +9182,15 @@ var BrowsePathNotFoundError = class extends Error {
9101
9182
  }
9102
9183
  };
9103
9184
  async function resolveBrowsePath(browseRoot, relativePath) {
9104
- const normalizedRoot = (0, import_path13.resolve)(browseRoot);
9185
+ const normalizedRoot = (0, import_path14.resolve)(browseRoot);
9105
9186
  let sanitized;
9106
9187
  if (process.platform !== "win32" && relativePath.startsWith("/") && relativePath.length > 1 && relativePath.includes("/", 1)) {
9107
9188
  sanitized = relativePath;
9108
9189
  } else {
9109
9190
  sanitized = relativePath.replace(/^[/\\]+/, "");
9110
9191
  }
9111
- const target = sanitized ? (0, import_path13.resolve)(normalizedRoot, sanitized) : normalizedRoot;
9112
- const rootPrefix = normalizedRoot.endsWith(import_path13.sep) ? normalizedRoot : `${normalizedRoot}${import_path13.sep}`;
9192
+ const target = sanitized ? (0, import_path14.resolve)(normalizedRoot, sanitized) : normalizedRoot;
9193
+ const rootPrefix = normalizedRoot.endsWith(import_path14.sep) ? normalizedRoot : `${normalizedRoot}${import_path14.sep}`;
9113
9194
  if (!target.startsWith(rootPrefix) && target !== normalizedRoot) {
9114
9195
  throw new Error("Path outside browse root");
9115
9196
  }
@@ -9135,7 +9216,7 @@ async function createDirectory(parentAbsolutePath, name) {
9135
9216
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
9136
9217
  throw new Error("Invalid directory name");
9137
9218
  }
9138
- const target = (0, import_path13.join)(parentAbsolutePath, name);
9219
+ const target = (0, import_path14.join)(parentAbsolutePath, name);
9139
9220
  try {
9140
9221
  const s = await (0, import_promises4.stat)(target);
9141
9222
  if (s.isDirectory()) throw new Error("Directory already exists");
@@ -9298,7 +9379,7 @@ async function findRolloutOwner(rolloutPath, options = {}) {
9298
9379
  }
9299
9380
 
9300
9381
  // src/services/sessions/conversationBusy.ts
9301
- var import_fs13 = require("fs");
9382
+ var import_fs14 = require("fs");
9302
9383
 
9303
9384
  // src/utils/canonicalizeProjectPath.ts
9304
9385
  function canonicalizeProjectPath(projectPath) {
@@ -9322,7 +9403,7 @@ function conversationBusy(input) {
9322
9403
  let lastActivityMs = null;
9323
9404
  if (input.jsonlPath) {
9324
9405
  try {
9325
- const mtimeMs = (0, import_fs13.statSync)(input.jsonlPath).mtimeMs;
9406
+ const mtimeMs = (0, import_fs14.statSync)(input.jsonlPath).mtimeMs;
9326
9407
  const age = now - mtimeMs;
9327
9408
  lastActivityMs = Math.max(0, age);
9328
9409
  const isSelfEcho = input.selfPtyEndedAt != null && mtimeMs <= input.selfPtyEndedAt + SELF_ACTIVITY_SKEW_MS;
@@ -9407,10 +9488,10 @@ function readIdempotencyKey(body) {
9407
9488
  }
9408
9489
 
9409
9490
  // src/uploads.ts
9410
- var import_crypto7 = require("crypto");
9491
+ var import_crypto8 = require("crypto");
9411
9492
  var import_promises5 = require("fs/promises");
9412
9493
  var import_heic_convert = __toESM(require("heic-convert"), 1);
9413
- var import_path14 = require("path");
9494
+ var import_path15 = require("path");
9414
9495
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
9415
9496
  var MAX_BYTES = 25 * 1024 * 1024;
9416
9497
  var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
@@ -9441,11 +9522,11 @@ async function saveUploadFile(input) {
9441
9522
  mimeType = "image/jpeg";
9442
9523
  originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
9443
9524
  }
9444
- const id = `up_${(0, import_crypto7.randomBytes)(8).toString("hex")}`;
9525
+ const id = `up_${(0, import_crypto8.randomBytes)(8).toString("hex")}`;
9445
9526
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
9446
- const dir = (0, import_path14.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
9527
+ const dir = (0, import_path15.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
9447
9528
  await (0, import_promises5.mkdir)(dir, { recursive: true });
9448
- const filePath = (0, import_path14.join)(dir, `${Date.now()}-${id}-${safeName}`);
9529
+ const filePath = (0, import_path15.join)(dir, `${Date.now()}-${id}-${safeName}`);
9449
9530
  await (0, import_promises5.writeFile)(filePath, buffer);
9450
9531
  return {
9451
9532
  id,
@@ -9677,7 +9758,7 @@ var SessionHandlers = class {
9677
9758
  const reconciled = this.deps.withReconciledLifecycle([base])[0];
9678
9759
  const session = {
9679
9760
  ...base,
9680
- ...(0, import_fs14.existsSync)(base.projectPath) ? {} : { failureReason: `Project directory not found: ${base.projectPath}` },
9761
+ ...(0, import_fs15.existsSync)(base.projectPath) ? {} : { failureReason: `Project directory not found: ${base.projectPath}` },
9681
9762
  lifecycle: reconciled.lifecycle,
9682
9763
  lifecycleSource: reconciled.lifecycleSource
9683
9764
  };
@@ -10289,7 +10370,7 @@ var SessionHandlers = class {
10289
10370
  const jsonlCwd = jsonlPath ? await this.deps.readCwdFromJsonl(jsonlPath) : null;
10290
10371
  if (jsonlCwd) {
10291
10372
  projectPath = jsonlCwd;
10292
- projectName = projectName || (0, import_path15.basename)(jsonlCwd);
10373
+ projectName = projectName || (0, import_path16.basename)(jsonlCwd);
10293
10374
  }
10294
10375
  }
10295
10376
  if (!projectPath) {
@@ -10366,7 +10447,7 @@ var SessionHandlers = class {
10366
10447
  sessionStore: this.sessionStore,
10367
10448
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
10368
10449
  agentClient: this.agentClient,
10369
- conversationsDir: this.cacheDir ? (0, import_path15.join)((0, import_path15.dirname)(this.cacheDir), "conversations") : "",
10450
+ conversationsDir: this.cacheDir ? (0, import_path16.join)((0, import_path16.dirname)(this.cacheDir), "conversations") : "",
10370
10451
  agentConfig: this.agentConfig
10371
10452
  });
10372
10453
  json(res, result.status, result.body);
@@ -10721,7 +10802,7 @@ var ManagedSessionsRepository = class {
10721
10802
  };
10722
10803
 
10723
10804
  // src/db/repositories/projects.repository.ts
10724
- var import_crypto8 = require("crypto");
10805
+ var import_crypto9 = require("crypto");
10725
10806
  function rowToProject(row) {
10726
10807
  return {
10727
10808
  id: row.id,
@@ -10806,7 +10887,7 @@ var ProjectsRepository = class {
10806
10887
  });
10807
10888
  return rowToProject(this.getById.get(existing.id));
10808
10889
  }
10809
- const id = (0, import_crypto8.randomUUID)();
10890
+ const id = (0, import_crypto9.randomUUID)();
10810
10891
  this.insert.run({
10811
10892
  id,
10812
10893
  path,
@@ -10843,10 +10924,10 @@ var SessionsRepository = class {
10843
10924
 
10844
10925
  // src/db/runtime-store.ts
10845
10926
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
10846
- var import_os7 = require("os");
10847
- var import_path16 = require("path");
10927
+ var import_os8 = require("os");
10928
+ var import_path17 = require("path");
10848
10929
  function resolveRuntimeDbPath(override) {
10849
- return override ?? process.env.THREADBASE_RUNTIME_DB ?? (0, import_path16.join)(process.env.THREADBASE_CONFIG_DIR ?? (0, import_path16.join)((0, import_os7.homedir)(), ".threadbase"), "runtime.db");
10930
+ return override ?? process.env.THREADBASE_RUNTIME_DB ?? (0, import_path17.join)(process.env.THREADBASE_CONFIG_DIR ?? (0, import_path17.join)((0, import_os8.homedir)(), ".threadbase"), "runtime.db");
10850
10931
  }
10851
10932
  var RuntimeStore = class _RuntimeStore {
10852
10933
  constructor(db) {
@@ -10936,7 +11017,7 @@ var RuntimeStore = class _RuntimeStore {
10936
11017
  };
10937
11018
 
10938
11019
  // src/external-tails.ts
10939
- var import_fs15 = require("fs");
11020
+ var import_fs16 = require("fs");
10940
11021
  var EXTERNAL_TAIL_RECENCY_MS = RESUME_BUSY_WINDOW_MS;
10941
11022
  var EXTERNAL_TAIL_MAX = 32;
10942
11023
  var EXTERNAL_TAIL_IDLE_MS = 3e5;
@@ -10969,7 +11050,7 @@ var ExternalTailManager = class {
10969
11050
  if (this.isManagedTailPath(key)) return;
10970
11051
  let mtimeMs;
10971
11052
  try {
10972
- mtimeMs = (0, import_fs15.statSync)(filePath).mtimeMs;
11053
+ mtimeMs = (0, import_fs16.statSync)(filePath).mtimeMs;
10973
11054
  } catch {
10974
11055
  return;
10975
11056
  }
@@ -11106,7 +11187,7 @@ var ExternalTailManager = class {
11106
11187
  };
11107
11188
 
11108
11189
  // src/pair-store.ts
11109
- var import_crypto9 = require("crypto");
11190
+ var import_crypto10 = require("crypto");
11110
11191
  var DEFAULT_TTL_SECONDS = 180;
11111
11192
  var SWEEP_INTERVAL_MS = 6e4;
11112
11193
  var PairTokenStore = class {
@@ -11121,7 +11202,7 @@ var PairTokenStore = class {
11121
11202
  }
11122
11203
  }
11123
11204
  mint() {
11124
- const token = `pt_${(0, import_crypto9.randomBytes)(16).toString("hex")}`;
11205
+ const token = `pt_${(0, import_crypto10.randomBytes)(16).toString("hex")}`;
11125
11206
  const expiresAt = Date.now() + this.ttlMs;
11126
11207
  this.current = { token, expiresAt, used: false };
11127
11208
  return {
@@ -11243,9 +11324,9 @@ function spawnDetachedHost(socketPath, entryPoint) {
11243
11324
 
11244
11325
  // src/scanner-manager.ts
11245
11326
  var import_scanner4 = require("@threadbase-sh/scanner");
11246
- var import_fs17 = require("fs");
11247
- var import_os9 = require("os");
11248
- var import_path18 = require("path");
11327
+ var import_fs18 = require("fs");
11328
+ var import_os10 = require("os");
11329
+ var import_path19 = require("path");
11249
11330
 
11250
11331
  // src/services/cache/cacheMetadata.ts
11251
11332
  function getCacheMetadata(repo, key) {
@@ -11344,22 +11425,22 @@ function refreshConversationCache(deps) {
11344
11425
  }
11345
11426
 
11346
11427
  // src/services/conversations/shouldRefreshProjectsFromHdd.ts
11347
- var import_fs16 = require("fs");
11348
- var import_os8 = require("os");
11349
- var import_path17 = require("path");
11350
- var DEFAULT_PROJECTS_DIR = (0, import_path17.join)((0, import_os8.homedir)(), ".claude", "projects");
11428
+ var import_fs17 = require("fs");
11429
+ var import_os9 = require("os");
11430
+ var import_path18 = require("path");
11431
+ var DEFAULT_PROJECTS_DIR = (0, import_path18.join)((0, import_os9.homedir)(), ".claude", "projects");
11351
11432
  function maxProjectsTreeMtimeMs(projectsDir) {
11352
11433
  let maxMs;
11353
11434
  try {
11354
- maxMs = (0, import_fs16.statSync)(projectsDir).mtimeMs;
11435
+ maxMs = (0, import_fs17.statSync)(projectsDir).mtimeMs;
11355
11436
  } catch {
11356
11437
  return null;
11357
11438
  }
11358
11439
  try {
11359
- for (const ent of (0, import_fs16.readdirSync)(projectsDir, { withFileTypes: true })) {
11440
+ for (const ent of (0, import_fs17.readdirSync)(projectsDir, { withFileTypes: true })) {
11360
11441
  if (!ent.isDirectory()) continue;
11361
11442
  try {
11362
- const childMs = (0, import_fs16.statSync)((0, import_path17.join)(projectsDir, ent.name)).mtimeMs;
11443
+ const childMs = (0, import_fs17.statSync)((0, import_path18.join)(projectsDir, ent.name)).mtimeMs;
11363
11444
  if (childMs > maxMs) maxMs = childMs;
11364
11445
  } catch {
11365
11446
  }
@@ -11531,9 +11612,9 @@ var ScannerManager = class {
11531
11612
  projectsDirs() {
11532
11613
  const profiles = this.deps.scanProfiles;
11533
11614
  if (profiles && profiles.length > 0) {
11534
- return profiles.filter((p) => p.enabled).map((p) => (0, import_path18.join)(p.configDir, "projects"));
11615
+ return profiles.filter((p) => p.enabled).map((p) => (0, import_path19.join)(p.configDir, "projects"));
11535
11616
  }
11536
- return [(0, import_path18.join)((0, import_os9.homedir)(), ".claude", "projects")];
11617
+ return [(0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects")];
11537
11618
  }
11538
11619
  // ─── staleness ────────────────────────────────────────────────────
11539
11620
  // Drain the stale set and disarm the flag together. The caller owns the
@@ -11571,7 +11652,7 @@ var ScannerManager = class {
11571
11652
  if (!conv.filePath) return false;
11572
11653
  let mtimeMs = null;
11573
11654
  try {
11574
- mtimeMs = (0, import_fs17.statSync)(conv.filePath).mtimeMs;
11655
+ mtimeMs = (0, import_fs18.statSync)(conv.filePath).mtimeMs;
11575
11656
  } catch {
11576
11657
  return false;
11577
11658
  }
@@ -11810,27 +11891,27 @@ function seal(plaintext, recipientPublicKeyBase64) {
11810
11891
  }
11811
11892
 
11812
11893
  // src/server-wiring.ts
11813
- var import_fs19 = require("fs");
11894
+ var import_fs20 = require("fs");
11814
11895
 
11815
11896
  // src/handlers/handleListProjects.ts
11816
- var import_fs18 = require("fs");
11817
- var import_os10 = require("os");
11818
- var import_path19 = require("path");
11897
+ var import_fs19 = require("fs");
11898
+ var import_os11 = require("os");
11899
+ var import_path20 = require("path");
11819
11900
  var HEAD_BYTES = 64 * 1024;
11820
11901
  var MAX_FILES_PROBED = 3;
11821
11902
  function readRecordedCwd(dir) {
11822
11903
  let files;
11823
11904
  try {
11824
- files = (0, import_fs18.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
11905
+ files = (0, import_fs19.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
11825
11906
  } catch {
11826
11907
  return null;
11827
11908
  }
11828
11909
  for (const file of files.slice(0, MAX_FILES_PROBED)) {
11829
11910
  let fd;
11830
11911
  try {
11831
- fd = (0, import_fs18.openSync)((0, import_path19.join)(dir, file), "r");
11912
+ fd = (0, import_fs19.openSync)((0, import_path20.join)(dir, file), "r");
11832
11913
  const buf = Buffer.alloc(HEAD_BYTES);
11833
- const bytes = (0, import_fs18.readSync)(fd, buf, 0, HEAD_BYTES, 0);
11914
+ const bytes = (0, import_fs19.readSync)(fd, buf, 0, HEAD_BYTES, 0);
11834
11915
  for (const line of buf.subarray(0, bytes).toString("utf8").split("\n")) {
11835
11916
  if (!line.includes('"cwd"')) continue;
11836
11917
  try {
@@ -11841,7 +11922,7 @@ function readRecordedCwd(dir) {
11841
11922
  }
11842
11923
  } catch {
11843
11924
  } finally {
11844
- if (fd !== void 0) (0, import_fs18.closeSync)(fd);
11925
+ if (fd !== void 0) (0, import_fs19.closeSync)(fd);
11845
11926
  }
11846
11927
  }
11847
11928
  return null;
@@ -11852,14 +11933,14 @@ function decodeProjectPath(dirName) {
11852
11933
  function handleListProjects(url, res) {
11853
11934
  const limit = Math.max(1, parseInt(url.searchParams.get("limit") ?? "50", 10) || 50);
11854
11935
  const offset = Math.max(0, parseInt(url.searchParams.get("offset") ?? "0", 10) || 0);
11855
- const projectsDir = (0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects");
11936
+ const projectsDir = (0, import_path20.join)((0, import_os11.homedir)(), ".claude", "projects");
11856
11937
  let entries;
11857
11938
  try {
11858
- entries = (0, import_fs18.readdirSync)(projectsDir).map((dirName) => {
11859
- const fullPath = (0, import_path19.join)(projectsDir, dirName);
11939
+ entries = (0, import_fs19.readdirSync)(projectsDir).map((dirName) => {
11940
+ const fullPath = (0, import_path20.join)(projectsDir, dirName);
11860
11941
  let mtime = 0;
11861
11942
  try {
11862
- mtime = (0, import_fs18.statSync)(fullPath).mtimeMs;
11943
+ mtime = (0, import_fs19.statSync)(fullPath).mtimeMs;
11863
11944
  } catch {
11864
11945
  }
11865
11946
  return { dirName: String(dirName), mtime };
@@ -11871,7 +11952,7 @@ function handleListProjects(url, res) {
11871
11952
  }
11872
11953
  const total = entries.length;
11873
11954
  const page = entries.slice(offset, offset + limit).map(({ dirName }) => {
11874
- const path = readRecordedCwd((0, import_path19.join)(projectsDir, dirName)) ?? decodeProjectPath(String(dirName));
11955
+ const path = readRecordedCwd((0, import_path20.join)(projectsDir, dirName)) ?? decodeProjectPath(String(dirName));
11875
11956
  const name = path.split(/[\\/]/).filter(Boolean).pop() ?? dirName;
11876
11957
  return { name, path, dirName };
11877
11958
  });
@@ -11893,7 +11974,7 @@ function createConversationWatcherEvents(deps) {
11893
11974
  const seqs = cache.extendMessageIndex(
11894
11975
  filePath,
11895
11976
  spans,
11896
- (0, import_fs19.statSync)(filePath),
11977
+ (0, import_fs20.statSync)(filePath),
11897
11978
  readFrom,
11898
11979
  endOffset
11899
11980
  );
@@ -11940,7 +12021,7 @@ function createConversationWatcherEvents(deps) {
11940
12021
  },
11941
12022
  onConversationChanged: (filePath) => {
11942
12023
  try {
11943
- (0, import_fs19.statSync)(filePath);
12024
+ (0, import_fs20.statSync)(filePath);
11944
12025
  } catch {
11945
12026
  deps.externalTailManager().handleJsonlDeleted(filePath);
11946
12027
  return;
@@ -12269,20 +12350,20 @@ function createApiDeps(deps) {
12269
12350
  }
12270
12351
 
12271
12352
  // src/services/cache-integrity/cacheIntegrityMonitor.ts
12272
- var import_crypto10 = require("crypto");
12273
- var import_fs22 = require("fs");
12353
+ var import_crypto11 = require("crypto");
12354
+ var import_fs23 = require("fs");
12274
12355
 
12275
12356
  // src/services/cache-integrity/alertStore.ts
12276
- var import_fs20 = require("fs");
12277
- var import_os11 = require("os");
12278
- var import_path20 = require("path");
12357
+ var import_fs21 = require("fs");
12358
+ var import_os12 = require("os");
12359
+ var import_path21 = require("path");
12279
12360
  function alertStatePath() {
12280
- const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path20.join)((0, import_os11.homedir)(), ".threadbase");
12281
- return (0, import_path20.join)(dir, "cache-alert.json");
12361
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path21.join)((0, import_os12.homedir)(), ".threadbase");
12362
+ return (0, import_path21.join)(dir, "cache-alert.json");
12282
12363
  }
12283
12364
  function loadAlertState() {
12284
12365
  try {
12285
- const parsed = JSON.parse((0, import_fs20.readFileSync)(alertStatePath(), "utf-8"));
12366
+ const parsed = JSON.parse((0, import_fs21.readFileSync)(alertStatePath(), "utf-8"));
12286
12367
  return parsed && typeof parsed === "object" ? parsed : {};
12287
12368
  } catch {
12288
12369
  return {};
@@ -12290,14 +12371,14 @@ function loadAlertState() {
12290
12371
  }
12291
12372
  function saveAlertState(state) {
12292
12373
  const path = alertStatePath();
12293
- (0, import_fs20.mkdirSync)((0, import_path20.dirname)(path), { recursive: true });
12294
- (0, import_fs20.writeFileSync)(path, `${JSON.stringify(state, null, 2)}
12374
+ (0, import_fs21.mkdirSync)((0, import_path21.dirname)(path), { recursive: true });
12375
+ (0, import_fs21.writeFileSync)(path, `${JSON.stringify(state, null, 2)}
12295
12376
  `);
12296
12377
  }
12297
12378
 
12298
12379
  // src/services/cache-integrity/backup.ts
12299
- var import_fs21 = require("fs");
12300
- var import_path21 = require("path");
12380
+ var import_fs22 = require("fs");
12381
+ var import_path22 = require("path");
12301
12382
  var DEFAULT_RETAIN = 3;
12302
12383
  function retainCount() {
12303
12384
  const parsed = Number.parseInt(process.env.THREADBASE_CACHE_BACKUP_RETAIN ?? "", 10);
@@ -12308,17 +12389,17 @@ function timestamp(d) {
12308
12389
  return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
12309
12390
  }
12310
12391
  async function backupCacheDb(db, cacheDir) {
12311
- const backupsDir = (0, import_path21.join)(cacheDir, "backups");
12312
- (0, import_fs21.mkdirSync)(backupsDir, { recursive: true });
12313
- const destPath = (0, import_path21.join)(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
12392
+ const backupsDir = (0, import_path22.join)(cacheDir, "backups");
12393
+ (0, import_fs22.mkdirSync)(backupsDir, { recursive: true });
12394
+ const destPath = (0, import_path22.join)(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
12314
12395
  await db.backup(destPath);
12315
12396
  const retain = retainCount();
12316
- const backups = (0, import_fs21.readdirSync)(backupsDir).filter((f) => f.startsWith("cache-") && f.endsWith(".db")).map((f) => {
12317
- const full = (0, import_path21.join)(backupsDir, f);
12318
- return { full, mtime: (0, import_fs21.statSync)(full).mtimeMs };
12397
+ const backups = (0, import_fs22.readdirSync)(backupsDir).filter((f) => f.startsWith("cache-") && f.endsWith(".db")).map((f) => {
12398
+ const full = (0, import_path22.join)(backupsDir, f);
12399
+ return { full, mtime: (0, import_fs22.statSync)(full).mtimeMs };
12319
12400
  }).sort((a, b) => b.mtime - a.mtime);
12320
12401
  for (const stale of backups.slice(retain)) {
12321
- if ((0, import_fs21.existsSync)(stale.full)) (0, import_fs21.unlinkSync)(stale.full);
12402
+ if ((0, import_fs22.existsSync)(stale.full)) (0, import_fs22.unlinkSync)(stale.full);
12322
12403
  }
12323
12404
  return destPath;
12324
12405
  }
@@ -12334,7 +12415,7 @@ function envInt(name, fallback) {
12334
12415
  }
12335
12416
  function fingerprintOf(ids) {
12336
12417
  const sorted = [...ids].sort();
12337
- return `sha256:${(0, import_crypto10.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
12418
+ return `sha256:${(0, import_crypto11.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
12338
12419
  }
12339
12420
  var CacheIntegrityMonitor = class {
12340
12421
  constructor(cache, wsHub, log10, cacheDir, rescan, runDuringReset) {
@@ -12415,7 +12496,7 @@ var CacheIntegrityMonitor = class {
12415
12496
  * the pending record, back up on high severity, and broadcast the alert.
12416
12497
  */
12417
12498
  async runDetection(detectedAt = (/* @__PURE__ */ new Date()).toISOString()) {
12418
- const all = this.cache.listMissingFiles(import_fs22.existsSync);
12499
+ const all = this.cache.listMissingFiles(import_fs23.existsSync);
12419
12500
  const missing = all.filter((m) => !this.ignoredIds.has(m.id));
12420
12501
  if (missing.length === 0) {
12421
12502
  if (this._pending) {
@@ -12519,7 +12600,7 @@ var CacheIntegrityMonitor = class {
12519
12600
  case "prune_all": {
12520
12601
  await this.ensureBackup(pending);
12521
12602
  const backupPath = pending.backupPath;
12522
- const stillMissing = pending.missing.filter((m) => !(0, import_fs22.existsSync)(m.filePath)).map((m) => m.id);
12603
+ const stillMissing = pending.missing.filter((m) => !(0, import_fs23.existsSync)(m.filePath)).map((m) => m.id);
12523
12604
  const pruned = this.cache.dropRowsById(stillMissing);
12524
12605
  this.applyDeferredUnlinks();
12525
12606
  this.clearPending();
@@ -12581,7 +12662,7 @@ var CacheIntegrityMonitor = class {
12581
12662
 
12582
12663
  // src/services/conversations/conversationWatcher.ts
12583
12664
  var import_chokidar = __toESM(require("chokidar"), 1);
12584
- var import_fs23 = require("fs");
12665
+ var import_fs24 = require("fs");
12585
12666
  var import_promises6 = require("fs/promises");
12586
12667
  var ConversationWatcher = class {
12587
12668
  files = /* @__PURE__ */ new Map();
@@ -12607,7 +12688,7 @@ var ConversationWatcher = class {
12607
12688
  if (this.files.has(key)) return;
12608
12689
  let offset;
12609
12690
  try {
12610
- offset = (0, import_fs23.statSync)(filePath).size;
12691
+ offset = (0, import_fs24.statSync)(filePath).size;
12611
12692
  } catch {
12612
12693
  offset = 0;
12613
12694
  }
@@ -12765,14 +12846,14 @@ var ConversationWatcher = class {
12765
12846
  };
12766
12847
 
12767
12848
  // src/services/conversations/pruneAgentConversations.ts
12768
- var import_fs24 = require("fs");
12849
+ var import_fs25 = require("fs");
12769
12850
  function pruneAgentConversations(cache) {
12770
12851
  const db = cache.getDatabase();
12771
12852
  const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
12772
12853
  let pruned = 0;
12773
12854
  let missing = 0;
12774
12855
  for (const row of rows) {
12775
- if (!(0, import_fs24.existsSync)(row.file_path)) {
12856
+ if (!(0, import_fs25.existsSync)(row.file_path)) {
12776
12857
  missing += 1;
12777
12858
  continue;
12778
12859
  }
@@ -13478,7 +13559,7 @@ function shouldBroadcastQuestion(args) {
13478
13559
  }
13479
13560
 
13480
13561
  // src/session-registry-boot.ts
13481
- var import_fs25 = require("fs");
13562
+ var import_fs26 = require("fs");
13482
13563
 
13483
13564
  // src/lifecycle/process-liveness.ts
13484
13565
  function isPidAlive(pid) {
@@ -13724,7 +13805,7 @@ var SessionRegistryBoot = class {
13724
13805
  const skippedBy = {};
13725
13806
  for (const row of candidates) {
13726
13807
  if (this.sessionStore.getManaged(row.session_id)) continue;
13727
- const skip = rehydrateSkipReason(row, { now, projectExists: import_fs25.existsSync });
13808
+ const skip = rehydrateSkipReason(row, { now, projectExists: import_fs26.existsSync });
13728
13809
  if (skip) {
13729
13810
  skippedBy[skip] = (skippedBy[skip] ?? 0) + 1;
13730
13811
  this.log.info(`[rehydrate] skipped ${row.session_id}: ${skip}`, {
@@ -13766,7 +13847,7 @@ var SessionRegistryBoot = class {
13766
13847
  async autoResumePreviousSessions(rows) {
13767
13848
  if (!this.autoResumeOnBoot) return;
13768
13849
  const now = Date.now();
13769
- const baseOptions = { now, projectExists: import_fs25.existsSync, historyExists: () => true };
13850
+ const baseOptions = { now, projectExists: import_fs26.existsSync, historyExists: () => true };
13770
13851
  const historyExists = /* @__PURE__ */ new Map();
13771
13852
  const preflights = /* @__PURE__ */ new Set();
13772
13853
  for (const row of rows) {
@@ -14282,9 +14363,9 @@ function discoveredToResponse(d, conversationId) {
14282
14363
  }
14283
14364
 
14284
14365
  // src/session-watchers.ts
14285
- var import_fs26 = require("fs");
14286
- var import_os12 = require("os");
14287
- var import_path22 = require("path");
14366
+ var import_fs27 = require("fs");
14367
+ var import_os13 = require("os");
14368
+ var import_path23 = require("path");
14288
14369
  var SessionWatchers = class {
14289
14370
  constructor(deps) {
14290
14371
  this.deps = deps;
@@ -14350,7 +14431,7 @@ var SessionWatchers = class {
14350
14431
  // file isn't slurped in full.
14351
14432
  readFirstLineSessionId(filePath) {
14352
14433
  try {
14353
- const content = (0, import_fs26.readFileSync)(filePath, "utf8");
14434
+ const content = (0, import_fs27.readFileSync)(filePath, "utf8");
14354
14435
  const nl = content.indexOf("\n");
14355
14436
  const firstLine = nl === -1 ? content : content.slice(0, nl);
14356
14437
  if (!firstLine.trim()) return null;
@@ -14365,9 +14446,9 @@ var SessionWatchers = class {
14365
14446
  // was passed to Claude via --session-id so the filename matches from the start.
14366
14447
  watchForJsonl(sessionId, projectPath) {
14367
14448
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
14368
- const projectsDir = (0, import_path22.join)((0, import_os12.homedir)(), ".claude", "projects", encoded);
14449
+ const projectsDir = (0, import_path23.join)((0, import_os13.homedir)(), ".claude", "projects", encoded);
14369
14450
  const expectedFile = `${sessionId}.jsonl`;
14370
- const filePath = (0, import_path22.join)(projectsDir, expectedFile);
14451
+ const filePath = (0, import_path23.join)(projectsDir, expectedFile);
14371
14452
  const deadline = Date.now() + 12e4;
14372
14453
  let watcher = null;
14373
14454
  const cleanup = () => {
@@ -14385,14 +14466,14 @@ var SessionWatchers = class {
14385
14466
  cleanup();
14386
14467
  return;
14387
14468
  }
14388
- let resolvedFilePath = (0, import_fs26.existsSync)(filePath) ? filePath : null;
14389
- if (!resolvedFilePath && (0, import_fs26.existsSync)(projectsDir)) {
14469
+ let resolvedFilePath = (0, import_fs27.existsSync)(filePath) ? filePath : null;
14470
+ if (!resolvedFilePath && (0, import_fs27.existsSync)(projectsDir)) {
14390
14471
  try {
14391
14472
  const now = Date.now();
14392
- const match = (0, import_fs26.readdirSync)(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: (0, import_fs26.statSync)((0, import_path22.join)(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
14393
- ({ f }) => (0, import_path22.basename)(f, ".jsonl") === sessionId || this.readFirstLineSessionId((0, import_path22.join)(projectsDir, f)) === sessionId
14473
+ const match = (0, import_fs27.readdirSync)(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: (0, import_fs27.statSync)((0, import_path23.join)(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
14474
+ ({ f }) => (0, import_path23.basename)(f, ".jsonl") === sessionId || this.readFirstLineSessionId((0, import_path23.join)(projectsDir, f)) === sessionId
14394
14475
  ).sort((a, b) => b.mtime - a.mtime)[0];
14395
- if (match) resolvedFilePath = (0, import_path22.join)(projectsDir, match.f);
14476
+ if (match) resolvedFilePath = (0, import_path23.join)(projectsDir, match.f);
14396
14477
  } catch {
14397
14478
  }
14398
14479
  }
@@ -14400,7 +14481,7 @@ var SessionWatchers = class {
14400
14481
  cleanup();
14401
14482
  this.deps.sessionFileMap.set(sessionId, resolvedFilePath);
14402
14483
  try {
14403
- const existing = (0, import_fs26.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
14484
+ const existing = (0, import_fs27.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
14404
14485
  if (existing.length > 0) {
14405
14486
  this.deps.broadcastConversationLines(sessionId, existing);
14406
14487
  }
@@ -14420,7 +14501,7 @@ var SessionWatchers = class {
14420
14501
  if (this.deps.sessionFileMap.has(sessionId)) return;
14421
14502
  try {
14422
14503
  require("fs").mkdirSync(projectsDir, { recursive: true });
14423
- watcher = (0, import_fs26.watch)(projectsDir, tryWire);
14504
+ watcher = (0, import_fs27.watch)(projectsDir, tryWire);
14424
14505
  watcher.on("error", cleanup);
14425
14506
  } catch {
14426
14507
  }
@@ -14436,7 +14517,7 @@ var SessionWatchers = class {
14436
14517
  watchForCodexRollout(sessionId, projectPath) {
14437
14518
  const deadline = Date.now() + 12e4;
14438
14519
  const now = /* @__PURE__ */ new Date();
14439
- const dateDir = (0, import_path22.join)(
14520
+ const dateDir = (0, import_path23.join)(
14440
14521
  String(now.getFullYear()),
14441
14522
  String(now.getMonth() + 1).padStart(2, "0"),
14442
14523
  String(now.getDate()).padStart(2, "0")
@@ -14449,7 +14530,7 @@ var SessionWatchers = class {
14449
14530
  };
14450
14531
  const matchesProjectPath = (candidatePath) => {
14451
14532
  try {
14452
- const firstLine = (0, import_fs26.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
14533
+ const firstLine = (0, import_fs27.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
14453
14534
  if (!firstLine) return null;
14454
14535
  const parsed = JSON.parse(firstLine);
14455
14536
  if (parsed?.type !== "session_meta") return null;
@@ -14477,18 +14558,18 @@ var SessionWatchers = class {
14477
14558
  this.deps.sessionStore.listManaged().filter((s) => s.id !== sessionId && s.boundConversationId != null).map((s) => s.boundConversationId)
14478
14559
  );
14479
14560
  for (const root of this.deps.codexRoots) {
14480
- const sessionsDir = (0, import_path22.join)(root, dateDir);
14481
- if (!(0, import_fs26.existsSync)(sessionsDir)) continue;
14561
+ const sessionsDir = (0, import_path23.join)(root, dateDir);
14562
+ if (!(0, import_fs27.existsSync)(sessionsDir)) continue;
14482
14563
  let candidateFiles;
14483
14564
  try {
14484
- candidateFiles = (0, import_fs26.readdirSync)(sessionsDir).filter((f) => f.endsWith(".jsonl"));
14565
+ candidateFiles = (0, import_fs27.readdirSync)(sessionsDir).filter((f) => f.endsWith(".jsonl"));
14485
14566
  } catch {
14486
14567
  continue;
14487
14568
  }
14488
14569
  const nowMs = Date.now();
14489
- const recentCandidates = candidateFiles.map((f) => ({ f, mtime: (0, import_fs26.statSync)((0, import_path22.join)(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
14570
+ const recentCandidates = candidateFiles.map((f) => ({ f, mtime: (0, import_fs27.statSync)((0, import_path23.join)(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
14490
14571
  for (const { f } of recentCandidates) {
14491
- const candidatePath = (0, import_path22.join)(sessionsDir, f);
14572
+ const candidatePath = (0, import_path23.join)(sessionsDir, f);
14492
14573
  const match = matchesProjectPath(candidatePath);
14493
14574
  if (!match) continue;
14494
14575
  if (boundElsewhere.has(match.id)) continue;
@@ -14509,7 +14590,7 @@ var SessionWatchers = class {
14509
14590
  this.deps.sessionFileMap.set(sessionId, candidatePath);
14510
14591
  this.deps.fileWatcher.watch(candidatePath);
14511
14592
  try {
14512
- const existing = (0, import_fs26.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
14593
+ const existing = (0, import_fs27.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
14513
14594
  if (existing.length > 0) {
14514
14595
  this.deps.broadcastConversationLines(sessionId, existing);
14515
14596
  }
@@ -14840,7 +14921,7 @@ var StreamerServer = class {
14840
14921
  runtimeStore = null;
14841
14922
  // Identifies this streamer run. A registry row carrying a different id is a
14842
14923
  // session that outlived the process that started it.
14843
- streamerInstanceId = (0, import_crypto11.randomUUID)();
14924
+ streamerInstanceId = (0, import_crypto12.randomUUID)();
14844
14925
  cacheMetadataRepo = null;
14845
14926
  // Push registration + delivery state (C7). Null when the cache DB failed to
14846
14927
  // open — registration then degrades to a no-op rather than 500ing.
@@ -14892,7 +14973,7 @@ var StreamerServer = class {
14892
14973
  this.skipStartupWarmup = config.skipStartupWarmup ?? false;
14893
14974
  this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
14894
14975
  this.scanProfiles = config.scanProfiles;
14895
- this.codexRoots = config.codexRoots ?? [(0, import_path23.join)((0, import_os13.homedir)(), ".codex", "sessions")];
14976
+ this.codexRoots = config.codexRoots ?? [(0, import_path24.join)((0, import_os14.homedir)(), ".codex", "sessions")];
14896
14977
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
14897
14978
  this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
14898
14979
  const flagResolution = resolveFeatureFlags({
@@ -14909,7 +14990,7 @@ var StreamerServer = class {
14909
14990
  this.claudeFlagsPersistable = config.claudeFlags === void 0;
14910
14991
  this.claudeFlags = config.claudeFlags ?? loadClaudeFlags();
14911
14992
  this.claudeExtraArgs = config.claudeExtraArgs ?? loadClaudeExtraArgs();
14912
- this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path23.join)((0, import_os13.homedir)(), ".threadbase", "cache");
14993
+ this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path24.join)((0, import_os14.homedir)(), ".threadbase", "cache");
14913
14994
  this.runtimeDbPath = resolveRuntimeDbPath(config.runtimeDbPath);
14914
14995
  this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
14915
14996
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
@@ -15086,7 +15167,7 @@ var StreamerServer = class {
15086
15167
  temporalClient,
15087
15168
  taskQueue: agentConfig.temporal.taskQueue
15088
15169
  });
15089
- const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path23.join)((0, import_path23.dirname)(this.cacheDir), "conversations");
15170
+ const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path24.join)((0, import_path24.dirname)(this.cacheDir), "conversations");
15090
15171
  conversationWriter = createConversationWriter({
15091
15172
  baseDir: conversationsBaseDir
15092
15173
  });
@@ -15338,14 +15419,14 @@ var StreamerServer = class {
15338
15419
  }
15339
15420
  this.apnsClient = new ApnsClient(creds);
15340
15421
  const sender = new LiveActivitySender(this.apnsClient, pushRepo);
15341
- const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os13.hostname)();
15342
- this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os13.hostname)());
15422
+ const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os14.hostname)();
15423
+ this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os14.hostname)());
15343
15424
  this.liveActivityRenewal = new LiveActivityRenewalScheduler({
15344
15425
  repo: pushRepo,
15345
15426
  sender,
15346
15427
  sessionStore: this.sessionStore,
15347
15428
  serverId,
15348
- serverLabel: (0, import_os13.hostname)()
15429
+ serverLabel: (0, import_os14.hostname)()
15349
15430
  });
15350
15431
  this.liveActivityRenewal.start();
15351
15432
  this.log.info("Live Activity push enabled", {
@@ -15365,7 +15446,7 @@ var StreamerServer = class {
15365
15446
  */
15366
15447
  initWaitingInputPush(pushRepo) {
15367
15448
  const sender = new ExpoPushSender(pushRepo, process.env.THREADBASE_EXPO_ACCESS_TOKEN);
15368
- const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os13.hostname)();
15449
+ const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os14.hostname)();
15369
15450
  this.waitingInputNotifier = new WaitingInputNotifier(
15370
15451
  sender,
15371
15452
  serverId,
@@ -15502,7 +15583,7 @@ var StreamerServer = class {
15502
15583
  let sessions = null;
15503
15584
  for (let attempt = 0; attempt < 2; attempt += 1) {
15504
15585
  const transport = await connectOrSpawnHost({
15505
- instanceId: process.env.THREADBASE_INSTANCE_ID ?? (0, import_os13.hostname)()
15586
+ instanceId: process.env.THREADBASE_INSTANCE_ID ?? (0, import_os14.hostname)()
15506
15587
  });
15507
15588
  try {
15508
15589
  sessions = await this.ptyManager.useRemoteRunner(transport);
@@ -15579,7 +15660,7 @@ var StreamerServer = class {
15579
15660
  }
15580
15661
  try {
15581
15662
  this.cache = ConversationCache.open(
15582
- (0, import_path23.join)(this.cacheDir, "cache.db"),
15663
+ (0, import_path24.join)(this.cacheDir, "cache.db"),
15583
15664
  this.tailSize,
15584
15665
  void 0,
15585
15666
  {
@@ -15663,7 +15744,7 @@ var StreamerServer = class {
15663
15744
  this.fileWatcher.watchDirectory(dir);
15664
15745
  }
15665
15746
  for (const dir of this.codexRoots) {
15666
- if (!(0, import_fs27.existsSync)(dir)) continue;
15747
+ if (!(0, import_fs28.existsSync)(dir)) continue;
15667
15748
  this.fileWatcher.watchDirectory(dir);
15668
15749
  }
15669
15750
  } catch (err) {
@@ -15967,7 +16048,7 @@ var StreamerServer = class {
15967
16048
  nonce: sealed.nonce,
15968
16049
  ephemeralPublicKey: sealed.ephemeralPublicKey,
15969
16050
  publicUrl: this.publicUrl,
15970
- machineName: (0, import_os13.hostname)(),
16051
+ machineName: (0, import_os14.hostname)(),
15971
16052
  ...device && {
15972
16053
  deviceId: device.deviceId,
15973
16054
  deviceToken: device.deviceToken,
@@ -16176,7 +16257,7 @@ var StreamerServer = class {
16176
16257
  }
16177
16258
  const cachedConvMeta = this.cache?.getMetaById(historyId);
16178
16259
  const cachedPath = cachedConvMeta?.filePath ? toNativeFilePath(cachedConvMeta.filePath) : null;
16179
- const cachedCodexPath = (registryProvider === CODEX_CLI_PROVIDER || cachedConvMeta?.provider === CODEX_CLI_PROVIDER) && cachedPath != null && (0, import_fs27.existsSync)(cachedPath) ? cachedPath : null;
16260
+ const cachedCodexPath = (registryProvider === CODEX_CLI_PROVIDER || cachedConvMeta?.provider === CODEX_CLI_PROVIDER) && cachedPath != null && (0, import_fs28.existsSync)(cachedPath) ? cachedPath : null;
16180
16261
  const jsonlCwd = jsonlPath ? await this.conversationHandlers.readCwdFromJsonl(jsonlPath) : null;
16181
16262
  const projectPath = jsonlCwd ?? conv?.projectPath ?? (cachedCodexPath ? cachedConvMeta?.projectPath : null);
16182
16263
  if (!projectPath) {