@threadbase-sh/streamer 1.47.2 → 1.48.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
@@ -4637,11 +4637,11 @@ var require_tools = __commonJS({
4637
4637
  }
4638
4638
  }
4639
4639
  }
4640
- function buildFormatters(level, bindings, log13) {
4640
+ function buildFormatters(level, bindings, log15) {
4641
4641
  return {
4642
4642
  level,
4643
4643
  bindings,
4644
- log: log13
4644
+ log: log15
4645
4645
  };
4646
4646
  }
4647
4647
  function normalizeDestFileDescriptor(destination) {
@@ -5023,11 +5023,11 @@ var require_proto = __commonJS({
5023
5023
  }
5024
5024
  } else instance[serializersSym] = serializers;
5025
5025
  if (options.hasOwnProperty("formatters")) {
5026
- const { level, bindings: chindings, log: log13 } = options.formatters;
5026
+ const { level, bindings: chindings, log: log15 } = options.formatters;
5027
5027
  instance[formattersSym] = buildFormatters(
5028
5028
  level || formatters.level,
5029
5029
  chindings || resetChildingsFormatter,
5030
- log13 || formatters.log
5030
+ log15 || formatters.log
5031
5031
  );
5032
5032
  } else {
5033
5033
  instance[formattersSym] = buildFormatters(
@@ -8206,7 +8206,7 @@ var require_merge = __commonJS({
8206
8206
  var require_addPairToJSMap = __commonJS({
8207
8207
  "node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports2) {
8208
8208
  "use strict";
8209
- var log13 = require_log();
8209
+ var log15 = require_log();
8210
8210
  var merge2 = require_merge();
8211
8211
  var stringify = require_stringify();
8212
8212
  var identity = require_identity();
@@ -8255,7 +8255,7 @@ var require_addPairToJSMap = __commonJS({
8255
8255
  let jsonStr = JSON.stringify(strKey);
8256
8256
  if (jsonStr.length > 40)
8257
8257
  jsonStr = jsonStr.substring(0, 36) + '..."';
8258
- log13.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
8258
+ log15.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`);
8259
8259
  ctx.mapKeyWarned = true;
8260
8260
  }
8261
8261
  return strKey;
@@ -13671,7 +13671,7 @@ var require_public_api = __commonJS({
13671
13671
  var composer = require_composer();
13672
13672
  var Document = require_Document();
13673
13673
  var errors = require_errors();
13674
- var log13 = require_log();
13674
+ var log15 = require_log();
13675
13675
  var identity = require_identity();
13676
13676
  var lineCounter = require_line_counter();
13677
13677
  var parser = require_parser();
@@ -13723,7 +13723,7 @@ var require_public_api = __commonJS({
13723
13723
  const doc = parseDocument(src, options);
13724
13724
  if (!doc)
13725
13725
  return null;
13726
- doc.warnings.forEach((warning) => log13.warn(doc.options.logLevel, warning));
13726
+ doc.warnings.forEach((warning) => log15.warn(doc.options.logLevel, warning));
13727
13727
  if (doc.errors.length > 0) {
13728
13728
  if (doc.options.logLevel !== "silent")
13729
13729
  throw doc.errors[0];
@@ -37243,7 +37243,7 @@ var require_logging = __commonJS({
37243
37243
  _logVerbosity = verbosity;
37244
37244
  };
37245
37245
  exports2.setLoggerVerbosity = setLoggerVerbosity;
37246
- var log13 = (severity, ...args) => {
37246
+ var log15 = (severity, ...args) => {
37247
37247
  let logFunction;
37248
37248
  if (severity >= _logVerbosity) {
37249
37249
  switch (severity) {
@@ -37265,7 +37265,7 @@ var require_logging = __commonJS({
37265
37265
  }
37266
37266
  }
37267
37267
  };
37268
- exports2.log = log13;
37268
+ exports2.log = log15;
37269
37269
  var tracersString = (_d = (_c = process.env.GRPC_NODE_TRACE) !== null && _c !== void 0 ? _c : process.env.GRPC_TRACE) !== null && _d !== void 0 ? _d : "";
37270
37270
  var enabledTracers = /* @__PURE__ */ new Set();
37271
37271
  var disabledTracers = /* @__PURE__ */ new Set();
@@ -127026,7 +127026,7 @@ function readMarker() {
127026
127026
  const parsed = JSON.parse(raw2);
127027
127027
  return MarkerSchema.parse(parsed);
127028
127028
  } catch (err) {
127029
- log8.warn(`marker at ${markerPath()} is malformed; treating as absent`, {
127029
+ log10.warn(`marker at ${markerPath()} is malformed; treating as absent`, {
127030
127030
  error: err instanceof Error ? err.message : String(err)
127031
127031
  });
127032
127032
  return null;
@@ -127043,7 +127043,7 @@ function writeMarker(marker) {
127043
127043
  function clearMarker() {
127044
127044
  if ((0, import_node_fs22.existsSync)(markerPath())) (0, import_node_fs22.rmSync)(markerPath());
127045
127045
  }
127046
- var import_node_fs22, import_node_path25, log8;
127046
+ var import_node_fs22, import_node_path25, log10;
127047
127047
  var init_marker = __esm({
127048
127048
  "src/lifecycle/marker.ts"() {
127049
127049
  "use strict";
@@ -127052,7 +127052,7 @@ var init_marker = __esm({
127052
127052
  init_logger();
127053
127053
  init_constants();
127054
127054
  init_marker_schema();
127055
- log8 = getLogger("lifecycle.marker");
127055
+ log10 = getLogger("lifecycle.marker");
127056
127056
  }
127057
127057
  });
127058
127058
 
@@ -127292,7 +127292,7 @@ function readPrefs() {
127292
127292
  try {
127293
127293
  return PrefsSchema.parse(JSON.parse((0, import_node_fs25.readFileSync)(path2, "utf8")));
127294
127294
  } catch (err) {
127295
- log10.warn(`prefs at ${path2} are malformed; treating as empty`, {
127295
+ log12.warn(`prefs at ${path2} are malformed; treating as empty`, {
127296
127296
  error: err instanceof Error ? err.message : String(err)
127297
127297
  });
127298
127298
  return { repos: {} };
@@ -127327,7 +127327,7 @@ function forgetAll() {
127327
127327
  const path2 = prefsPath();
127328
127328
  if ((0, import_node_fs25.existsSync)(path2)) (0, import_node_fs25.rmSync)(path2);
127329
127329
  }
127330
- var import_node_fs25, import_node_path26, log10, PrefsSchema;
127330
+ var import_node_fs25, import_node_path26, log12, PrefsSchema;
127331
127331
  var init_prefs = __esm({
127332
127332
  "src/lifecycle/prefs.ts"() {
127333
127333
  "use strict";
@@ -127336,7 +127336,7 @@ var init_prefs = __esm({
127336
127336
  init_zod();
127337
127337
  init_logger();
127338
127338
  init_constants();
127339
- log10 = getLogger("lifecycle.prefs");
127339
+ log12 = getLogger("lifecycle.prefs");
127340
127340
  PrefsSchema = external_exports.object({
127341
127341
  repos: external_exports.record(
127342
127342
  external_exports.string(),
@@ -127439,7 +127439,7 @@ function takeoverProd(opts) {
127439
127439
  `prod is already suspended by dev pid ${existing.devPid} (since ${existing.suspendedAt}). Stop that dev session first, or run 'tb-streamer prod doctor'.`
127440
127440
  );
127441
127441
  }
127442
- log11.info(`stale marker found (pid ${existing.devPid} is gone) \u2014 clearing and proceeding`);
127442
+ log13.info(`stale marker found (pid ${existing.devPid} is gone) \u2014 clearing and proceeding`);
127443
127443
  }
127444
127444
  getSupervisor().bootoutAgent();
127445
127445
  writeMarker({
@@ -127454,7 +127454,7 @@ function takeoverProd(opts) {
127454
127454
  const m2 = readMarker();
127455
127455
  if (m2 && m2.devPid === process.pid) {
127456
127456
  writeMarker({ ...m2, userHeld: true });
127457
- log11.info(
127457
+ log13.info(
127458
127458
  `prod is suspended (userHeld). Run 'tb-streamer prod start' to restore the supervised instance.`
127459
127459
  );
127460
127460
  }
@@ -127473,14 +127473,14 @@ function takeoverProd(opts) {
127473
127473
  });
127474
127474
  process.on("uncaughtException", (err) => {
127475
127475
  flipUserHeld();
127476
- log11.error(`uncaught: ${err.message}`);
127476
+ log13.error(`uncaught: ${err.message}`);
127477
127477
  process.exit(1);
127478
127478
  });
127479
127479
  process.on("exit", () => {
127480
127480
  flipUserHeld();
127481
127481
  });
127482
127482
  }
127483
- var import_node_net2, log11;
127483
+ var import_node_net2, log13;
127484
127484
  var init_dev_takeover = __esm({
127485
127485
  "src/lifecycle/dev-takeover.ts"() {
127486
127486
  "use strict";
@@ -127489,7 +127489,7 @@ var init_dev_takeover = __esm({
127489
127489
  init_marker();
127490
127490
  init_platform2();
127491
127491
  init_prefs();
127492
- log11 = getLogger("lifecycle.dev-takeover");
127492
+ log13 = getLogger("lifecycle.dev-takeover");
127493
127493
  }
127494
127494
  });
127495
127495
 
@@ -135584,7 +135584,7 @@ var REF_PREFIX = "ref: refs/heads/";
135584
135584
  var MAX_DEPTH = 6;
135585
135585
  function readGitBranch(projectPath) {
135586
135586
  if (!projectPath) return null;
135587
- const log13 = getLogger2();
135587
+ const log15 = getLogger2();
135588
135588
  let dir = projectPath;
135589
135589
  let depth = 0;
135590
135590
  while (depth < MAX_DEPTH) {
@@ -135593,11 +135593,11 @@ function readGitBranch(projectPath) {
135593
135593
  const content = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
135594
135594
  if (content.startsWith(REF_PREFIX)) {
135595
135595
  const branch = content.slice(REF_PREFIX.length);
135596
- log13.trace({ projectPath, dir, branch }, "git: branch resolved");
135596
+ log15.trace({ projectPath, dir, branch }, "git: branch resolved");
135597
135597
  return branch;
135598
135598
  }
135599
135599
  if (content.length >= 7) {
135600
- log13.trace({ projectPath, dir }, "git: detached HEAD");
135600
+ log15.trace({ projectPath, dir }, "git: detached HEAD");
135601
135601
  return "(detached)";
135602
135602
  }
135603
135603
  return null;
@@ -135608,7 +135608,7 @@ function readGitBranch(projectPath) {
135608
135608
  dir = parent;
135609
135609
  depth++;
135610
135610
  }
135611
- log13.trace({ projectPath }, "git: no .git found within depth");
135611
+ log15.trace({ projectPath }, "git: no .git found within depth");
135612
135612
  return null;
135613
135613
  }
135614
135614
  function generateMatches(meta3, query) {
@@ -135901,8 +135901,8 @@ function cleanSystemTags(text) {
135901
135901
  return text.replace(SYSTEM_TAG_RE, "").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
135902
135902
  }
135903
135903
  async function parseMeta(filePath, account, tier) {
135904
- const log13 = getLogger2();
135905
- log13.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
135904
+ const log15 = getLogger2();
135905
+ log15.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
135906
135906
  const state = initialReducerState();
135907
135907
  const fileStream = (0, import_fs3.createReadStream)(filePath);
135908
135908
  const rl = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
@@ -135919,22 +135919,22 @@ async function parseMeta(filePath, account, tier) {
135919
135919
  reduceLine(state, entry, tier);
135920
135920
  }
135921
135921
  } catch (err) {
135922
- log13.warn({ filePath, err }, "parseMeta: read failed");
135922
+ log15.warn({ filePath, err }, "parseMeta: read failed");
135923
135923
  return null;
135924
135924
  }
135925
135925
  if (state.badJsonLines > 0) {
135926
- log13.warn(
135926
+ log15.warn(
135927
135927
  { filePath, badJsonLines: state.badJsonLines },
135928
135928
  "parseMeta: skipped malformed JSON lines"
135929
135929
  );
135930
135930
  }
135931
135931
  const meta3 = finalizeMeta(state, filePath, account, tier);
135932
- if (!meta3) log13.trace({ filePath }, "parseMeta: no messages");
135932
+ if (!meta3) log15.trace({ filePath }, "parseMeta: no messages");
135933
135933
  return meta3;
135934
135934
  }
135935
135935
  async function parseConversation(filePath, account) {
135936
- const log13 = getLogger2();
135937
- log13.trace({ filePath, account }, "parseConversation: start");
135936
+ const log15 = getLogger2();
135937
+ log15.trace({ filePath, account }, "parseConversation: start");
135938
135938
  const messages = [];
135939
135939
  let badJsonLines = 0;
135940
135940
  const textParts = [];
@@ -135967,17 +135967,17 @@ async function parseConversation(filePath, account) {
135967
135967
  }
135968
135968
  }
135969
135969
  } catch (err) {
135970
- log13.warn({ filePath, err }, "parseConversation: read failed");
135970
+ log15.warn({ filePath, err }, "parseConversation: read failed");
135971
135971
  return null;
135972
135972
  }
135973
135973
  if (badJsonLines > 0) {
135974
- log13.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
135974
+ log15.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
135975
135975
  }
135976
135976
  if (messages.length === 0) {
135977
- log13.trace({ filePath }, "parseConversation: no messages");
135977
+ log15.trace({ filePath }, "parseConversation: no messages");
135978
135978
  return null;
135979
135979
  }
135980
- log13.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
135980
+ log15.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
135981
135981
  applyTeamInfo(messages, state);
135982
135982
  return {
135983
135983
  id: filePath,
@@ -136242,15 +136242,15 @@ async function detectDefaultProfile() {
136242
136242
  };
136243
136243
  }
136244
136244
  async function loadProfiles(configPath) {
136245
- const log13 = getLogger2();
136245
+ const log15 = getLogger2();
136246
136246
  try {
136247
136247
  const resolved = resolveConfigDir(configPath);
136248
136248
  const data = await (0, import_promises4.readFile)((0, import_path5.join)(resolved, PROFILES_FILE), "utf-8");
136249
136249
  const profiles = JSON.parse(data);
136250
- log13.debug({ configPath, count: profiles.length }, "profiles: loaded");
136250
+ log15.debug({ configPath, count: profiles.length }, "profiles: loaded");
136251
136251
  return profiles;
136252
136252
  } catch (err) {
136253
- log13.debug({ configPath, err }, "profiles: load failed, using default");
136253
+ log15.debug({ configPath, err }, "profiles: load failed, using default");
136254
136254
  const defaultProfile = await detectDefaultProfile();
136255
136255
  return [defaultProfile];
136256
136256
  }
@@ -136261,7 +136261,7 @@ function canonicalPath(p2) {
136261
136261
  var CodexCliProvider = class {
136262
136262
  name = CODEX_CLI_PROVIDER;
136263
136263
  async discover(roots) {
136264
- const log13 = getLogger2();
136264
+ const log15 = getLogger2();
136265
136265
  const results = [];
136266
136266
  for (const root of roots) {
136267
136267
  let paths;
@@ -136273,7 +136273,7 @@ var CodexCliProvider = class {
136273
136273
  unique: true
136274
136274
  });
136275
136275
  } catch (err) {
136276
- log13.warn({ root, err }, "codex discovery: glob failed");
136276
+ log15.warn({ root, err }, "codex discovery: glob failed");
136277
136277
  continue;
136278
136278
  }
136279
136279
  for (const filePath of paths) {
@@ -136281,7 +136281,7 @@ var CodexCliProvider = class {
136281
136281
  const s3 = await (0, import_promises5.stat)(filePath);
136282
136282
  if (s3.size > 0) results.push({ filePath: canonicalPath(filePath), account: "codex" });
136283
136283
  } catch (err) {
136284
- log13.warn({ filePath, err }, "codex discovery: stat failed");
136284
+ log15.warn({ filePath, err }, "codex discovery: stat failed");
136285
136285
  }
136286
136286
  }
136287
136287
  }
@@ -136425,7 +136425,7 @@ function getShortProjectName3(fullPath) {
136425
136425
  return fullPath.split("/").filter(Boolean).slice(-3).join("/");
136426
136426
  }
136427
136427
  async function parseCodexConversation(filePath, account) {
136428
- const log13 = getLogger2();
136428
+ const log15 = getLogger2();
136429
136429
  const messages = [];
136430
136430
  const textParts = [];
136431
136431
  let sessionId = "";
@@ -136461,7 +136461,7 @@ async function parseCodexConversation(filePath, account) {
136461
136461
  if (role === "user") lastUserText = text;
136462
136462
  }
136463
136463
  } catch (err) {
136464
- log13.warn({ filePath, err }, "parseCodexConversation: read failed");
136464
+ log15.warn({ filePath, err }, "parseCodexConversation: read failed");
136465
136465
  return null;
136466
136466
  }
136467
136467
  if (messages.length === 0) return null;
@@ -136483,7 +136483,7 @@ async function parseCodexConversation(filePath, account) {
136483
136483
  var EXCLUDED_SEGMENTS = ["/memory/", "/tool-results/"];
136484
136484
  var STAT_CONCURRENCY = 32;
136485
136485
  async function discoverJsonlFiles(dirs, onProgress) {
136486
- const log13 = getLogger2();
136486
+ const log15 = getLogger2();
136487
136487
  const results = [];
136488
136488
  for (const { projectsDir, account } of dirs) {
136489
136489
  let filePaths;
@@ -136494,7 +136494,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
136494
136494
  dot: false
136495
136495
  });
136496
136496
  } catch (err) {
136497
- log13.warn({ projectsDir, account, err }, "discovery: glob failed");
136497
+ log15.warn({ projectsDir, account, err }, "discovery: glob failed");
136498
136498
  continue;
136499
136499
  }
136500
136500
  const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));
@@ -136509,7 +136509,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
136509
136509
  const s3 = await (0, import_promises6.stat)(filePath);
136510
136510
  return { filePath, size: s3.size };
136511
136511
  } catch (err) {
136512
- log13.warn({ filePath, err }, "discovery: stat failed");
136512
+ log15.warn({ filePath, err }, "discovery: stat failed");
136513
136513
  return { filePath, size: -1 };
136514
136514
  }
136515
136515
  })
@@ -136525,7 +136525,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
136525
136525
  }
136526
136526
  }
136527
136527
  }
136528
- log13.debug(
136528
+ log15.debug(
136529
136529
  {
136530
136530
  projectsDir,
136531
136531
  account,
@@ -136539,7 +136539,7 @@ async function discoverJsonlFiles(dirs, onProgress) {
136539
136539
  );
136540
136540
  onProgress?.(results.length);
136541
136541
  }
136542
- log13.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
136542
+ log15.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
136543
136543
  return results;
136544
136544
  }
136545
136545
  var ThreadbaseProvider = class {
@@ -136849,7 +136849,7 @@ function classify(filePath, existing) {
136849
136849
  return { change: "reindex", stat: stat42 };
136850
136850
  }
136851
136851
  async function parseMetaWithProvider(provider, filePath, account, tier) {
136852
- const log13 = getLogger2();
136852
+ const log15 = getLogger2();
136853
136853
  const acc = provider.createEmptyAccumulator();
136854
136854
  const rl = (0, import_readline3.createInterface)({
136855
136855
  input: (0, import_fs11.createReadStream)(filePath),
@@ -136867,11 +136867,11 @@ async function parseMetaWithProvider(provider, filePath, account, tier) {
136867
136867
  try {
136868
136868
  provider.reduceEntry(acc, entry, tier);
136869
136869
  } catch (err) {
136870
- log13.warn({ filePath, provider: provider.name, err }, "provider reduce threw; line skipped");
136870
+ log15.warn({ filePath, provider: provider.name, err }, "provider reduce threw; line skipped");
136871
136871
  }
136872
136872
  }
136873
136873
  } catch (err) {
136874
- log13.warn({ filePath, provider: provider.name, err }, "provider parse: read failed");
136874
+ log15.warn({ filePath, provider: provider.name, err }, "provider parse: read failed");
136875
136875
  return null;
136876
136876
  }
136877
136877
  return provider.finalize(acc, filePath, account, tier);
@@ -137045,8 +137045,8 @@ CREATE INDEX IF NOT EXISTS idx_scanned_dirs_parent_root ON scanned_dirs(parent_r
137045
137045
  function runMigrations(db) {
137046
137046
  const current = db.pragma("user_version", { simple: true });
137047
137047
  if (current >= SCHEMA_VERSION) return;
137048
- const log13 = getLogger2();
137049
- log13.info({ from: current, to: SCHEMA_VERSION }, "migrations: applying");
137048
+ const log15 = getLogger2();
137049
+ log15.info({ from: current, to: SCHEMA_VERSION }, "migrations: applying");
137050
137050
  if (current >= 1 && current < 2 && tableExists(db, "conversations")) {
137051
137051
  for (const [col, ddl] of [
137052
137052
  [
@@ -137088,7 +137088,7 @@ function openDatabase(dbPath) {
137088
137088
  }
137089
137089
  var FULL_RECONCILE_EVERY_N_SCANS = 20;
137090
137090
  async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
137091
- const log13 = getLogger2();
137091
+ const log15 = getLogger2();
137092
137092
  if (options.fullRescan) {
137093
137093
  return discoverJsonlFiles(dirs);
137094
137094
  }
@@ -137130,7 +137130,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
137130
137130
  }
137131
137131
  }
137132
137132
  }
137133
- log13.debug(
137133
+ log15.debug(
137134
137134
  { totalFiles: results.length, dirs: dirs.length },
137135
137135
  "dir-watermark: gated discovery complete"
137136
137136
  );
@@ -137636,7 +137636,7 @@ var PersistentEngine = class {
137636
137636
  // changed since the last index, and upsert their metadata. Returns the number
137637
137637
  // of files seen on disk this pass (the scan "scanned" count).
137638
137638
  async indexAll(activeProfiles, options) {
137639
- const log13 = getLogger2();
137639
+ const log15 = getLogger2();
137640
137640
  const tier = resolveTier(options.tier ?? "standard", options.tiers);
137641
137641
  const enabled = options.providers ?? [CLAUDE_CODE_PROVIDER];
137642
137642
  const discovered = [];
@@ -137700,7 +137700,7 @@ var PersistentEngine = class {
137700
137700
  for (const path2 of this.files.activePathsByAccounts([...coveredAccounts])) {
137701
137701
  if (!seen.has(path2)) this.markDeleted(path2);
137702
137702
  }
137703
- log13.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
137703
+ log15.info({ scanned, indexed: this.conversations.count() }, "persistent: indexAll complete");
137704
137704
  return { scanned };
137705
137705
  }
137706
137706
  // (Re)index a single file. Classifies the change vs. the persisted cursor:
@@ -137711,7 +137711,7 @@ var PersistentEngine = class {
137711
137711
  // alongside the meta so callers (refreshFile) can keep, extend, or evict
137712
137712
  // their own per-file caches without re-stat'ing the file (racy) themselves.
137713
137713
  async indexFile(rawFilePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
137714
- const log13 = getLogger2();
137714
+ const log15 = getLogger2();
137715
137715
  const tier = resolveTier(tierName, customTiers);
137716
137716
  const filePath = canonicalPath(rawFilePath);
137717
137717
  const existing = this.files.getByPath(filePath);
@@ -137742,7 +137742,7 @@ var PersistentEngine = class {
137742
137742
  try {
137743
137743
  result = await tailReduce(filePath, startOffset, startLine, state, tier);
137744
137744
  } catch (err) {
137745
- log13.warn({ filePath, err }, "persistent: tail read failed");
137745
+ log15.warn({ filePath, err }, "persistent: tail read failed");
137746
137746
  return { meta: null, change };
137747
137747
  }
137748
137748
  const meta3 = finalizeMeta(state, filePath, account, tier);
@@ -137785,7 +137785,7 @@ var PersistentEngine = class {
137785
137785
  )
137786
137786
  );
137787
137787
  }
137788
- log13.debug(
137788
+ log15.debug(
137789
137789
  { filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta3.messageCount },
137790
137790
  "persistent: indexed file"
137791
137791
  );
@@ -137797,7 +137797,7 @@ var PersistentEngine = class {
137797
137797
  // so the next pass classifies an unchanged file as "unchanged" and skips it;
137798
137798
  // any change reparses from 0 again. No reducer_state is persisted.
137799
137799
  async indexFileWithProvider(provider, filePath, account, tier, stat42, resolveGitBranch) {
137800
- const log13 = getLogger2();
137800
+ const log15 = getLogger2();
137801
137801
  const meta3 = await parseMetaWithProvider(provider, filePath, account, tier);
137802
137802
  if (!meta3) {
137803
137803
  this.markDeleted(filePath);
@@ -137825,7 +137825,7 @@ var PersistentEngine = class {
137825
137825
  });
137826
137826
  });
137827
137827
  upsert();
137828
- log13.debug(
137828
+ log15.debug(
137829
137829
  { filePath, provider: provider.name, msgs: meta3.messageCount },
137830
137830
  "persistent: indexed provider file"
137831
137831
  );
@@ -137939,7 +137939,7 @@ var FileWatcher = class {
137939
137939
  // Resolves once every underlying chokidar watcher has finished its initial
137940
137940
  // scan, so the caller knows subsequent FS changes will be observed.
137941
137941
  async start() {
137942
- const log13 = getLogger2();
137942
+ const log15 = getLogger2();
137943
137943
  const ready = [];
137944
137944
  for (const profile of this.profiles) {
137945
137945
  const dir = getProjectsDir(profile);
@@ -137951,7 +137951,7 @@ var FileWatcher = class {
137951
137951
  watcher.on("add", (p2) => this.dispatch(p2, profile.id, "add")).on("change", (p2) => this.dispatch(p2, profile.id, "change")).on("unlink", (p2) => this.dispatch(p2, profile.id, "unlink"));
137952
137952
  ready.push(new Promise((resolve4) => watcher.once("ready", () => resolve4())));
137953
137953
  this.watchers.push(watcher);
137954
- log13.debug({ dir, account: profile.id }, "watcher: watching");
137954
+ log15.debug({ dir, account: profile.id }, "watcher: watching");
137955
137955
  }
137956
137956
  await Promise.all(ready);
137957
137957
  }
@@ -137998,14 +137998,14 @@ var IndexQueue = class {
137998
137998
  async drain() {
137999
137999
  if (this.running) return;
138000
138000
  this.running = true;
138001
- const log13 = getLogger2();
138001
+ const log15 = getLogger2();
138002
138002
  while (this.pending.size > 0) {
138003
138003
  const [path2, job] = this.pending.entries().next().value;
138004
138004
  this.pending.delete(path2);
138005
138005
  try {
138006
138006
  await this.process(job);
138007
138007
  } catch (err) {
138008
- log13.warn({ filePath: job.filePath, err }, "index-queue: job failed");
138008
+ log15.warn({ filePath: job.filePath, err }, "index-queue: job failed");
138009
138009
  }
138010
138010
  }
138011
138011
  this.running = false;
@@ -138114,13 +138114,13 @@ var ConversationScanner = class {
138114
138114
  // active metas and run the identical filter/sort/view/paginate pipeline as
138115
138115
  // the in-memory path — guaranteeing an identical ScanResult shape.
138116
138116
  async scanPersistent(activeProfiles, options) {
138117
- const log13 = getLogger2();
138117
+ const log15 = getLogger2();
138118
138118
  const startedAt = Date.now();
138119
138119
  const engine = this.engine();
138120
138120
  const { scanned } = await engine.indexAll(activeProfiles, options);
138121
138121
  const allMetas = engine.allActive();
138122
138122
  const { conversations, total } = this.finalize(allMetas, options);
138123
- log13.info(
138123
+ log15.info(
138124
138124
  { scanned, kept: allMetas.length, filteredTotal: total, elapsedMs: Date.now() - startedAt },
138125
138125
  "scan: complete (persistent)"
138126
138126
  );
@@ -138147,10 +138147,10 @@ var ConversationScanner = class {
138147
138147
  return { conversations, total };
138148
138148
  }
138149
138149
  async scanInMemory(activeProfiles, options) {
138150
- const log13 = getLogger2();
138150
+ const log15 = getLogger2();
138151
138151
  const startedAt = Date.now();
138152
138152
  const tier = this.lastTier;
138153
- log13.info(
138153
+ log15.info(
138154
138154
  {
138155
138155
  activeProfiles: activeProfiles.length,
138156
138156
  tier: tier.name,
@@ -138204,7 +138204,7 @@ var ConversationScanner = class {
138204
138204
  return meta3;
138205
138205
  } catch (err) {
138206
138206
  parseFailures++;
138207
- log13.warn({ filePath, account, provider: provider.name, err }, "scan: parse threw");
138207
+ log15.warn({ filePath, account, provider: provider.name, err }, "scan: parse threw");
138208
138208
  return null;
138209
138209
  }
138210
138210
  })
@@ -138224,12 +138224,12 @@ var ConversationScanner = class {
138224
138224
  options.onBatch?.(batchMetas);
138225
138225
  }
138226
138226
  scanned += batch.length;
138227
- log13.debug({ scanned, totalFiles, batchKept: batchMetas.length }, "scan: batch complete");
138227
+ log15.debug({ scanned, totalFiles, batchKept: batchMetas.length }, "scan: batch complete");
138228
138228
  options.onProgress?.(scanned, totalFiles);
138229
138229
  }
138230
138230
  const { conversations, total } = this.finalize(allMetas, options);
138231
138231
  const elapsedMs = Date.now() - startedAt;
138232
- log13.info(
138232
+ log15.info(
138233
138233
  {
138234
138234
  totalFiles,
138235
138235
  scanned,
@@ -138243,13 +138243,13 @@ var ConversationScanner = class {
138243
138243
  return { conversations, total, scanned };
138244
138244
  }
138245
138245
  async search(query, options = {}) {
138246
- const log13 = getLogger2();
138247
- log13.debug({ query, indexSize: this.indexer.getDocumentCount() }, "search: start");
138246
+ const log15 = getLogger2();
138247
+ log15.debug({ query, indexSize: this.indexer.getDocumentCount() }, "search: start");
138248
138248
  let results;
138249
138249
  if (this.persistent) {
138250
138250
  const engine = this.engine();
138251
138251
  if (engine.conversations.count() === 0) {
138252
- log13.debug("search: persistent index empty, triggering scan");
138252
+ log15.debug("search: persistent index empty, triggering scan");
138253
138253
  const profiles = await this.resolveProfiles(options.profiles);
138254
138254
  const activeProfiles = profiles.filter((p2) => p2.enabled && p2.scanHistory !== false);
138255
138255
  await engine.indexAll(activeProfiles, { ...options, limit: void 0, offset: void 0 });
@@ -138262,7 +138262,7 @@ var ConversationScanner = class {
138262
138262
  }));
138263
138263
  } else {
138264
138264
  if (this.indexer.getDocumentCount() === 0) {
138265
- log13.debug("search: index empty, triggering scan");
138265
+ log15.debug("search: index empty, triggering scan");
138266
138266
  await this.scan({ ...options, limit: void 0, offset: void 0 });
138267
138267
  }
138268
138268
  results = this.indexer.search(query, {
@@ -138305,23 +138305,23 @@ var ConversationScanner = class {
138305
138305
  const limit = options.limit ?? 50;
138306
138306
  const offset = options.offset ?? 0;
138307
138307
  const sliced = results.slice(offset, offset + limit);
138308
- log13.debug({ query, matched: results.length, returned: sliced.length }, "search: complete");
138308
+ log15.debug({ query, matched: results.length, returned: sliced.length }, "search: complete");
138309
138309
  return sliced;
138310
138310
  }
138311
138311
  async getConversation(id, _options) {
138312
- const log13 = getLogger2();
138312
+ const log15 = getLogger2();
138313
138313
  const cid = canonicalPath(id);
138314
138314
  const cached4 = this.conversationLRU.get(cid);
138315
138315
  if (cached4) {
138316
- log13.debug({ id }, "getConversation: cache hit");
138316
+ log15.debug({ id }, "getConversation: cache hit");
138317
138317
  return cached4.conversation;
138318
138318
  }
138319
138319
  const meta3 = this.persistent ? this.engine().getByIdOrSession(cid) : this.metadataCache.get(cid) ?? this.resolveSessionId(cid);
138320
138320
  if (!meta3) {
138321
- log13.debug({ id }, "getConversation: not found in metadata");
138321
+ log15.debug({ id }, "getConversation: not found in metadata");
138322
138322
  return null;
138323
138323
  }
138324
- log13.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
138324
+ log15.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
138325
138325
  try {
138326
138326
  if (this.persistent && meta3.provider !== CODEX_CLI_PROVIDER) {
138327
138327
  const parsed = await parseConversationResumable(meta3.filePath, meta3.account);
@@ -138334,7 +138334,7 @@ var ConversationScanner = class {
138334
138334
  }
138335
138335
  return conversation;
138336
138336
  } catch (err) {
138337
- log13.warn({ id, filePath: meta3.filePath, err }, "getConversation: parse failed");
138337
+ log15.warn({ id, filePath: meta3.filePath, err }, "getConversation: parse failed");
138338
138338
  return null;
138339
138339
  }
138340
138340
  }
@@ -138420,7 +138420,7 @@ var ConversationScanner = class {
138420
138420
  return refresh;
138421
138421
  }
138422
138422
  async doRefreshFile(filePath, account) {
138423
- const log13 = getLogger2();
138423
+ const log15 = getLogger2();
138424
138424
  if (this.persistent) {
138425
138425
  const engine = this.engine();
138426
138426
  const previous2 = engine.getByIdOrSession(filePath);
@@ -138447,7 +138447,7 @@ var ConversationScanner = class {
138447
138447
  } else if (change === "appended") {
138448
138448
  await this.extendCachedConversations(cacheKeys, filePath, meta22.account);
138449
138449
  }
138450
- log13.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
138450
+ log15.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
138451
138451
  return meta22;
138452
138452
  }
138453
138453
  const previous = this.metadataCache.get(filePath);
@@ -138456,7 +138456,7 @@ var ConversationScanner = class {
138456
138456
  try {
138457
138457
  meta3 = await parseMeta(filePath, resolvedAccount, this.lastTier);
138458
138458
  } catch (err) {
138459
- log13.warn({ filePath, err }, "refreshFile: parseMeta threw");
138459
+ log15.warn({ filePath, err }, "refreshFile: parseMeta threw");
138460
138460
  meta3 = null;
138461
138461
  }
138462
138462
  const evict = (m2) => {
@@ -138472,7 +138472,7 @@ var ConversationScanner = class {
138472
138472
  this.removeFromSessionIndex(previous);
138473
138473
  this.indexer.removeDocument(previous.id);
138474
138474
  }
138475
- log13.debug({ filePath }, "refreshFile: dropped (no parseable messages)");
138475
+ log15.debug({ filePath }, "refreshFile: dropped (no parseable messages)");
138476
138476
  return null;
138477
138477
  }
138478
138478
  meta3.gitBranch = readGitBranch(meta3.projectPath);
@@ -138485,7 +138485,7 @@ var ConversationScanner = class {
138485
138485
  } else {
138486
138486
  this.indexer.addDocument(meta3);
138487
138487
  }
138488
- log13.debug(
138488
+ log15.debug(
138489
138489
  { filePath, messageCount: meta3.messageCount },
138490
138490
  "refreshFile: updated in-memory indexes"
138491
138491
  );
@@ -145950,10 +145950,10 @@ function fingerprintOf(ids) {
145950
145950
  return `sha256:${(0, import_crypto11.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
145951
145951
  }
145952
145952
  var CacheIntegrityMonitor = class {
145953
- constructor(cache, wsHub, log13, cacheDir, rescan, runDuringReset) {
145953
+ constructor(cache, wsHub, log15, cacheDir, rescan, runDuringReset) {
145954
145954
  this.cache = cache;
145955
145955
  this.wsHub = wsHub;
145956
- this.log = log13;
145956
+ this.log = log15;
145957
145957
  this.cacheDir = cacheDir;
145958
145958
  this.rescan = rescan;
145959
145959
  this.runDuringReset = runDuringReset;
@@ -146972,6 +146972,102 @@ var ApnsClient = class {
146972
146972
  }
146973
146973
  };
146974
146974
 
146975
+ // src/services/push/expoPushSender.ts
146976
+ init_logger();
146977
+ var log5 = getLogger("expo-push");
146978
+ var EXPO_PUSH_ENDPOINT = "https://exp.host/--/api/v2/push/send";
146979
+ var EXPO_PUSH_BATCH_SIZE = 100;
146980
+ var DEAD_TOKEN_ERROR = "DeviceNotRegistered";
146981
+ var ExpoPushSender = class {
146982
+ /**
146983
+ * @param accessToken Expo access token, when the project has enhanced
146984
+ * security enabled. Optional on purpose: a self-hoster does not own the Expo
146985
+ * project and cannot obtain one, so requiring it would break the deployment
146986
+ * this transport exists to serve.
146987
+ */
146988
+ constructor(repo, accessToken) {
146989
+ this.repo = repo;
146990
+ this.accessToken = accessToken;
146991
+ }
146992
+ repo;
146993
+ accessToken;
146994
+ /**
146995
+ * Send one message to every deliverable Expo token.
146996
+ *
146997
+ * Sends are independent: Expo returns a ticket per token in one response, so
146998
+ * a dead device is recorded against its own row and never silences the other
146999
+ * devices in the batch.
147000
+ */
147001
+ async send(message, now = Date.now()) {
147002
+ const rows = this.repo.listDeliverable();
147003
+ const outcome = { attempted: rows.length, succeeded: 0, retired: 0 };
147004
+ if (rows.length === 0) return outcome;
147005
+ for (let i = 0; i < rows.length; i += EXPO_PUSH_BATCH_SIZE) {
147006
+ const chunk = rows.slice(i, i + EXPO_PUSH_BATCH_SIZE);
147007
+ await this.sendChunk(chunk, message, now, outcome);
147008
+ }
147009
+ return outcome;
147010
+ }
147011
+ async sendChunk(rows, message, now, outcome) {
147012
+ let payload;
147013
+ try {
147014
+ const res = await fetch(EXPO_PUSH_ENDPOINT, {
147015
+ method: "POST",
147016
+ headers: {
147017
+ "content-type": "application/json",
147018
+ accept: "application/json",
147019
+ ...this.accessToken && { authorization: `Bearer ${this.accessToken}` }
147020
+ },
147021
+ body: JSON.stringify(rows.map((row) => ({ to: row.token, ...message })))
147022
+ });
147023
+ if (!res.ok) {
147024
+ const code = `HTTP_${res.status}`;
147025
+ for (const row of rows) this.repo.recordFailure(row.token, code, now);
147026
+ log5.warn("expo_push.request_rejected", {
147027
+ event: "expo_push.request_rejected",
147028
+ status: res.status,
147029
+ tokens: rows.length
147030
+ });
147031
+ return;
147032
+ }
147033
+ payload = await res.json();
147034
+ } catch (err) {
147035
+ for (const row of rows) this.repo.recordFailure(row.token, "SendError", now);
147036
+ log5.error("expo_push.send_failed", {
147037
+ event: "expo_push.send_failed",
147038
+ tokens: rows.length,
147039
+ err: String(err)
147040
+ });
147041
+ return;
147042
+ }
147043
+ const tickets = payload?.data;
147044
+ rows.forEach((row, index) => {
147045
+ const ticket = Array.isArray(tickets) ? tickets[index] : void 0;
147046
+ if (!ticket) {
147047
+ this.repo.recordFailure(row.token, "NoTicket", now);
147048
+ return;
147049
+ }
147050
+ if (ticket.status === "ok") {
147051
+ this.repo.recordSuccess(row.token, now);
147052
+ outcome.succeeded += 1;
147053
+ return;
147054
+ }
147055
+ const code = ticket.details?.error ?? "PushError";
147056
+ this.repo.recordFailure(row.token, code, now);
147057
+ if (code === DEAD_TOKEN_ERROR) {
147058
+ this.repo.revoke(row.token, now);
147059
+ outcome.retired += 1;
147060
+ }
147061
+ log5.warn("expo_push.send_rejected", {
147062
+ event: "expo_push.send_rejected",
147063
+ code,
147064
+ message: ticket.message,
147065
+ retired: code === DEAD_TOKEN_ERROR
147066
+ });
147067
+ });
147068
+ }
147069
+ };
147070
+
146975
147071
  // src/services/push/liveActivityNotifier.ts
146976
147072
  init_logger();
146977
147073
 
@@ -146986,7 +147082,7 @@ function truncateLastOutput(raw2) {
146986
147082
  }
146987
147083
 
146988
147084
  // src/services/push/liveActivityNotifier.ts
146989
- var log5 = getLogger("live-activity");
147085
+ var log6 = getLogger("live-activity");
146990
147086
  function contentStateForSession(args) {
146991
147087
  const status = toLiveActivityStatus(args.session.status);
146992
147088
  if (!status) return null;
@@ -147046,7 +147142,7 @@ var LiveActivityNotifier = class {
147046
147142
  }
147047
147143
  await this.maybeSendName(session);
147048
147144
  } catch (err) {
147049
- log5.error("live_activity.notify_failed", {
147145
+ log6.error("live_activity.notify_failed", {
147050
147146
  event: "live_activity.notify_failed",
147051
147147
  sessionId: session.id,
147052
147148
  status: session.status,
@@ -147068,7 +147164,7 @@ var LiveActivityNotifier = class {
147068
147164
  });
147069
147165
  this.openActivity.set(session.id, { sessionNameSent: session.sessionName != null });
147070
147166
  if (outcome.attempted > 0) {
147071
- log5.info("live_activity.updated", {
147167
+ log6.info("live_activity.updated", {
147072
147168
  event: "live_activity.updated",
147073
147169
  sessionId: session.id,
147074
147170
  status: contentState.status,
@@ -147092,7 +147188,7 @@ var LiveActivityNotifier = class {
147092
147188
  });
147093
147189
  open3.sessionNameSent = true;
147094
147190
  if (outcome.attempted > 0) {
147095
- log5.info("live_activity.updated", {
147191
+ log6.info("live_activity.updated", {
147096
147192
  event: "live_activity.updated",
147097
147193
  sessionId: session.id,
147098
147194
  status: contentState.status,
@@ -147111,7 +147207,7 @@ var LiveActivityNotifier = class {
147111
147207
  if (!contentState) return;
147112
147208
  const outcome = await this.sender.end({ sessionId: session.id, contentState });
147113
147209
  if (outcome.attempted > 0) {
147114
- log5.info("live_activity.ended", {
147210
+ log6.info("live_activity.ended", {
147115
147211
  event: "live_activity.ended",
147116
147212
  sessionId: session.id,
147117
147213
  ...outcome
@@ -147129,7 +147225,7 @@ init_logger();
147129
147225
 
147130
147226
  // src/services/push/liveActivitySender.ts
147131
147227
  init_logger();
147132
- var log6 = getLogger("live-activity");
147228
+ var log7 = getLogger("live-activity");
147133
147229
  var ACTIVITY_MAX_LIFETIME_MS = 8 * 60 * 60 * 1e3;
147134
147230
  function buildActivityKitPayload(args) {
147135
147231
  return {
@@ -147193,7 +147289,7 @@ var LiveActivitySender = class {
147193
147289
  );
147194
147290
  for (const { row, result, error: error51 } of results) {
147195
147291
  if (error51) {
147196
- log6.error("live_activity.send_failed", {
147292
+ log7.error("live_activity.send_failed", {
147197
147293
  event: "live_activity.send_failed",
147198
147294
  sessionId: args.sessionId,
147199
147295
  activityId: row.activity_id,
@@ -147214,7 +147310,7 @@ var LiveActivitySender = class {
147214
147310
  this.repo.expire(row.token, now);
147215
147311
  outcome.retired += 1;
147216
147312
  }
147217
- log6.warn("live_activity.send_rejected", {
147313
+ log7.warn("live_activity.send_rejected", {
147218
147314
  event: "live_activity.send_rejected",
147219
147315
  sessionId: args.sessionId,
147220
147316
  activityId: row.activity_id,
@@ -147259,7 +147355,7 @@ var LiveActivitySender = class {
147259
147355
  };
147260
147356
 
147261
147357
  // src/services/push/liveActivityRenewal.ts
147262
- var log7 = getLogger("live-activity");
147358
+ var log8 = getLogger("live-activity");
147263
147359
  var RENEWAL_LEAD_MS = 30 * 60 * 1e3;
147264
147360
  var MAX_TIMER_MS = 60 * 60 * 1e3;
147265
147361
  function renewalDueAt(row) {
@@ -147307,7 +147403,7 @@ var LiveActivityRenewalScheduler = class {
147307
147403
  await this.renew(row, now);
147308
147404
  }
147309
147405
  } catch (err) {
147310
- log7.error("live_activity.renewal_sweep_failed", {
147406
+ log8.error("live_activity.renewal_sweep_failed", {
147311
147407
  event: "live_activity.renewal_sweep_failed",
147312
147408
  err: String(err)
147313
147409
  });
@@ -147336,7 +147432,7 @@ var LiveActivityRenewalScheduler = class {
147336
147432
  if (!session || !status) {
147337
147433
  this.deps.repo.claimRenewal(row.token, now);
147338
147434
  this.deps.repo.expire(row.token, now);
147339
- log7.info("live_activity.renewal_skipped", {
147435
+ log8.info("live_activity.renewal_skipped", {
147340
147436
  event: "live_activity.renewal_skipped",
147341
147437
  sessionId: row.session_id,
147342
147438
  activityId: row.activity_id,
@@ -147371,7 +147467,7 @@ var LiveActivityRenewalScheduler = class {
147371
147467
  startedAt,
147372
147468
  now
147373
147469
  });
147374
- log7.info("live_activity.renewed", {
147470
+ log8.info("live_activity.renewed", {
147375
147471
  event: "live_activity.renewed",
147376
147472
  sessionId: session.id,
147377
147473
  activityId: row.activity_id,
@@ -147381,7 +147477,7 @@ var LiveActivityRenewalScheduler = class {
147381
147477
  replacementRequested: started
147382
147478
  });
147383
147479
  } catch (err) {
147384
- log7.error("live_activity.renewal_failed", {
147480
+ log8.error("live_activity.renewal_failed", {
147385
147481
  event: "live_activity.renewal_failed",
147386
147482
  sessionId: session.id,
147387
147483
  activityId: row.activity_id,
@@ -147426,6 +147522,78 @@ var LiveActivityRenewalScheduler = class {
147426
147522
  }
147427
147523
  };
147428
147524
 
147525
+ // src/services/push/waitingInputNotifier.ts
147526
+ init_logger();
147527
+ var log9 = getLogger("expo-push");
147528
+ function waitingInputMessage(session, serverId) {
147529
+ return {
147530
+ title: session.projectName || "Threadbase",
147531
+ body: "Waiting for your input",
147532
+ data: { sessionId: session.id, serverId }
147533
+ };
147534
+ }
147535
+ var WaitingInputNotifier = class {
147536
+ /**
147537
+ * @param isWatched Whether a client is currently subscribed to this session
147538
+ * over WebSocket. Mobile subscribes while the session screen is open and the
147539
+ * socket dies when the app is backgrounded, so this is the available signal
147540
+ * for "the user is already looking" — and a push to someone already reading
147541
+ * the output is pure noise.
147542
+ */
147543
+ constructor(sender, serverId, isWatched) {
147544
+ this.sender = sender;
147545
+ this.serverId = serverId;
147546
+ this.isWatched = isWatched;
147547
+ }
147548
+ sender;
147549
+ serverId;
147550
+ isWatched;
147551
+ /** Sessions with a turn the user started that has not yet been answered. */
147552
+ openTurn = /* @__PURE__ */ new Set();
147553
+ /**
147554
+ * React to a session status change.
147555
+ *
147556
+ * Fire-and-forget by design: a push must never delay or fail a session
147557
+ * transition, so this returns a promise the caller may ignore and every error
147558
+ * is logged rather than propagated.
147559
+ */
147560
+ async onStatusChange(session, previousStatus) {
147561
+ try {
147562
+ if (session.status === "running") {
147563
+ if (previousStatus === "waiting_input") this.openTurn.add(session.id);
147564
+ return;
147565
+ }
147566
+ if (session.status !== "waiting_input") {
147567
+ this.openTurn.delete(session.id);
147568
+ return;
147569
+ }
147570
+ if (!this.openTurn.delete(session.id)) return;
147571
+ if (this.isWatched(session.id)) {
147572
+ log9.debug("expo_push.suppressed_watched", {
147573
+ event: "expo_push.suppressed_watched",
147574
+ sessionId: session.id
147575
+ });
147576
+ return;
147577
+ }
147578
+ const outcome = await this.sender.send(waitingInputMessage(session, this.serverId));
147579
+ if (outcome.attempted > 0) {
147580
+ log9.info("expo_push.waiting_input", {
147581
+ event: "expo_push.waiting_input",
147582
+ sessionId: session.id,
147583
+ ...outcome
147584
+ });
147585
+ }
147586
+ } catch (err) {
147587
+ log9.error("expo_push.notify_failed", {
147588
+ event: "expo_push.notify_failed",
147589
+ sessionId: session.id,
147590
+ status: session.status,
147591
+ err: String(err)
147592
+ });
147593
+ }
147594
+ }
147595
+ };
147596
+
147429
147597
  // src/server.ts
147430
147598
  init_detectPermissionGate();
147431
147599
  init_detectQuestionFromScreen();
@@ -148773,6 +148941,10 @@ var StreamerServer = class {
148773
148941
  apnsClient = null;
148774
148942
  liveActivityNotifier = null;
148775
148943
  liveActivityRenewal = null;
148944
+ // "Your turn" notifications over Expo's relay (#528). Needs no credential of
148945
+ // its own, so unlike the Live Activity path it is on wherever the cache DB
148946
+ // opened — with no registered device it simply sends nothing.
148947
+ waitingInputNotifier = null;
148776
148948
  discoveryCache = null;
148777
148949
  // Single-flight for process discovery. Mobile polls GET /api/sessions and
148778
148950
  // retries on timeout; without this, every concurrent request starts its own
@@ -149072,6 +149244,7 @@ var StreamerServer = class {
149072
149244
  this.wsHub.broadcast({ type: "session_update", session: resp });
149073
149245
  }
149074
149246
  void this.liveActivityNotifier?.onStatusChange(session, previousStatus);
149247
+ void this.waitingInputNotifier?.onStatusChange(session, previousStatus);
149075
149248
  this.sessionStatusBus.emit(`status:${session.id}`, session.status, session);
149076
149249
  }
149077
149250
  });
@@ -149384,6 +149557,33 @@ var StreamerServer = class {
149384
149557
  topic: `${creds.bundleId}.push-type.liveactivity`
149385
149558
  });
149386
149559
  }
149560
+ /**
149561
+ * Bring up "your turn" notifications over Expo's relay (#528).
149562
+ *
149563
+ * Unconditional, unlike Live Activity push: Expo holds the app's APNs and FCM
149564
+ * credentials, so a self-hosted streamer needs no credential of its own. The
149565
+ * access token is optional and only relevant if the Expo project has enhanced
149566
+ * security enabled — requiring one would lock out every self-hoster, since
149567
+ * they do not own the project. It is never logged.
149568
+ */
149569
+ initWaitingInputPush(pushRepo) {
149570
+ const sender = new ExpoPushSender(pushRepo, process.env.THREADBASE_EXPO_ACCESS_TOKEN);
149571
+ const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os13.hostname)();
149572
+ this.waitingInputNotifier = new WaitingInputNotifier(
149573
+ sender,
149574
+ serverId,
149575
+ (id) => this.hasSessionSubscriber(id)
149576
+ );
149577
+ }
149578
+ /** Whether any live socket is subscribed to this session — "someone is looking". */
149579
+ hasSessionSubscriber(sessionId) {
149580
+ const subs = this.sessionSubscribers.get(sessionId);
149581
+ if (!subs) return false;
149582
+ for (const ws2 of subs) {
149583
+ if (ws2.readyState === ws2.OPEN) return true;
149584
+ }
149585
+ return false;
149586
+ }
149387
149587
  /**
149388
149588
  * Classify sessions left behind by previous streamer runs (C1 Phase 3a).
149389
149589
  *
@@ -149986,6 +150186,7 @@ var StreamerServer = class {
149986
150186
  this.pushRepo = new PushRepository(db);
149987
150187
  this.devicesRepo = new DevicesRepository(db);
149988
150188
  this.initLiveActivityPush(this.pushRepo);
150189
+ this.initWaitingInputPush(this.pushRepo);
149989
150190
  this.cacheMonitor = new CacheIntegrityMonitor(
149990
150191
  this.cache,
149991
150192
  this.wsHub,
@@ -156833,7 +157034,7 @@ init_platform2();
156833
157034
  init_logger();
156834
157035
  init_protocol();
156835
157036
  init_socket();
156836
- var log9 = getLogger("prod");
157037
+ var log11 = getLogger("prod");
156837
157038
  function clearSupervisorLogs() {
156838
157039
  let paths;
156839
157040
  try {
@@ -157017,12 +157218,12 @@ function registerProdCommands(program3) {
157017
157218
  prod.command("start").description("Restore prod after a user-held suspension").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
157018
157219
  if (opts.clearLogs === true) clearSupervisorLogs();
157019
157220
  const r = await runProdStart();
157020
- log9.info(r.message, void 0, "console");
157221
+ log11.info(r.message, void 0, "console");
157021
157222
  if (!r.ok) process.exitCode = 1;
157022
157223
  });
157023
157224
  prod.command("stop").description("Unload the launchd agent (prod will not auto-restart)").action(async () => {
157024
157225
  const r = await runProdStop();
157025
- log9.info(r.message, void 0, "console");
157226
+ log11.info(r.message, void 0, "console");
157026
157227
  });
157027
157228
  prod.command("status").description("Report whether prod is supervised, suspended, or down").action(async () => {
157028
157229
  const s3 = await runProdStatus();
@@ -157031,7 +157232,7 @@ function registerProdCommands(program3) {
157031
157232
  `pid: ${s3.agentPid ?? "(none)"}`,
157032
157233
  s3.marker ? `marker: ${s3.marker.userHeld ? "userHeld (intentional stop)" : "dev-suspended"}, devPid=${s3.marker.devPid}, port=${s3.marker.port}, repo=${s3.marker.repoToplevel}` : "marker: none"
157033
157234
  ];
157034
- log9.info(parts.join("\n "), void 0, "console");
157235
+ log11.info(parts.join("\n "), void 0, "console");
157035
157236
  });
157036
157237
  prod.command("restart").description("Stop + restart the supervised streamer (re-reads service definition)").option("--clear-logs", "Clear existing stdout + stderr logs", false).action(async (opts) => {
157037
157238
  const sup = getSupervisor();
@@ -157040,24 +157241,24 @@ function registerProdCommands(program3) {
157040
157241
  sup.bootoutAgent();
157041
157242
  sup.bootstrapAgent(specPath, { afterBootout: true });
157042
157243
  const what = process.platform === "darwin" ? `agent restarted from ${specPath}` : `task '${TASK_NAME}' restarted`;
157043
- log9.info(what, void 0, "console");
157244
+ log11.info(what, void 0, "console");
157044
157245
  });
157045
157246
  prod.command("doctor").description("Detect stale markers, missing agents, and pty-host health").option("--fix", "Apply repairs (default is dry-run)", false).action(async (opts) => {
157046
157247
  const r = await runProdDoctor({ fix: opts.fix === true });
157047
157248
  if (r.ptyHost) {
157048
- log9.info(
157249
+ log11.info(
157049
157250
  r.ptyHost.reachable ? `pty-host: reachable, protocol=${r.ptyHost.protocolVersion}, sessions=${r.ptyHost.sessionCount}` : `pty-host: unreachable (${r.ptyHost.error})`,
157050
157251
  void 0,
157051
157252
  "console"
157052
157253
  );
157053
157254
  }
157054
- log9.info(`findings: ${r.findings.length === 0 ? "(none)" : ""}`, void 0, "console");
157055
- for (const f2 of r.findings) log9.info(` - ${f2}`, void 0, "console");
157255
+ log11.info(`findings: ${r.findings.length === 0 ? "(none)" : ""}`, void 0, "console");
157256
+ for (const f2 of r.findings) log11.info(` - ${f2}`, void 0, "console");
157056
157257
  if (r.repairs.length) {
157057
- log9.info(`repairs:`, void 0, "console");
157058
- for (const fix of r.repairs) log9.info(` - ${fix}`, void 0, "console");
157258
+ log11.info(`repairs:`, void 0, "console");
157259
+ for (const fix of r.repairs) log11.info(` - ${fix}`, void 0, "console");
157059
157260
  } else if (!opts.fix && r.findings.length > 0) {
157060
- log9.info(`(re-run with --fix to apply repairs)`, void 0, "console");
157261
+ log11.info(`(re-run with --fix to apply repairs)`, void 0, "console");
157061
157262
  }
157062
157263
  });
157063
157264
  prod.command("logs").description("Tail the supervised streamer's stdout + stderr log files").option("-n, --lines <count>", "Seed with the last N lines (default 50)", "50").option("--no-follow", "Print last N lines and exit (do not follow)").option("--errors-only", "Tail only stderr", false).option("--clear", "Truncate stdout + stderr logs in place, then exit", false).action(async (opts) => {
@@ -157068,14 +157269,14 @@ function registerProdCommands(program3) {
157068
157269
  errorsOnly: opts.errorsOnly === true,
157069
157270
  clear: opts.clear === true
157070
157271
  });
157071
- if (r.message) log9.info(r.message, void 0, "console");
157272
+ if (r.message) log11.info(r.message, void 0, "console");
157072
157273
  if (!r.ok) process.exitCode = 1;
157073
157274
  });
157074
157275
  program3.addCommand(prod);
157075
157276
  }
157076
157277
 
157077
157278
  // cli/index.ts
157078
- var log12 = getLogger("cli");
157279
+ var log14 = getLogger("cli");
157079
157280
  var program2 = new Command();
157080
157281
  program2.name("threadbase-streamer").description("PTY session management, WebSocket streaming, and REST API server for Claude Code").version(getVersion());
157081
157282
  program2.command("serve").description("Start the streamer server").option("-p, --port <number>", "Port to listen on", "8766").option("--api-key <key>", "API key for authentication").option("--local-no-auth", "Skip auth for localhost requests", false).option("-v, --verbose", "Verbose output", false).option("--log-menubar-requests", "Log /healthz requests from the menubar app", false).option("--browse-root <path>", "Root directory for file browsing").option(
@@ -157117,14 +157318,14 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157117
157318
  const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
157118
157319
  checkSqliteAbi2();
157119
157320
  } catch (err) {
157120
- log12.error(err instanceof Error ? err.message : String(err), void 0, "console");
157321
+ log14.error(err instanceof Error ? err.message : String(err), void 0, "console");
157121
157322
  process.exit(1);
157122
157323
  }
157123
157324
  if (opts.multiAgentFlow) {
157124
157325
  process.env.MULTI_AGENT_FLOW = "true";
157125
157326
  }
157126
157327
  if (opts.defaultPermissionMode !== void 0 && !isPermissionMode(opts.defaultPermissionMode)) {
157127
- log12.error(
157328
+ log14.error(
157128
157329
  `Invalid --default-permission-mode: ${opts.defaultPermissionMode} (expected one of ${PERMISSION_MODES.join(", ")})`,
157129
157330
  void 0,
157130
157331
  "console"
@@ -157140,7 +157341,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157140
157341
  const value = eq === -1 ? true : entry.slice(eq + 1);
157141
157342
  const def = findFlag(id);
157142
157343
  if (!def) {
157143
- log12.error(
157344
+ log14.error(
157144
157345
  `Invalid --claude-flag id: ${id} (expected one of ${CLAUDE_FLAGS.map((f2) => f2.id).join(", ")})`,
157145
157346
  void 0,
157146
157347
  "console"
@@ -157157,7 +157358,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157157
157358
  claudeFlags = validateFlagValues(raw2);
157158
157359
  for (const id of Object.keys(raw2)) {
157159
157360
  if (!(id in claudeFlags)) {
157160
- log12.error(`Invalid value for --claude-flag ${id}`, void 0, "console");
157361
+ log14.error(`Invalid value for --claude-flag ${id}`, void 0, "console");
157161
157362
  process.exit(1);
157162
157363
  }
157163
157364
  }
@@ -157166,13 +157367,13 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157166
157367
  if (Array.isArray(opts.feature) && opts.feature.length > 0) {
157167
157368
  const parsed = parseFeatureFlagArgs(opts.feature);
157168
157369
  if (parsed.errors.length > 0) {
157169
- for (const error51 of parsed.errors) log12.error(`--feature: ${error51}`, void 0, "console");
157370
+ for (const error51 of parsed.errors) log14.error(`--feature: ${error51}`, void 0, "console");
157170
157371
  process.exit(1);
157171
157372
  }
157172
157373
  featureFlags = parsed.values;
157173
157374
  }
157174
157375
  if (opts.defaultEffort !== void 0 && !isEffortLevel(opts.defaultEffort)) {
157175
- log12.error(
157376
+ log14.error(
157176
157377
  `Invalid --default-effort: ${opts.defaultEffort} (expected one of ${EFFORT_LEVELS.join(", ")})`,
157177
157378
  void 0,
157178
157379
  "console"
@@ -157183,7 +157384,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157183
157384
  if (opts.ptyGracePeriodMs !== void 0) {
157184
157385
  const parsed = Number(opts.ptyGracePeriodMs);
157185
157386
  if (!Number.isInteger(parsed) || parsed < 0) {
157186
- log12.error(
157387
+ log14.error(
157187
157388
  `Invalid --pty-grace-period-ms: ${opts.ptyGracePeriodMs} (expected a non-negative integer; 0 disables auto-hold)`,
157188
157389
  void 0,
157189
157390
  "console"
@@ -157202,14 +157403,14 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157202
157403
  if (isProdInvocation) {
157203
157404
  const { truncateOversizedLogs: truncateOversizedLogs2 } = await Promise.resolve().then(() => (init_log_cap(), log_cap_exports));
157204
157405
  for (const f2 of truncateOversizedLogs2()) {
157205
- log12.info(`truncated oversized log at boot: ${f2}`, { path: f2, event: "log.truncated" });
157406
+ log14.info(`truncated oversized log at boot: ${f2}`, { path: f2, event: "log.truncated" });
157206
157407
  }
157207
157408
  }
157208
157409
  if (process.platform === "darwin") {
157209
157410
  const { detectConflictingAgents: detectConflictingAgents2, formatConflictMessage: formatConflictMessage2 } = await Promise.resolve().then(() => (init_conflict_check(), conflict_check_exports));
157210
157411
  const conflicts = detectConflictingAgents2();
157211
157412
  if (conflicts.length > 0) {
157212
- log12.warn(formatConflictMessage2(conflicts), void 0, "console");
157413
+ log14.warn(formatConflictMessage2(conflicts), void 0, "console");
157213
157414
  }
157214
157415
  }
157215
157416
  let resolvedDefaultPermissionMode = opts.defaultPermissionMode;
@@ -157224,7 +157425,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157224
157425
  resolvedAutoResumeOnBoot = await interactiveAutoResumePrompt2();
157225
157426
  setAutoResumeOnBoot(resolvedAutoResumeOnBoot);
157226
157427
  } else if (resolvedAutoResumeOnBoot === void 0) {
157227
- log12.info(
157428
+ log14.info(
157228
157429
  "auto_resume_on_boot is not set; interrupted sessions will wait for you to resume them.\nSet `auto_resume_on_boot: true` in ~/.threadbase/server.yaml to resume them automatically.",
157229
157430
  void 0,
157230
157431
  "console"
@@ -157274,15 +157475,15 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157274
157475
  await server.listen(resolvedPort);
157275
157476
  {
157276
157477
  const v2 = getVersion();
157277
- log12.info(`Threadbase Streamer v${v2}`, { version: v2, port: resolvedPort });
157478
+ log14.info(`Threadbase Streamer v${v2}`, { version: v2, port: resolvedPort });
157278
157479
  }
157279
- log12.info(`Listening on http://localhost:${resolvedPort}`, {
157480
+ log14.info(`Listening on http://localhost:${resolvedPort}`, {
157280
157481
  url: `http://localhost:${resolvedPort}`
157281
157482
  });
157282
- log12.info(`WebSocket at ws://localhost:${resolvedPort}/ws`, {
157483
+ log14.info(`WebSocket at ws://localhost:${resolvedPort}/ws`, {
157283
157484
  wsUrl: `ws://localhost:${resolvedPort}/ws`
157284
157485
  });
157285
- log12.info(`API key: ${apiKey}`, { apiKeyMasked: `${apiKey.slice(0, 6)}\u2026` });
157486
+ log14.info(`API key: ${apiKey}`, { apiKeyMasked: `${apiKey.slice(0, 6)}\u2026` });
157286
157487
  try {
157287
157488
  await printServerBanner({
157288
157489
  port: resolvedPort,
@@ -157292,15 +157493,15 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157292
157493
  });
157293
157494
  } catch (err) {
157294
157495
  const message = err instanceof Error ? err.message : String(err);
157295
- log12.warn(`(skipped pairing QR: ${message})`, { reason: message });
157296
- log12.info(
157496
+ log14.warn(`(skipped pairing QR: ${message})`, { reason: message });
157497
+ log14.info(
157297
157498
  printUrlBanner({ url: resolveServerUrl({ publicUrl, port: resolvedPort }) }),
157298
157499
  void 0,
157299
157500
  "console"
157300
157501
  );
157301
157502
  }
157302
157503
  const shutdown = async () => {
157303
- log12.info("Shutting down...");
157504
+ log14.info("Shutting down...");
157304
157505
  await server.close();
157305
157506
  process.exit(0);
157306
157507
  };
@@ -157308,7 +157509,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157308
157509
  process.on("SIGINT", shutdown);
157309
157510
  process.on("SIGTERM", shutdown);
157310
157511
  process.on("uncaughtException", (err) => {
157311
- log12.error(`uncaught: ${err.message}`, {
157512
+ log14.error(`uncaught: ${err.message}`, {
157312
157513
  error: err.message,
157313
157514
  stack: err.stack,
157314
157515
  event: "process.uncaught"
@@ -157317,7 +157518,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
157317
157518
  });
157318
157519
  process.on("unhandledRejection", (reason) => {
157319
157520
  const msg = reason instanceof Error ? reason.message : String(reason);
157320
- log12.error(`unhandled rejection: ${msg}`, {
157521
+ log14.error(`unhandled rejection: ${msg}`, {
157321
157522
  error: msg,
157322
157523
  event: "process.unhandled_rejection"
157323
157524
  });
@@ -157342,10 +157543,10 @@ program2.command("cache").description("Manage the local SQLite conversation cach
157342
157543
  const f2 = dbPath + suffix;
157343
157544
  if (existsSync16(f2)) {
157344
157545
  rmSync6(f2);
157345
- log12.info(`Deleted ${f2}`, { path: f2 }, "console");
157546
+ log14.info(`Deleted ${f2}`, { path: f2 }, "console");
157346
157547
  }
157347
157548
  }
157348
- log12.info("Cache cleared. Restart the server to rebuild.", void 0, "console");
157549
+ log14.info("Cache cleared. Restart the server to rebuild.", void 0, "console");
157349
157550
  })
157350
157551
  );
157351
157552
  program2.command("pair").description("Print a pairing QR code (server must already be running)").option("-p, --port <number>", "Port the server is listening on", "8766").action(async (opts) => {
@@ -157370,7 +157571,7 @@ program2.command("set-key [key]").description("Set the streamer API key in ~/.th
157370
157571
  program2.command("update").description("Check for streamer updates from GitHub Releases and install them").option("--check", "Check only; do not install", false).option("--version <version>", "Pin to a specific release tag").option("--allow-major", "Allow a major-version bump", false).option("--force", "Skip the active-session defer check", false).option("--dry-run", "Print what would be installed without writing to disk", false).option("-p, --port <number>", "Port of the running streamer for active-session check", "8766").action(async (opts) => {
157371
157572
  const cfg = loadUpdateConfig();
157372
157573
  if (!cfg) {
157373
- log12.warn(
157574
+ log14.warn(
157374
157575
  `No update config found at ${DEFAULT_CONFIG_PATH}. Create one with at least 'github_repo: owner/name' to enable updates.`,
157375
157576
  void 0,
157376
157577
  "console"
@@ -157389,11 +157590,11 @@ program2.command("update").description("Check for streamer updates from GitHub R
157389
157590
  appendUpdateLog(
157390
157591
  `[check] current=${result2.current} latest=${result2.latest ?? "none"} status=${result2.reason}`
157391
157592
  );
157392
- log12.info(`Current : ${result2.current}`, void 0, "console");
157393
- log12.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
157394
- log12.info(`Channel : ${cfg.channel}`, void 0, "console");
157395
- log12.info(`Diff : ${result2.diff ?? "(none)"}`, void 0, "console");
157396
- log12.info(`Status : ${result2.reason}`, void 0, "console");
157593
+ log14.info(`Current : ${result2.current}`, void 0, "console");
157594
+ log14.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
157595
+ log14.info(`Channel : ${cfg.channel}`, void 0, "console");
157596
+ log14.info(`Diff : ${result2.diff ?? "(none)"}`, void 0, "console");
157597
+ log14.info(`Status : ${result2.reason}`, void 0, "console");
157397
157598
  return;
157398
157599
  }
157399
157600
  const port = Number.parseInt(opts.port, 10);
@@ -157409,20 +157610,20 @@ program2.command("update").description("Check for streamer updates from GitHub R
157409
157610
  });
157410
157611
  switch (result.kind) {
157411
157612
  case "no-op":
157412
- log12.info(`Current : ${result.current}`, void 0, "console");
157413
- log12.info(`Latest : ${result.latest ?? "(none)"}`, void 0, "console");
157414
- log12.info(`Status : ${result.reason}`, void 0, "console");
157613
+ log14.info(`Current : ${result.current}`, void 0, "console");
157614
+ log14.info(`Latest : ${result.latest ?? "(none)"}`, void 0, "console");
157615
+ log14.info(`Status : ${result.reason}`, void 0, "console");
157415
157616
  break;
157416
157617
  case "unsupported-install":
157417
- log12.warn(result.reason, void 0, "console");
157618
+ log14.warn(result.reason, void 0, "console");
157418
157619
  process.exitCode = 2;
157419
157620
  break;
157420
157621
  case "deferred":
157421
- log12.warn(`Deferred: ${result.reason}`, void 0, "console");
157622
+ log14.warn(`Deferred: ${result.reason}`, void 0, "console");
157422
157623
  process.exitCode = 2;
157423
157624
  break;
157424
157625
  case "dry-run":
157425
- log12.info(
157626
+ log14.info(
157426
157627
  `Would install ${result.latest} from ${result.tarballUrl}`,
157427
157628
  void 0,
157428
157629
  "console"
@@ -157430,28 +157631,28 @@ program2.command("update").description("Check for streamer updates from GitHub R
157430
157631
  break;
157431
157632
  case "installed":
157432
157633
  if (result.restart.method.startsWith("failed:")) {
157433
- log12.error(
157634
+ log14.error(
157434
157635
  `Installed ${result.installed} on disk, but the running service was not updated. Restart: ${result.restart.method}.`,
157435
157636
  void 0,
157436
157637
  "console"
157437
157638
  );
157438
157639
  process.exitCode = 1;
157439
157640
  } else {
157440
- log12.info(
157641
+ log14.info(
157441
157642
  `Installed ${result.installed} (was ${result.previous}). Restart: ${result.restart.method}.`,
157442
157643
  void 0,
157443
157644
  "console"
157444
157645
  );
157445
157646
  }
157446
157647
  if (result.pruned.length > 0) {
157447
- log12.info(`Pruned old releases: ${result.pruned.join(", ")}`, void 0, "console");
157648
+ log14.info(`Pruned old releases: ${result.pruned.join(", ")}`, void 0, "console");
157448
157649
  }
157449
157650
  break;
157450
157651
  }
157451
157652
  } catch (err) {
157452
157653
  const message = err instanceof Error ? err.message : String(err);
157453
157654
  appendUpdateLog(`[error] ${message}`);
157454
- log12.error(`Update failed: ${message}`, { error: message }, "console");
157655
+ log14.error(`Update failed: ${message}`, { error: message }, "console");
157455
157656
  process.exitCode = 1;
157456
157657
  }
157457
157658
  });
@@ -157481,14 +157682,14 @@ program2.command("pty-host").description(
157481
157682
  onConnection: (transport) => host.accept(transport)
157482
157683
  });
157483
157684
  } catch (err) {
157484
- log12.error(
157685
+ log14.error(
157485
157686
  `pty-host could not listen on ${opts.socket}: ${err instanceof Error ? err.message : String(err)}`,
157486
157687
  void 0,
157487
157688
  "console"
157488
157689
  );
157489
157690
  process.exit(1);
157490
157691
  }
157491
- log12.info(`pty-host listening on ${opts.socket}`, {
157692
+ log14.info(`pty-host listening on ${opts.socket}`, {
157492
157693
  event: "pty_host.listening",
157493
157694
  socket: opts.socket
157494
157695
  });
@@ -157542,7 +157743,7 @@ async function printServerBanner({
157542
157743
  }) {
157543
157744
  const url2 = resolveServerUrl({ publicUrl, port });
157544
157745
  if (!includeQr) {
157545
- log12.info(printUrlBanner({ url: url2 }), void 0, "console");
157746
+ log14.info(printUrlBanner({ url: url2 }), void 0, "console");
157546
157747
  return;
157547
157748
  }
157548
157749
  const res = await fetch(`http://localhost:${port}/api/pair/start`, {
@@ -157557,9 +157758,9 @@ async function printServerBanner({
157557
157758
  const expSeconds = Math.floor(expiresAt / 1e3);
157558
157759
  const payload = `threadbase://pair?url=${encodeURIComponent(url2)}&token=${token}&exp=${expSeconds}`;
157559
157760
  const qr2 = await generateQr(payload);
157560
- log12.info(printUrlBanner({ url: url2, qr: qr2, expiresAt }), void 0, "console");
157561
- log12.info(`Pair URL: ${payload}`, void 0, "console");
157562
- log12.info(`Expires in ${expiresInSeconds}s`, void 0, "console");
157761
+ log14.info(printUrlBanner({ url: url2, qr: qr2, expiresAt }), void 0, "console");
157762
+ log14.info(`Pair URL: ${payload}`, void 0, "console");
157763
+ log14.info(`Expires in ${expiresInSeconds}s`, void 0, "console");
157563
157764
  }
157564
157765
  /*! Bundled license information:
157565
157766