@threadbase-sh/streamer 1.52.3 → 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.d.cts CHANGED
@@ -114,7 +114,53 @@ interface FeatureFlagDefinition {
114
114
  /** Full env var name. */
115
115
  env: string;
116
116
  }
117
- type FeatureFlagValues = Record<string, boolean>;
117
+ /**
118
+ * Values arriving from a config source. PARTIAL on purpose: server.yaml, the
119
+ * CLI and ServerConfig each speak about the flags they mention and stay silent
120
+ * on the rest, which is what lets the precedence chain fall through.
121
+ */
122
+ type FeatureFlagValues = Partial<Record<FeatureFlagId, boolean>>;
123
+ /** Every flag, resolved. Total — see resolveFeatureFlags(). */
124
+ type ResolvedFeatureFlags = Record<FeatureFlagId, boolean>;
125
+ /**
126
+ * Which rung of the precedence chain decided a flag.
127
+ *
128
+ * Kept because the resolved boolean alone cannot answer "why is this on?", and
129
+ * that is the first question asked of a flag that is on when nobody expected it.
130
+ * `override` is the legacy ServerConfig field (see resolveFeatureFlags).
131
+ */
132
+ type FeatureFlagSource = "override" | "env" | "cli" | "yaml" | "default";
133
+ declare const FEATURE_FLAGS: readonly [{
134
+ readonly id: "codexSystemPrompt";
135
+ readonly description: string;
136
+ readonly default: false;
137
+ readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT";
138
+ }, {
139
+ readonly id: "sessionRehydration";
140
+ readonly description: string;
141
+ readonly default: true;
142
+ readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION";
143
+ }, {
144
+ readonly id: "liveActivityPush";
145
+ readonly description: string;
146
+ readonly default: false;
147
+ readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH";
148
+ }, {
149
+ readonly id: "ptyHost";
150
+ readonly description: string;
151
+ readonly default: false;
152
+ readonly env: "THREADBASE_FEATURE_PTY_HOST";
153
+ }];
154
+ /**
155
+ * The registry's ids as a union, derived rather than declared.
156
+ *
157
+ * This is what makes `flags.ptyHsot` a compile error instead of `undefined`.
158
+ * Under the old `Record<string, boolean>` an index signature accepted any key,
159
+ * so a typo in a consumer read as falsy and silently disabled the feature it
160
+ * was meant to gate — the exact failure the "total map" contract exists to
161
+ * prevent, reachable through the one door that contract left open.
162
+ */
163
+ type FeatureFlagId = (typeof FEATURE_FLAGS)[number]["id"];
118
164
 
119
165
  declare const CLAUDE_CODE_PROVIDER: "claude-code";
120
166
  declare const CODEX_CLI_PROVIDER: "codex-cli";
@@ -1695,7 +1741,8 @@ type ApiDeps = {
1695
1741
  };
1696
1742
  featureFlagsConfig: () => {
1697
1743
  registry: readonly FeatureFlagDefinition[];
1698
- values: FeatureFlagValues;
1744
+ values: ResolvedFeatureFlags;
1745
+ sources: Record<FeatureFlagId, FeatureFlagSource>;
1699
1746
  };
1700
1747
  publicUrl: string | null;
1701
1748
  browseRoot: string | null;
@@ -2028,6 +2075,7 @@ declare class StreamerServer {
2028
2075
  private ptyGracePeriodMs;
2029
2076
  private defaultSystemPrompt;
2030
2077
  private featureFlags;
2078
+ private featureFlagSources;
2031
2079
  private codexSystemPromptEnabled;
2032
2080
  private defaultPermissionMode;
2033
2081
  private defaultModel;
package/dist/index.d.ts CHANGED
@@ -114,7 +114,53 @@ interface FeatureFlagDefinition {
114
114
  /** Full env var name. */
115
115
  env: string;
116
116
  }
117
- type FeatureFlagValues = Record<string, boolean>;
117
+ /**
118
+ * Values arriving from a config source. PARTIAL on purpose: server.yaml, the
119
+ * CLI and ServerConfig each speak about the flags they mention and stay silent
120
+ * on the rest, which is what lets the precedence chain fall through.
121
+ */
122
+ type FeatureFlagValues = Partial<Record<FeatureFlagId, boolean>>;
123
+ /** Every flag, resolved. Total — see resolveFeatureFlags(). */
124
+ type ResolvedFeatureFlags = Record<FeatureFlagId, boolean>;
125
+ /**
126
+ * Which rung of the precedence chain decided a flag.
127
+ *
128
+ * Kept because the resolved boolean alone cannot answer "why is this on?", and
129
+ * that is the first question asked of a flag that is on when nobody expected it.
130
+ * `override` is the legacy ServerConfig field (see resolveFeatureFlags).
131
+ */
132
+ type FeatureFlagSource = "override" | "env" | "cli" | "yaml" | "default";
133
+ declare const FEATURE_FLAGS: readonly [{
134
+ readonly id: "codexSystemPrompt";
135
+ readonly description: string;
136
+ readonly default: false;
137
+ readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT";
138
+ }, {
139
+ readonly id: "sessionRehydration";
140
+ readonly description: string;
141
+ readonly default: true;
142
+ readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION";
143
+ }, {
144
+ readonly id: "liveActivityPush";
145
+ readonly description: string;
146
+ readonly default: false;
147
+ readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH";
148
+ }, {
149
+ readonly id: "ptyHost";
150
+ readonly description: string;
151
+ readonly default: false;
152
+ readonly env: "THREADBASE_FEATURE_PTY_HOST";
153
+ }];
154
+ /**
155
+ * The registry's ids as a union, derived rather than declared.
156
+ *
157
+ * This is what makes `flags.ptyHsot` a compile error instead of `undefined`.
158
+ * Under the old `Record<string, boolean>` an index signature accepted any key,
159
+ * so a typo in a consumer read as falsy and silently disabled the feature it
160
+ * was meant to gate — the exact failure the "total map" contract exists to
161
+ * prevent, reachable through the one door that contract left open.
162
+ */
163
+ type FeatureFlagId = (typeof FEATURE_FLAGS)[number]["id"];
118
164
 
119
165
  declare const CLAUDE_CODE_PROVIDER: "claude-code";
120
166
  declare const CODEX_CLI_PROVIDER: "codex-cli";
@@ -1695,7 +1741,8 @@ type ApiDeps = {
1695
1741
  };
1696
1742
  featureFlagsConfig: () => {
1697
1743
  registry: readonly FeatureFlagDefinition[];
1698
- values: FeatureFlagValues;
1744
+ values: ResolvedFeatureFlags;
1745
+ sources: Record<FeatureFlagId, FeatureFlagSource>;
1699
1746
  };
1700
1747
  publicUrl: string | null;
1701
1748
  browseRoot: string | null;
@@ -2028,6 +2075,7 @@ declare class StreamerServer {
2028
2075
  private ptyGracePeriodMs;
2029
2076
  private defaultSystemPrompt;
2030
2077
  private featureFlags;
2078
+ private featureFlagSources;
2031
2079
  private codexSystemPromptEnabled;
2032
2080
  private defaultPermissionMode;
2033
2081
  private defaultModel;
package/dist/index.js CHANGED
@@ -485,11 +485,12 @@ function validateFeatureFlagValues(raw) {
485
485
  const out = {};
486
486
  const dropped = [];
487
487
  for (const [id, value] of Object.entries(raw)) {
488
- if (!findFeatureFlag(id) || typeof value !== "boolean") {
488
+ const def = findFeatureFlag(id);
489
+ if (!def || typeof value !== "boolean") {
489
490
  dropped.push(id);
490
491
  continue;
491
492
  }
492
- out[id] = value;
493
+ out[def.id] = value;
493
494
  }
494
495
  if (dropped.length > 0) {
495
496
  getLogger("feature-flags").warn(
@@ -504,15 +505,29 @@ function validateFeatureFlagValues(raw) {
504
505
  }
505
506
  function resolveFeatureFlags(opts) {
506
507
  const env = opts?.env ?? process.env;
507
- const out = {};
508
+ const values = {};
509
+ const sources = {};
508
510
  for (const def of FEATURE_FLAGS) {
509
- out[def.id] = parseBooleanEnv(env[def.env]) ?? opts?.cli?.[def.id] ?? opts?.yaml?.[def.id] ?? def.default;
511
+ const rungs = [
512
+ ["override", opts?.override?.[def.id]],
513
+ ["env", parseBooleanEnv(env[def.env])],
514
+ ["cli", opts?.cli?.[def.id]],
515
+ ["yaml", opts?.yaml?.[def.id]]
516
+ ];
517
+ const won = rungs.find(([, v]) => v !== void 0);
518
+ values[def.id] = won ? won[1] : def.default;
519
+ sources[def.id] = won ? won[0] : "default";
510
520
  }
511
- return out;
521
+ return { values, sources };
512
522
  }
513
523
  function nonDefaultFeatureFlags(values) {
514
524
  return FEATURE_FLAGS.filter((f) => values[f.id] !== f.default).map((f) => f.id);
515
525
  }
526
+ function describeFeatureFlags(resolution) {
527
+ return FEATURE_FLAGS.map(
528
+ (f) => `${f.id}=${resolution.values[f.id]}(${resolution.sources[f.id]})`
529
+ ).join(" ");
530
+ }
516
531
 
517
532
  // src/auth.ts
518
533
  function configDir() {
@@ -14453,6 +14468,10 @@ var StreamerServer = class {
14453
14468
  // Resolved once at boot; see src/feature-flags.ts. Total map — every registry
14454
14469
  // id is present, so indexing it never yields undefined.
14455
14470
  featureFlags;
14471
+ // Which rung of the precedence chain decided each flag. Reported at boot and
14472
+ // over GET /api/config/feature-flags — the resolved boolean alone cannot say
14473
+ // whether a value came from the environment, the CLI, server.yaml or nowhere.
14474
+ featureFlagSources;
14456
14475
  // Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
14457
14476
  // read site in startFresh() is unchanged.
14458
14477
  codexSystemPromptEnabled;
@@ -14566,10 +14585,13 @@ var StreamerServer = class {
14566
14585
  this.codexRoots = config.codexRoots ?? [join23(homedir12(), ".codex", "sessions")];
14567
14586
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
14568
14587
  this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
14569
- this.featureFlags = resolveFeatureFlags({ cli: config.featureFlags, yaml: loadFeatureFlags() });
14570
- if (config.codexSystemPromptEnabled !== void 0) {
14571
- this.featureFlags.codexSystemPrompt = config.codexSystemPromptEnabled;
14572
- }
14588
+ const flagResolution = resolveFeatureFlags({
14589
+ override: config.codexSystemPromptEnabled === void 0 ? void 0 : { codexSystemPrompt: config.codexSystemPromptEnabled },
14590
+ cli: config.featureFlags,
14591
+ yaml: loadFeatureFlags()
14592
+ });
14593
+ this.featureFlags = flagResolution.values;
14594
+ this.featureFlagSources = flagResolution.sources;
14573
14595
  this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
14574
14596
  this.defaultPermissionMode = config.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
14575
14597
  this.defaultModel = config.defaultModel ?? "sonnet";
@@ -14614,13 +14636,12 @@ var StreamerServer = class {
14614
14636
  });
14615
14637
  this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
14616
14638
  this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
14617
- const enabledFlags = nonDefaultFeatureFlags(this.featureFlags);
14618
- if (enabledFlags.length > 0) {
14619
- this.log.info(`Feature flags active: ${enabledFlags.join(", ")}`, {
14620
- event: "config.feature_flags_active",
14621
- flags: enabledFlags
14622
- });
14623
- }
14639
+ this.log.info(`Feature flags: ${describeFeatureFlags(flagResolution)}`, {
14640
+ event: "config.feature_flags",
14641
+ values: this.featureFlags,
14642
+ sources: this.featureFlagSources,
14643
+ nonDefault: nonDefaultFeatureFlags(this.featureFlags)
14644
+ });
14624
14645
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config.browseRoot;
14625
14646
  if (rawRoot) {
14626
14647
  realpath2(rawRoot).then((resolved) => {
@@ -15643,7 +15664,11 @@ var StreamerServer = class {
15643
15664
  * the absence of that field is the signal that this endpoint is read-only.
15644
15665
  */
15645
15666
  getFeatureFlagsConfig() {
15646
- return { registry: FEATURE_FLAGS, values: this.featureFlags };
15667
+ return {
15668
+ registry: FEATURE_FLAGS,
15669
+ values: this.featureFlags,
15670
+ sources: this.featureFlagSources
15671
+ };
15647
15672
  }
15648
15673
  getClaudeFlagsConfig() {
15649
15674
  return {