@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/cli.cjs +119 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +81 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -2
- package/dist/index.d.ts +50 -2
- package/dist/index.js +81 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
508
|
+
const values = {};
|
|
509
|
+
const sources = {};
|
|
508
510
|
for (const def of FEATURE_FLAGS) {
|
|
509
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
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
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
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 {
|
|
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)
|
|
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
|