@threadbase-sh/streamer 1.52.3 → 1.52.5

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() {
@@ -13224,6 +13239,7 @@ function autoResumeSkipReason(row, opts) {
13224
13239
  if (opts.now - row.status_updated_at > AUTO_RESUME_WINDOW_MS) return "too_old";
13225
13240
  if (!opts.projectExists(row.project_path)) return "project_missing";
13226
13241
  if (resumeIdForRow(row) == null) return "resume_identity_missing";
13242
+ if (!opts.historyExists(row)) return "history_missing";
13227
13243
  return null;
13228
13244
  }
13229
13245
  function planAutoResume(rows, opts) {
@@ -13485,7 +13501,34 @@ var SessionRegistryBoot = class {
13485
13501
  /** Resume only the recent sessions the user explicitly allowed us to start at boot. */
13486
13502
  async autoResumePreviousSessions(rows) {
13487
13503
  if (!this.autoResumeOnBoot) return;
13488
- const plan = planAutoResume(rows, { now: Date.now(), projectExists: existsSync14 });
13504
+ const now = Date.now();
13505
+ const baseOptions = { now, projectExists: existsSync14, historyExists: () => true };
13506
+ const historyExists = /* @__PURE__ */ new Map();
13507
+ const preflights = /* @__PURE__ */ new Set();
13508
+ for (const row of rows) {
13509
+ if (autoResumeSkipReason(row, baseOptions) != null) {
13510
+ historyExists.set(row.session_id, false);
13511
+ continue;
13512
+ }
13513
+ while (preflights.size >= AUTO_RESUME_CONCURRENCY) {
13514
+ await Promise.race(preflights);
13515
+ }
13516
+ const preflight = (async () => {
13517
+ try {
13518
+ const target = await this.deps.resolveConversationTarget(row.session_id);
13519
+ historyExists.set(row.session_id, target.ok || target.reason !== "history_file_missing");
13520
+ } catch {
13521
+ historyExists.set(row.session_id, true);
13522
+ }
13523
+ })();
13524
+ preflights.add(preflight);
13525
+ void preflight.then(() => preflights.delete(preflight));
13526
+ }
13527
+ await Promise.all(preflights);
13528
+ const plan = planAutoResume(rows, {
13529
+ ...baseOptions,
13530
+ historyExists: (row) => historyExists.get(row.session_id) ?? false
13531
+ });
13489
13532
  const skippedBy = {};
13490
13533
  for (const { row, reason } of plan.skipped) {
13491
13534
  skippedBy[reason] = (skippedBy[reason] ?? 0) + 1;
@@ -14453,6 +14496,10 @@ var StreamerServer = class {
14453
14496
  // Resolved once at boot; see src/feature-flags.ts. Total map — every registry
14454
14497
  // id is present, so indexing it never yields undefined.
14455
14498
  featureFlags;
14499
+ // Which rung of the precedence chain decided each flag. Reported at boot and
14500
+ // over GET /api/config/feature-flags — the resolved boolean alone cannot say
14501
+ // whether a value came from the environment, the CLI, server.yaml or nowhere.
14502
+ featureFlagSources;
14456
14503
  // Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
14457
14504
  // read site in startFresh() is unchanged.
14458
14505
  codexSystemPromptEnabled;
@@ -14566,10 +14613,13 @@ var StreamerServer = class {
14566
14613
  this.codexRoots = config.codexRoots ?? [join23(homedir12(), ".codex", "sessions")];
14567
14614
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
14568
14615
  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
- }
14616
+ const flagResolution = resolveFeatureFlags({
14617
+ override: config.codexSystemPromptEnabled === void 0 ? void 0 : { codexSystemPrompt: config.codexSystemPromptEnabled },
14618
+ cli: config.featureFlags,
14619
+ yaml: loadFeatureFlags()
14620
+ });
14621
+ this.featureFlags = flagResolution.values;
14622
+ this.featureFlagSources = flagResolution.sources;
14573
14623
  this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
14574
14624
  this.defaultPermissionMode = config.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
14575
14625
  this.defaultModel = config.defaultModel ?? "sonnet";
@@ -14610,17 +14660,17 @@ var StreamerServer = class {
14610
14660
  selfPtyEndedAt: this.selfPtyEndedAt,
14611
14661
  resumeSession: (opts) => this.sessionHandlers.resumeSession(opts),
14612
14662
  watchConversationFile: (sessionId, historyId) => this.sessionWatchers.watchConversationFile(sessionId, historyId),
14613
- broadcastSessionList: () => this.wsHub.broadcast(this.sessionListPayload())
14663
+ broadcastSessionList: () => this.wsHub.broadcast(this.sessionListPayload()),
14664
+ resolveConversationTarget: (sessionId) => this.resolveConversationTarget(sessionId)
14614
14665
  });
14615
14666
  this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
14616
14667
  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
- }
14668
+ this.log.info(`Feature flags: ${describeFeatureFlags(flagResolution)}`, {
14669
+ event: "config.feature_flags",
14670
+ values: this.featureFlags,
14671
+ sources: this.featureFlagSources,
14672
+ nonDefault: nonDefaultFeatureFlags(this.featureFlags)
14673
+ });
14624
14674
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config.browseRoot;
14625
14675
  if (rawRoot) {
14626
14676
  realpath2(rawRoot).then((resolved) => {
@@ -15643,7 +15693,11 @@ var StreamerServer = class {
15643
15693
  * the absence of that field is the signal that this endpoint is read-only.
15644
15694
  */
15645
15695
  getFeatureFlagsConfig() {
15646
- return { registry: FEATURE_FLAGS, values: this.featureFlags };
15696
+ return {
15697
+ registry: FEATURE_FLAGS,
15698
+ values: this.featureFlags,
15699
+ sources: this.featureFlagSources
15700
+ };
15647
15701
  }
15648
15702
  getClaudeFlagsConfig() {
15649
15703
  return {
@@ -15816,13 +15870,17 @@ var StreamerServer = class {
15816
15870
  conv = await this.conversationHandlers.findConversationByUuid(boundId);
15817
15871
  }
15818
15872
  }
15873
+ const cachedConvMeta = this.cache?.getMetaById(historyId);
15874
+ const cachedPath = cachedConvMeta?.filePath ? toNativeFilePath(cachedConvMeta.filePath) : null;
15875
+ const cachedCodexPath = (registryProvider === CODEX_CLI_PROVIDER || cachedConvMeta?.provider === CODEX_CLI_PROVIDER) && cachedPath != null && existsSync16(cachedPath) ? cachedPath : null;
15819
15876
  const jsonlCwd = jsonlPath ? await this.conversationHandlers.readCwdFromJsonl(jsonlPath) : null;
15820
- const projectPath = jsonlCwd ?? conv?.projectPath;
15877
+ const projectPath = jsonlCwd ?? conv?.projectPath ?? (cachedCodexPath ? cachedConvMeta?.projectPath : null);
15821
15878
  if (!projectPath) {
15822
- if (!conv && !jsonlPath) return { ok: false, reason: "history_file_missing" };
15879
+ if (!conv && !jsonlPath && !cachedCodexPath) {
15880
+ return { ok: false, reason: "history_file_missing" };
15881
+ }
15823
15882
  return { ok: false, reason: "no_project_path" };
15824
15883
  }
15825
- const cachedConvMeta = this.cache?.getMetaById(historyId);
15826
15884
  const provider = coerceProviderForRunner(
15827
15885
  conv?.provider ?? cachedConvMeta?.provider ?? registryProvider
15828
15886
  );
@@ -15836,7 +15894,7 @@ var StreamerServer = class {
15836
15894
  // conversation. Kept separate from `jsonlPath` deliberately: feeding it to
15837
15895
  // conversationBusy() would newly arm the mtime heuristic for Codex, which
15838
15896
  // is exactly the over-broad signal the report ruled out.
15839
- historyPath: jsonlPath ?? conv?.filePath ?? null,
15897
+ historyPath: jsonlPath ?? conv?.filePath ?? cachedCodexPath ?? null,
15840
15898
  conv,
15841
15899
  projectPath,
15842
15900
  provider