@threadbase-sh/streamer 1.54.1 → 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.js CHANGED
@@ -3934,8 +3934,8 @@ import { EventEmitter } from "events";
3934
3934
  import { existsSync as existsSync16 } from "fs";
3935
3935
  import { realpath as realpath2 } from "fs/promises";
3936
3936
  import { createServer as createServer2 } from "http";
3937
- import { homedir as homedir13, hostname as hostname3 } from "os";
3938
- import { dirname as dirname10, join as join24 } from "path";
3937
+ import { homedir as homedir14, hostname as hostname3 } from "os";
3938
+ import { dirname as dirname11, join as join25 } from "path";
3939
3939
 
3940
3940
  // src/api/app.ts
3941
3941
  import { Hono as Hono18 } from "hono";
@@ -5399,6 +5399,66 @@ var PushRepository = class {
5399
5399
  }
5400
5400
  };
5401
5401
 
5402
+ // src/server-identity.ts
5403
+ import { createPrivateKey, createPublicKey, generateKeyPairSync } from "crypto";
5404
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
5405
+ import { homedir as homedir6 } from "os";
5406
+ import { dirname as dirname6, join as join10 } from "path";
5407
+ var IDENTITY_FILE_VERSION = 1;
5408
+ function serverIdentityKeyPath() {
5409
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? join10(homedir6(), ".threadbase");
5410
+ return join10(dir, "keys", "server-identity.key");
5411
+ }
5412
+ function loadOrCreateServerIdentity() {
5413
+ const path = serverIdentityKeyPath();
5414
+ let raw;
5415
+ try {
5416
+ raw = readFileSync7(path, "utf-8");
5417
+ } catch (err) {
5418
+ if (err.code !== "ENOENT") throw err;
5419
+ return generateIdentity(path);
5420
+ }
5421
+ let privateKey;
5422
+ try {
5423
+ privateKey = createPrivateKey({ key: JSON.parse(raw).key, format: "jwk" });
5424
+ } catch {
5425
+ throw new Error(
5426
+ `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.`
5427
+ );
5428
+ }
5429
+ return { publicKey: publicKeyOf(privateKey), privateKey };
5430
+ }
5431
+ function serverIdentityPublicKey() {
5432
+ return loadOrCreateServerIdentity().publicKey;
5433
+ }
5434
+ function generateIdentity(path) {
5435
+ const { privateKey } = generateKeyPairSync("x25519");
5436
+ const file = {
5437
+ v: IDENTITY_FILE_VERSION,
5438
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5439
+ key: privateKey.export({ format: "jwk" })
5440
+ };
5441
+ mkdirSync3(dirname6(path), { recursive: true, mode: 448 });
5442
+ const tmp = `${path}.tmp`;
5443
+ writeFileSync3(tmp, `${JSON.stringify(file)}
5444
+ `, { encoding: "utf-8", mode: 384 });
5445
+ chmodSync2(tmp, 384);
5446
+ renameSync2(tmp, path);
5447
+ const publicKey = publicKeyOf(privateKey);
5448
+ getLogger("identity").info(`Generated server identity key ${publicKey}`, {
5449
+ event: "identity.key_generated",
5450
+ path
5451
+ });
5452
+ return { publicKey, privateKey };
5453
+ }
5454
+ function publicKeyOf(privateKey) {
5455
+ const jwk = createPublicKey(privateKey).export({ format: "jwk" });
5456
+ if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
5457
+ throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
5458
+ }
5459
+ return jwk.x;
5460
+ }
5461
+
5402
5462
  // src/services/push/apnsClient.ts
5403
5463
  import { createSign } from "crypto";
5404
5464
  import { connect, constants } from "http2";
@@ -5607,6 +5667,21 @@ function describePushCapability(liveActivityEnabled, env = process.env) {
5607
5667
  liveActivityReason: describeMissingApnsCredentials(env) ?? "APNs credentials are set but the push token store is unavailable, so Live Activity push is disabled."
5608
5668
  };
5609
5669
  }
5670
+ var identityKeyFailureLogged = false;
5671
+ function describeServerIdentityKey() {
5672
+ try {
5673
+ return serverIdentityPublicKey();
5674
+ } catch (err) {
5675
+ if (!identityKeyFailureLogged) {
5676
+ identityKeyFailureLogged = true;
5677
+ getLogger("identity").error(
5678
+ `Server identity key unavailable, so /api/info will omit it: ${err instanceof Error ? err.message : String(err)}`,
5679
+ { event: "identity.unavailable" }
5680
+ );
5681
+ }
5682
+ return void 0;
5683
+ }
5684
+ }
5610
5685
  var clientLog = getLogger("client");
5611
5686
  var createMiscRoutes = (deps) => {
5612
5687
  const app = new Hono11();
@@ -5640,7 +5715,13 @@ var createMiscRoutes = (deps) => {
5640
5715
  // actually send a push, so mobile can hide an affordance instead of
5641
5716
  // registering tokens nothing will ever send to. Absent on older servers,
5642
5717
  // which a client should read as "unknown", not "unavailable".
5643
- push: describePushCapability(deps.liveActivityPushEnabled())
5718
+ push: describePushCapability(deps.liveActivityPushEnabled()),
5719
+ // This server's long-term X25519 public key, base64url. The same value the
5720
+ // pair QR carries as `spk`, served here so an already-paired client can
5721
+ // learn it without re-scanning. Additive: absent means a server with no
5722
+ // readable identity key, which a client must read as "cannot verify this
5723
+ // server" — never as a reason to fail the rest of this response.
5724
+ serverIdentityKey: describeServerIdentityKey()
5644
5725
  });
5645
5726
  });
5646
5727
  app.get("/api/profiles", (c) => c.json([]));
@@ -6158,7 +6239,7 @@ import {
6158
6239
  search
6159
6240
  } from "@threadbase-sh/scanner";
6160
6241
  import { createReadStream, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
6161
- import { join as join11 } from "path";
6242
+ import { join as join12 } from "path";
6162
6243
  import { createInterface } from "readline";
6163
6244
 
6164
6245
  // src/conversation-cache.ts
@@ -6167,9 +6248,9 @@ import {
6167
6248
  parseJsonlLine
6168
6249
  } from "@threadbase-sh/scanner";
6169
6250
  import Database from "better-sqlite3";
6170
- import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, openSync as openSync3, readSync as readSync3, statSync as statSync3 } from "fs";
6251
+ import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync as openSync3, readSync as readSync3, statSync as statSync3 } from "fs";
6171
6252
  import { open as openAsync } from "fs/promises";
6172
- import { dirname as dirname7 } from "path";
6253
+ import { dirname as dirname8 } from "path";
6173
6254
  import { setImmediate as yieldToEventLoop } from "timers/promises";
6174
6255
 
6175
6256
  // src/db/query-timing.ts
@@ -6241,17 +6322,17 @@ function labelStatements(statements) {
6241
6322
  }
6242
6323
 
6243
6324
  // src/db/sqlite-migrate.ts
6244
- import { readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
6245
- import { dirname as dirname6, join as join10 } from "path";
6325
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
6326
+ import { dirname as dirname7, join as join11 } from "path";
6246
6327
  import { fileURLToPath as fileURLToPath2 } from "url";
6247
6328
  function getMigrationsDir2() {
6248
6329
  if (typeof import.meta !== "undefined" && import.meta.url) {
6249
- return dirname6(fileURLToPath2(import.meta.url));
6330
+ return dirname7(fileURLToPath2(import.meta.url));
6250
6331
  }
6251
6332
  return __dirname;
6252
6333
  }
6253
6334
  function resolveMigrationsDir(name = "migrations") {
6254
- return join10(getMigrationsDir2(), name);
6335
+ return join11(getMigrationsDir2(), name);
6255
6336
  }
6256
6337
  var SCHEMA_MIGRATIONS_SQL = `
6257
6338
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -6273,7 +6354,7 @@ function runSqliteMigrations(db, migrationsDir) {
6273
6354
  skipped.push(file);
6274
6355
  continue;
6275
6356
  }
6276
- const sql = readFileSync7(join10(dir, file), "utf-8");
6357
+ const sql = readFileSync8(join11(dir, file), "utf-8");
6277
6358
  const tx = db.transaction(() => {
6278
6359
  db.exec(sql);
6279
6360
  recordApplied.run(file, (/* @__PURE__ */ new Date()).toISOString());
@@ -7016,7 +7097,7 @@ var ConversationCache = class _ConversationCache {
7016
7097
  return this.classifyAgentFile(filePath, s.mtimeMs, s.size);
7017
7098
  }
7018
7099
  static open(dbPath, tailSize = 10, migrationsDir, options) {
7019
- mkdirSync3(dirname7(dbPath), { recursive: true });
7100
+ mkdirSync4(dirname8(dbPath), { recursive: true });
7020
7101
  const db = instrumentDatabase(new Database(dbPath));
7021
7102
  db.pragma("journal_mode = WAL");
7022
7103
  db.pragma("foreign_keys = ON");
@@ -8309,12 +8390,12 @@ var ConversationHandlers = class {
8309
8390
  for (const projectsDir of this.scannerManager.projectsDirs()) {
8310
8391
  if (!existsSync8(projectsDir)) continue;
8311
8392
  for (const dir of readdirSync3(projectsDir)) {
8312
- const fp = join11(projectsDir, dir, filename);
8393
+ const fp = join12(projectsDir, dir, filename);
8313
8394
  if (existsSync8(fp)) return fp;
8314
- const projectDir = join11(projectsDir, dir);
8395
+ const projectDir = join12(projectsDir, dir);
8315
8396
  try {
8316
8397
  for (const sub of readdirSync3(projectDir)) {
8317
- const subagentPath = join11(projectDir, sub, "subagents", filename);
8398
+ const subagentPath = join12(projectDir, sub, "subagents", filename);
8318
8399
  if (existsSync8(subagentPath)) return subagentPath;
8319
8400
  }
8320
8401
  } catch {
@@ -8789,7 +8870,7 @@ var ConversationHandlers = class {
8789
8870
 
8790
8871
  // src/api/handlers/sessions.handlers.ts
8791
8872
  import { existsSync as existsSync10 } from "fs";
8792
- import { basename as basename5, dirname as dirname8, join as join15 } from "path";
8873
+ import { basename as basename5, dirname as dirname9, join as join16 } from "path";
8793
8874
 
8794
8875
  // node_modules/nanoid/index.js
8795
8876
  import crypto2 from "crypto";
@@ -8972,7 +9053,7 @@ async function handleSendAgentInput(sessionId, body, deps) {
8972
9053
 
8973
9054
  // src/agent/handle-start-agent-session.ts
8974
9055
  import { existsSync as existsSync9 } from "fs";
8975
- import { join as join12 } from "path";
9056
+ import { join as join13 } from "path";
8976
9057
  function validateBody(body) {
8977
9058
  if (body === null || body === void 0 || typeof body !== "object") {
8978
9059
  return { ok: false };
@@ -9002,7 +9083,7 @@ async function handleStartAgentSession(body, deps) {
9002
9083
  }
9003
9084
  let conversationId = parsed.conversationId;
9004
9085
  if (conversationId) {
9005
- const jsonlPath = join12(deps.conversationsDir, `${conversationId}.jsonl`);
9086
+ const jsonlPath = join13(deps.conversationsDir, `${conversationId}.jsonl`);
9006
9087
  if (!existsSync9(jsonlPath)) {
9007
9088
  return {
9008
9089
  status: 404,
@@ -9048,7 +9129,7 @@ async function handleStartAgentSession(body, deps) {
9048
9129
 
9049
9130
  // src/browse.ts
9050
9131
  import { mkdir as mkdir2, readdir, realpath, stat } from "fs/promises";
9051
- import { join as join13, resolve, sep } from "path";
9132
+ import { join as join14, resolve, sep } from "path";
9052
9133
  var BrowsePathNotFoundError = class extends Error {
9053
9134
  constructor(message) {
9054
9135
  super(message);
@@ -9090,7 +9171,7 @@ async function createDirectory(parentAbsolutePath, name) {
9090
9171
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
9091
9172
  throw new Error("Invalid directory name");
9092
9173
  }
9093
- const target = join13(parentAbsolutePath, name);
9174
+ const target = join14(parentAbsolutePath, name);
9094
9175
  try {
9095
9176
  const s = await stat(target);
9096
9177
  if (s.isDirectory()) throw new Error("Directory already exists");
@@ -9365,7 +9446,7 @@ function readIdempotencyKey(body) {
9365
9446
  import { randomBytes as randomBytes3 } from "crypto";
9366
9447
  import { mkdir as mkdir3, writeFile } from "fs/promises";
9367
9448
  import heicConvert from "heic-convert";
9368
- import { join as join14 } from "path";
9449
+ import { join as join15 } from "path";
9369
9450
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
9370
9451
  var MAX_BYTES = 25 * 1024 * 1024;
9371
9452
  var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
@@ -9398,9 +9479,9 @@ async function saveUploadFile(input) {
9398
9479
  }
9399
9480
  const id = `up_${randomBytes3(8).toString("hex")}`;
9400
9481
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
9401
- const dir = join14(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
9482
+ const dir = join15(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
9402
9483
  await mkdir3(dir, { recursive: true });
9403
- const filePath = join14(dir, `${Date.now()}-${id}-${safeName}`);
9484
+ const filePath = join15(dir, `${Date.now()}-${id}-${safeName}`);
9404
9485
  await writeFile(filePath, buffer);
9405
9486
  return {
9406
9487
  id,
@@ -10321,7 +10402,7 @@ var SessionHandlers = class {
10321
10402
  sessionStore: this.sessionStore,
10322
10403
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
10323
10404
  agentClient: this.agentClient,
10324
- conversationsDir: this.cacheDir ? join15(dirname8(this.cacheDir), "conversations") : "",
10405
+ conversationsDir: this.cacheDir ? join16(dirname9(this.cacheDir), "conversations") : "",
10325
10406
  agentConfig: this.agentConfig
10326
10407
  });
10327
10408
  json(res, result.status, result.body);
@@ -10798,10 +10879,10 @@ var SessionsRepository = class {
10798
10879
 
10799
10880
  // src/db/runtime-store.ts
10800
10881
  import Database2 from "better-sqlite3";
10801
- import { homedir as homedir6 } from "os";
10802
- import { join as join16 } from "path";
10882
+ import { homedir as homedir7 } from "os";
10883
+ import { join as join17 } from "path";
10803
10884
  function resolveRuntimeDbPath(override) {
10804
- return override ?? process.env.THREADBASE_RUNTIME_DB ?? join16(process.env.THREADBASE_CONFIG_DIR ?? join16(homedir6(), ".threadbase"), "runtime.db");
10885
+ return override ?? process.env.THREADBASE_RUNTIME_DB ?? join17(process.env.THREADBASE_CONFIG_DIR ?? join17(homedir7(), ".threadbase"), "runtime.db");
10805
10886
  }
10806
10887
  var RuntimeStore = class _RuntimeStore {
10807
10888
  constructor(db) {
@@ -11119,14 +11200,14 @@ import { spawn as spawn2 } from "child_process";
11119
11200
 
11120
11201
  // src/pty-host/socket.ts
11121
11202
  import { createConnection, createServer } from "net";
11122
- import { homedir as homedir7 } from "os";
11123
- import { join as join17 } from "path";
11203
+ import { homedir as homedir8 } from "os";
11204
+ import { join as join18 } from "path";
11124
11205
  function hostSocketPath(instanceId) {
11125
11206
  if (process.platform === "win32") {
11126
11207
  return `\\\\.\\pipe\\threadbase-pty-host-${instanceId}`;
11127
11208
  }
11128
- const dir = process.env.THREADBASE_CONFIG_DIR ?? join17(homedir7(), ".threadbase");
11129
- return join17(dir, "run", `pty-host-${instanceId}.sock`);
11209
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? join18(homedir8(), ".threadbase");
11210
+ return join18(dir, "run", `pty-host-${instanceId}.sock`);
11130
11211
  }
11131
11212
  function socketTransport(socket) {
11132
11213
  socket.setEncoding("utf8");
@@ -11201,8 +11282,8 @@ import {
11201
11282
  ConversationScanner
11202
11283
  } from "@threadbase-sh/scanner";
11203
11284
  import { statSync as statSync7 } from "fs";
11204
- import { homedir as homedir9 } from "os";
11205
- import { join as join19 } from "path";
11285
+ import { homedir as homedir10 } from "os";
11286
+ import { join as join20 } from "path";
11206
11287
 
11207
11288
  // src/services/cache/cacheMetadata.ts
11208
11289
  function getCacheMetadata(repo, key) {
@@ -11302,9 +11383,9 @@ function refreshConversationCache(deps) {
11302
11383
 
11303
11384
  // src/services/conversations/shouldRefreshProjectsFromHdd.ts
11304
11385
  import { readdirSync as readdirSync4, statSync as statSync6 } from "fs";
11305
- import { homedir as homedir8 } from "os";
11306
- import { join as join18 } from "path";
11307
- var DEFAULT_PROJECTS_DIR = join18(homedir8(), ".claude", "projects");
11386
+ import { homedir as homedir9 } from "os";
11387
+ import { join as join19 } from "path";
11388
+ var DEFAULT_PROJECTS_DIR = join19(homedir9(), ".claude", "projects");
11308
11389
  function maxProjectsTreeMtimeMs(projectsDir) {
11309
11390
  let maxMs;
11310
11391
  try {
@@ -11316,7 +11397,7 @@ function maxProjectsTreeMtimeMs(projectsDir) {
11316
11397
  for (const ent of readdirSync4(projectsDir, { withFileTypes: true })) {
11317
11398
  if (!ent.isDirectory()) continue;
11318
11399
  try {
11319
- const childMs = statSync6(join18(projectsDir, ent.name)).mtimeMs;
11400
+ const childMs = statSync6(join19(projectsDir, ent.name)).mtimeMs;
11320
11401
  if (childMs > maxMs) maxMs = childMs;
11321
11402
  } catch {
11322
11403
  }
@@ -11488,9 +11569,9 @@ var ScannerManager = class {
11488
11569
  projectsDirs() {
11489
11570
  const profiles = this.deps.scanProfiles;
11490
11571
  if (profiles && profiles.length > 0) {
11491
- return profiles.filter((p) => p.enabled).map((p) => join19(p.configDir, "projects"));
11572
+ return profiles.filter((p) => p.enabled).map((p) => join20(p.configDir, "projects"));
11492
11573
  }
11493
- return [join19(homedir9(), ".claude", "projects")];
11574
+ return [join20(homedir10(), ".claude", "projects")];
11494
11575
  }
11495
11576
  // ─── staleness ────────────────────────────────────────────────────
11496
11577
  // Drain the stale set and disarm the flag together. The caller owns the
@@ -11771,8 +11852,8 @@ import { statSync as statSync9 } from "fs";
11771
11852
 
11772
11853
  // src/handlers/handleListProjects.ts
11773
11854
  import { closeSync as closeSync4, openSync as openSync4, readdirSync as readdirSync5, readSync as readSync4, statSync as statSync8 } from "fs";
11774
- import { homedir as homedir10 } from "os";
11775
- import { join as join20 } from "path";
11855
+ import { homedir as homedir11 } from "os";
11856
+ import { join as join21 } from "path";
11776
11857
  var HEAD_BYTES = 64 * 1024;
11777
11858
  var MAX_FILES_PROBED = 3;
11778
11859
  function readRecordedCwd(dir) {
@@ -11785,7 +11866,7 @@ function readRecordedCwd(dir) {
11785
11866
  for (const file of files.slice(0, MAX_FILES_PROBED)) {
11786
11867
  let fd;
11787
11868
  try {
11788
- fd = openSync4(join20(dir, file), "r");
11869
+ fd = openSync4(join21(dir, file), "r");
11789
11870
  const buf = Buffer.alloc(HEAD_BYTES);
11790
11871
  const bytes = readSync4(fd, buf, 0, HEAD_BYTES, 0);
11791
11872
  for (const line of buf.subarray(0, bytes).toString("utf8").split("\n")) {
@@ -11809,11 +11890,11 @@ function decodeProjectPath(dirName) {
11809
11890
  function handleListProjects(url, res) {
11810
11891
  const limit = Math.max(1, parseInt(url.searchParams.get("limit") ?? "50", 10) || 50);
11811
11892
  const offset = Math.max(0, parseInt(url.searchParams.get("offset") ?? "0", 10) || 0);
11812
- const projectsDir = join20(homedir10(), ".claude", "projects");
11893
+ const projectsDir = join21(homedir11(), ".claude", "projects");
11813
11894
  let entries;
11814
11895
  try {
11815
11896
  entries = readdirSync5(projectsDir).map((dirName) => {
11816
- const fullPath = join20(projectsDir, dirName);
11897
+ const fullPath = join21(projectsDir, dirName);
11817
11898
  let mtime = 0;
11818
11899
  try {
11819
11900
  mtime = statSync8(fullPath).mtimeMs;
@@ -11828,7 +11909,7 @@ function handleListProjects(url, res) {
11828
11909
  }
11829
11910
  const total = entries.length;
11830
11911
  const page = entries.slice(offset, offset + limit).map(({ dirName }) => {
11831
- const path = readRecordedCwd(join20(projectsDir, dirName)) ?? decodeProjectPath(String(dirName));
11912
+ const path = readRecordedCwd(join21(projectsDir, dirName)) ?? decodeProjectPath(String(dirName));
11832
11913
  const name = path.split(/[\\/]/).filter(Boolean).pop() ?? dirName;
11833
11914
  return { name, path, dirName };
11834
11915
  });
@@ -12230,16 +12311,16 @@ import { createHash as createHash4 } from "crypto";
12230
12311
  import { existsSync as existsSync12 } from "fs";
12231
12312
 
12232
12313
  // src/services/cache-integrity/alertStore.ts
12233
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
12234
- import { homedir as homedir11 } from "os";
12235
- import { dirname as dirname9, join as join21 } from "path";
12314
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
12315
+ import { homedir as homedir12 } from "os";
12316
+ import { dirname as dirname10, join as join22 } from "path";
12236
12317
  function alertStatePath() {
12237
- const dir = process.env.THREADBASE_CONFIG_DIR ?? join21(homedir11(), ".threadbase");
12238
- return join21(dir, "cache-alert.json");
12318
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? join22(homedir12(), ".threadbase");
12319
+ return join22(dir, "cache-alert.json");
12239
12320
  }
12240
12321
  function loadAlertState() {
12241
12322
  try {
12242
- const parsed = JSON.parse(readFileSync8(alertStatePath(), "utf-8"));
12323
+ const parsed = JSON.parse(readFileSync9(alertStatePath(), "utf-8"));
12243
12324
  return parsed && typeof parsed === "object" ? parsed : {};
12244
12325
  } catch {
12245
12326
  return {};
@@ -12247,14 +12328,14 @@ function loadAlertState() {
12247
12328
  }
12248
12329
  function saveAlertState(state) {
12249
12330
  const path = alertStatePath();
12250
- mkdirSync4(dirname9(path), { recursive: true });
12251
- writeFileSync3(path, `${JSON.stringify(state, null, 2)}
12331
+ mkdirSync5(dirname10(path), { recursive: true });
12332
+ writeFileSync4(path, `${JSON.stringify(state, null, 2)}
12252
12333
  `);
12253
12334
  }
12254
12335
 
12255
12336
  // src/services/cache-integrity/backup.ts
12256
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync6, statSync as statSync10, unlinkSync } from "fs";
12257
- import { join as join22 } from "path";
12337
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync6, statSync as statSync10, unlinkSync } from "fs";
12338
+ import { join as join23 } from "path";
12258
12339
  var DEFAULT_RETAIN = 3;
12259
12340
  function retainCount() {
12260
12341
  const parsed = Number.parseInt(process.env.THREADBASE_CACHE_BACKUP_RETAIN ?? "", 10);
@@ -12265,13 +12346,13 @@ function timestamp(d) {
12265
12346
  return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
12266
12347
  }
12267
12348
  async function backupCacheDb(db, cacheDir) {
12268
- const backupsDir = join22(cacheDir, "backups");
12269
- mkdirSync5(backupsDir, { recursive: true });
12270
- const destPath = join22(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
12349
+ const backupsDir = join23(cacheDir, "backups");
12350
+ mkdirSync6(backupsDir, { recursive: true });
12351
+ const destPath = join23(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
12271
12352
  await db.backup(destPath);
12272
12353
  const retain = retainCount();
12273
12354
  const backups = readdirSync6(backupsDir).filter((f) => f.startsWith("cache-") && f.endsWith(".db")).map((f) => {
12274
- const full = join22(backupsDir, f);
12355
+ const full = join23(backupsDir, f);
12275
12356
  return { full, mtime: statSync10(full).mtimeMs };
12276
12357
  }).sort((a, b) => b.mtime - a.mtime);
12277
12358
  for (const stale of backups.slice(retain)) {
@@ -14239,9 +14320,9 @@ function discoveredToResponse(d, conversationId) {
14239
14320
  }
14240
14321
 
14241
14322
  // src/session-watchers.ts
14242
- import { existsSync as existsSync15, watch as fsWatch, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync12 } from "fs";
14243
- import { homedir as homedir12 } from "os";
14244
- import { basename as basename6, join as join23 } from "path";
14323
+ import { existsSync as existsSync15, watch as fsWatch, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync12 } from "fs";
14324
+ import { homedir as homedir13 } from "os";
14325
+ import { basename as basename6, join as join24 } from "path";
14245
14326
  var SessionWatchers = class {
14246
14327
  constructor(deps) {
14247
14328
  this.deps = deps;
@@ -14307,7 +14388,7 @@ var SessionWatchers = class {
14307
14388
  // file isn't slurped in full.
14308
14389
  readFirstLineSessionId(filePath) {
14309
14390
  try {
14310
- const content = readFileSync9(filePath, "utf8");
14391
+ const content = readFileSync10(filePath, "utf8");
14311
14392
  const nl = content.indexOf("\n");
14312
14393
  const firstLine = nl === -1 ? content : content.slice(0, nl);
14313
14394
  if (!firstLine.trim()) return null;
@@ -14322,9 +14403,9 @@ var SessionWatchers = class {
14322
14403
  // was passed to Claude via --session-id so the filename matches from the start.
14323
14404
  watchForJsonl(sessionId, projectPath) {
14324
14405
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
14325
- const projectsDir = join23(homedir12(), ".claude", "projects", encoded);
14406
+ const projectsDir = join24(homedir13(), ".claude", "projects", encoded);
14326
14407
  const expectedFile = `${sessionId}.jsonl`;
14327
- const filePath = join23(projectsDir, expectedFile);
14408
+ const filePath = join24(projectsDir, expectedFile);
14328
14409
  const deadline = Date.now() + 12e4;
14329
14410
  let watcher = null;
14330
14411
  const cleanup = () => {
@@ -14346,10 +14427,10 @@ var SessionWatchers = class {
14346
14427
  if (!resolvedFilePath && existsSync15(projectsDir)) {
14347
14428
  try {
14348
14429
  const now = Date.now();
14349
- const match = readdirSync7(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync12(join23(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
14350
- ({ f }) => basename6(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join23(projectsDir, f)) === sessionId
14430
+ const match = readdirSync7(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync12(join24(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
14431
+ ({ f }) => basename6(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join24(projectsDir, f)) === sessionId
14351
14432
  ).sort((a, b) => b.mtime - a.mtime)[0];
14352
- if (match) resolvedFilePath = join23(projectsDir, match.f);
14433
+ if (match) resolvedFilePath = join24(projectsDir, match.f);
14353
14434
  } catch {
14354
14435
  }
14355
14436
  }
@@ -14357,7 +14438,7 @@ var SessionWatchers = class {
14357
14438
  cleanup();
14358
14439
  this.deps.sessionFileMap.set(sessionId, resolvedFilePath);
14359
14440
  try {
14360
- const existing = readFileSync9(resolvedFilePath, "utf8").split("\n").filter(Boolean);
14441
+ const existing = readFileSync10(resolvedFilePath, "utf8").split("\n").filter(Boolean);
14361
14442
  if (existing.length > 0) {
14362
14443
  this.deps.broadcastConversationLines(sessionId, existing);
14363
14444
  }
@@ -14393,7 +14474,7 @@ var SessionWatchers = class {
14393
14474
  watchForCodexRollout(sessionId, projectPath) {
14394
14475
  const deadline = Date.now() + 12e4;
14395
14476
  const now = /* @__PURE__ */ new Date();
14396
- const dateDir = join23(
14477
+ const dateDir = join24(
14397
14478
  String(now.getFullYear()),
14398
14479
  String(now.getMonth() + 1).padStart(2, "0"),
14399
14480
  String(now.getDate()).padStart(2, "0")
@@ -14406,7 +14487,7 @@ var SessionWatchers = class {
14406
14487
  };
14407
14488
  const matchesProjectPath = (candidatePath) => {
14408
14489
  try {
14409
- const firstLine = readFileSync9(candidatePath, "utf8").split("\n", 1)[0];
14490
+ const firstLine = readFileSync10(candidatePath, "utf8").split("\n", 1)[0];
14410
14491
  if (!firstLine) return null;
14411
14492
  const parsed = JSON.parse(firstLine);
14412
14493
  if (parsed?.type !== "session_meta") return null;
@@ -14434,7 +14515,7 @@ var SessionWatchers = class {
14434
14515
  this.deps.sessionStore.listManaged().filter((s) => s.id !== sessionId && s.boundConversationId != null).map((s) => s.boundConversationId)
14435
14516
  );
14436
14517
  for (const root of this.deps.codexRoots) {
14437
- const sessionsDir = join23(root, dateDir);
14518
+ const sessionsDir = join24(root, dateDir);
14438
14519
  if (!existsSync15(sessionsDir)) continue;
14439
14520
  let candidateFiles;
14440
14521
  try {
@@ -14443,9 +14524,9 @@ var SessionWatchers = class {
14443
14524
  continue;
14444
14525
  }
14445
14526
  const nowMs = Date.now();
14446
- const recentCandidates = candidateFiles.map((f) => ({ f, mtime: statSync12(join23(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
14527
+ const recentCandidates = candidateFiles.map((f) => ({ f, mtime: statSync12(join24(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
14447
14528
  for (const { f } of recentCandidates) {
14448
- const candidatePath = join23(sessionsDir, f);
14529
+ const candidatePath = join24(sessionsDir, f);
14449
14530
  const match = matchesProjectPath(candidatePath);
14450
14531
  if (!match) continue;
14451
14532
  if (boundElsewhere.has(match.id)) continue;
@@ -14466,7 +14547,7 @@ var SessionWatchers = class {
14466
14547
  this.deps.sessionFileMap.set(sessionId, candidatePath);
14467
14548
  this.deps.fileWatcher.watch(candidatePath);
14468
14549
  try {
14469
- const existing = readFileSync9(candidatePath, "utf8").split("\n").filter(Boolean);
14550
+ const existing = readFileSync10(candidatePath, "utf8").split("\n").filter(Boolean);
14470
14551
  if (existing.length > 0) {
14471
14552
  this.deps.broadcastConversationLines(sessionId, existing);
14472
14553
  }
@@ -14849,7 +14930,7 @@ var StreamerServer = class {
14849
14930
  this.skipStartupWarmup = config.skipStartupWarmup ?? false;
14850
14931
  this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
14851
14932
  this.scanProfiles = config.scanProfiles;
14852
- this.codexRoots = config.codexRoots ?? [join24(homedir13(), ".codex", "sessions")];
14933
+ this.codexRoots = config.codexRoots ?? [join25(homedir14(), ".codex", "sessions")];
14853
14934
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
14854
14935
  this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
14855
14936
  const flagResolution = resolveFeatureFlags({
@@ -14866,7 +14947,7 @@ var StreamerServer = class {
14866
14947
  this.claudeFlagsPersistable = config.claudeFlags === void 0;
14867
14948
  this.claudeFlags = config.claudeFlags ?? loadClaudeFlags();
14868
14949
  this.claudeExtraArgs = config.claudeExtraArgs ?? loadClaudeExtraArgs();
14869
- this.cacheDir = config.cacheDir ?? loadCacheDir() ?? join24(homedir13(), ".threadbase", "cache");
14950
+ this.cacheDir = config.cacheDir ?? loadCacheDir() ?? join25(homedir14(), ".threadbase", "cache");
14870
14951
  this.runtimeDbPath = resolveRuntimeDbPath(config.runtimeDbPath);
14871
14952
  this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
14872
14953
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
@@ -15043,7 +15124,7 @@ var StreamerServer = class {
15043
15124
  temporalClient,
15044
15125
  taskQueue: agentConfig.temporal.taskQueue
15045
15126
  });
15046
- const conversationsBaseDir = agentConfig.conversationsDir || join24(dirname10(this.cacheDir), "conversations");
15127
+ const conversationsBaseDir = agentConfig.conversationsDir || join25(dirname11(this.cacheDir), "conversations");
15047
15128
  conversationWriter = createConversationWriter({
15048
15129
  baseDir: conversationsBaseDir
15049
15130
  });
@@ -15536,7 +15617,7 @@ var StreamerServer = class {
15536
15617
  }
15537
15618
  try {
15538
15619
  this.cache = ConversationCache.open(
15539
- join24(this.cacheDir, "cache.db"),
15620
+ join25(this.cacheDir, "cache.db"),
15540
15621
  this.tailSize,
15541
15622
  void 0,
15542
15623
  {