@threadbase-sh/streamer 1.39.1 → 1.41.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/cli.cjs CHANGED
@@ -4121,7 +4121,7 @@ var require_transport = __commonJS({
4121
4121
  "node_modules/pino/lib/transport.js"(exports2, module2) {
4122
4122
  "use strict";
4123
4123
  var { createRequire } = require("module");
4124
- var { existsSync: existsSync15 } = require("fs");
4124
+ var { existsSync: existsSync16 } = require("fs");
4125
4125
  var getCallers = require_caller();
4126
4126
  var { join: join29, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
4127
4127
  var { fileURLToPath: fileURLToPath3 } = require("url");
@@ -4195,7 +4195,7 @@ var require_transport = __commonJS({
4195
4195
  return false;
4196
4196
  }
4197
4197
  }
4198
- return isAbsolute2(path2) && !existsSync15(path2);
4198
+ return isAbsolute2(path2) && !existsSync16(path2);
4199
4199
  }
4200
4200
  function stripQuotes(value) {
4201
4201
  const first = value[0];
@@ -5937,7 +5937,7 @@ var require_pino = __commonJS({
5937
5937
  } = symbols;
5938
5938
  var { epochTime, nullTime } = time3;
5939
5939
  var { pid } = process;
5940
- var hostname5 = os2.hostname();
5940
+ var hostname6 = os2.hostname();
5941
5941
  var defaultErrorSerializer = stdSerializers.err;
5942
5942
  var defaultOptions2 = {
5943
5943
  level: "info",
@@ -5947,7 +5947,7 @@ var require_pino = __commonJS({
5947
5947
  errorKey: "err",
5948
5948
  nestedKey: null,
5949
5949
  enabled: true,
5950
- base: { pid, hostname: hostname5 },
5950
+ base: { pid, hostname: hostname6 },
5951
5951
  serializers: Object.assign(/* @__PURE__ */ Object.create(null), {
5952
5952
  err: defaultErrorSerializer
5953
5953
  }),
@@ -6213,6 +6213,12 @@ var init_feature_flags = __esm({
6213
6213
  description: "Send the built system prompt to fresh Codex sessions. Off by default: Codex has no --system-prompt flag, so the prompt goes in the positional [PROMPT] argument, which Codex treats as the user's opening turn rather than a system-level instruction.",
6214
6214
  default: false,
6215
6215
  env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT"
6216
+ },
6217
+ {
6218
+ id: "sessionRehydration",
6219
+ description: "Seed the session list at boot with sessions a previous streamer run left behind, so a restart leaves them one tap from resuming instead of silently gone. On by default, with a kill switch: it changes what GET /api/sessions contains.",
6220
+ default: true,
6221
+ env: "THREADBASE_FEATURE_SESSION_REHYDRATION"
6216
6222
  }
6217
6223
  ];
6218
6224
  }
@@ -38620,8 +38626,8 @@ var require_service_config = __commonJS({
38620
38626
  }
38621
38627
  if (Array.isArray(validatedConfig.clientHostname)) {
38622
38628
  let hostnameMatched = false;
38623
- for (const hostname5 of validatedConfig.clientHostname) {
38624
- if (hostname5 === os2.hostname()) {
38629
+ for (const hostname6 of validatedConfig.clientHostname) {
38630
+ if (hostname6 === os2.hostname()) {
38625
38631
  hostnameMatched = true;
38626
38632
  }
38627
38633
  }
@@ -51190,8 +51196,8 @@ var require_single_subchannel_channel = __commonJS({
51190
51196
  if (splitPath2.length >= 2) {
51191
51197
  serviceName = splitPath2[1];
51192
51198
  }
51193
- const hostname5 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.options.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
51194
- this.serviceUrl = `https://${hostname5}/${serviceName}`;
51199
+ const hostname6 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.options.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
51200
+ this.serviceUrl = `https://${hostname6}/${serviceName}`;
51195
51201
  const timeout = (0, deadline_1.getRelativeTimeout)(options.deadline);
51196
51202
  if (timeout !== Infinity) {
51197
51203
  if (timeout <= 0) {
@@ -51838,8 +51844,8 @@ var require_resolver_dns = __commonJS({
51838
51844
  }
51839
51845
  trace("Looking up DNS hostname " + this.dnsHostname);
51840
51846
  this.latestLookupResult = null;
51841
- const hostname5 = this.dnsHostname;
51842
- this.pendingLookupPromise = this.lookup(hostname5);
51847
+ const hostname6 = this.dnsHostname;
51848
+ this.pendingLookupPromise = this.lookup(hostname6);
51843
51849
  this.pendingLookupPromise.then((addressList) => {
51844
51850
  if (this.pendingLookupPromise === null) {
51845
51851
  return;
@@ -51862,7 +51868,7 @@ var require_resolver_dns = __commonJS({
51862
51868
  this.listener((0, call_interface_1.statusOrFromError)(this.defaultResolutionError), {}, this.latestServiceConfigResult, "");
51863
51869
  });
51864
51870
  if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) {
51865
- this.pendingTxtPromise = this.resolveTxt(hostname5);
51871
+ this.pendingTxtPromise = this.resolveTxt(hostname6);
51866
51872
  this.pendingTxtPromise.then((txtRecord) => {
51867
51873
  if (this.pendingTxtPromise === null) {
51868
51874
  return;
@@ -51904,12 +51910,12 @@ var require_resolver_dns = __commonJS({
51904
51910
  this.continueResolving = true;
51905
51911
  }
51906
51912
  }
51907
- async lookup(hostname5) {
51913
+ async lookup(hostname6) {
51908
51914
  if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
51909
51915
  trace("Using alternative DNS resolver.");
51910
51916
  const records = await Promise.allSettled([
51911
- this.alternativeResolver.resolve4(hostname5),
51912
- this.alternativeResolver.resolve6(hostname5)
51917
+ this.alternativeResolver.resolve4(hostname6),
51918
+ this.alternativeResolver.resolve6(hostname6)
51913
51919
  ]);
51914
51920
  if (records.every((result) => result.status === "rejected")) {
51915
51921
  throw new Error(records[0].reason);
@@ -51921,15 +51927,15 @@ var require_resolver_dns = __commonJS({
51921
51927
  port: +this.port
51922
51928
  }));
51923
51929
  }
51924
- const addressList = await dns_1.promises.lookup(hostname5, { all: true });
51930
+ const addressList = await dns_1.promises.lookup(hostname6, { all: true });
51925
51931
  return addressList.map((addr) => ({ host: addr.address, port: +this.port }));
51926
51932
  }
51927
- async resolveTxt(hostname5) {
51933
+ async resolveTxt(hostname6) {
51928
51934
  if (environment_1.GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
51929
51935
  trace("Using alternative DNS resolver.");
51930
- return this.alternativeResolver.resolveTxt(hostname5);
51936
+ return this.alternativeResolver.resolveTxt(hostname6);
51931
51937
  }
51932
- return dns_1.promises.resolveTxt(hostname5);
51938
+ return dns_1.promises.resolveTxt(hostname6);
51933
51939
  }
51934
51940
  startNextResolutionTimer() {
51935
51941
  var _a3, _b;
@@ -52057,13 +52063,13 @@ var require_http_proxy = __commonJS({
52057
52063
  userCred = proxyUrl.username;
52058
52064
  }
52059
52065
  }
52060
- const hostname5 = proxyUrl.hostname;
52066
+ const hostname6 = proxyUrl.hostname;
52061
52067
  let port = proxyUrl.port;
52062
52068
  if (port === "") {
52063
52069
  port = "80";
52064
52070
  }
52065
52071
  const result = {
52066
- address: `${hostname5}:${port}`
52072
+ address: `${hostname6}:${port}`
52067
52073
  };
52068
52074
  if (userCred) {
52069
52075
  result.creds = userCred;
@@ -53376,8 +53382,8 @@ var require_load_balancing_call = __commonJS({
53376
53382
  if (splitPath2.length >= 2) {
53377
53383
  serviceName = splitPath2[1];
53378
53384
  }
53379
- const hostname5 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
53380
- this.serviceUrl = `https://${hostname5}/${serviceName}`;
53385
+ const hostname6 = (_b = (_a3 = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a3 === void 0 ? void 0 : _a3.host) !== null && _b !== void 0 ? _b : "localhost";
53386
+ this.serviceUrl = `https://${hostname6}/${serviceName}`;
53381
53387
  this.startTime = /* @__PURE__ */ new Date();
53382
53388
  }
53383
53389
  getDeadlineInfo() {
@@ -91186,9 +91192,9 @@ var require_parse_host_uri = __commonJS({
91186
91192
  var ipv4Hostname = "(?:\\d{1,3}(?:\\.\\d{1,3}){3})";
91187
91193
  var ipv6Hostname = "(?:\\[(?<ipv6>[0-9a-fA-F.:]+)\\])";
91188
91194
  var dnsHostname = "(?:[^:/]+)";
91189
- var hostname5 = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
91195
+ var hostname6 = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
91190
91196
  var port = "(?::(?<port>\\d+))";
91191
- var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname5})${port}?$`);
91197
+ var protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname6})${port}?$`);
91192
91198
  function splitProtoHostPort(uri) {
91193
91199
  const match2 = protoHostPortRegex.exec(uri);
91194
91200
  if (!match2?.groups)
@@ -91200,9 +91206,9 @@ var require_parse_host_uri = __commonJS({
91200
91206
  };
91201
91207
  }
91202
91208
  function joinProtoHostPort(components) {
91203
- const { scheme: scheme2, hostname: hostname6, port: port2 } = components;
91209
+ const { scheme: scheme2, hostname: hostname7, port: port2 } = components;
91204
91210
  const schemeText = scheme2 ? `${scheme2}:` : "";
91205
- const hostnameText = hostname6.includes(":") ? `[${hostname6}]` : hostname6;
91211
+ const hostnameText = hostname7.includes(":") ? `[${hostname7}]` : hostname7;
91206
91212
  const portText = port2 !== void 0 ? `:${port2}` : "";
91207
91213
  return `${schemeText}${hostnameText}${portText}`;
91208
91214
  }
@@ -104140,11 +104146,11 @@ var require_pg_connection_string = __commonJS({
104140
104146
  config2.client_encoding = result.searchParams.get("encoding");
104141
104147
  return config2;
104142
104148
  }
104143
- const hostname5 = dummyHost ? "" : result.hostname;
104149
+ const hostname6 = dummyHost ? "" : result.hostname;
104144
104150
  if (!config2.host) {
104145
- config2.host = decodeURIComponent(hostname5);
104146
- } else if (hostname5 && /^%2f/i.test(hostname5)) {
104147
- result.pathname = hostname5 + result.pathname;
104151
+ config2.host = decodeURIComponent(hostname6);
104152
+ } else if (hostname6 && /^%2f/i.test(hostname6)) {
104153
+ result.pathname = hostname6 + result.pathname;
104148
104154
  }
104149
104155
  if (!config2.port) {
104150
104156
  config2.port = result.port;
@@ -124077,8 +124083,8 @@ function isAbiMismatch(message) {
124077
124083
  }
124078
124084
  function checkSqliteAbi() {
124079
124085
  try {
124080
- const Database3 = require("better-sqlite3");
124081
- const db = new Database3(":memory:");
124086
+ const Database4 = require("better-sqlite3");
124087
+ const db = new Database4(":memory:");
124082
124088
  db.close();
124083
124089
  } catch (err) {
124084
124090
  const message = err instanceof Error ? err.message : String(err);
@@ -135423,10 +135429,10 @@ async function search(query, options, scanner) {
135423
135429
  // src/server.ts
135424
135430
  var import_crypto13 = require("crypto");
135425
135431
  var import_events2 = require("events");
135426
- var import_fs29 = require("fs");
135432
+ var import_fs30 = require("fs");
135427
135433
  var import_promises16 = require("fs/promises");
135428
135434
  var import_http = require("http");
135429
- var import_os12 = require("os");
135435
+ var import_os13 = require("os");
135430
135436
  var import_path29 = require("path");
135431
135437
  var import_readline4 = require("readline");
135432
135438
 
@@ -138231,6 +138237,215 @@ var errorMiddleware = (err, c) => {
138231
138237
  return c.json({ error: message }, 500);
138232
138238
  };
138233
138239
 
138240
+ // src/api/routes/backup.routes.ts
138241
+ var import_os5 = require("os");
138242
+
138243
+ // src/services/backup/backup.ts
138244
+ var BACKUP_FORMAT_VERSION = 1;
138245
+ var BackupError = class extends Error {
138246
+ constructor(message, code) {
138247
+ super(message);
138248
+ this.code = code;
138249
+ }
138250
+ code;
138251
+ };
138252
+ function validateArchive(input) {
138253
+ if (!input || typeof input !== "object") {
138254
+ throw new BackupError("Backup is not an object", "INVALID_ARCHIVE");
138255
+ }
138256
+ const archive = input;
138257
+ const manifest = archive.manifest;
138258
+ if (!manifest || typeof manifest !== "object") {
138259
+ throw new BackupError("Backup is missing its manifest", "INVALID_ARCHIVE");
138260
+ }
138261
+ if (manifest.formatVersion !== BACKUP_FORMAT_VERSION) {
138262
+ throw new BackupError(
138263
+ `Unsupported backup format version ${String(manifest.formatVersion)}; this build reads version ${BACKUP_FORMAT_VERSION}`,
138264
+ "UNSUPPORTED_VERSION"
138265
+ );
138266
+ }
138267
+ if (!Array.isArray(archive.projects)) {
138268
+ throw new BackupError("Backup is missing its projects array", "INVALID_ARCHIVE");
138269
+ }
138270
+ for (const [i, p2] of archive.projects.entries()) {
138271
+ if (!p2 || typeof p2 !== "object") {
138272
+ throw new BackupError(`Project at index ${i} is not an object`, "INVALID_ARCHIVE");
138273
+ }
138274
+ if (typeof p2.id !== "string" || p2.id.length === 0) {
138275
+ throw new BackupError(`Project at index ${i} has no id`, "INVALID_ARCHIVE");
138276
+ }
138277
+ if (typeof p2.path !== "string" || p2.path.length === 0) {
138278
+ throw new BackupError(`Project at index ${i} has no path`, "INVALID_ARCHIVE");
138279
+ }
138280
+ }
138281
+ const ids = new Set(archive.projects.map((p2) => p2.id));
138282
+ if (ids.size !== archive.projects.length) {
138283
+ throw new BackupError("Backup contains duplicate project ids", "INVALID_ARCHIVE");
138284
+ }
138285
+ return archive;
138286
+ }
138287
+ function remapPaths(projects, rules) {
138288
+ const ordered = [...rules].sort((a, b2) => b2.from.length - a.from.length);
138289
+ return projects.map((p2) => {
138290
+ const rule = ordered.find((r) => p2.path === r.from || p2.path.startsWith(`${r.from}/`));
138291
+ if (!rule) return p2;
138292
+ return { ...p2, path: `${rule.to}${p2.path.slice(rule.from.length)}` };
138293
+ });
138294
+ }
138295
+ function planRestore(incoming, existing) {
138296
+ const byId = new Map(existing.map((e) => [e.id, e]));
138297
+ const byPath = new Map(existing.map((e) => [e.path, e]));
138298
+ const plan = { create: [], update: [], conflict: [] };
138299
+ for (const p2 of incoming) {
138300
+ const sameId = byId.get(p2.id);
138301
+ if (sameId) {
138302
+ if (sameId.path !== p2.path) plan.update.push(p2);
138303
+ continue;
138304
+ }
138305
+ const samePath = byPath.get(p2.path);
138306
+ if (samePath) {
138307
+ plan.conflict.push({ incoming: p2, existingId: samePath.id });
138308
+ continue;
138309
+ }
138310
+ plan.create.push(p2);
138311
+ }
138312
+ return plan;
138313
+ }
138314
+
138315
+ // src/version.ts
138316
+ var import_node_fs7 = require("fs");
138317
+ var import_node_path8 = require("path");
138318
+ var cached2;
138319
+ function getVersion() {
138320
+ if (cached2 !== void 0) return cached2;
138321
+ cached2 = resolveVersion();
138322
+ return cached2;
138323
+ }
138324
+ function resolveVersion() {
138325
+ const scriptPath = process.argv[1] ?? "";
138326
+ const here = scriptPath ? (0, import_node_path8.dirname)(scriptPath) : process.cwd();
138327
+ let realHere = here;
138328
+ try {
138329
+ realHere = (0, import_node_path8.dirname)((0, import_node_fs7.realpathSync)(scriptPath));
138330
+ } catch {
138331
+ }
138332
+ const searchDirs = realHere === here ? [here, (0, import_node_path8.join)(here, "..")] : [here, (0, import_node_path8.join)(here, ".."), realHere, (0, import_node_path8.join)(realHere, "..")];
138333
+ for (const dir of searchDirs) {
138334
+ try {
138335
+ const v2 = (0, import_node_fs7.readFileSync)((0, import_node_path8.join)(dir, "version.txt"), "utf8").trim();
138336
+ if (v2) return v2;
138337
+ } catch {
138338
+ }
138339
+ }
138340
+ try {
138341
+ const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path8.join)(here, "..", "package.json"), "utf8"));
138342
+ if (pkg.version) return `${pkg.version}+source`;
138343
+ } catch {
138344
+ }
138345
+ return "0.0.0+unknown";
138346
+ }
138347
+
138348
+ // src/api/routes/backup.routes.ts
138349
+ function readBody(c) {
138350
+ return new Promise((resolve4, reject) => {
138351
+ const chunks = [];
138352
+ c.env.incoming.on("data", (chunk) => chunks.push(chunk));
138353
+ c.env.incoming.on("end", () => {
138354
+ try {
138355
+ const raw2 = Buffer.concat(chunks).toString("utf-8");
138356
+ resolve4(raw2 ? JSON.parse(raw2) : {});
138357
+ } catch {
138358
+ reject(new Error("Invalid JSON body"));
138359
+ }
138360
+ });
138361
+ c.env.incoming.on("error", reject);
138362
+ });
138363
+ }
138364
+ var createBackupRoutes = (deps) => {
138365
+ const app = new Hono2();
138366
+ app.get("/export", (c) => {
138367
+ const repo = deps.projectsRepo();
138368
+ if (!repo) {
138369
+ return c.json({ error: "Project store is unavailable", code: "STORE_UNAVAILABLE" }, 503);
138370
+ }
138371
+ const projects = repo.listProjects().map((p2) => ({
138372
+ id: p2.id,
138373
+ path: p2.path,
138374
+ name: p2.name ?? null,
138375
+ createdAt: p2.createdAt,
138376
+ updatedAt: p2.updatedAt
138377
+ }));
138378
+ return c.json({
138379
+ manifest: {
138380
+ formatVersion: BACKUP_FORMAT_VERSION,
138381
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
138382
+ streamerVersion: getVersion(),
138383
+ sourceHost: (0, import_os5.hostname)(),
138384
+ // No endpoint here exports the API key. The flag is recorded so an
138385
+ // archive is self-describing about its own sensitivity rather than
138386
+ // requiring a reader to infer it.
138387
+ includesSecrets: false,
138388
+ counts: { projects: projects.length }
138389
+ },
138390
+ projects
138391
+ });
138392
+ });
138393
+ app.post("/restore", async (c) => {
138394
+ const repo = deps.projectsRepo();
138395
+ if (!repo) {
138396
+ return c.json({ error: "Project store is unavailable", code: "STORE_UNAVAILABLE" }, 503);
138397
+ }
138398
+ let body;
138399
+ try {
138400
+ body = await readBody(c);
138401
+ } catch {
138402
+ return c.json({ error: "Invalid JSON body", code: "INVALID_BODY" }, 400);
138403
+ }
138404
+ let archive;
138405
+ try {
138406
+ archive = validateArchive(body.archive);
138407
+ } catch (err) {
138408
+ if (err instanceof BackupError) {
138409
+ return c.json({ error: err.message, code: err.code }, 400);
138410
+ }
138411
+ throw err;
138412
+ }
138413
+ const rules = Array.isArray(body.pathMap) ? body.pathMap.filter((r) => typeof r?.from === "string" && typeof r?.to === "string").map((r) => ({ from: r.from, to: r.to })) : [];
138414
+ const incoming = rules.length > 0 ? remapPaths(archive.projects, rules) : archive.projects;
138415
+ const existing = repo.listProjects().map((p2) => ({ id: p2.id, path: p2.path }));
138416
+ const plan = planRestore(incoming, existing);
138417
+ const summary = {
138418
+ create: plan.create.length,
138419
+ update: plan.update.length,
138420
+ conflict: plan.conflict.length
138421
+ };
138422
+ if (body.apply !== true) {
138423
+ return c.json({ applied: false, summary, plan });
138424
+ }
138425
+ if (plan.conflict.length > 0) {
138426
+ return c.json(
138427
+ {
138428
+ error: "Restore has unresolved conflicts",
138429
+ code: "RESTORE_CONFLICT",
138430
+ summary,
138431
+ plan
138432
+ },
138433
+ 409
138434
+ );
138435
+ }
138436
+ let applied = 0;
138437
+ for (const p2 of [...plan.create, ...plan.update]) {
138438
+ try {
138439
+ repo.upsertProjectByPath(p2.path, { name: p2.name });
138440
+ applied++;
138441
+ } catch {
138442
+ }
138443
+ }
138444
+ return c.json({ applied: true, summary, appliedCount: applied });
138445
+ });
138446
+ return app;
138447
+ };
138448
+
138234
138449
  // src/api/routes/browse.routes.ts
138235
138450
  var ALREADY_HANDLED = 597;
138236
138451
  var alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });
@@ -138413,38 +138628,251 @@ var createDeviceRoutes = (deps) => {
138413
138628
  return app;
138414
138629
  };
138415
138630
 
138416
- // src/version.ts
138417
- var import_node_fs7 = require("fs");
138418
- var import_node_path8 = require("path");
138419
- var cached2;
138420
- function getVersion() {
138421
- if (cached2 !== void 0) return cached2;
138422
- cached2 = resolveVersion();
138423
- return cached2;
138424
- }
138425
- function resolveVersion() {
138426
- const scriptPath = process.argv[1] ?? "";
138427
- const here = scriptPath ? (0, import_node_path8.dirname)(scriptPath) : process.cwd();
138428
- let realHere = here;
138429
- try {
138430
- realHere = (0, import_node_path8.dirname)((0, import_node_fs7.realpathSync)(scriptPath));
138431
- } catch {
138631
+ // src/api/routes/diagnostics.routes.ts
138632
+ var import_fs14 = require("fs");
138633
+
138634
+ // src/platform.ts
138635
+ var import_child_process = require("child_process");
138636
+ var import_fs13 = require("fs");
138637
+ var import_os6 = require("os");
138638
+ var import_path13 = require("path");
138639
+ var isWindows2 = (0, import_os6.platform)() === "win32";
138640
+ var _claudeExe;
138641
+ function resolveClaudeExe() {
138642
+ if (_claudeExe !== void 0) return _claudeExe;
138643
+ if (isWindows2) {
138644
+ try {
138645
+ const found = (0, import_child_process.execFileSync)("where.exe", ["claude"], {
138646
+ encoding: "utf-8",
138647
+ windowsHide: true,
138648
+ timeout: 3e3
138649
+ }).trim().split("\n")[0].trim();
138650
+ if (found) {
138651
+ _claudeExe = found;
138652
+ return _claudeExe;
138653
+ }
138654
+ } catch {
138655
+ }
138656
+ const candidates = [
138657
+ (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "claude.exe"),
138658
+ (0, import_path13.join)(
138659
+ process.env.LOCALAPPDATA ?? (0, import_path13.join)((0, import_os6.homedir)(), "AppData", "Local"),
138660
+ "Microsoft",
138661
+ "WindowsApps",
138662
+ "claude.exe"
138663
+ )
138664
+ ];
138665
+ for (const p2 of candidates) {
138666
+ if ((0, import_fs13.existsSync)(p2)) {
138667
+ _claudeExe = p2;
138668
+ return _claudeExe;
138669
+ }
138670
+ }
138671
+ } else {
138672
+ try {
138673
+ const found = (0, import_child_process.execFileSync)("/usr/bin/which", ["claude"], {
138674
+ encoding: "utf-8",
138675
+ timeout: 3e3
138676
+ }).trim().split("\n")[0].trim();
138677
+ if (found && (0, import_fs13.existsSync)(found)) {
138678
+ _claudeExe = found;
138679
+ return _claudeExe;
138680
+ }
138681
+ } catch {
138682
+ }
138683
+ const candidates = [
138684
+ "/opt/homebrew/bin/claude",
138685
+ "/usr/local/bin/claude",
138686
+ (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "claude")
138687
+ ];
138688
+ for (const p2 of candidates) {
138689
+ if ((0, import_fs13.existsSync)(p2)) {
138690
+ _claudeExe = p2;
138691
+ return _claudeExe;
138692
+ }
138693
+ }
138432
138694
  }
138433
- const searchDirs = realHere === here ? [here, (0, import_node_path8.join)(here, "..")] : [here, (0, import_node_path8.join)(here, ".."), realHere, (0, import_node_path8.join)(realHere, "..")];
138434
- for (const dir of searchDirs) {
138695
+ _claudeExe = "claude";
138696
+ return _claudeExe;
138697
+ }
138698
+ var _codexExe;
138699
+ function resolveCodexExe() {
138700
+ if (_codexExe !== void 0) return _codexExe;
138701
+ if (isWindows2) {
138435
138702
  try {
138436
- const v2 = (0, import_node_fs7.readFileSync)((0, import_node_path8.join)(dir, "version.txt"), "utf8").trim();
138437
- if (v2) return v2;
138703
+ const found = (0, import_child_process.execFileSync)("where.exe", ["codex"], {
138704
+ encoding: "utf-8",
138705
+ windowsHide: true,
138706
+ timeout: 3e3
138707
+ }).trim().split("\n")[0].trim();
138708
+ if (found) {
138709
+ _codexExe = found;
138710
+ return _codexExe;
138711
+ }
138438
138712
  } catch {
138439
138713
  }
138714
+ const candidates = [
138715
+ (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "codex.exe"),
138716
+ (0, import_path13.join)(
138717
+ process.env.LOCALAPPDATA ?? (0, import_path13.join)((0, import_os6.homedir)(), "AppData", "Local"),
138718
+ "Microsoft",
138719
+ "WindowsApps",
138720
+ "codex.exe"
138721
+ )
138722
+ ];
138723
+ for (const p2 of candidates) {
138724
+ if ((0, import_fs13.existsSync)(p2)) {
138725
+ _codexExe = p2;
138726
+ return _codexExe;
138727
+ }
138728
+ }
138729
+ } else {
138730
+ try {
138731
+ const found = (0, import_child_process.execFileSync)("/usr/bin/which", ["codex"], {
138732
+ encoding: "utf-8",
138733
+ timeout: 3e3
138734
+ }).trim().split("\n")[0].trim();
138735
+ if (found && (0, import_fs13.existsSync)(found)) {
138736
+ _codexExe = found;
138737
+ return _codexExe;
138738
+ }
138739
+ } catch {
138740
+ }
138741
+ const candidates = [
138742
+ "/opt/homebrew/bin/codex",
138743
+ "/usr/local/bin/codex",
138744
+ (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "codex")
138745
+ ];
138746
+ for (const p2 of candidates) {
138747
+ if ((0, import_fs13.existsSync)(p2)) {
138748
+ _codexExe = p2;
138749
+ return _codexExe;
138750
+ }
138751
+ }
138752
+ }
138753
+ _codexExe = "codex";
138754
+ return _codexExe;
138755
+ }
138756
+
138757
+ // src/services/diagnostics/diagnostics.ts
138758
+ var DIAGNOSTICS_CONTRACT_VERSION = 1;
138759
+ function redactPath(path2) {
138760
+ if (!path2) return null;
138761
+ const parts = path2.split(/[/\\]/).filter(Boolean);
138762
+ if (parts.length <= 2) return parts.join("/");
138763
+ return `\u2026/${parts.slice(-2).join("/")}`;
138764
+ }
138765
+ function worstStatus(checks) {
138766
+ const rank = { ok: 0, unknown: 1, degraded: 2, failed: 3 };
138767
+ return checks.reduce(
138768
+ (worst, c) => rank[c.status] > rank[worst] ? c.status : worst,
138769
+ "ok"
138770
+ );
138771
+ }
138772
+ function buildReport(checks, now = /* @__PURE__ */ new Date()) {
138773
+ return {
138774
+ contractVersion: DIAGNOSTICS_CONTRACT_VERSION,
138775
+ generatedAt: now.toISOString(),
138776
+ overall: worstStatus(checks),
138777
+ checks
138778
+ };
138779
+ }
138780
+ var SECRET_KEY_RE = /(key|token|secret|password|passwd|credential|authorization|cookie)/i;
138781
+ function redactValue(value) {
138782
+ if (Array.isArray(value)) {
138783
+ return value.map((v2) => redactValue(v2));
138784
+ }
138785
+ if (value && typeof value === "object") {
138786
+ const out = {};
138787
+ for (const [k2, v2] of Object.entries(value)) {
138788
+ out[k2] = SECRET_KEY_RE.test(k2) ? "[redacted]" : redactValue(v2);
138789
+ }
138790
+ return out;
138440
138791
  }
138792
+ return value;
138793
+ }
138794
+
138795
+ // src/api/routes/diagnostics.routes.ts
138796
+ function providerCheck(name, resolve4) {
138441
138797
  try {
138442
- const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path8.join)(here, "..", "package.json"), "utf8"));
138443
- if (pkg.version) return `${pkg.version}+source`;
138798
+ const exe = resolve4();
138799
+ return {
138800
+ id: `provider:${name}`,
138801
+ status: "ok",
138802
+ summary: `${name} CLI is installed.`,
138803
+ remediation: "NONE",
138804
+ detail: { location: redactPath(exe) }
138805
+ };
138444
138806
  } catch {
138807
+ return {
138808
+ id: `provider:${name}`,
138809
+ status: "failed",
138810
+ summary: `${name} CLI could not be located. Sessions for this provider cannot start.`,
138811
+ remediation: "PROVIDER_NOT_INSTALLED"
138812
+ };
138445
138813
  }
138446
- return "0.0.0+unknown";
138447
138814
  }
138815
+ var createDiagnosticsRoutes = (deps) => {
138816
+ const app = new Hono2();
138817
+ app.get("/", (c) => {
138818
+ const checks = [];
138819
+ checks.push({
138820
+ id: "streamer",
138821
+ status: "ok",
138822
+ summary: "Streamer is running.",
138823
+ remediation: "NONE",
138824
+ detail: { version: getVersion(), uptimeSeconds: Math.floor(process.uptime()) }
138825
+ });
138826
+ checks.push(providerCheck("claude-code", resolveClaudeExe));
138827
+ checks.push(providerCheck("codex-cli", resolveCodexExe));
138828
+ const cacheAlert = deps.cacheMonitor()?.healthzField();
138829
+ checks.push(
138830
+ cacheAlert ? {
138831
+ id: "cache",
138832
+ status: "degraded",
138833
+ summary: "Conversation cache reported an integrity alert.",
138834
+ remediation: "CACHE_DEGRADED"
138835
+ } : {
138836
+ id: "cache",
138837
+ status: "ok",
138838
+ summary: "Conversation cache is healthy.",
138839
+ remediation: "NONE"
138840
+ }
138841
+ );
138842
+ let ptyOk = true;
138843
+ try {
138844
+ require.resolve("node-pty");
138845
+ } catch {
138846
+ ptyOk = false;
138847
+ }
138848
+ checks.push(
138849
+ ptyOk ? { id: "pty", status: "ok", summary: "PTY subsystem is available.", remediation: "NONE" } : {
138850
+ id: "pty",
138851
+ status: "failed",
138852
+ summary: "node-pty failed to load, so no managed session can start.",
138853
+ remediation: "PTY_UNAVAILABLE"
138854
+ }
138855
+ );
138856
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
138857
+ const claudeProjects = home ? `${home}/.claude/projects` : "";
138858
+ checks.push(
138859
+ claudeProjects && (0, import_fs14.existsSync)(claudeProjects) ? {
138860
+ id: "filesystem",
138861
+ status: "ok",
138862
+ summary: "Provider history directory is present.",
138863
+ remediation: "NONE",
138864
+ detail: { location: redactPath(claudeProjects) }
138865
+ } : {
138866
+ id: "filesystem",
138867
+ status: "degraded",
138868
+ summary: "Provider history directory was not found; history may be unavailable.",
138869
+ remediation: "FS_SCOPE_MISSING"
138870
+ }
138871
+ );
138872
+ return c.json(redactValue(buildReport(checks)));
138873
+ });
138874
+ return app;
138875
+ };
138448
138876
 
138449
138877
  // src/api/routes/health.routes.ts
138450
138878
  var createHealthRoutes = (deps) => {
@@ -138579,7 +139007,7 @@ function createLogsRoutes() {
138579
139007
  // src/api/routes/misc.routes.ts
138580
139008
  var import_node_child_process2 = require("child_process");
138581
139009
  var import_node_crypto = require("crypto");
138582
- var import_os5 = require("os");
139010
+ var import_os7 = require("os");
138583
139011
 
138584
139012
  // src/db/repositories/push.repository.ts
138585
139013
  var FAILURE_STREAK_LIMIT = 5;
@@ -138879,7 +139307,7 @@ var createMiscRoutes = (deps) => {
138879
139307
  const ptyIds = deps.ptyAttachedIds();
138880
139308
  return c.json({
138881
139309
  version: getVersion(),
138882
- machineName: (0, import_os5.hostname)(),
139310
+ machineName: (0, import_os7.hostname)(),
138883
139311
  platform: process.platform,
138884
139312
  activeSessions: deps.sessionStore.list(ptyIds).filter((s3) => s3.status === "running").length,
138885
139313
  publicUrl: deps.publicUrl,
@@ -139168,129 +139596,6 @@ var createProjectRoutes = (deps) => {
139168
139596
  return app;
139169
139597
  };
139170
139598
 
139171
- // src/platform.ts
139172
- var import_child_process = require("child_process");
139173
- var import_fs13 = require("fs");
139174
- var import_os6 = require("os");
139175
- var import_path13 = require("path");
139176
- var isWindows2 = (0, import_os6.platform)() === "win32";
139177
- var _claudeExe;
139178
- function resolveClaudeExe() {
139179
- if (_claudeExe !== void 0) return _claudeExe;
139180
- if (isWindows2) {
139181
- try {
139182
- const found = (0, import_child_process.execFileSync)("where.exe", ["claude"], {
139183
- encoding: "utf-8",
139184
- windowsHide: true,
139185
- timeout: 3e3
139186
- }).trim().split("\n")[0].trim();
139187
- if (found) {
139188
- _claudeExe = found;
139189
- return _claudeExe;
139190
- }
139191
- } catch {
139192
- }
139193
- const candidates = [
139194
- (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "claude.exe"),
139195
- (0, import_path13.join)(
139196
- process.env.LOCALAPPDATA ?? (0, import_path13.join)((0, import_os6.homedir)(), "AppData", "Local"),
139197
- "Microsoft",
139198
- "WindowsApps",
139199
- "claude.exe"
139200
- )
139201
- ];
139202
- for (const p2 of candidates) {
139203
- if ((0, import_fs13.existsSync)(p2)) {
139204
- _claudeExe = p2;
139205
- return _claudeExe;
139206
- }
139207
- }
139208
- } else {
139209
- try {
139210
- const found = (0, import_child_process.execFileSync)("/usr/bin/which", ["claude"], {
139211
- encoding: "utf-8",
139212
- timeout: 3e3
139213
- }).trim().split("\n")[0].trim();
139214
- if (found && (0, import_fs13.existsSync)(found)) {
139215
- _claudeExe = found;
139216
- return _claudeExe;
139217
- }
139218
- } catch {
139219
- }
139220
- const candidates = [
139221
- "/opt/homebrew/bin/claude",
139222
- "/usr/local/bin/claude",
139223
- (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "claude")
139224
- ];
139225
- for (const p2 of candidates) {
139226
- if ((0, import_fs13.existsSync)(p2)) {
139227
- _claudeExe = p2;
139228
- return _claudeExe;
139229
- }
139230
- }
139231
- }
139232
- _claudeExe = "claude";
139233
- return _claudeExe;
139234
- }
139235
- var _codexExe;
139236
- function resolveCodexExe() {
139237
- if (_codexExe !== void 0) return _codexExe;
139238
- if (isWindows2) {
139239
- try {
139240
- const found = (0, import_child_process.execFileSync)("where.exe", ["codex"], {
139241
- encoding: "utf-8",
139242
- windowsHide: true,
139243
- timeout: 3e3
139244
- }).trim().split("\n")[0].trim();
139245
- if (found) {
139246
- _codexExe = found;
139247
- return _codexExe;
139248
- }
139249
- } catch {
139250
- }
139251
- const candidates = [
139252
- (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "codex.exe"),
139253
- (0, import_path13.join)(
139254
- process.env.LOCALAPPDATA ?? (0, import_path13.join)((0, import_os6.homedir)(), "AppData", "Local"),
139255
- "Microsoft",
139256
- "WindowsApps",
139257
- "codex.exe"
139258
- )
139259
- ];
139260
- for (const p2 of candidates) {
139261
- if ((0, import_fs13.existsSync)(p2)) {
139262
- _codexExe = p2;
139263
- return _codexExe;
139264
- }
139265
- }
139266
- } else {
139267
- try {
139268
- const found = (0, import_child_process.execFileSync)("/usr/bin/which", ["codex"], {
139269
- encoding: "utf-8",
139270
- timeout: 3e3
139271
- }).trim().split("\n")[0].trim();
139272
- if (found && (0, import_fs13.existsSync)(found)) {
139273
- _codexExe = found;
139274
- return _codexExe;
139275
- }
139276
- } catch {
139277
- }
139278
- const candidates = [
139279
- "/opt/homebrew/bin/codex",
139280
- "/usr/local/bin/codex",
139281
- (0, import_path13.join)((0, import_os6.homedir)(), ".local", "bin", "codex")
139282
- ];
139283
- for (const p2 of candidates) {
139284
- if ((0, import_fs13.existsSync)(p2)) {
139285
- _codexExe = p2;
139286
- return _codexExe;
139287
- }
139288
- }
139289
- }
139290
- _codexExe = "codex";
139291
- return _codexExe;
139292
- }
139293
-
139294
139599
  // src/providers.ts
139295
139600
  var CLAUDE_CODE_PROVIDER2 = "claude-code";
139296
139601
  var CODEX_CLI_PROVIDER2 = "codex-cli";
@@ -139576,6 +139881,7 @@ var createHonoApp = (deps, upgradeWebSocket) => {
139576
139881
  app.use("*", authMiddleware(deps));
139577
139882
  app.onError(errorMiddleware);
139578
139883
  app.route("/healthz", createHealthRoutes(deps));
139884
+ app.route("/api/diagnostics", createDiagnosticsRoutes(deps));
139579
139885
  app.route("/", createMiscRoutes(deps));
139580
139886
  app.route("/api/sessions", createSessionRoutes(deps));
139581
139887
  app.route("/api/conversations", createConversationRoutes(deps));
@@ -139584,6 +139890,7 @@ var createHonoApp = (deps, upgradeWebSocket) => {
139584
139890
  app.route("/api/projects", createProjectRoutes(deps));
139585
139891
  app.route("/api/providers", createProviderRoutes());
139586
139892
  app.route("/api/devices", createDeviceRoutes(deps));
139893
+ app.route("/api/backup", createBackupRoutes(deps));
139587
139894
  app.route("/api/pair", createPairRoutes(deps));
139588
139895
  app.route("/api", createBrowseRoutes(deps));
139589
139896
  app.route("/", createScannerRoutes(deps));
@@ -139654,13 +139961,13 @@ init_claude_flags();
139654
139961
 
139655
139962
  // src/conversation-cache.ts
139656
139963
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
139657
- var import_fs16 = require("fs");
139964
+ var import_fs17 = require("fs");
139658
139965
  var import_promises12 = require("fs/promises");
139659
139966
  var import_path17 = require("path");
139660
139967
  var import_promises13 = require("timers/promises");
139661
139968
 
139662
139969
  // src/db/sqlite-migrate.ts
139663
- var import_fs14 = require("fs");
139970
+ var import_fs15 = require("fs");
139664
139971
  var import_path15 = require("path");
139665
139972
  var import_url6 = require("url");
139666
139973
  var import_meta2 = {};
@@ -139670,6 +139977,9 @@ function getMigrationsDir() {
139670
139977
  }
139671
139978
  return __dirname;
139672
139979
  }
139980
+ function resolveMigrationsDir(name = "migrations") {
139981
+ return (0, import_path15.join)(getMigrationsDir(), name);
139982
+ }
139673
139983
  var SCHEMA_MIGRATIONS_SQL = `
139674
139984
  CREATE TABLE IF NOT EXISTS schema_migrations (
139675
139985
  id TEXT PRIMARY KEY,
@@ -139678,8 +139988,8 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
139678
139988
  `;
139679
139989
  function runSqliteMigrations(db, migrationsDir) {
139680
139990
  db.exec(SCHEMA_MIGRATIONS_SQL);
139681
- const dir = migrationsDir ?? (0, import_path15.join)(getMigrationsDir(), "migrations");
139682
- const files = (0, import_fs14.readdirSync)(dir).filter((f2) => f2.endsWith(".sql")).sort();
139991
+ const dir = migrationsDir ?? resolveMigrationsDir();
139992
+ const files = (0, import_fs15.readdirSync)(dir).filter((f2) => f2.endsWith(".sql")).sort();
139683
139993
  const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
139684
139994
  const appliedSet = new Set(appliedRows.map((r) => r.id));
139685
139995
  const recordApplied = db.prepare("INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)");
@@ -139690,7 +140000,7 @@ function runSqliteMigrations(db, migrationsDir) {
139690
140000
  skipped.push(file2);
139691
140001
  continue;
139692
140002
  }
139693
- const sql = (0, import_fs14.readFileSync)((0, import_path15.join)(dir, file2), "utf-8");
140003
+ const sql = (0, import_fs15.readFileSync)((0, import_path15.join)(dir, file2), "utf-8");
139694
140004
  const tx = db.transaction(() => {
139695
140005
  db.exec(sql);
139696
140006
  recordApplied.run(file2, (/* @__PURE__ */ new Date()).toISOString());
@@ -139702,7 +140012,7 @@ function runSqliteMigrations(db, migrationsDir) {
139702
140012
  }
139703
140013
 
139704
140014
  // src/services/conversations/isAgentConversation.ts
139705
- var import_fs15 = require("fs");
140015
+ var import_fs16 = require("fs");
139706
140016
  var DEFAULT_AGENT_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-cli", "claude-vscode"]);
139707
140017
  var CHUNK_BYTES = 64 * 1024;
139708
140018
  var ENTRYPOINT_PROBE = `"entrypoint":`;
@@ -139724,12 +140034,12 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
139724
140034
  if (cached3 !== void 0) return cached3;
139725
140035
  let fd;
139726
140036
  try {
139727
- fd = (0, import_fs15.openSync)(filePath, "r");
140037
+ fd = (0, import_fs16.openSync)(filePath, "r");
139728
140038
  } catch {
139729
140039
  return false;
139730
140040
  }
139731
140041
  try {
139732
- const fileSize = (0, import_fs15.statSync)(filePath).size;
140042
+ const fileSize = (0, import_fs16.statSync)(filePath).size;
139733
140043
  if (fileSize === 0) {
139734
140044
  fileDecisionCache.set(key, false);
139735
140045
  return false;
@@ -139740,7 +140050,7 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
139740
140050
  let carry = "";
139741
140051
  while (offset < fileSize) {
139742
140052
  const toRead = Math.min(CHUNK_BYTES, fileSize - offset);
139743
- const got = (0, import_fs15.readSync)(fd, buf, 0, toRead, offset);
140053
+ const got = (0, import_fs16.readSync)(fd, buf, 0, toRead, offset);
139744
140054
  if (got <= 0) break;
139745
140055
  const chunk = carry + buf.toString("utf8", 0, got);
139746
140056
  for (const marker of markers) {
@@ -139761,7 +140071,7 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
139761
140071
  } catch {
139762
140072
  return false;
139763
140073
  } finally {
139764
- (0, import_fs15.closeSync)(fd);
140074
+ (0, import_fs16.closeSync)(fd);
139765
140075
  }
139766
140076
  }
139767
140077
  function parseAgentEntrypointsEnv(raw2) {
@@ -140336,7 +140646,7 @@ var ConversationCache = class _ConversationCache {
140336
140646
  if (!fileState) return null;
140337
140647
  let stat7;
140338
140648
  try {
140339
- stat7 = (0, import_fs16.statSync)(filePath);
140649
+ stat7 = (0, import_fs17.statSync)(filePath);
140340
140650
  } catch {
140341
140651
  return null;
140342
140652
  }
@@ -140357,17 +140667,17 @@ var ConversationCache = class _ConversationCache {
140357
140667
  );
140358
140668
  if (rows.length === 0) return { messages: [], total, fromIndex: from };
140359
140669
  const messages = [];
140360
- const fd = (0, import_fs16.openSync)(filePath, "r");
140670
+ const fd = (0, import_fs17.openSync)(filePath, "r");
140361
140671
  try {
140362
140672
  const state = initialConvState();
140363
140673
  for (const row of rows) {
140364
140674
  const buf = Buffer.alloc(row.byte_length);
140365
- (0, import_fs16.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
140675
+ (0, import_fs17.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
140366
140676
  const msg = parseJsonlLine(buf.toString("utf-8"), state);
140367
140677
  if (msg) messages.push(msg);
140368
140678
  }
140369
140679
  } finally {
140370
- (0, import_fs16.closeSync)(fd);
140680
+ (0, import_fs17.closeSync)(fd);
140371
140681
  }
140372
140682
  return { messages, total, fromIndex: from };
140373
140683
  }
@@ -140395,14 +140705,14 @@ var ConversationCache = class _ConversationCache {
140395
140705
  isAgentFileCached(filePath) {
140396
140706
  let s3;
140397
140707
  try {
140398
- s3 = (0, import_fs16.statSync)(filePath);
140708
+ s3 = (0, import_fs17.statSync)(filePath);
140399
140709
  } catch {
140400
140710
  return false;
140401
140711
  }
140402
140712
  return this.classifyAgentFile(filePath, s3.mtimeMs, s3.size);
140403
140713
  }
140404
140714
  static open(dbPath, tailSize = 10, migrationsDir, options) {
140405
- (0, import_fs16.mkdirSync)((0, import_path17.dirname)(dbPath), { recursive: true });
140715
+ (0, import_fs17.mkdirSync)((0, import_path17.dirname)(dbPath), { recursive: true });
140406
140716
  const db = new import_better_sqlite32.default(dbPath);
140407
140717
  db.pragma("journal_mode = WAL");
140408
140718
  db.pragma("foreign_keys = ON");
@@ -140589,7 +140899,7 @@ var ConversationCache = class _ConversationCache {
140589
140899
  let mtimeMs = null;
140590
140900
  let fileSize = null;
140591
140901
  try {
140592
- const s3 = (0, import_fs16.statSync)(m2.filePath);
140902
+ const s3 = (0, import_fs17.statSync)(m2.filePath);
140593
140903
  mtimeMs = s3.mtimeMs;
140594
140904
  fileSize = s3.size;
140595
140905
  } catch {
@@ -140649,8 +140959,8 @@ var ConversationCache = class _ConversationCache {
140649
140959
  let fileSize;
140650
140960
  let fd;
140651
140961
  try {
140652
- fileSize = (0, import_fs16.statSync)(filePath).size;
140653
- fd = (0, import_fs16.openSync)(filePath, "r");
140962
+ fileSize = (0, import_fs17.statSync)(filePath).size;
140963
+ fd = (0, import_fs17.openSync)(filePath, "r");
140654
140964
  } catch {
140655
140965
  return false;
140656
140966
  }
@@ -140663,7 +140973,7 @@ var ConversationCache = class _ConversationCache {
140663
140973
  while (pos > 0 && lines.length < this.tailSize * 4) {
140664
140974
  const toRead = Math.min(CHUNK, pos);
140665
140975
  pos -= toRead;
140666
- (0, import_fs16.readSync)(fd, buf, 0, toRead, pos);
140976
+ (0, import_fs17.readSync)(fd, buf, 0, toRead, pos);
140667
140977
  const chunk = buf.subarray(0, toRead).toString("utf8");
140668
140978
  const combined = chunk + partial2;
140669
140979
  const parts = combined.split("\n");
@@ -140674,7 +140984,7 @@ var ConversationCache = class _ConversationCache {
140674
140984
  }
140675
140985
  if (partial2) lines.push(partial2);
140676
140986
  } finally {
140677
- (0, import_fs16.closeSync)(fd);
140987
+ (0, import_fs17.closeSync)(fd);
140678
140988
  }
140679
140989
  const msgs = [];
140680
140990
  for (let i = 0; i < lines.length && msgs.length < this.tailSize; i++) {
@@ -140887,7 +141197,7 @@ var ConversationCache = class _ConversationCache {
140887
141197
  * `handleGetConversation` can still serve the cached tail even when the
140888
141198
  * JSONL has been deleted.
140889
141199
  */
140890
- pruneGhostFiles(exists = import_fs16.existsSync) {
141200
+ pruneGhostFiles(exists = import_fs17.existsSync) {
140891
141201
  const rows = this.stmts.allFilePaths.all();
140892
141202
  const ghosts = [];
140893
141203
  const prune = this.db.transaction((ids) => {
@@ -140942,7 +141252,7 @@ var ConversationCache = class _ConversationCache {
140942
141252
  * Returns the removed IDs.
140943
141253
  */
140944
141254
  reconcileDeletions(livePaths, opts) {
140945
- const exists = opts?.exists ?? import_fs16.existsSync;
141255
+ const exists = opts?.exists ?? import_fs17.existsSync;
140946
141256
  const rows = this.stmts.allFilePaths.all();
140947
141257
  const removed = [];
140948
141258
  const drop = this.db.transaction((ids) => {
@@ -140977,7 +141287,7 @@ var ConversationCache = class _ConversationCache {
140977
141287
  * reports drift for the CacheIntegrityMonitor to classify. `tailed` flags
140978
141288
  * rows that still have cached history (which pruneGhostFiles would keep).
140979
141289
  */
140980
- listMissingFiles(exists = import_fs16.existsSync) {
141290
+ listMissingFiles(exists = import_fs17.existsSync) {
140981
141291
  const rows = this.stmts.allFilePathsWithTitle.all();
140982
141292
  const missing = [];
140983
141293
  for (const row of rows) {
@@ -141062,7 +141372,7 @@ function getDbConfig() {
141062
141372
  }
141063
141373
 
141064
141374
  // src/db/migrations.ts
141065
- var import_fs17 = require("fs");
141375
+ var import_fs18 = require("fs");
141066
141376
  var import_path18 = require("path");
141067
141377
  var import_url7 = require("url");
141068
141378
  var import_meta3 = {};
@@ -141085,10 +141395,10 @@ async function runMigrations2(pool2, migrationsDir) {
141085
141395
  );
141086
141396
  const appliedSet = new Set(applied.map((r) => r.name));
141087
141397
  const dir = migrationsDir ?? (0, import_path18.join)(getMigrationsDir2(), "pg-migrations");
141088
- const files = (0, import_fs17.readdirSync)(dir).filter((f2) => f2.endsWith(".sql")).sort();
141398
+ const files = (0, import_fs18.readdirSync)(dir).filter((f2) => f2.endsWith(".sql")).sort();
141089
141399
  for (const file2 of files) {
141090
141400
  if (appliedSet.has(file2)) continue;
141091
- const sql = (0, import_fs17.readFileSync)((0, import_path18.join)(dir, file2), "utf-8");
141401
+ const sql = (0, import_fs18.readFileSync)((0, import_path18.join)(dir, file2), "utf-8");
141092
141402
  await pool2.query(sql);
141093
141403
  await pool2.query("INSERT INTO _migrations (name) VALUES ($1)", [file2]);
141094
141404
  }
@@ -141179,6 +141489,7 @@ var ManagedSessionsRepository = class {
141179
141489
  updateStatusStmt;
141180
141490
  getStmt;
141181
141491
  listNonTerminalStmt;
141492
+ listRecoverableStmt;
141182
141493
  deleteStmt;
141183
141494
  constructor(db) {
141184
141495
  this.upsertStmt = db.prepare(`
@@ -141231,6 +141542,13 @@ var ManagedSessionsRepository = class {
141231
141542
  WHERE completed_at IS NULL
141232
141543
  ORDER BY started_at ASC
141233
141544
  `);
141545
+ this.listRecoverableStmt = db.prepare(`
141546
+ SELECT * FROM managed_sessions
141547
+ WHERE (completed_at IS NULL OR status_source = 'shutdown')
141548
+ AND status_updated_at >= @since
141549
+ ORDER BY status_updated_at DESC
141550
+ LIMIT @limit
141551
+ `);
141234
141552
  this.deleteStmt = db.prepare("DELETE FROM managed_sessions WHERE session_id = ?");
141235
141553
  }
141236
141554
  /** Record a session at spawn, or refresh every field of an existing row. */
@@ -141282,6 +141600,14 @@ var ManagedSessionsRepository = class {
141282
141600
  listNonTerminal() {
141283
141601
  return this.listNonTerminalStmt.all();
141284
141602
  }
141603
+ /**
141604
+ * Rows a restart could bring back: still open, or closed by our own shutdown,
141605
+ * and touched no longer ago than `sinceMs`. Newest first, capped — the caller
141606
+ * decides which of these actually deserve rehydrating (`shouldRehydrate`).
141607
+ */
141608
+ listRecoverable({ sinceMs, limit }) {
141609
+ return this.listRecoverableStmt.all({ since: sinceMs, limit });
141610
+ }
141285
141611
  delete(sessionId) {
141286
141612
  this.deleteStmt.run(sessionId);
141287
141613
  }
@@ -141417,6 +141743,54 @@ var SessionsRepository = class {
141417
141743
  }
141418
141744
  };
141419
141745
 
141746
+ // src/db/runtime-store.ts
141747
+ var import_better_sqlite33 = __toESM(require("better-sqlite3"), 1);
141748
+ var RuntimeStore = class _RuntimeStore {
141749
+ constructor(db) {
141750
+ this.db = db;
141751
+ }
141752
+ db;
141753
+ static open(dbPath, migrationsDir) {
141754
+ const db = new import_better_sqlite33.default(dbPath);
141755
+ db.pragma("journal_mode = WAL");
141756
+ runSqliteMigrations(db, migrationsDir ?? resolveMigrationsDir("runtime-migrations"));
141757
+ return new _RuntimeStore(db);
141758
+ }
141759
+ getDatabase() {
141760
+ return this.db;
141761
+ }
141762
+ /**
141763
+ * One-time move of `managed_sessions` rows out of a pre-split `cache.db`.
141764
+ *
141765
+ * Non-destructive by design: the source table is left in place so an older
141766
+ * streamer rolled back onto the same machine still finds its registry. Runs
141767
+ * only when this file's table is empty, so a second boot is a no-op rather
141768
+ * than a re-copy that would resurrect rows deleted since.
141769
+ *
141770
+ * Returns the number of rows copied.
141771
+ */
141772
+ importLegacyManagedSessions(source) {
141773
+ const existing = this.db.prepare("SELECT COUNT(*) AS n FROM managed_sessions").get();
141774
+ if (existing.n > 0) return 0;
141775
+ const hasTable = source.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'managed_sessions'").get();
141776
+ if (!hasTable) return 0;
141777
+ const rows = source.prepare("SELECT * FROM managed_sessions").all();
141778
+ if (rows.length === 0) return 0;
141779
+ const columns = Object.keys(rows[0]);
141780
+ const insert = this.db.prepare(
141781
+ `INSERT OR IGNORE INTO managed_sessions (${columns.join(", ")})
141782
+ VALUES (${columns.map((c) => `@${c}`).join(", ")})`
141783
+ );
141784
+ this.db.transaction((batch) => {
141785
+ for (const row of batch) insert.run(row);
141786
+ })(rows);
141787
+ return rows.length;
141788
+ }
141789
+ close() {
141790
+ this.db.close();
141791
+ }
141792
+ };
141793
+
141420
141794
  // src/db/upload-records.ts
141421
141795
  async function recordUpload(pool2, instanceId, row) {
141422
141796
  if (!pool2) return;
@@ -141440,8 +141814,8 @@ async function recordUpload(pool2, instanceId, row) {
141440
141814
  init_feature_flags();
141441
141815
 
141442
141816
  // src/handlers/handleListProjects.ts
141443
- var import_fs18 = require("fs");
141444
- var import_os7 = require("os");
141817
+ var import_fs19 = require("fs");
141818
+ var import_os8 = require("os");
141445
141819
  var import_path19 = require("path");
141446
141820
  function decodeProjectPath(dirName) {
141447
141821
  return dirName.replace(/-/g, "/");
@@ -141449,14 +141823,14 @@ function decodeProjectPath(dirName) {
141449
141823
  function handleListProjects(url2, res) {
141450
141824
  const limit = Math.max(1, parseInt(url2.searchParams.get("limit") ?? "50", 10) || 50);
141451
141825
  const offset = Math.max(0, parseInt(url2.searchParams.get("offset") ?? "0", 10) || 0);
141452
- const projectsDir = (0, import_path19.join)((0, import_os7.homedir)(), ".claude", "projects");
141826
+ const projectsDir = (0, import_path19.join)((0, import_os8.homedir)(), ".claude", "projects");
141453
141827
  let entries;
141454
141828
  try {
141455
- entries = (0, import_fs18.readdirSync)(projectsDir).map((dirName) => {
141829
+ entries = (0, import_fs19.readdirSync)(projectsDir).map((dirName) => {
141456
141830
  const fullPath = (0, import_path19.join)(projectsDir, dirName);
141457
141831
  let mtime = 0;
141458
141832
  try {
141459
- mtime = (0, import_fs18.statSync)(fullPath).mtimeMs;
141833
+ mtime = (0, import_fs19.statSync)(fullPath).mtimeMs;
141460
141834
  } catch {
141461
141835
  }
141462
141836
  const path2 = decodeProjectPath(dirName);
@@ -141492,21 +141866,21 @@ var import_path23 = require("path");
141492
141866
  // src/codex-pty-runner.ts
141493
141867
  var import_headless = __toESM(require_xterm_headless(), 1);
141494
141868
  var import_crypto8 = require("crypto");
141495
- var import_fs20 = require("fs");
141869
+ var import_fs21 = require("fs");
141496
141870
  var import_path21 = require("path");
141497
141871
  init_logger();
141498
141872
 
141499
141873
  // src/services/questions/codexGateAnswers.ts
141500
- var import_fs19 = require("fs");
141501
- var import_os8 = require("os");
141874
+ var import_fs20 = require("fs");
141875
+ var import_os9 = require("os");
141502
141876
  var import_path20 = require("path");
141503
141877
  function gateAnswersPath() {
141504
- const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path20.join)((0, import_os8.homedir)(), ".threadbase");
141878
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path20.join)((0, import_os9.homedir)(), ".threadbase");
141505
141879
  return (0, import_path20.join)(dir, "gate-answers.json");
141506
141880
  }
141507
141881
  function loadGateAnswers() {
141508
141882
  try {
141509
- const parsed = JSON.parse((0, import_fs19.readFileSync)(gateAnswersPath(), "utf-8"));
141883
+ const parsed = JSON.parse((0, import_fs20.readFileSync)(gateAnswersPath(), "utf-8"));
141510
141884
  return parsed && typeof parsed === "object" ? parsed : {};
141511
141885
  } catch {
141512
141886
  return {};
@@ -141514,8 +141888,8 @@ function loadGateAnswers() {
141514
141888
  }
141515
141889
  function saveGateAnswer(key, value) {
141516
141890
  const path2 = gateAnswersPath();
141517
- (0, import_fs19.mkdirSync)((0, import_path20.dirname)(path2), { recursive: true });
141518
- (0, import_fs19.writeFileSync)(path2, `${JSON.stringify({ ...loadGateAnswers(), [key]: value }, null, 2)}
141891
+ (0, import_fs20.mkdirSync)((0, import_path20.dirname)(path2), { recursive: true });
141892
+ (0, import_fs20.writeFileSync)(path2, `${JSON.stringify({ ...loadGateAnswers(), [key]: value }, null, 2)}
141519
141893
  `);
141520
141894
  }
141521
141895
  function rememberedGateDigit(gate) {
@@ -142191,7 +142565,7 @@ var CodexPtyRunner = class {
142191
142565
  session.statusUpdatedAt = /* @__PURE__ */ new Date();
142192
142566
  const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
142193
142567
  if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
142194
- if (!(0, import_fs20.existsSync)(session.projectPath)) {
142568
+ if (!(0, import_fs21.existsSync)(session.projectPath)) {
142195
142569
  session.failureReason = `Project directory not found: ${session.projectPath}`;
142196
142570
  } else {
142197
142571
  session.failureReason = `Codex process exited immediately (code ${exitCode}).`;
@@ -142230,7 +142604,7 @@ function stripAnsi(str) {
142230
142604
  // src/pty-manager.ts
142231
142605
  var import_headless2 = __toESM(require_xterm_headless(), 1);
142232
142606
  var import_crypto9 = require("crypto");
142233
- var import_fs21 = require("fs");
142607
+ var import_fs22 = require("fs");
142234
142608
  var import_path22 = require("path");
142235
142609
  init_claude_flags();
142236
142610
  init_logger();
@@ -142438,6 +142812,12 @@ function detectShellPrompt(lines) {
142438
142812
  return null;
142439
142813
  }
142440
142814
 
142815
+ // src/utils/deriveSessionName.ts
142816
+ function deriveSessionName(firstMessageText) {
142817
+ const firstLine = firstMessageText.split("\n", 1)[0]?.trim() ?? "";
142818
+ return firstLine.slice(0, 80);
142819
+ }
142820
+
142441
142821
  // src/pty-manager.ts
142442
142822
  var OUTPUT_BUFFER_MAX2 = 65536;
142443
142823
  var INPUT_HISTORY_MAX2 = 50;
@@ -142911,6 +143291,10 @@ var PTYManager = class {
142911
143291
  if (session.inputHistory.length > INPUT_HISTORY_MAX2) {
142912
143292
  session.inputHistory.shift();
142913
143293
  }
143294
+ if (session.firstMessageText === void 0) {
143295
+ session.firstMessageText = text;
143296
+ session.sessionName = deriveSessionName(text);
143297
+ }
142914
143298
  this.onUserMessage?.(session.id, text, ts2);
142915
143299
  }
142916
143300
  getSession(sessionId) {
@@ -143142,7 +143526,7 @@ var PTYManager = class {
143142
143526
  session.statusUpdatedAt = /* @__PURE__ */ new Date();
143143
143527
  const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
143144
143528
  if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
143145
- if (!(0, import_fs21.existsSync)(session.projectPath)) {
143529
+ if (!(0, import_fs22.existsSync)(session.projectPath)) {
143146
143530
  session.failureReason = `Project directory not found: ${session.projectPath}`;
143147
143531
  } else {
143148
143532
  session.failureReason = `Process exited immediately (code ${exitCode}). Check that the Claude binary is installed and accessible.`;
@@ -143176,7 +143560,9 @@ function toPublicSession2(s3) {
143176
143560
  ...s3.lastActivityAt != null && { lastActivityAt: s3.lastActivityAt },
143177
143561
  ...s3.statusSource != null && { statusSource: s3.statusSource },
143178
143562
  ...s3.statusUpdatedAt != null && { statusUpdatedAt: s3.statusUpdatedAt },
143179
- ...s3.filePath != null && { filePath: s3.filePath }
143563
+ ...s3.filePath != null && { filePath: s3.filePath },
143564
+ ...s3.sessionName != null && { sessionName: s3.sessionName },
143565
+ ...s3.firstMessageText != null && { firstMessageText: s3.firstMessageText }
143180
143566
  };
143181
143567
  }
143182
143568
  function stripAnsi2(str) {
@@ -143351,10 +143737,10 @@ var PairTokenStore = class {
143351
143737
 
143352
143738
  // src/process-discovery.ts
143353
143739
  var import_child_process3 = require("child_process");
143354
- var import_os9 = require("os");
143740
+ var import_os10 = require("os");
143355
143741
  var import_path24 = require("path");
143356
143742
  async function discoverClaudeProcesses() {
143357
- if ((0, import_os9.platform)() === "win32") return discoverWindows();
143743
+ if ((0, import_os10.platform)() === "win32") return discoverWindows();
143358
143744
  return discoverUnix();
143359
143745
  }
143360
143746
  var CLAUDE_CLI_SCRIPT = /claude-code[\\/](?:cli|index)\.(?:js|mjs|cjs)$/i;
@@ -143494,7 +143880,7 @@ async function getProcessArgsUnix(pid) {
143494
143880
  async function getProcessArgs(pid) {
143495
143881
  if (!Number.isInteger(pid) || pid < 1) return "";
143496
143882
  try {
143497
- if ((0, import_os9.platform)() === "win32") {
143883
+ if ((0, import_os10.platform)() === "win32") {
143498
143884
  const info = await getProcessInfoWindows(pid);
143499
143885
  return info?.args ?? "";
143500
143886
  }
@@ -143645,19 +144031,19 @@ function setCacheMetadata(repo, key, value) {
143645
144031
 
143646
144032
  // src/services/cache-integrity/cacheIntegrityMonitor.ts
143647
144033
  var import_crypto11 = require("crypto");
143648
- var import_fs24 = require("fs");
144034
+ var import_fs25 = require("fs");
143649
144035
 
143650
144036
  // src/services/cache-integrity/alertStore.ts
143651
- var import_fs22 = require("fs");
143652
- var import_os10 = require("os");
144037
+ var import_fs23 = require("fs");
144038
+ var import_os11 = require("os");
143653
144039
  var import_path25 = require("path");
143654
144040
  function alertStatePath() {
143655
- const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path25.join)((0, import_os10.homedir)(), ".threadbase");
144041
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path25.join)((0, import_os11.homedir)(), ".threadbase");
143656
144042
  return (0, import_path25.join)(dir, "cache-alert.json");
143657
144043
  }
143658
144044
  function loadAlertState() {
143659
144045
  try {
143660
- const parsed = JSON.parse((0, import_fs22.readFileSync)(alertStatePath(), "utf-8"));
144046
+ const parsed = JSON.parse((0, import_fs23.readFileSync)(alertStatePath(), "utf-8"));
143661
144047
  return parsed && typeof parsed === "object" ? parsed : {};
143662
144048
  } catch {
143663
144049
  return {};
@@ -143665,13 +144051,13 @@ function loadAlertState() {
143665
144051
  }
143666
144052
  function saveAlertState(state) {
143667
144053
  const path2 = alertStatePath();
143668
- (0, import_fs22.mkdirSync)((0, import_path25.dirname)(path2), { recursive: true });
143669
- (0, import_fs22.writeFileSync)(path2, `${JSON.stringify(state, null, 2)}
144054
+ (0, import_fs23.mkdirSync)((0, import_path25.dirname)(path2), { recursive: true });
144055
+ (0, import_fs23.writeFileSync)(path2, `${JSON.stringify(state, null, 2)}
143670
144056
  `);
143671
144057
  }
143672
144058
 
143673
144059
  // src/services/cache-integrity/backup.ts
143674
- var import_fs23 = require("fs");
144060
+ var import_fs24 = require("fs");
143675
144061
  var import_path26 = require("path");
143676
144062
  var DEFAULT_RETAIN = 3;
143677
144063
  function retainCount() {
@@ -143684,16 +144070,16 @@ function timestamp(d) {
143684
144070
  }
143685
144071
  async function backupCacheDb(db, cacheDir) {
143686
144072
  const backupsDir = (0, import_path26.join)(cacheDir, "backups");
143687
- (0, import_fs23.mkdirSync)(backupsDir, { recursive: true });
144073
+ (0, import_fs24.mkdirSync)(backupsDir, { recursive: true });
143688
144074
  const destPath = (0, import_path26.join)(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
143689
144075
  await db.backup(destPath);
143690
144076
  const retain = retainCount();
143691
- const backups = (0, import_fs23.readdirSync)(backupsDir).filter((f2) => f2.startsWith("cache-") && f2.endsWith(".db")).map((f2) => {
144077
+ const backups = (0, import_fs24.readdirSync)(backupsDir).filter((f2) => f2.startsWith("cache-") && f2.endsWith(".db")).map((f2) => {
143692
144078
  const full = (0, import_path26.join)(backupsDir, f2);
143693
- return { full, mtime: (0, import_fs23.statSync)(full).mtimeMs };
144079
+ return { full, mtime: (0, import_fs24.statSync)(full).mtimeMs };
143694
144080
  }).sort((a, b2) => b2.mtime - a.mtime);
143695
144081
  for (const stale of backups.slice(retain)) {
143696
- if ((0, import_fs23.existsSync)(stale.full)) (0, import_fs23.unlinkSync)(stale.full);
144082
+ if ((0, import_fs24.existsSync)(stale.full)) (0, import_fs24.unlinkSync)(stale.full);
143697
144083
  }
143698
144084
  return destPath;
143699
144085
  }
@@ -143790,7 +144176,7 @@ var CacheIntegrityMonitor = class {
143790
144176
  * the pending record, back up on high severity, and broadcast the alert.
143791
144177
  */
143792
144178
  async runDetection(detectedAt = (/* @__PURE__ */ new Date()).toISOString()) {
143793
- const all = this.cache.listMissingFiles(import_fs24.existsSync);
144179
+ const all = this.cache.listMissingFiles(import_fs25.existsSync);
143794
144180
  const missing = all.filter((m2) => !this.ignoredIds.has(m2.id));
143795
144181
  if (missing.length === 0) {
143796
144182
  if (this._pending) {
@@ -143894,7 +144280,7 @@ var CacheIntegrityMonitor = class {
143894
144280
  case "prune_all": {
143895
144281
  await this.ensureBackup(pending);
143896
144282
  const backupPath = pending.backupPath;
143897
- const stillMissing = pending.missing.filter((m2) => !(0, import_fs24.existsSync)(m2.filePath)).map((m2) => m2.id);
144283
+ const stillMissing = pending.missing.filter((m2) => !(0, import_fs25.existsSync)(m2.filePath)).map((m2) => m2.id);
143898
144284
  const pruned = this.cache.dropRowsById(stillMissing);
143899
144285
  this.applyDeferredUnlinks();
143900
144286
  this.clearPending();
@@ -143955,7 +144341,7 @@ var CacheIntegrityMonitor = class {
143955
144341
  };
143956
144342
 
143957
144343
  // src/services/conversations/conversationWatcher.ts
143958
- var import_fs25 = require("fs");
144344
+ var import_fs26 = require("fs");
143959
144345
  var import_promises14 = require("fs/promises");
143960
144346
  var ConversationWatcher = class {
143961
144347
  files = /* @__PURE__ */ new Map();
@@ -143981,7 +144367,7 @@ var ConversationWatcher = class {
143981
144367
  if (this.files.has(key)) return;
143982
144368
  let offset;
143983
144369
  try {
143984
- offset = (0, import_fs25.statSync)(filePath).size;
144370
+ offset = (0, import_fs26.statSync)(filePath).size;
143985
144371
  } catch {
143986
144372
  offset = 0;
143987
144373
  }
@@ -144163,14 +144549,14 @@ function findSearchTarget(messages, query) {
144163
144549
  }
144164
144550
 
144165
144551
  // src/services/conversations/pruneAgentConversations.ts
144166
- var import_fs26 = require("fs");
144552
+ var import_fs27 = require("fs");
144167
144553
  function pruneAgentConversations(cache) {
144168
144554
  const db = cache.getDatabase();
144169
144555
  const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
144170
144556
  let pruned = 0;
144171
144557
  let missing = 0;
144172
144558
  for (const row of rows) {
144173
- if (!(0, import_fs26.existsSync)(row.file_path)) {
144559
+ if (!(0, import_fs27.existsSync)(row.file_path)) {
144174
144560
  missing += 1;
144175
144561
  continue;
144176
144562
  }
@@ -144491,22 +144877,22 @@ function refreshConversationCache(deps) {
144491
144877
  }
144492
144878
 
144493
144879
  // src/services/conversations/shouldRefreshProjectsFromHdd.ts
144494
- var import_fs27 = require("fs");
144495
- var import_os11 = require("os");
144880
+ var import_fs28 = require("fs");
144881
+ var import_os12 = require("os");
144496
144882
  var import_path27 = require("path");
144497
- var DEFAULT_PROJECTS_DIR = (0, import_path27.join)((0, import_os11.homedir)(), ".claude", "projects");
144883
+ var DEFAULT_PROJECTS_DIR = (0, import_path27.join)((0, import_os12.homedir)(), ".claude", "projects");
144498
144884
  function maxProjectsTreeMtimeMs(projectsDir) {
144499
144885
  let maxMs;
144500
144886
  try {
144501
- maxMs = (0, import_fs27.statSync)(projectsDir).mtimeMs;
144887
+ maxMs = (0, import_fs28.statSync)(projectsDir).mtimeMs;
144502
144888
  } catch {
144503
144889
  return null;
144504
144890
  }
144505
144891
  try {
144506
- for (const ent of (0, import_fs27.readdirSync)(projectsDir, { withFileTypes: true })) {
144892
+ for (const ent of (0, import_fs28.readdirSync)(projectsDir, { withFileTypes: true })) {
144507
144893
  if (!ent.isDirectory()) continue;
144508
144894
  try {
144509
- const childMs = (0, import_fs27.statSync)((0, import_path27.join)(projectsDir, ent.name)).mtimeMs;
144895
+ const childMs = (0, import_fs28.statSync)((0, import_path27.join)(projectsDir, ent.name)).mtimeMs;
144510
144896
  if (childMs > maxMs) maxMs = childMs;
144511
144897
  } catch {
144512
144898
  }
@@ -144734,7 +145120,8 @@ function contentStateForSession(args) {
144734
145120
  status,
144735
145121
  startedAt: args.startedAtOverride ?? args.session.startedAt.getTime(),
144736
145122
  lastOutput: truncateLastOutput(args.session.lastOutput ?? ""),
144737
- ...args.serverLabel != null && { serverLabel: args.serverLabel }
145123
+ ...args.serverLabel != null && { serverLabel: args.serverLabel },
145124
+ ...args.session.sessionName != null && { sessionName: args.session.sessionName }
144738
145125
  };
144739
145126
  }
144740
145127
  var LiveActivityNotifier = class {
@@ -144747,14 +145134,17 @@ var LiveActivityNotifier = class {
144747
145134
  serverId;
144748
145135
  serverLabel;
144749
145136
  /**
144750
- * Last status pushed per session.
145137
+ * Sessions with a currently open (pushed) activity.
144751
145138
  *
144752
- * Live Activity pushes are rate-limited by iOS and the surface only renders
144753
- * `running` vs `waiting_input`, so re-pushing an unchanged status is pure
144754
- * budget spend for no visible change. This is what makes the notifier
144755
- * edge-triggered rather than level-triggered.
145139
+ * An activity opens on a `waiting_input running` edge (the user sent a
145140
+ * prompt) and closes on the matching `running waiting_input` edge (the
145141
+ * response, including any sub-agents, finished) so this set is what makes
145142
+ * the notifier per-turn rather than per-session. A session's very first
145143
+ * `running` (right after spawn, before any user prompt) has no prior
145144
+ * `waiting_input` and therefore no edge, so it never opens an activity —
145145
+ * this is what keeps a fresh/idle session from pushing anything.
144756
145146
  */
144757
- lastPushed = /* @__PURE__ */ new Map();
145147
+ openActivity = /* @__PURE__ */ new Map();
144758
145148
  /**
144759
145149
  * React to a session status change.
144760
145150
  *
@@ -144762,34 +145152,22 @@ var LiveActivityNotifier = class {
144762
145152
  * transition, so this returns a promise the caller may ignore and every error
144763
145153
  * is logged rather than propagated.
144764
145154
  */
144765
- async onStatusChange(session) {
145155
+ async onStatusChange(session, previousStatus) {
144766
145156
  const status = toLiveActivityStatus(session.status);
144767
145157
  try {
144768
145158
  if (!status) {
144769
- await this.endFor(session);
145159
+ if (this.openActivity.has(session.id)) await this.endFor(session);
144770
145160
  return;
144771
145161
  }
144772
- if (this.lastPushed.get(session.id) === status) return;
144773
- const contentState = contentStateForSession({
144774
- session,
144775
- serverId: this.serverId,
144776
- serverLabel: this.serverLabel
144777
- });
144778
- if (!contentState) return;
144779
- const outcome = await this.sender.send({
144780
- sessionId: session.id,
144781
- event: "update",
144782
- contentState
144783
- });
144784
- this.lastPushed.set(session.id, status);
144785
- if (outcome.attempted > 0) {
144786
- log4.info("live_activity.updated", {
144787
- event: "live_activity.updated",
144788
- sessionId: session.id,
144789
- status,
144790
- ...outcome
144791
- });
145162
+ if (status === "running" && previousStatus === "waiting_input") {
145163
+ await this.startTurn(session);
145164
+ return;
145165
+ }
145166
+ if (status === "waiting_input" && previousStatus === "running") {
145167
+ if (this.openActivity.has(session.id)) await this.endFor(session);
145168
+ return;
144792
145169
  }
145170
+ await this.maybeSendName(session);
144793
145171
  } catch (err) {
144794
145172
  log4.error("live_activity.notify_failed", {
144795
145173
  event: "live_activity.notify_failed",
@@ -144799,14 +145177,57 @@ var LiveActivityNotifier = class {
144799
145177
  });
144800
145178
  }
144801
145179
  }
145180
+ async startTurn(session) {
145181
+ const contentState = contentStateForSession({
145182
+ session,
145183
+ serverId: this.serverId,
145184
+ serverLabel: this.serverLabel
145185
+ });
145186
+ if (!contentState) return;
145187
+ const outcome = await this.sender.send({
145188
+ sessionId: session.id,
145189
+ event: "update",
145190
+ contentState
145191
+ });
145192
+ this.openActivity.set(session.id, { sessionNameSent: session.sessionName != null });
145193
+ if (outcome.attempted > 0) {
145194
+ log4.info("live_activity.updated", {
145195
+ event: "live_activity.updated",
145196
+ sessionId: session.id,
145197
+ status: contentState.status,
145198
+ ...outcome
145199
+ });
145200
+ }
145201
+ }
145202
+ async maybeSendName(session) {
145203
+ const open3 = this.openActivity.get(session.id);
145204
+ if (!open3 || open3.sessionNameSent || session.sessionName == null) return;
145205
+ const contentState = contentStateForSession({
145206
+ session,
145207
+ serverId: this.serverId,
145208
+ serverLabel: this.serverLabel
145209
+ });
145210
+ if (!contentState) return;
145211
+ const outcome = await this.sender.send({
145212
+ sessionId: session.id,
145213
+ event: "update",
145214
+ contentState
145215
+ });
145216
+ open3.sessionNameSent = true;
145217
+ if (outcome.attempted > 0) {
145218
+ log4.info("live_activity.updated", {
145219
+ event: "live_activity.updated",
145220
+ sessionId: session.id,
145221
+ status: contentState.status,
145222
+ ...outcome
145223
+ });
145224
+ }
145225
+ }
144802
145226
  async endFor(session) {
144803
- const lastStatus = this.lastPushed.get(session.id);
144804
- this.lastPushed.delete(session.id);
145227
+ this.openActivity.delete(session.id);
145228
+ const status = toLiveActivityStatus(session.status);
144805
145229
  const contentState = contentStateForSession({
144806
- session: {
144807
- ...session,
144808
- status: lastStatus === "waiting_input" ? "waiting_input" : "running"
144809
- },
145230
+ session: { ...session, status: status ?? "waiting_input" },
144810
145231
  serverId: this.serverId,
144811
145232
  serverLabel: this.serverLabel
144812
145233
  });
@@ -144820,9 +145241,9 @@ var LiveActivityNotifier = class {
144820
145241
  });
144821
145242
  }
144822
145243
  }
144823
- /** Drop cached state for a session, so a resume re-pushes its first status. */
145244
+ /** Drop cached state for a session, so a resume re-opens on its next turn. */
144824
145245
  forget(sessionId) {
144825
- this.lastPushed.delete(sessionId);
145246
+ this.openActivity.delete(sessionId);
144826
145247
  }
144827
145248
  };
144828
145249
 
@@ -145057,7 +145478,8 @@ var LiveActivityRenewalScheduler = class {
145057
145478
  status,
145058
145479
  startedAt,
145059
145480
  lastOutput: session.lastOutput ?? "",
145060
- ...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
145481
+ ...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel },
145482
+ ...session.sessionName != null && { sessionName: session.sessionName }
145061
145483
  };
145062
145484
  try {
145063
145485
  await this.deps.sender.send({
@@ -145117,7 +145539,8 @@ var LiveActivityRenewalScheduler = class {
145117
145539
  // Carried through unchanged — the whole point of the renewal.
145118
145540
  startedAt: args.startedAt,
145119
145541
  lastOutput: truncateLastOutput(session.lastOutput ?? ""),
145120
- ...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel }
145542
+ ...this.deps.serverLabel != null && { serverLabel: this.deps.serverLabel },
145543
+ ...session.sessionName != null && { sessionName: session.sessionName }
145121
145544
  },
145122
145545
  now: args.now,
145123
145546
  staleDate: args.startedAt + ACTIVITY_MAX_LIFETIME_MS
@@ -145275,8 +145698,90 @@ function resolveAnswer(pending, body) {
145275
145698
  }
145276
145699
  }
145277
145700
 
145701
+ // src/services/search/searchQuery.ts
145702
+ var DEFAULT_SEARCH_LIMIT = 50;
145703
+ var MAX_SEARCH_LIMIT = 200;
145704
+ var MAX_QUERY_LENGTH = 256;
145705
+ var SearchQueryError = class extends Error {
145706
+ constructor(message, code) {
145707
+ super(message);
145708
+ this.code = code;
145709
+ }
145710
+ code;
145711
+ };
145712
+ function intOr(raw2, fallback) {
145713
+ if (raw2 === null) return fallback;
145714
+ const n = Number.parseInt(raw2, 10);
145715
+ return Number.isFinite(n) ? n : fallback;
145716
+ }
145717
+ function parseSearchQuery(params) {
145718
+ const q2 = (params.get("q") ?? "").trim();
145719
+ if (!q2) {
145720
+ throw new SearchQueryError("Missing query parameter: q", "invalid_query");
145721
+ }
145722
+ if (q2.length > MAX_QUERY_LENGTH) {
145723
+ throw new SearchQueryError(`Query exceeds ${MAX_QUERY_LENGTH} characters`, "query_too_long");
145724
+ }
145725
+ const limit = Math.min(
145726
+ Math.max(intOr(params.get("limit"), DEFAULT_SEARCH_LIMIT), 1),
145727
+ MAX_SEARCH_LIMIT
145728
+ );
145729
+ const offset = Math.max(intOr(params.get("offset"), 0), 0);
145730
+ const filters = {};
145731
+ const provider = params.get("provider");
145732
+ if (provider !== null) {
145733
+ if (!isProviderName(provider)) {
145734
+ throw new SearchQueryError(`Unknown provider: ${provider}`, "invalid_filter");
145735
+ }
145736
+ filters.provider = provider;
145737
+ }
145738
+ const projectPath = params.get("projectPath");
145739
+ if (projectPath) filters.projectPath = projectPath;
145740
+ const branch = params.get("branch");
145741
+ if (branch) filters.branch = branch;
145742
+ for (const [key, field] of [
145743
+ ["since", "since"],
145744
+ ["until", "until"]
145745
+ ]) {
145746
+ const raw2 = params.get(key);
145747
+ if (raw2 === null) continue;
145748
+ const ms2 = Date.parse(raw2);
145749
+ if (Number.isNaN(ms2)) {
145750
+ throw new SearchQueryError(`Invalid ${key}: expected an ISO 8601 date`, "invalid_filter");
145751
+ }
145752
+ filters[field] = ms2;
145753
+ }
145754
+ if (filters.since != null && filters.until != null && filters.since > filters.until) {
145755
+ throw new SearchQueryError("`since` must not be after `until`", "invalid_filter");
145756
+ }
145757
+ return { q: q2, limit, offset, filters };
145758
+ }
145759
+ function applyFilters(results, filters) {
145760
+ return results.filter((r) => {
145761
+ if (filters.provider && r.provider !== filters.provider) return false;
145762
+ if (filters.projectPath && r.projectPath !== filters.projectPath) return false;
145763
+ if (filters.branch && r.branch !== filters.branch) return false;
145764
+ if (filters.since != null || filters.until != null) {
145765
+ const ts2 = r.lastActivity == null ? Number.NaN : new Date(r.lastActivity).getTime();
145766
+ if (Number.isNaN(ts2)) return false;
145767
+ if (filters.since != null && ts2 < filters.since) return false;
145768
+ if (filters.until != null && ts2 > filters.until) return false;
145769
+ }
145770
+ return true;
145771
+ });
145772
+ }
145773
+ function paginate(results, offset, limit) {
145774
+ const items = results.slice(offset, offset + limit);
145775
+ return {
145776
+ items,
145777
+ total: results.length,
145778
+ offset,
145779
+ hasMore: offset + items.length < results.length
145780
+ };
145781
+ }
145782
+
145278
145783
  // src/services/sessions/conversationBusy.ts
145279
- var import_fs28 = require("fs");
145784
+ var import_fs29 = require("fs");
145280
145785
  var RESUME_BUSY_WINDOW_MS = 12e4;
145281
145786
  function resolveResumeBusyWindowMs(env = process.env) {
145282
145787
  const raw2 = env.THREADBASE_RESUME_BUSY_WINDOW_MS;
@@ -145293,7 +145798,7 @@ function conversationBusy(input) {
145293
145798
  let lastActivityMs = null;
145294
145799
  if (input.jsonlPath) {
145295
145800
  try {
145296
- const mtimeMs = (0, import_fs28.statSync)(input.jsonlPath).mtimeMs;
145801
+ const mtimeMs = (0, import_fs29.statSync)(input.jsonlPath).mtimeMs;
145297
145802
  const age = now - mtimeMs;
145298
145803
  lastActivityMs = Math.max(0, age);
145299
145804
  const isSelfEcho = input.selfPtyEndedAt != null && mtimeMs <= input.selfPtyEndedAt + SELF_ACTIVITY_SKEW_MS;
@@ -145422,6 +145927,50 @@ async function reconcileSessions(rows, probe, currentInstanceId) {
145422
145927
  return Promise.all(rows.map((row) => classifySession(row, probe, currentInstanceId)));
145423
145928
  }
145424
145929
 
145930
+ // src/services/sessions/rehydrateSessions.ts
145931
+ var REHYDRATE_MAX = 25;
145932
+ var REHYDRATE_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
145933
+ var AGENT_EXIT_SOURCES = /* @__PURE__ */ new Set(["exit", "process-exit"]);
145934
+ function shouldRehydrate(row, opts) {
145935
+ if (!opts.projectExists(row.project_path)) return false;
145936
+ if (opts.now - row.status_updated_at > REHYDRATE_WINDOW_MS) return false;
145937
+ if (AGENT_EXIT_SOURCES.has(row.status_source) && row.failure_reason == null) return false;
145938
+ return true;
145939
+ }
145940
+ function rowToStubSession(row) {
145941
+ return {
145942
+ id: row.session_id,
145943
+ provider: row.provider,
145944
+ projectPath: row.project_path,
145945
+ projectName: row.project_name,
145946
+ branch: row.branch,
145947
+ // No PTY exists for a stub, so this is the only truthful status.
145948
+ status: "idle",
145949
+ startedAt: new Date(row.started_at),
145950
+ completedAt: row.completed_at != null ? new Date(row.completed_at) : null,
145951
+ promptCount: row.prompt_count,
145952
+ lastOutput: "",
145953
+ rehydrated: true,
145954
+ ...row.session_name != null && { sessionName: row.session_name },
145955
+ ...row.project_id != null && { projectId: row.project_id },
145956
+ ...row.bound_conversation_id != null && { boundConversationId: row.bound_conversation_id },
145957
+ ...row.resumed_from_conversation_id != null && {
145958
+ resumedFromConversationId: row.resumed_from_conversation_id
145959
+ },
145960
+ ...row.failure_reason != null && { failureReason: row.failure_reason },
145961
+ ...row.last_activity_at != null && { lastActivityAt: new Date(row.last_activity_at) },
145962
+ // Only `shutdown` crosses over. It is the one registry source that is also a
145963
+ // wire StatusSource *and* that genuinely describes the `idle` above — the
145964
+ // streamer stopped this session. A crashed row still says `transition` over
145965
+ // a `running` status, and copying that here would attach observed-confidence
145966
+ // provenance to a status we derived at boot, so leave it unset instead.
145967
+ ...row.status_source === "shutdown" && {
145968
+ statusSource: "shutdown",
145969
+ statusUpdatedAt: new Date(row.status_updated_at)
145970
+ }
145971
+ };
145972
+ }
145973
+
145425
145974
  // src/agent/dedupe.ts
145426
145975
  function createProgressDedupeLRU(capacity) {
145427
145976
  if (!Number.isFinite(capacity) || capacity < 1) {
@@ -145595,14 +146144,15 @@ function managedToResponse(s3, ptyAttached) {
145595
146144
  // Lifecycle for a session this run knows about. `attached` while we hold
145596
146145
  // its PTY; once the PTY is gone the session is terminal from this run's
145597
146146
  // perspective — `failed` when it recorded a reason, else `completed`.
145598
- // Sessions left by *previous* runs never reach here: they aren't in the
145599
- // in-memory store, and the boot reconciler classifies them instead
145600
- // (docs/architecture/2026-07-24-durable-session-runtime.md).
145601
- lifecycle: ptyAttached ? "attached" : s3.failureReason != null ? "failed" : "completed",
145602
- lifecycleSource: ptyAttached ? "spawn" : "exit",
146147
+ // A `rehydrated` stub is the exception: the boot rehydrator seeded it from
146148
+ // the durable registry, so it is a previous run's session with no process
146149
+ // behind it — `resumable`, and `historical` rather than `managed`
146150
+ // (docs/plans/live-sessions-persistence-plan.md §4, Phase 1).
146151
+ lifecycle: ptyAttached ? "attached" : s3.rehydrated ? "resumable" : s3.failureReason != null ? "failed" : "completed",
146152
+ lifecycleSource: ptyAttached ? "spawn" : s3.rehydrated ? "reconcile" : "exit",
145603
146153
  // We spawned it, so `status` is the authoritative signal — no inferred
145604
146154
  // `activity` is attached for managed sessions.
145605
- ownership: "managed",
146155
+ ownership: s3.rehydrated ? "historical" : "managed",
145606
146156
  projectPath: s3.projectPath,
145607
146157
  projectName: s3.projectName,
145608
146158
  branch: s3.branch,
@@ -146003,6 +146553,8 @@ var DEFAULT_PTY_GRACE_PERIOD_MS = 27e4;
146003
146553
  var GRACE_MAX_DEFERS = 4;
146004
146554
  var IDLE_REAP_AFTER_MS = 6 * 60 * 60 * 1e3;
146005
146555
  var IDLE_REAP_SWEEP_MS = 5 * 60 * 1e3;
146556
+ var SEARCH_OVERFETCH = 4;
146557
+ var SEARCH_MAX_SCAN = 1e3;
146006
146558
  var RESUME_DISCOVERY_TIMEOUT_MS = 750;
146007
146559
  var DISCOVERY_TTL_MS = 15e3;
146008
146560
  var ADOPT_KILL_TIMEOUT_MS = 5e3;
@@ -146190,10 +146742,13 @@ var StreamerServer = class {
146190
146742
  projectsRepo = null;
146191
146743
  conversationsRepo = null;
146192
146744
  sessionsRepo = null;
146193
- // Durable session registry (C1 Phase 2). Null when the cache DB failed to
146194
- // open — persistence degrades to today's in-memory-only behaviour rather than
146195
- // taking the server down with it, so every write goes through `?.`.
146745
+ // Durable session registry (C1 Phase 2). Null when runtime.db failed to open
146746
+ // — persistence degrades to today's in-memory-only behaviour rather than
146747
+ // taking the server down with it, so every write goes through `?.`. Note the
146748
+ // handle is runtime.db, NOT the conversation cache: a cache failure used to
146749
+ // null this repo and silently disable all session persistence.
146196
146750
  managedSessionsRepo = null;
146751
+ runtimeStore = null;
146197
146752
  // Identifies this streamer run. A registry row carrying a different id is a
146198
146753
  // session that outlived the process that started it.
146199
146754
  streamerInstanceId = (0, import_crypto13.randomUUID)();
@@ -146212,6 +146767,7 @@ var StreamerServer = class {
146212
146767
  liveActivityRenewal = null;
146213
146768
  discoveryCache = null;
146214
146769
  cacheDir;
146770
+ runtimeDbPath;
146215
146771
  tailSize;
146216
146772
  directoryDebounceMs;
146217
146773
  // Trailing-debounced trigger that flags the scanner stale after a quiet
@@ -146243,7 +146799,7 @@ var StreamerServer = class {
146243
146799
  this.skipStartupWarmup = config2.skipStartupWarmup ?? false;
146244
146800
  this.scannerPersistenceDisabled = config2.scannerPersistent === false;
146245
146801
  this.scanProfiles = config2.scanProfiles;
146246
- this.codexRoots = config2.codexRoots ?? [(0, import_path29.join)((0, import_os12.homedir)(), ".codex", "sessions")];
146802
+ this.codexRoots = config2.codexRoots ?? [(0, import_path29.join)((0, import_os13.homedir)(), ".codex", "sessions")];
146247
146803
  this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
146248
146804
  this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
146249
146805
  this.featureFlags = resolveFeatureFlags({ cli: config2.featureFlags, yaml: loadFeatureFlags() });
@@ -146257,7 +146813,8 @@ var StreamerServer = class {
146257
146813
  this.claudeFlagsPersistable = config2.claudeFlags === void 0;
146258
146814
  this.claudeFlags = config2.claudeFlags ?? loadClaudeFlags();
146259
146815
  this.claudeExtraArgs = config2.claudeExtraArgs ?? loadClaudeExtraArgs();
146260
- this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path29.join)((0, import_os12.homedir)(), ".threadbase", "cache");
146816
+ this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path29.join)((0, import_os13.homedir)(), ".threadbase", "cache");
146817
+ this.runtimeDbPath = config2.runtimeDbPath ?? process.env.THREADBASE_RUNTIME_DB ?? (0, import_path29.join)(process.env.THREADBASE_CONFIG_DIR ?? (0, import_path29.join)((0, import_os13.homedir)(), ".threadbase"), "runtime.db");
146261
146818
  this.tailSize = config2.tailSize ?? loadTailSize() ?? 10;
146262
146819
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config2.directoryScanDebounceMs ?? 1e3;
146263
146820
  this.markScannerStaleDebounced = debounce(() => {
@@ -146308,7 +146865,7 @@ var StreamerServer = class {
146308
146865
  const seqs = cache.extendMessageIndex(
146309
146866
  filePath,
146310
146867
  spans,
146311
- (0, import_fs29.statSync)(filePath),
146868
+ (0, import_fs30.statSync)(filePath),
146312
146869
  readFrom,
146313
146870
  endOffset
146314
146871
  );
@@ -146428,6 +146985,7 @@ var StreamerServer = class {
146428
146985
  if (resp) this.wsHub.broadcast({ type: "session_ready", session: resp });
146429
146986
  },
146430
146987
  onStatusChange: (session) => {
146988
+ const previousStatus = this.sessionStore.getManaged(session.id)?.status;
146431
146989
  this.sessionStore.updateManaged(session.id, {
146432
146990
  status: session.status,
146433
146991
  completedAt: session.completedAt,
@@ -146482,7 +147040,7 @@ var StreamerServer = class {
146482
147040
  if (resp) {
146483
147041
  this.wsHub.broadcast({ type: "session_update", session: resp });
146484
147042
  }
146485
- void this.liveActivityNotifier?.onStatusChange(session);
147043
+ void this.liveActivityNotifier?.onStatusChange(session, previousStatus);
146486
147044
  this.sessionStatusBus.emit(`status:${session.id}`, session.status);
146487
147045
  }
146488
147046
  });
@@ -146533,6 +147091,7 @@ var StreamerServer = class {
146533
147091
  conversationsRepo: () => this.conversationsRepo,
146534
147092
  sessionsRepo: () => this.sessionsRepo,
146535
147093
  cacheMetadataRepo: () => this.cacheMetadataRepo,
147094
+ runtimeStore: () => this.runtimeStore,
146536
147095
  ptyAttachedIds: () => this.ptyAttachedIds(),
146537
147096
  handleListSessions: (url2, res) => this.handleListSessions(url2, res),
146538
147097
  handleSessionsCount: (res) => this.handleSessionsCount(res),
@@ -146771,14 +147330,14 @@ var StreamerServer = class {
146771
147330
  }
146772
147331
  this.apnsClient = new ApnsClient(creds);
146773
147332
  const sender = new LiveActivitySender(this.apnsClient, pushRepo);
146774
- const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os12.hostname)();
146775
- this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os12.hostname)());
147333
+ const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os13.hostname)();
147334
+ this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os13.hostname)());
146776
147335
  this.liveActivityRenewal = new LiveActivityRenewalScheduler({
146777
147336
  repo: pushRepo,
146778
147337
  sender,
146779
147338
  sessionStore: this.sessionStore,
146780
147339
  serverId,
146781
- serverLabel: (0, import_os12.hostname)()
147340
+ serverLabel: (0, import_os13.hostname)()
146782
147341
  });
146783
147342
  this.liveActivityRenewal.start();
146784
147343
  this.log.info("Live Activity push enabled", {
@@ -146832,6 +147391,58 @@ var StreamerServer = class {
146832
147391
  }
146833
147392
  return verdicts;
146834
147393
  }
147394
+ /**
147395
+ * Seed the session list with what previous runs left behind (persistence plan
147396
+ * Phase 1, gaps G1/G2/G8).
147397
+ *
147398
+ * Reconciliation classifies rows and stops there; a verdict is overlaid onto a
147399
+ * SessionResponse that already exists, and after a clean restart none does —
147400
+ * `SessionStore` starts empty. So the user's session did not become
147401
+ * `resumable`, it became *absent*. This is the half that puts it back.
147402
+ *
147403
+ * The seeded stubs hold no PTY and are never handed to `LiveSessionManager`,
147404
+ * so `reapIdleSessions` and `startGraceTimer` — both of which iterate
147405
+ * `ptyManager.listSessions()` — cannot observe them. A later resume calls
147406
+ * `sessionStore.addManaged` with the real session, which overwrites the stub
147407
+ * by id rather than duplicating it.
147408
+ */
147409
+ rehydratePreviousSessions(verdicts) {
147410
+ if (!this.featureFlags.sessionRehydration || !this.managedSessionsRepo) return;
147411
+ try {
147412
+ const now = Date.now();
147413
+ const rows = this.managedSessionsRepo.listRecoverable({
147414
+ sinceMs: now - REHYDRATE_WINDOW_MS,
147415
+ limit: REHYDRATE_MAX + 1
147416
+ });
147417
+ const truncated = rows.length > REHYDRATE_MAX;
147418
+ const candidates = truncated ? rows.slice(0, REHYDRATE_MAX) : rows;
147419
+ if (candidates.length === 0) return;
147420
+ const lifecycleByVerdict = new Map(verdicts.map((v2) => [v2.sessionId, v2.lifecycle]));
147421
+ let rehydrated = 0;
147422
+ for (const row of candidates) {
147423
+ if (this.sessionStore.getManaged(row.session_id)) continue;
147424
+ if (!shouldRehydrate(row, { now, projectExists: import_fs30.existsSync })) continue;
147425
+ this.sessionStore.addManaged(rowToStubSession(row));
147426
+ this.sessionLifecycles.set(
147427
+ row.session_id,
147428
+ lifecycleByVerdict.get(row.session_id) ?? "resumable"
147429
+ );
147430
+ if (row.completed_at != null) this.selfPtyEndedAt.set(row.session_id, row.completed_at);
147431
+ rehydrated++;
147432
+ }
147433
+ this.log.info(`[rehydrate] recovered ${rehydrated} session(s) from the registry`, {
147434
+ event: "sessions.rehydrated",
147435
+ rehydrated,
147436
+ skipped: candidates.length - rehydrated,
147437
+ truncated
147438
+ });
147439
+ } catch (err) {
147440
+ this.log.warn("[rehydrate] failed to rehydrate previous sessions", {
147441
+ event: "sessions.rehydrate_failed",
147442
+ err
147443
+ });
147444
+ }
147445
+ }
146835
147446
  /**
146836
147447
  * Pick a token guaranteed to appear in the spawned process's argv, for the
146837
147448
  * reconciler's pid-reuse guard.
@@ -147065,6 +147676,17 @@ var StreamerServer = class {
147065
147676
  port,
147066
147677
  event: "server.listening"
147067
147678
  });
147679
+ try {
147680
+ this.runtimeStore = RuntimeStore.open(this.runtimeDbPath);
147681
+ this.managedSessionsRepo = new ManagedSessionsRepository(this.runtimeStore.getDatabase());
147682
+ } catch (err) {
147683
+ const message = err instanceof Error ? err.message : String(err);
147684
+ const abiMismatch = message.includes("NODE_MODULE_VERSION") || message.includes("was compiled against a different Node.js version");
147685
+ this.log.error(
147686
+ `Runtime store failed to open \u2014 session persistence DISABLED; sessions will not survive a restart.` + (abiMismatch ? ` Fix: npm rebuild better-sqlite3` : "") + ` (${message})`,
147687
+ { error: message, abiMismatch, path: this.runtimeDbPath, event: "runtime.open_failed" }
147688
+ );
147689
+ }
147068
147690
  try {
147069
147691
  this.cache = ConversationCache.open(
147070
147692
  (0, import_path29.join)(this.cacheDir, "cache.db"),
@@ -147092,8 +147714,20 @@ var StreamerServer = class {
147092
147714
  this.projectsRepo = new ProjectsRepository(db);
147093
147715
  this.conversationsRepo = new ConversationsRepository(this.cache);
147094
147716
  this.sessionsRepo = new SessionsRepository(this.sessionStore);
147095
- this.managedSessionsRepo = new ManagedSessionsRepository(db);
147096
- void this.reconcilePreviousSessions();
147717
+ try {
147718
+ const copied = this.runtimeStore?.importLegacyManagedSessions(db) ?? 0;
147719
+ if (copied > 0) {
147720
+ this.log.info(`Copied ${copied} managed session row(s) from cache.db to runtime.db`, {
147721
+ copied,
147722
+ event: "runtime.legacy_import"
147723
+ });
147724
+ }
147725
+ } catch (err) {
147726
+ this.log.warn("[registry] legacy managed_sessions copy failed", {
147727
+ event: "runtime.legacy_import_failed",
147728
+ err
147729
+ });
147730
+ }
147097
147731
  this.cacheMetadataRepo = new CacheMetadataRepository(db);
147098
147732
  this.pushRepo = new PushRepository(db);
147099
147733
  this.devicesRepo = new DevicesRepository(db);
@@ -147117,7 +147751,7 @@ var StreamerServer = class {
147117
147751
  this.fileWatcher.watchDirectory(dir);
147118
147752
  }
147119
147753
  for (const dir of this.codexRoots) {
147120
- if (!(0, import_fs29.existsSync)(dir)) continue;
147754
+ if (!(0, import_fs30.existsSync)(dir)) continue;
147121
147755
  this.fileWatcher.watchDirectory(dir);
147122
147756
  }
147123
147757
  } catch (err) {
@@ -147129,6 +147763,7 @@ var StreamerServer = class {
147129
147763
  );
147130
147764
  this.scannerPersistenceDisabled = true;
147131
147765
  }
147766
+ void this.reconcilePreviousSessions().then((v2) => this.rehydratePreviousSessions(v2));
147132
147767
  if (this.skipStartupWarmup) {
147133
147768
  this.log.debug?.("startup warm-up scan skipped (skipStartupWarmup)", {
147134
147769
  event: "cache.warmup_skipped"
@@ -147336,6 +147971,7 @@ var StreamerServer = class {
147336
147971
  this.allScanners.clear();
147337
147972
  this.scanner = null;
147338
147973
  this.cache?.close();
147974
+ this.runtimeStore?.close();
147339
147975
  this.ptyManager.dispose();
147340
147976
  this.fileWatcher.dispose();
147341
147977
  this.externalTails.clear();
@@ -147390,7 +148026,7 @@ var StreamerServer = class {
147390
148026
  }
147391
148027
  let body;
147392
148028
  try {
147393
- body = await readBody(req);
148029
+ body = await readBody2(req);
147394
148030
  } catch (err) {
147395
148031
  const message = err instanceof Error ? err.message : "Invalid body";
147396
148032
  json2(res, 400, { error: message });
@@ -147436,7 +148072,7 @@ var StreamerServer = class {
147436
148072
  nonce: sealed.nonce,
147437
148073
  ephemeralPublicKey: sealed.ephemeralPublicKey,
147438
148074
  publicUrl: this.publicUrl,
147439
- machineName: (0, import_os12.hostname)(),
148075
+ machineName: (0, import_os13.hostname)(),
147440
148076
  ...device && {
147441
148077
  deviceId: device.deviceId,
147442
148078
  deviceToken: device.deviceToken,
@@ -147564,7 +148200,7 @@ var StreamerServer = class {
147564
148200
  if (this.scanProfiles && this.scanProfiles.length > 0) {
147565
148201
  return this.scanProfiles.filter((p2) => p2.enabled).map((p2) => (0, import_path29.join)(p2.configDir, "projects"));
147566
148202
  }
147567
- return [(0, import_path29.join)((0, import_os12.homedir)(), ".claude", "projects")];
148203
+ return [(0, import_path29.join)((0, import_os13.homedir)(), ".claude", "projects")];
147568
148204
  }
147569
148205
  /**
147570
148206
  * Full-glob scan + cache upsert/delete reconcile. Used by ?refresh=1 and by
@@ -147783,7 +148419,8 @@ var StreamerServer = class {
147783
148419
  }
147784
148420
  handleSessionsCount(res) {
147785
148421
  if (this.rejectIfWarmingUp(res)) return;
147786
- json2(res, 200, { total: this.sessionStore.list(this.ptyAttachedIds()).length });
148422
+ const total = this.sessionStore.list(this.ptyAttachedIds()).filter((s3) => s3.ownership !== "historical").length;
148423
+ json2(res, 200, { total });
147787
148424
  }
147788
148425
  handleGetRecentSessions(url2, res) {
147789
148426
  if (this.rejectIfWarmingUp(res)) return;
@@ -147966,20 +148603,20 @@ var StreamerServer = class {
147966
148603
  if (this.scanProfiles && this.scanProfiles.length > 0) {
147967
148604
  return this.scanProfiles.filter((p2) => p2.enabled).map((p2) => (0, import_path29.join)(p2.configDir, "projects"));
147968
148605
  }
147969
- return [(0, import_path29.join)((0, import_os12.homedir)(), ".claude", "projects")];
148606
+ return [(0, import_path29.join)((0, import_os13.homedir)(), ".claude", "projects")];
147970
148607
  }
147971
148608
  findJsonlPath(uuid3) {
147972
148609
  const filename = `${uuid3}.jsonl`;
147973
148610
  for (const projectsDir of this.projectsDirs()) {
147974
- if (!(0, import_fs29.existsSync)(projectsDir)) continue;
147975
- for (const dir of (0, import_fs29.readdirSync)(projectsDir)) {
148611
+ if (!(0, import_fs30.existsSync)(projectsDir)) continue;
148612
+ for (const dir of (0, import_fs30.readdirSync)(projectsDir)) {
147976
148613
  const fp = (0, import_path29.join)(projectsDir, dir, filename);
147977
- if ((0, import_fs29.existsSync)(fp)) return fp;
148614
+ if ((0, import_fs30.existsSync)(fp)) return fp;
147978
148615
  const projectDir = (0, import_path29.join)(projectsDir, dir);
147979
148616
  try {
147980
- for (const sub of (0, import_fs29.readdirSync)(projectDir)) {
148617
+ for (const sub of (0, import_fs30.readdirSync)(projectDir)) {
147981
148618
  const subagentPath = (0, import_path29.join)(projectDir, sub, "subagents", filename);
147982
- if ((0, import_fs29.existsSync)(subagentPath)) return subagentPath;
148619
+ if ((0, import_fs30.existsSync)(subagentPath)) return subagentPath;
147983
148620
  }
147984
148621
  } catch {
147985
148622
  }
@@ -147989,7 +148626,7 @@ var StreamerServer = class {
147989
148626
  }
147990
148627
  async readCwdFromJsonl(filePath) {
147991
148628
  return new Promise((resolve4) => {
147992
- const rl = (0, import_readline4.createInterface)({ input: (0, import_fs29.createReadStream)(filePath), crlfDelay: Infinity });
148629
+ const rl = (0, import_readline4.createInterface)({ input: (0, import_fs30.createReadStream)(filePath), crlfDelay: Infinity });
147993
148630
  let found = false;
147994
148631
  rl.on("line", (line) => {
147995
148632
  if (found) return;
@@ -148079,7 +148716,7 @@ var StreamerServer = class {
148079
148716
  if (this.isManagedTailPath(key)) return;
148080
148717
  let mtimeMs;
148081
148718
  try {
148082
- mtimeMs = (0, import_fs29.statSync)(filePath).mtimeMs;
148719
+ mtimeMs = (0, import_fs30.statSync)(filePath).mtimeMs;
148083
148720
  } catch {
148084
148721
  return;
148085
148722
  }
@@ -148261,7 +148898,7 @@ var StreamerServer = class {
148261
148898
  if (!conv.filePath) return false;
148262
148899
  let mtimeMs = null;
148263
148900
  try {
148264
- mtimeMs = (0, import_fs29.statSync)(conv.filePath).mtimeMs;
148901
+ mtimeMs = (0, import_fs30.statSync)(conv.filePath).mtimeMs;
148265
148902
  } catch {
148266
148903
  return false;
148267
148904
  }
@@ -148516,7 +149153,7 @@ var StreamerServer = class {
148516
149153
  }
148517
149154
  let body;
148518
149155
  try {
148519
- body = await readBody(req);
149156
+ body = await readBody2(req);
148520
149157
  } catch {
148521
149158
  res.setHeader("Accept-Query", "application/json");
148522
149159
  json2(res, 422, { error: "Malformed JSON body", code: "invalid_query" });
@@ -148554,17 +149191,27 @@ var StreamerServer = class {
148554
149191
  });
148555
149192
  }
148556
149193
  async handleSearch(url2, res) {
148557
- const q2 = url2.searchParams.get("q") ?? "";
148558
- if (!q2) {
148559
- json2(res, 400, { error: "Missing query parameter: q" });
148560
- return;
149194
+ let parsed;
149195
+ try {
149196
+ parsed = parseSearchQuery(url2.searchParams);
149197
+ } catch (err) {
149198
+ if (err instanceof SearchQueryError) {
149199
+ json2(res, 400, { error: err.message, code: err.code });
149200
+ return;
149201
+ }
149202
+ throw err;
148561
149203
  }
148562
- const limit = intParam(url2, "limit", 50);
149204
+ const { q: q2, limit, offset, filters } = parsed;
149205
+ const startedAt = Date.now();
148563
149206
  const scanner = await this.getScanner();
148564
149207
  const results = await search(
148565
149208
  q2,
148566
149209
  {
148567
- limit,
149210
+ // Fetch beyond the requested page: filters below are applied AFTER the
149211
+ // scanner returns, so slicing at `limit` here would drop results that a
149212
+ // later page should contain. Bounded so a broad query cannot pull an
149213
+ // unbounded set into memory.
149214
+ limit: Math.min(offset + limit * SEARCH_OVERFETCH, SEARCH_MAX_SCAN),
148568
149215
  include: "conversations",
148569
149216
  ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
148570
149217
  ...this.codexScanOpts()
@@ -148588,13 +149235,24 @@ var StreamerServer = class {
148588
149235
  lastActivity: r.meta.timestamp,
148589
149236
  firstMessage: r.meta.firstMessage ?? void 0,
148590
149237
  lastMessage: r.meta.lastMessage ?? void 0,
148591
- provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER2
149238
+ provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER2,
149239
+ // The scanner already computes relevance and match snippets; the previous
149240
+ // adapter discarded both, so results arrived in an unexplained order with
149241
+ // no indication of WHY anything matched.
149242
+ score: r.score,
149243
+ matches: Array.isArray(r.matches) ? r.matches.map((m2) => ({
149244
+ field: m2.field,
149245
+ snippet: m2.snippet
149246
+ })) : []
148592
149247
  }));
149248
+ const page = paginate(applyFilters(adapted, filters), offset, limit);
148593
149249
  json2(res, 200, {
148594
- conversations: adapted,
148595
- hasMore: false,
148596
- offset: 0,
148597
- total: adapted.length
149250
+ conversations: page.items,
149251
+ hasMore: page.hasMore,
149252
+ offset: page.offset,
149253
+ total: page.total,
149254
+ // Query timing, so a slow search is diagnosable rather than merely felt.
149255
+ tookMs: Date.now() - startedAt
148598
149256
  });
148599
149257
  }
148600
149258
  async handleListSessions(url2, res) {
@@ -148640,7 +149298,7 @@ var StreamerServer = class {
148640
149298
  if (this.rejectIfWarmingUp(res)) return;
148641
149299
  const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
148642
149300
  if (session) {
148643
- if (!(0, import_fs29.existsSync)(session.projectPath)) {
149301
+ if (!(0, import_fs30.existsSync)(session.projectPath)) {
148644
149302
  session.failureReason = `Project directory not found: ${session.projectPath}`;
148645
149303
  }
148646
149304
  const reconciled = this.withReconciledLifecycle([session])[0];
@@ -148667,7 +149325,7 @@ var StreamerServer = class {
148667
149325
  json2(res, 404, { error: "Session not found" });
148668
149326
  }
148669
149327
  async handleResume(req, res) {
148670
- const body = await readBody(req);
149328
+ const body = await readBody2(req);
148671
149329
  const sessionId = body.sessionId ?? body.conversationId;
148672
149330
  if (!sessionId) {
148673
149331
  json2(res, 400, { error: "Missing sessionId" });
@@ -148799,7 +149457,7 @@ var StreamerServer = class {
148799
149457
  return;
148800
149458
  }
148801
149459
  if (this.agentConfig.enabled) {
148802
- const body2 = await readBody(req);
149460
+ const body2 = await readBody2(req);
148803
149461
  const cache = this.cache;
148804
149462
  if (!cache) {
148805
149463
  json2(res, 503, {
@@ -148818,7 +149476,7 @@ var StreamerServer = class {
148818
149476
  json2(res, result.status, result.body);
148819
149477
  return;
148820
149478
  }
148821
- const body = await readBody(req);
149479
+ const body = await readBody2(req);
148822
149480
  const { input, keys } = body;
148823
149481
  let idempotencyKey;
148824
149482
  try {
@@ -148982,7 +149640,7 @@ var StreamerServer = class {
148982
149640
  });
148983
149641
  }
148984
149642
  async handleSendAnswer(sessionId, req, res) {
148985
- const body = await readBody(req);
149643
+ const body = await readBody2(req);
148986
149644
  const pending = this.pendingQuestions.get(sessionId);
148987
149645
  const resolution = resolveAnswer(pending, body);
148988
149646
  if (!resolution.ok) {
@@ -149011,7 +149669,7 @@ var StreamerServer = class {
149011
149669
  json2(res, 400, { error: "Session has no project path" });
149012
149670
  return;
149013
149671
  }
149014
- const body = await readBody(req);
149672
+ const body = await readBody2(req);
149015
149673
  const { filename, mimeType, dataBase64 } = body ?? {};
149016
149674
  if (typeof filename !== "string" || typeof mimeType !== "string" || typeof dataBase64 !== "string") {
149017
149675
  json2(res, 400, { error: "Missing filename, mimeType, or dataBase64" });
@@ -149213,7 +149871,7 @@ var StreamerServer = class {
149213
149871
  return;
149214
149872
  }
149215
149873
  if (this.agentConfig.enabled) {
149216
- const body2 = await readBody(req);
149874
+ const body2 = await readBody2(req);
149217
149875
  const result = await handleStartAgentSession(body2, {
149218
149876
  sessionStore: this.sessionStore,
149219
149877
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
@@ -149227,7 +149885,7 @@ var StreamerServer = class {
149227
149885
  }
149228
149886
  return;
149229
149887
  }
149230
- const body = await readBody(req);
149888
+ const body = await readBody2(req);
149231
149889
  const { path: relativePath, provider: requestedProvider, systemPrompt: clientPrompt } = body;
149232
149890
  if (requestedProvider !== void 0 && !isProviderName(requestedProvider)) {
149233
149891
  json2(res, 400, { error: "Invalid provider" });
@@ -149364,7 +150022,7 @@ var StreamerServer = class {
149364
150022
  // file isn't slurped in full.
149365
150023
  readFirstLineSessionId(filePath) {
149366
150024
  try {
149367
- const content = (0, import_fs29.readFileSync)(filePath, "utf8");
150025
+ const content = (0, import_fs30.readFileSync)(filePath, "utf8");
149368
150026
  const nl = content.indexOf("\n");
149369
150027
  const firstLine = nl === -1 ? content : content.slice(0, nl);
149370
150028
  if (!firstLine.trim()) return null;
@@ -149379,7 +150037,7 @@ var StreamerServer = class {
149379
150037
  // was passed to Claude via --session-id so the filename matches from the start.
149380
150038
  watchForJsonl(sessionId, projectPath) {
149381
150039
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
149382
- const projectsDir = (0, import_path29.join)((0, import_os12.homedir)(), ".claude", "projects", encoded);
150040
+ const projectsDir = (0, import_path29.join)((0, import_os13.homedir)(), ".claude", "projects", encoded);
149383
150041
  const expectedFile = `${sessionId}.jsonl`;
149384
150042
  const filePath = (0, import_path29.join)(projectsDir, expectedFile);
149385
150043
  const deadline = Date.now() + 12e4;
@@ -149399,11 +150057,11 @@ var StreamerServer = class {
149399
150057
  cleanup();
149400
150058
  return;
149401
150059
  }
149402
- let resolvedFilePath = (0, import_fs29.existsSync)(filePath) ? filePath : null;
149403
- if (!resolvedFilePath && (0, import_fs29.existsSync)(projectsDir)) {
150060
+ let resolvedFilePath = (0, import_fs30.existsSync)(filePath) ? filePath : null;
150061
+ if (!resolvedFilePath && (0, import_fs30.existsSync)(projectsDir)) {
149404
150062
  try {
149405
150063
  const now = Date.now();
149406
- const match2 = (0, import_fs29.readdirSync)(projectsDir).filter((f2) => f2.endsWith(".jsonl")).map((f2) => ({ f: f2, mtime: (0, import_fs29.statSync)((0, import_path29.join)(projectsDir, f2)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
150064
+ const match2 = (0, import_fs30.readdirSync)(projectsDir).filter((f2) => f2.endsWith(".jsonl")).map((f2) => ({ f: f2, mtime: (0, import_fs30.statSync)((0, import_path29.join)(projectsDir, f2)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
149407
150065
  ({ f: f2 }) => (0, import_path29.basename)(f2, ".jsonl") === sessionId || this.readFirstLineSessionId((0, import_path29.join)(projectsDir, f2)) === sessionId
149408
150066
  ).sort((a, b2) => b2.mtime - a.mtime)[0];
149409
150067
  if (match2) resolvedFilePath = (0, import_path29.join)(projectsDir, match2.f);
@@ -149414,7 +150072,7 @@ var StreamerServer = class {
149414
150072
  cleanup();
149415
150073
  this.sessionFileMap.set(sessionId, resolvedFilePath);
149416
150074
  try {
149417
- const existing = (0, import_fs29.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
150075
+ const existing = (0, import_fs30.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
149418
150076
  if (existing.length > 0) {
149419
150077
  this.broadcastConversationLines(sessionId, existing);
149420
150078
  }
@@ -149438,7 +150096,7 @@ var StreamerServer = class {
149438
150096
  if (this.sessionFileMap.has(sessionId)) return;
149439
150097
  try {
149440
150098
  require("fs").mkdirSync(projectsDir, { recursive: true });
149441
- watcher = (0, import_fs29.watch)(projectsDir, tryWire);
150099
+ watcher = (0, import_fs30.watch)(projectsDir, tryWire);
149442
150100
  watcher.on("error", cleanup);
149443
150101
  } catch {
149444
150102
  }
@@ -149467,7 +150125,7 @@ var StreamerServer = class {
149467
150125
  };
149468
150126
  const matchesProjectPath = (candidatePath) => {
149469
150127
  try {
149470
- const firstLine = (0, import_fs29.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
150128
+ const firstLine = (0, import_fs30.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
149471
150129
  if (!firstLine) return null;
149472
150130
  const parsed = JSON.parse(firstLine);
149473
150131
  if (parsed?.type !== "session_meta") return null;
@@ -149496,15 +150154,15 @@ var StreamerServer = class {
149496
150154
  );
149497
150155
  for (const root of this.codexRoots) {
149498
150156
  const sessionsDir = (0, import_path29.join)(root, dateDir);
149499
- if (!(0, import_fs29.existsSync)(sessionsDir)) continue;
150157
+ if (!(0, import_fs30.existsSync)(sessionsDir)) continue;
149500
150158
  let candidateFiles;
149501
150159
  try {
149502
- candidateFiles = (0, import_fs29.readdirSync)(sessionsDir).filter((f2) => f2.endsWith(".jsonl"));
150160
+ candidateFiles = (0, import_fs30.readdirSync)(sessionsDir).filter((f2) => f2.endsWith(".jsonl"));
149503
150161
  } catch {
149504
150162
  continue;
149505
150163
  }
149506
150164
  const nowMs = Date.now();
149507
- const recentCandidates = candidateFiles.map((f2) => ({ f: f2, mtime: (0, import_fs29.statSync)((0, import_path29.join)(sessionsDir, f2)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b2) => b2.mtime - a.mtime);
150165
+ const recentCandidates = candidateFiles.map((f2) => ({ f: f2, mtime: (0, import_fs30.statSync)((0, import_path29.join)(sessionsDir, f2)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b2) => b2.mtime - a.mtime);
149508
150166
  for (const { f: f2 } of recentCandidates) {
149509
150167
  const candidatePath = (0, import_path29.join)(sessionsDir, f2);
149510
150168
  const match2 = matchesProjectPath(candidatePath);
@@ -149516,7 +150174,7 @@ var StreamerServer = class {
149516
150174
  this.sessionFileMap.set(sessionId, candidatePath);
149517
150175
  this.fileWatcher.watch(candidatePath);
149518
150176
  try {
149519
- const existing = (0, import_fs29.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
150177
+ const existing = (0, import_fs30.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
149520
150178
  if (existing.length > 0) {
149521
150179
  this.broadcastConversationLines(sessionId, existing);
149522
150180
  }
@@ -149585,7 +150243,7 @@ var StreamerServer = class {
149585
150243
  });
149586
150244
  return;
149587
150245
  }
149588
- const body = await readBody(req);
150246
+ const body = await readBody2(req);
149589
150247
  const { path: relativePath, name } = body;
149590
150248
  if (!name || typeof name !== "string") {
149591
150249
  json2(res, 400, { error: "Missing name field" });
@@ -149615,7 +150273,7 @@ var StreamerServer = class {
149615
150273
  }
149616
150274
  let parsed;
149617
150275
  try {
149618
- parsed = await readBody(req);
150276
+ parsed = await readBody2(req);
149619
150277
  } catch {
149620
150278
  json2(res, 400, { error: "Invalid JSON" });
149621
150279
  return;
@@ -149672,7 +150330,7 @@ var StreamerServer = class {
149672
150330
  }
149673
150331
  let parsed;
149674
150332
  try {
149675
- parsed = await readBody(req);
150333
+ parsed = await readBody2(req);
149676
150334
  } catch {
149677
150335
  json2(res, 400, { error: "Invalid JSON" });
149678
150336
  return;
@@ -149731,7 +150389,7 @@ async function waitForProcessExit(pid, timeoutMs, pollMs = ADOPT_KILL_POLL_MS) {
149731
150389
  }
149732
150390
  function classifyResumability(cwd) {
149733
150391
  if (!cwd) return { resumable: true };
149734
- if ((0, import_fs29.existsSync)(cwd)) return { resumable: true };
150392
+ if ((0, import_fs30.existsSync)(cwd)) return { resumable: true };
149735
150393
  const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
149736
150394
  return {
149737
150395
  resumable: false,
@@ -149849,7 +150507,7 @@ function parseSessionListQuery(url2) {
149849
150507
  const cursor = url2.searchParams.get("cursor") ?? void 0;
149850
150508
  return { query: { limit, sortBy, order, status, cursor } };
149851
150509
  }
149852
- function readBody(req) {
150510
+ function readBody2(req) {
149853
150511
  return new Promise((resolve4, reject) => {
149854
150512
  const chunks = [];
149855
150513
  req.on("data", (chunk) => chunks.push(chunk));
@@ -150401,7 +151059,7 @@ var import_node_fs18 = require("fs");
150401
151059
 
150402
151060
  // node_modules/tar/dist/esm/index.min.js
150403
151061
  var import_events3 = __toESM(require("events"), 1);
150404
- var import_fs30 = __toESM(require("fs"), 1);
151062
+ var import_fs31 = __toESM(require("fs"), 1);
150405
151063
  var import_node_events3 = require("events");
150406
151064
  var import_node_stream2 = __toESM(require("stream"), 1);
150407
151065
  var import_node_string_decoder = require("string_decoder");
@@ -150415,8 +151073,8 @@ var Ps = __toESM(require("zlib"), 1);
150415
151073
  var import_zlib = __toESM(require("zlib"), 1);
150416
151074
  var import_node_path15 = require("path");
150417
151075
  var import_node_path16 = require("path");
150418
- var import_fs31 = __toESM(require("fs"), 1);
150419
151076
  var import_fs32 = __toESM(require("fs"), 1);
151077
+ var import_fs33 = __toESM(require("fs"), 1);
150420
151078
  var import_path31 = __toESM(require("path"), 1);
150421
151079
  var import_node_path17 = require("path");
150422
151080
  var import_path32 = __toESM(require("path"), 1);
@@ -150425,7 +151083,7 @@ var import_node_assert = __toESM(require("assert"), 1);
150425
151083
  var import_node_crypto6 = require("crypto");
150426
151084
  var import_node_fs14 = __toESM(require("fs"), 1);
150427
151085
  var import_node_path18 = __toESM(require("path"), 1);
150428
- var import_fs33 = __toESM(require("fs"), 1);
151086
+ var import_fs34 = __toESM(require("fs"), 1);
150429
151087
  var import_node_fs15 = __toESM(require("fs"), 1);
150430
151088
  var import_node_path19 = __toESM(require("path"), 1);
150431
151089
  var import_node_fs16 = __toESM(require("fs"), 1);
@@ -150777,7 +151435,7 @@ var A = class extends import_node_events3.EventEmitter {
150777
151435
  return Wr;
150778
151436
  }
150779
151437
  };
150780
- var Jr = import_fs30.default.writev;
151438
+ var Jr = import_fs31.default.writev;
150781
151439
  var ht = /* @__PURE__ */ Symbol("_autoClose");
150782
151440
  var H2 = /* @__PURE__ */ Symbol("_close");
150783
151441
  var te = /* @__PURE__ */ Symbol("_ended");
@@ -150832,7 +151490,7 @@ var _t = class extends A {
150832
151490
  throw new TypeError("this is a readable stream");
150833
151491
  }
150834
151492
  [at]() {
150835
- import_fs30.default.open(this[U], "r", (t, e) => this[Ht](t, e));
151493
+ import_fs31.default.open(this[U], "r", (t, e) => this[Ht](t, e));
150836
151494
  }
150837
151495
  [Ht](t, e) {
150838
151496
  t ? this[Ut](t) : (this[u] = e, this.emit("open", e), this[zt]());
@@ -150845,7 +151503,7 @@ var _t = class extends A {
150845
151503
  this[j] = true;
150846
151504
  let t = this[vi]();
150847
151505
  if (t.length === 0) return process.nextTick(() => this[Ii](null, 0, t));
150848
- import_fs30.default.read(this[u], t, 0, t.length, null, (e, i, r) => this[Ii](e, i, r));
151506
+ import_fs31.default.read(this[u], t, 0, t.length, null, (e, i, r) => this[Ii](e, i, r));
150849
151507
  }
150850
151508
  }
150851
151509
  [Ii](t, e, i) {
@@ -150854,7 +151512,7 @@ var _t = class extends A {
150854
151512
  [H2]() {
150855
151513
  if (this[ht] && typeof this[u] == "number") {
150856
151514
  let t = this[u];
150857
- this[u] = void 0, import_fs30.default.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
151515
+ this[u] = void 0, import_fs31.default.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
150858
151516
  }
150859
151517
  }
150860
151518
  [Ut](t) {
@@ -150882,7 +151540,7 @@ var Be = class extends _t {
150882
151540
  [at]() {
150883
151541
  let t = true;
150884
151542
  try {
150885
- this[Ht](null, import_fs30.default.openSync(this[U], "r")), t = false;
151543
+ this[Ht](null, import_fs31.default.openSync(this[U], "r")), t = false;
150886
151544
  } finally {
150887
151545
  t && this[H2]();
150888
151546
  }
@@ -150893,7 +151551,7 @@ var Be = class extends _t {
150893
151551
  if (!this[j]) {
150894
151552
  this[j] = true;
150895
151553
  do {
150896
- let e = this[vi](), i = e.length === 0 ? 0 : import_fs30.default.readSync(this[u], e, 0, e.length, null);
151554
+ let e = this[vi](), i = e.length === 0 ? 0 : import_fs31.default.readSync(this[u], e, 0, e.length, null);
150897
151555
  if (!this[ki](i, e)) break;
150898
151556
  } while (true);
150899
151557
  this[j] = false;
@@ -150906,7 +151564,7 @@ var Be = class extends _t {
150906
151564
  [H2]() {
150907
151565
  if (this[ht] && typeof this[u] == "number") {
150908
151566
  let t = this[u];
150909
- this[u] = void 0, import_fs30.default.closeSync(t), this.emit("close");
151567
+ this[u] = void 0, import_fs31.default.closeSync(t), this.emit("close");
150910
151568
  }
150911
151569
  }
150912
151570
  };
@@ -150948,7 +151606,7 @@ var et = class extends import_events3.default {
150948
151606
  this[H2](), this[gt] = true, this.emit("error", t);
150949
151607
  }
150950
151608
  [at]() {
150951
- import_fs30.default.open(this[U], this[tt], this[ie], (t, e) => this[Ht](t, e));
151609
+ import_fs31.default.open(this[U], this[tt], this[ie], (t, e) => this[Ht](t, e));
150952
151610
  }
150953
151611
  [Ht](t, e) {
150954
151612
  this[Me] && this[tt] === "r+" && t && t.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t ? this[Ut](t) : (this[u] = e, this.emit("open", e), this[gt] || this[Ai]());
@@ -150960,7 +151618,7 @@ var et = class extends import_events3.default {
150960
151618
  return typeof t == "string" && (t = Buffer.from(t, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[u] === void 0 || this[gt] || this[Y2].length ? (this[Y2].push(t), this[ke] = true, false) : (this[gt] = true, this[ve](t), true);
150961
151619
  }
150962
151620
  [ve](t) {
150963
- import_fs30.default.write(this[u], t, 0, t.length, this[ot], (e, i) => this[Pt](e, i));
151621
+ import_fs31.default.write(this[u], t, 0, t.length, this[ot], (e, i) => this[Pt](e, i));
150964
151622
  }
150965
151623
  [Pt](t, e) {
150966
151624
  t ? this[Ut](t) : (this[ot] !== void 0 && typeof e == "number" && (this[ot] += e), this[Y2].length ? this[Ai]() : (this[gt] = false, this[te] && !this[Ni] ? (this[Ni] = true, this[H2](), this.emit("finish")) : this[ke] && (this[ke] = false, this.emit("drain"))));
@@ -150976,7 +151634,7 @@ var et = class extends import_events3.default {
150976
151634
  [H2]() {
150977
151635
  if (this[ht] && typeof this[u] == "number") {
150978
151636
  let t = this[u];
150979
- this[u] = void 0, import_fs30.default.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
151637
+ this[u] = void 0, import_fs31.default.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
150980
151638
  }
150981
151639
  }
150982
151640
  };
@@ -150984,24 +151642,24 @@ var Wt = class extends et {
150984
151642
  [at]() {
150985
151643
  let t;
150986
151644
  if (this[Me] && this[tt] === "r+") try {
150987
- t = import_fs30.default.openSync(this[U], this[tt], this[ie]);
151645
+ t = import_fs31.default.openSync(this[U], this[tt], this[ie]);
150988
151646
  } catch (e) {
150989
151647
  if (e?.code === "ENOENT") return this[tt] = "w", this[at]();
150990
151648
  throw e;
150991
151649
  }
150992
- else t = import_fs30.default.openSync(this[U], this[tt], this[ie]);
151650
+ else t = import_fs31.default.openSync(this[U], this[tt], this[ie]);
150993
151651
  this[Ht](null, t);
150994
151652
  }
150995
151653
  [H2]() {
150996
151654
  if (this[ht] && typeof this[u] == "number") {
150997
151655
  let t = this[u];
150998
- this[u] = void 0, import_fs30.default.closeSync(t), this.emit("close");
151656
+ this[u] = void 0, import_fs31.default.closeSync(t), this.emit("close");
150999
151657
  }
151000
151658
  }
151001
151659
  [ve](t) {
151002
151660
  let e = true;
151003
151661
  try {
151004
- this[Pt](null, import_fs30.default.writeSync(this[u], t, 0, t.length, this[ot])), e = false;
151662
+ this[Pt](null, import_fs31.default.writeSync(this[u], t, 0, t.length, this[ot])), e = false;
151005
151663
  } finally {
151006
151664
  if (e) try {
151007
151665
  this[H2]();
@@ -151919,7 +152577,7 @@ var de = class extends A {
151919
152577
  return t === "error" && (this.#t = true), super.emit(t, ...e);
151920
152578
  }
151921
152579
  [ss]() {
151922
- import_fs32.default.lstat(this.absolute, (t, e) => {
152580
+ import_fs33.default.lstat(this.absolute, (t, e) => {
151923
152581
  if (t) return this.emit("error", t);
151924
152582
  this[si](e);
151925
152583
  });
@@ -151957,7 +152615,7 @@ var de = class extends A {
151957
152615
  this.path.slice(-1) !== "/" && (this.path += "/"), this.stat.size = 0, this[fe](), this.end();
151958
152616
  }
151959
152617
  [is]() {
151960
- import_fs32.default.readlink(this.absolute, (t, e) => {
152618
+ import_fs33.default.readlink(this.absolute, (t, e) => {
151961
152619
  if (t) return this.emit("error", t);
151962
152620
  this[ns](e);
151963
152621
  });
@@ -151980,7 +152638,7 @@ var de = class extends A {
151980
152638
  this[os]();
151981
152639
  }
151982
152640
  [os]() {
151983
- import_fs32.default.open(this.absolute, "r", (t, e) => {
152641
+ import_fs33.default.open(this.absolute, "r", (t, e) => {
151984
152642
  if (t) return this.emit("error", t);
151985
152643
  this[hs](e);
151986
152644
  });
@@ -151995,14 +152653,14 @@ var de = class extends A {
151995
152653
  [ii]() {
151996
152654
  let { fd: t, buf: e, offset: i, length: r, pos: n } = this;
151997
152655
  if (t === void 0 || e === void 0) throw new Error("cannot read file without first opening");
151998
- import_fs32.default.read(t, e, i, r, n, (o, h) => {
152656
+ import_fs33.default.read(t, e, i, r, n, (o, h) => {
151999
152657
  if (o) return this[pt](() => this.emit("error", o));
152000
152658
  this[rs](h);
152001
152659
  });
152002
152660
  }
152003
152661
  [pt](t = () => {
152004
152662
  }) {
152005
- this.fd !== void 0 && import_fs32.default.close(this.fd, t);
152663
+ this.fd !== void 0 && import_fs33.default.close(this.fd, t);
152006
152664
  }
152007
152665
  [rs](t) {
152008
152666
  if (t <= 0 && this.remain > 0) {
@@ -152037,20 +152695,20 @@ var de = class extends A {
152037
152695
  var ni = class extends de {
152038
152696
  sync = true;
152039
152697
  [ss]() {
152040
- this[si](import_fs32.default.lstatSync(this.absolute));
152698
+ this[si](import_fs33.default.lstatSync(this.absolute));
152041
152699
  }
152042
152700
  [is]() {
152043
- this[ns](import_fs32.default.readlinkSync(this.absolute));
152701
+ this[ns](import_fs33.default.readlinkSync(this.absolute));
152044
152702
  }
152045
152703
  [os]() {
152046
- this[hs](import_fs32.default.openSync(this.absolute, "r"));
152704
+ this[hs](import_fs33.default.openSync(this.absolute, "r"));
152047
152705
  }
152048
152706
  [ii]() {
152049
152707
  let t = true;
152050
152708
  try {
152051
152709
  let { fd: e, buf: i, offset: r, length: n, pos: o } = this;
152052
152710
  if (e === void 0 || i === void 0) throw new Error("fd and buf must be set in READ method");
152053
- let h = import_fs32.default.readSync(e, i, r, n, o);
152711
+ let h = import_fs33.default.readSync(e, i, r, n, o);
152054
152712
  this[rs](h), t = false;
152055
152713
  } finally {
152056
152714
  if (t) try {
@@ -152065,7 +152723,7 @@ var ni = class extends de {
152065
152723
  }
152066
152724
  [pt](t = () => {
152067
152725
  }) {
152068
- this.fd !== void 0 && import_fs32.default.closeSync(this.fd), t();
152726
+ this.fd !== void 0 && import_fs33.default.closeSync(this.fd), t();
152069
152727
  }
152070
152728
  };
152071
152729
  var oi = class extends A {
@@ -152396,7 +153054,7 @@ var wt = class extends A {
152396
153054
  [ds](t) {
152397
153055
  t.pending = true, this[G] += 1;
152398
153056
  let e = this.follow ? "stat" : "lstat";
152399
- import_fs31.default[e](t.absolute, (i, r) => {
153057
+ import_fs32.default[e](t.absolute, (i, r) => {
152400
153058
  t.pending = false, this[G] -= 1, i ? this.emit("error", i) : this[li](t, r);
152401
153059
  });
152402
153060
  }
@@ -152410,7 +153068,7 @@ var wt = class extends A {
152410
153068
  this[Ft]();
152411
153069
  }
152412
153070
  [ms](t) {
152413
- t.pending = true, this[G] += 1, import_fs31.default.readdir(t.absolute, (e, i) => {
153071
+ t.pending = true, this[G] += 1, import_fs32.default.readdir(t.absolute, (e, i) => {
152414
153072
  if (t.pending = false, this[G] -= 1, e) return this.emit("error", e);
152415
153073
  this[fi](t, i);
152416
153074
  });
@@ -152511,10 +153169,10 @@ var kt = class extends wt {
152511
153169
  }
152512
153170
  [ds](t) {
152513
153171
  let e = this.follow ? "statSync" : "lstatSync";
152514
- this[li](t, import_fs31.default[e](t.absolute));
153172
+ this[li](t, import_fs32.default[e](t.absolute));
152515
153173
  }
152516
153174
  [ms](t) {
152517
- this[fi](t, import_fs31.default.readdirSync(t.absolute));
153175
+ this[fi](t, import_fs32.default.readdirSync(t.absolute));
152518
153176
  }
152519
153177
  [di](t) {
152520
153178
  let e = t.entry, i = this.zip;
@@ -152565,8 +153223,8 @@ var Qn = K(Vn, $n, Xn, qn, (s3, t) => {
152565
153223
  });
152566
153224
  var Jn = process.env.__FAKE_PLATFORM__ || process.platform;
152567
153225
  var Er = Jn === "win32";
152568
- var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs33.default.constants;
152569
- var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) || import_fs33.default.constants.UV_FS_O_FILEMAP || 0;
153226
+ var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs34.default.constants;
153227
+ var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) || import_fs34.default.constants.UV_FS_O_FILEMAP || 0;
152570
153228
  var jn = Er && !!Rr;
152571
153229
  var to = 512 * 1024;
152572
153230
  var eo = Rr | Sr | wr | yr;
@@ -153944,12 +154602,12 @@ program2.command("cache").description("Manage the local SQLite conversation cach
153944
154602
  "Cache directory (default: ~/.threadbase/cache)",
153945
154603
  `${process.env.HOME}/.threadbase/cache`
153946
154604
  ).action((opts) => {
153947
- const { rmSync: rmSync6, existsSync: existsSync15 } = require("fs");
154605
+ const { rmSync: rmSync6, existsSync: existsSync16 } = require("fs");
153948
154606
  const { join: join29 } = require("path");
153949
154607
  const dbPath = join29(opts.cacheDir, "cache.db");
153950
154608
  for (const suffix of ["", "-shm", "-wal"]) {
153951
154609
  const f2 = dbPath + suffix;
153952
- if (existsSync15(f2)) {
154610
+ if (existsSync16(f2)) {
153953
154611
  rmSync6(f2);
153954
154612
  log11.info(`Deleted ${f2}`, { path: f2 }, "console");
153955
154613
  }