@threadbase-sh/streamer 1.52.2 → 1.52.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -537,11 +537,12 @@ function validateFeatureFlagValues(raw) {
537
537
  const out = {};
538
538
  const dropped = [];
539
539
  for (const [id, value] of Object.entries(raw)) {
540
- if (!findFeatureFlag(id) || typeof value !== "boolean") {
540
+ const def = findFeatureFlag(id);
541
+ if (!def || typeof value !== "boolean") {
541
542
  dropped.push(id);
542
543
  continue;
543
544
  }
544
- out[id] = value;
545
+ out[def.id] = value;
545
546
  }
546
547
  if (dropped.length > 0) {
547
548
  getLogger("feature-flags").warn(
@@ -556,15 +557,29 @@ function validateFeatureFlagValues(raw) {
556
557
  }
557
558
  function resolveFeatureFlags(opts) {
558
559
  const env = opts?.env ?? process.env;
559
- const out = {};
560
+ const values = {};
561
+ const sources = {};
560
562
  for (const def of FEATURE_FLAGS) {
561
- out[def.id] = parseBooleanEnv(env[def.env]) ?? opts?.cli?.[def.id] ?? opts?.yaml?.[def.id] ?? def.default;
563
+ const rungs = [
564
+ ["override", opts?.override?.[def.id]],
565
+ ["env", parseBooleanEnv(env[def.env])],
566
+ ["cli", opts?.cli?.[def.id]],
567
+ ["yaml", opts?.yaml?.[def.id]]
568
+ ];
569
+ const won = rungs.find(([, v]) => v !== void 0);
570
+ values[def.id] = won ? won[1] : def.default;
571
+ sources[def.id] = won ? won[0] : "default";
562
572
  }
563
- return out;
573
+ return { values, sources };
564
574
  }
565
575
  function nonDefaultFeatureFlags(values) {
566
576
  return FEATURE_FLAGS.filter((f) => values[f.id] !== f.default).map((f) => f.id);
567
577
  }
578
+ function describeFeatureFlags(resolution) {
579
+ return FEATURE_FLAGS.map(
580
+ (f) => `${f.id}=${resolution.values[f.id]}(${resolution.sources[f.id]})`
581
+ ).join(" ");
582
+ }
568
583
 
569
584
  // src/auth.ts
570
585
  function configDir() {
@@ -14480,6 +14495,7 @@ var StreamerServer = class {
14480
14495
  dbPool = null;
14481
14496
  dbInstanceId = null;
14482
14497
  disableDb = false;
14498
+ host;
14483
14499
  // Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
14484
14500
  skipStartupWarmup;
14485
14501
  autoResumeOnBoot;
@@ -14495,6 +14511,10 @@ var StreamerServer = class {
14495
14511
  // Resolved once at boot; see src/feature-flags.ts. Total map — every registry
14496
14512
  // id is present, so indexing it never yields undefined.
14497
14513
  featureFlags;
14514
+ // Which rung of the precedence chain decided each flag. Reported at boot and
14515
+ // over GET /api/config/feature-flags — the resolved boolean alone cannot say
14516
+ // whether a value came from the environment, the CLI, server.yaml or nowhere.
14517
+ featureFlagSources;
14498
14518
  // Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
14499
14519
  // read site in startFresh() is unchanged.
14500
14520
  codexSystemPromptEnabled;
@@ -14601,16 +14621,20 @@ var StreamerServer = class {
14601
14621
  }
14602
14622
  this.verbose = config.verbose ?? false;
14603
14623
  this.disableDb = config.disableDb ?? false;
14624
+ this.host = config.host;
14604
14625
  this.skipStartupWarmup = config.skipStartupWarmup ?? false;
14605
14626
  this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
14606
14627
  this.scanProfiles = config.scanProfiles;
14607
14628
  this.codexRoots = config.codexRoots ?? [(0, import_path22.join)((0, import_os12.homedir)(), ".codex", "sessions")];
14608
14629
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
14609
14630
  this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
14610
- this.featureFlags = resolveFeatureFlags({ cli: config.featureFlags, yaml: loadFeatureFlags() });
14611
- if (config.codexSystemPromptEnabled !== void 0) {
14612
- this.featureFlags.codexSystemPrompt = config.codexSystemPromptEnabled;
14613
- }
14631
+ const flagResolution = resolveFeatureFlags({
14632
+ override: config.codexSystemPromptEnabled === void 0 ? void 0 : { codexSystemPrompt: config.codexSystemPromptEnabled },
14633
+ cli: config.featureFlags,
14634
+ yaml: loadFeatureFlags()
14635
+ });
14636
+ this.featureFlags = flagResolution.values;
14637
+ this.featureFlagSources = flagResolution.sources;
14614
14638
  this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
14615
14639
  this.defaultPermissionMode = config.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
14616
14640
  this.defaultModel = config.defaultModel ?? "sonnet";
@@ -14655,13 +14679,12 @@ var StreamerServer = class {
14655
14679
  });
14656
14680
  this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
14657
14681
  this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
14658
- const enabledFlags = nonDefaultFeatureFlags(this.featureFlags);
14659
- if (enabledFlags.length > 0) {
14660
- this.log.info(`Feature flags active: ${enabledFlags.join(", ")}`, {
14661
- event: "config.feature_flags_active",
14662
- flags: enabledFlags
14663
- });
14664
- }
14682
+ this.log.info(`Feature flags: ${describeFeatureFlags(flagResolution)}`, {
14683
+ event: "config.feature_flags",
14684
+ values: this.featureFlags,
14685
+ sources: this.featureFlagSources,
14686
+ nonDefault: nonDefaultFeatureFlags(this.featureFlags)
14687
+ });
14665
14688
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config.browseRoot;
14666
14689
  if (rawRoot) {
14667
14690
  (0, import_promises7.realpath)(rawRoot).then((resolved) => {
@@ -15253,7 +15276,7 @@ var StreamerServer = class {
15253
15276
  await runMigrations(this.dbPool);
15254
15277
  this.log.info("Database migrations applied", { event: "db.migrations_applied" });
15255
15278
  }
15256
- await this.bindWithRetry(port);
15279
+ await this.bindWithRetry(port, this.host);
15257
15280
  if (!this.ptyManager.isRemote()) {
15258
15281
  this.idleReaperTimer = setInterval(() => this.reapIdleSessions(), IDLE_REAP_SWEEP_MS);
15259
15282
  this.idleReaperTimer.unref?.();
@@ -15262,7 +15285,8 @@ var StreamerServer = class {
15262
15285
  {
15263
15286
  this.log.info(`Streamer server listening on port ${port}`, {
15264
15287
  port,
15265
- event: "server.listening"
15288
+ event: "server.listening",
15289
+ ...this.host !== void 0 && { host: this.host }
15266
15290
  });
15267
15291
  try {
15268
15292
  this.runtimeStore = RuntimeStore.open(this.runtimeDbPath);
@@ -15476,15 +15500,15 @@ var StreamerServer = class {
15476
15500
  // Bind the HTTP listener, retrying on a transient EADDRINUSE. See the call
15477
15501
  // site in listen() for why the race exists (kickstart -k relaunch). Total
15478
15502
  // worst case ≈ 6 × 500 ms = 3 s before the final attempt rethrows.
15479
- async bindWithRetry(port, attempts = 6, delayMs = 500) {
15503
+ async bindWithRetry(port, host, attempts = 6, delayMs = 500) {
15480
15504
  this.binding = true;
15481
15505
  try {
15482
- await this.bindWithRetryLoop(port, attempts, delayMs);
15506
+ await this.bindWithRetryLoop(port, host, attempts, delayMs);
15483
15507
  } finally {
15484
15508
  this.binding = false;
15485
15509
  }
15486
15510
  }
15487
- async bindWithRetryLoop(port, attempts, delayMs) {
15511
+ async bindWithRetryLoop(port, host, attempts, delayMs) {
15488
15512
  for (let attempt = 1; attempt <= attempts; attempt++) {
15489
15513
  try {
15490
15514
  await new Promise((resolve2, reject) => {
@@ -15498,7 +15522,11 @@ var StreamerServer = class {
15498
15522
  };
15499
15523
  this.httpServer.once("error", onError);
15500
15524
  this.httpServer.once("listening", onListening);
15501
- this.httpServer.listen(port);
15525
+ if (host === void 0) {
15526
+ this.httpServer.listen(port);
15527
+ } else {
15528
+ this.httpServer.listen(port, host);
15529
+ }
15502
15530
  });
15503
15531
  return;
15504
15532
  } catch (err) {
@@ -15506,13 +15534,18 @@ var StreamerServer = class {
15506
15534
  if (e.code === "EADDRINUSE" && attempt === attempts) {
15507
15535
  this.log.error(
15508
15536
  `port ${port} still busy (EADDRINUSE) after ${attempts} attempts; giving up`,
15509
- { port, attempts, event: "server.bind_failed" }
15537
+ {
15538
+ port,
15539
+ attempts,
15540
+ event: "server.bind_failed",
15541
+ ...host !== void 0 && { host }
15542
+ }
15510
15543
  );
15511
15544
  }
15512
15545
  if (e.code !== "EADDRINUSE" || attempt === attempts) throw err;
15513
15546
  this.log.debug?.(
15514
15547
  `port ${port} busy (EADDRINUSE), retry ${attempt}/${attempts - 1} in ${delayMs}ms`,
15515
- { port, attempt, event: "server.bind_retry" }
15548
+ { port, attempt, event: "server.bind_retry", ...host !== void 0 && { host } }
15516
15549
  );
15517
15550
  await new Promise((r) => setTimeout(r, delayMs));
15518
15551
  }
@@ -15674,7 +15707,11 @@ var StreamerServer = class {
15674
15707
  * the absence of that field is the signal that this endpoint is read-only.
15675
15708
  */
15676
15709
  getFeatureFlagsConfig() {
15677
- return { registry: FEATURE_FLAGS, values: this.featureFlags };
15710
+ return {
15711
+ registry: FEATURE_FLAGS,
15712
+ values: this.featureFlags,
15713
+ sources: this.featureFlagSources
15714
+ };
15678
15715
  }
15679
15716
  getClaudeFlagsConfig() {
15680
15717
  return {