feat: 配置 Webterm 移动端历史与输出帧率

This commit is contained in:
Codex
2026-07-11 11:29:27 +02:00
parent b12dd85375
commit 2a536e8760
4 changed files with 90 additions and 6 deletions
+62 -2
View File
@@ -23,6 +23,8 @@ interface WebtermConfig {
defaults: { targetId: string };
sessionStartModes: SessionStartModes;
autoResumePrompt: AutoResumePrompt;
browserTerminalHistory: BrowserTerminalHistory;
browserOutputPolicy: BrowserOutputPolicy;
targets: WebtermTarget[];
}
@@ -39,6 +41,17 @@ interface AutoResumePrompt {
readiness: { minDelayMs: number; minOutputBytes: number; quietPeriodMs: number; timeoutMs: number };
}
interface BrowserTerminalHistory {
desktopScrollbackLines: number;
mobileScrollbackLines: number;
mobileMaxWidthPx: number;
}
interface BrowserOutputPolicy {
maxFramesPerSecond: number;
maxFrameBytes: number;
}
interface WebtermTarget {
id: string;
enabled: boolean;
@@ -59,6 +72,8 @@ interface WebtermTarget {
autoStartSessions: Array<{ title: string; cwd: string; command: string; cols: number; rows: number; resumePrompt: boolean; fallbackShell: boolean }>;
sessionStartModes: SessionStartModes;
autoResumePrompt: AutoResumePrompt;
browserTerminalHistory: BrowserTerminalHistory;
browserOutputPolicy: BrowserOutputPolicy;
environment: Record<string, string>;
};
publicExposure: {
@@ -256,17 +271,35 @@ function readWebtermConfig(): WebtermConfig {
const defaults = recordField(yaml, "defaults", "webterm");
const sessionStartModes = parseSessionStartModes(recordField(yaml, "sessionStartModes", "webterm"), "webterm.sessionStartModes");
const autoResumePrompt = parseAutoResumePrompt(recordField(yaml, "autoResumePrompt", "webterm"), "webterm.autoResumePrompt");
const browserTerminalHistory = parseBrowserTerminalHistory(recordField(yaml, "browserTerminalHistory", "webterm"), "webterm.browserTerminalHistory");
const browserOutputPolicy = parseBrowserOutputPolicy(recordField(yaml, "browserOutputPolicy", "webterm"), "webterm.browserOutputPolicy");
const targetsRaw = yaml.targets;
if (!Array.isArray(targetsRaw)) throw new Error("webterm.targets must be an array");
return {
defaults: { targetId: stringField(defaults, "targetId", "webterm.defaults") },
sessionStartModes,
autoResumePrompt,
targets: targetsRaw.map((item, index) => parseTarget(asRecord(item, `webterm.targets[${index}]`), `webterm.targets[${index}]`, sessionStartModes, autoResumePrompt)),
browserTerminalHistory,
browserOutputPolicy,
targets: targetsRaw.map((item, index) => parseTarget(
asRecord(item, `webterm.targets[${index}]`),
`webterm.targets[${index}]`,
sessionStartModes,
autoResumePrompt,
browserTerminalHistory,
browserOutputPolicy,
)),
};
}
function parseTarget(record: Record<string, unknown>, path: string, sessionStartModes: SessionStartModes, autoResumePrompt: AutoResumePrompt): WebtermTarget {
function parseTarget(
record: Record<string, unknown>,
path: string,
sessionStartModes: SessionStartModes,
autoResumePrompt: AutoResumePrompt,
browserTerminalHistory: BrowserTerminalHistory,
browserOutputPolicy: BrowserOutputPolicy,
): WebtermTarget {
const runtime = recordField(record, "runtime", path);
const exposure = recordField(record, "publicExposure", path);
const dns = recordField(exposure, "dns", `${path}.publicExposure`);
@@ -295,6 +328,8 @@ function parseTarget(record: Record<string, unknown>, path: string, sessionStart
autoStartSessions,
sessionStartModes,
autoResumePrompt,
browserTerminalHistory,
browserOutputPolicy,
environment,
},
publicExposure: {
@@ -339,6 +374,8 @@ function renderCompose(target: WebtermTarget): string {
SESSION_START_MODES_JSON: JSON.stringify(target.runtime.sessionStartModes.options),
SESSION_START_DEFAULT_MODE: target.runtime.sessionStartModes.defaultMode,
AUTO_RESUME_PROMPT_JSON: JSON.stringify(target.runtime.autoResumePrompt),
BROWSER_TERMINAL_HISTORY_JSON: JSON.stringify(target.runtime.browserTerminalHistory),
BROWSER_OUTPUT_POLICY_JSON: JSON.stringify(target.runtime.browserOutputPolicy),
};
const envLines = Object.entries(environment)
.map(([key, value]) => ` ${key}: ${quoteYaml(value)}`)
@@ -442,6 +479,8 @@ function targetSummary(target: WebtermTarget): Record<string, unknown> {
sendEnter: target.runtime.autoResumePrompt.sendEnter,
enterDelayMs: target.runtime.autoResumePrompt.enterDelayMs,
},
browserTerminalHistory: target.runtime.browserTerminalHistory,
browserOutputPolicy: target.runtime.browserOutputPolicy,
sourceMounts: target.runtime.sourceMounts.map((mount) => `${mount.source}->${mount.target}${mount.readOnly ? ":ro" : ""}`),
hostPort: target.runtime.hostPort,
containerPort: target.runtime.containerPort,
@@ -601,6 +640,27 @@ function parseAutoResumePrompt(record: Record<string, unknown>, path: string): A
};
}
function parseBrowserTerminalHistory(record: Record<string, unknown>, path: string): BrowserTerminalHistory {
return {
desktopScrollbackLines: positiveIntegerField(record, "desktopScrollbackLines", path),
mobileScrollbackLines: positiveIntegerField(record, "mobileScrollbackLines", path),
mobileMaxWidthPx: positiveIntegerField(record, "mobileMaxWidthPx", path),
};
}
function parseBrowserOutputPolicy(record: Record<string, unknown>, path: string): BrowserOutputPolicy {
return {
maxFramesPerSecond: positiveIntegerField(record, "maxFramesPerSecond", path),
maxFrameBytes: positiveIntegerField(record, "maxFrameBytes", path),
};
}
function positiveIntegerField(record: Record<string, unknown>, key: string, path: string): number {
const value = integerField(record, key, path);
if (value <= 0) throw new Error(`${path}.${key} must be positive`);
return value;
}
function parseLiteral<T extends string>(value: string, expected: T, path: string): T {
if (value !== expected) throw new Error(`${path} must be ${expected}`);
return expected;