@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/README.md +8 -4
- package/dist/cli.cjs +102 -32
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +62 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -2
- package/dist/index.d.ts +52 -2
- package/dist/index.js +62 -25
- 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";
|
|
@@ -530,6 +576,7 @@ interface SessionCursor {
|
|
|
530
576
|
}
|
|
531
577
|
interface ServerConfig {
|
|
532
578
|
port: number;
|
|
579
|
+
host?: string;
|
|
533
580
|
apiKey?: string;
|
|
534
581
|
/** 'cli' when --api-key was passed; rotation persists in-memory only and reverts on restart */
|
|
535
582
|
apiKeySource?: "config" | "cli";
|
|
@@ -1694,7 +1741,8 @@ type ApiDeps = {
|
|
|
1694
1741
|
};
|
|
1695
1742
|
featureFlagsConfig: () => {
|
|
1696
1743
|
registry: readonly FeatureFlagDefinition[];
|
|
1697
|
-
values:
|
|
1744
|
+
values: ResolvedFeatureFlags;
|
|
1745
|
+
sources: Record<FeatureFlagId, FeatureFlagSource>;
|
|
1698
1746
|
};
|
|
1699
1747
|
publicUrl: string | null;
|
|
1700
1748
|
browseRoot: string | null;
|
|
@@ -2014,6 +2062,7 @@ declare class StreamerServer {
|
|
|
2014
2062
|
private dbPool;
|
|
2015
2063
|
private dbInstanceId;
|
|
2016
2064
|
private disableDb;
|
|
2065
|
+
private host;
|
|
2017
2066
|
private skipStartupWarmup;
|
|
2018
2067
|
private autoResumeOnBoot;
|
|
2019
2068
|
private browseRoot;
|
|
@@ -2026,6 +2075,7 @@ declare class StreamerServer {
|
|
|
2026
2075
|
private ptyGracePeriodMs;
|
|
2027
2076
|
private defaultSystemPrompt;
|
|
2028
2077
|
private featureFlags;
|
|
2078
|
+
private featureFlagSources;
|
|
2029
2079
|
private codexSystemPromptEnabled;
|
|
2030
2080
|
private defaultPermissionMode;
|
|
2031
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";
|
|
@@ -530,6 +576,7 @@ interface SessionCursor {
|
|
|
530
576
|
}
|
|
531
577
|
interface ServerConfig {
|
|
532
578
|
port: number;
|
|
579
|
+
host?: string;
|
|
533
580
|
apiKey?: string;
|
|
534
581
|
/** 'cli' when --api-key was passed; rotation persists in-memory only and reverts on restart */
|
|
535
582
|
apiKeySource?: "config" | "cli";
|
|
@@ -1694,7 +1741,8 @@ type ApiDeps = {
|
|
|
1694
1741
|
};
|
|
1695
1742
|
featureFlagsConfig: () => {
|
|
1696
1743
|
registry: readonly FeatureFlagDefinition[];
|
|
1697
|
-
values:
|
|
1744
|
+
values: ResolvedFeatureFlags;
|
|
1745
|
+
sources: Record<FeatureFlagId, FeatureFlagSource>;
|
|
1698
1746
|
};
|
|
1699
1747
|
publicUrl: string | null;
|
|
1700
1748
|
browseRoot: string | null;
|
|
@@ -2014,6 +2062,7 @@ declare class StreamerServer {
|
|
|
2014
2062
|
private dbPool;
|
|
2015
2063
|
private dbInstanceId;
|
|
2016
2064
|
private disableDb;
|
|
2065
|
+
private host;
|
|
2017
2066
|
private skipStartupWarmup;
|
|
2018
2067
|
private autoResumeOnBoot;
|
|
2019
2068
|
private browseRoot;
|
|
@@ -2026,6 +2075,7 @@ declare class StreamerServer {
|
|
|
2026
2075
|
private ptyGracePeriodMs;
|
|
2027
2076
|
private defaultSystemPrompt;
|
|
2028
2077
|
private featureFlags;
|
|
2078
|
+
private featureFlagSources;
|
|
2029
2079
|
private codexSystemPromptEnabled;
|
|
2030
2080
|
private defaultPermissionMode;
|
|
2031
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() {
|
|
@@ -14437,6 +14452,7 @@ var StreamerServer = class {
|
|
|
14437
14452
|
dbPool = null;
|
|
14438
14453
|
dbInstanceId = null;
|
|
14439
14454
|
disableDb = false;
|
|
14455
|
+
host;
|
|
14440
14456
|
// Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
|
|
14441
14457
|
skipStartupWarmup;
|
|
14442
14458
|
autoResumeOnBoot;
|
|
@@ -14452,6 +14468,10 @@ var StreamerServer = class {
|
|
|
14452
14468
|
// Resolved once at boot; see src/feature-flags.ts. Total map — every registry
|
|
14453
14469
|
// id is present, so indexing it never yields undefined.
|
|
14454
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;
|
|
14455
14475
|
// Derived from featureFlags.codexSystemPrompt. Kept as its own field so the
|
|
14456
14476
|
// read site in startFresh() is unchanged.
|
|
14457
14477
|
codexSystemPromptEnabled;
|
|
@@ -14558,16 +14578,20 @@ var StreamerServer = class {
|
|
|
14558
14578
|
}
|
|
14559
14579
|
this.verbose = config.verbose ?? false;
|
|
14560
14580
|
this.disableDb = config.disableDb ?? false;
|
|
14581
|
+
this.host = config.host;
|
|
14561
14582
|
this.skipStartupWarmup = config.skipStartupWarmup ?? false;
|
|
14562
14583
|
this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
|
|
14563
14584
|
this.scanProfiles = config.scanProfiles;
|
|
14564
14585
|
this.codexRoots = config.codexRoots ?? [join23(homedir12(), ".codex", "sessions")];
|
|
14565
14586
|
this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
14566
14587
|
this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
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;
|
|
14571
14595
|
this.codexSystemPromptEnabled = this.featureFlags.codexSystemPrompt;
|
|
14572
14596
|
this.defaultPermissionMode = config.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
|
|
14573
14597
|
this.defaultModel = config.defaultModel ?? "sonnet";
|
|
@@ -14612,13 +14636,12 @@ var StreamerServer = class {
|
|
|
14612
14636
|
});
|
|
14613
14637
|
this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);
|
|
14614
14638
|
this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
}
|
|
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
|
+
});
|
|
14622
14645
|
const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config.browseRoot;
|
|
14623
14646
|
if (rawRoot) {
|
|
14624
14647
|
realpath2(rawRoot).then((resolved) => {
|
|
@@ -15210,7 +15233,7 @@ var StreamerServer = class {
|
|
|
15210
15233
|
await runMigrations(this.dbPool);
|
|
15211
15234
|
this.log.info("Database migrations applied", { event: "db.migrations_applied" });
|
|
15212
15235
|
}
|
|
15213
|
-
await this.bindWithRetry(port);
|
|
15236
|
+
await this.bindWithRetry(port, this.host);
|
|
15214
15237
|
if (!this.ptyManager.isRemote()) {
|
|
15215
15238
|
this.idleReaperTimer = setInterval(() => this.reapIdleSessions(), IDLE_REAP_SWEEP_MS);
|
|
15216
15239
|
this.idleReaperTimer.unref?.();
|
|
@@ -15219,7 +15242,8 @@ var StreamerServer = class {
|
|
|
15219
15242
|
{
|
|
15220
15243
|
this.log.info(`Streamer server listening on port ${port}`, {
|
|
15221
15244
|
port,
|
|
15222
|
-
event: "server.listening"
|
|
15245
|
+
event: "server.listening",
|
|
15246
|
+
...this.host !== void 0 && { host: this.host }
|
|
15223
15247
|
});
|
|
15224
15248
|
try {
|
|
15225
15249
|
this.runtimeStore = RuntimeStore.open(this.runtimeDbPath);
|
|
@@ -15433,15 +15457,15 @@ var StreamerServer = class {
|
|
|
15433
15457
|
// Bind the HTTP listener, retrying on a transient EADDRINUSE. See the call
|
|
15434
15458
|
// site in listen() for why the race exists (kickstart -k relaunch). Total
|
|
15435
15459
|
// worst case ≈ 6 × 500 ms = 3 s before the final attempt rethrows.
|
|
15436
|
-
async bindWithRetry(port, attempts = 6, delayMs = 500) {
|
|
15460
|
+
async bindWithRetry(port, host, attempts = 6, delayMs = 500) {
|
|
15437
15461
|
this.binding = true;
|
|
15438
15462
|
try {
|
|
15439
|
-
await this.bindWithRetryLoop(port, attempts, delayMs);
|
|
15463
|
+
await this.bindWithRetryLoop(port, host, attempts, delayMs);
|
|
15440
15464
|
} finally {
|
|
15441
15465
|
this.binding = false;
|
|
15442
15466
|
}
|
|
15443
15467
|
}
|
|
15444
|
-
async bindWithRetryLoop(port, attempts, delayMs) {
|
|
15468
|
+
async bindWithRetryLoop(port, host, attempts, delayMs) {
|
|
15445
15469
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
15446
15470
|
try {
|
|
15447
15471
|
await new Promise((resolve2, reject) => {
|
|
@@ -15455,7 +15479,11 @@ var StreamerServer = class {
|
|
|
15455
15479
|
};
|
|
15456
15480
|
this.httpServer.once("error", onError);
|
|
15457
15481
|
this.httpServer.once("listening", onListening);
|
|
15458
|
-
|
|
15482
|
+
if (host === void 0) {
|
|
15483
|
+
this.httpServer.listen(port);
|
|
15484
|
+
} else {
|
|
15485
|
+
this.httpServer.listen(port, host);
|
|
15486
|
+
}
|
|
15459
15487
|
});
|
|
15460
15488
|
return;
|
|
15461
15489
|
} catch (err) {
|
|
@@ -15463,13 +15491,18 @@ var StreamerServer = class {
|
|
|
15463
15491
|
if (e.code === "EADDRINUSE" && attempt === attempts) {
|
|
15464
15492
|
this.log.error(
|
|
15465
15493
|
`port ${port} still busy (EADDRINUSE) after ${attempts} attempts; giving up`,
|
|
15466
|
-
{
|
|
15494
|
+
{
|
|
15495
|
+
port,
|
|
15496
|
+
attempts,
|
|
15497
|
+
event: "server.bind_failed",
|
|
15498
|
+
...host !== void 0 && { host }
|
|
15499
|
+
}
|
|
15467
15500
|
);
|
|
15468
15501
|
}
|
|
15469
15502
|
if (e.code !== "EADDRINUSE" || attempt === attempts) throw err;
|
|
15470
15503
|
this.log.debug?.(
|
|
15471
15504
|
`port ${port} busy (EADDRINUSE), retry ${attempt}/${attempts - 1} in ${delayMs}ms`,
|
|
15472
|
-
{ port, attempt, event: "server.bind_retry" }
|
|
15505
|
+
{ port, attempt, event: "server.bind_retry", ...host !== void 0 && { host } }
|
|
15473
15506
|
);
|
|
15474
15507
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
15475
15508
|
}
|
|
@@ -15631,7 +15664,11 @@ var StreamerServer = class {
|
|
|
15631
15664
|
* the absence of that field is the signal that this endpoint is read-only.
|
|
15632
15665
|
*/
|
|
15633
15666
|
getFeatureFlagsConfig() {
|
|
15634
|
-
return {
|
|
15667
|
+
return {
|
|
15668
|
+
registry: FEATURE_FLAGS,
|
|
15669
|
+
values: this.featureFlags,
|
|
15670
|
+
sources: this.featureFlagSources
|
|
15671
|
+
};
|
|
15635
15672
|
}
|
|
15636
15673
|
getClaudeFlagsConfig() {
|
|
15637
15674
|
return {
|