From 37e1be9497f71b2f1bbe945b4897c13158bcb0f7 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 13:11:39 +0200 Subject: [PATCH] =?UTF-8?q?feat(webterm):=20=E9=85=8D=E7=BD=AE=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=E6=80=A7=E8=83=BD=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/platform-infra/webterm.yaml | 14 +++++++++ scripts/src/platform-infra-webterm.ts | 41 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index 09e51389..7848581f 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -38,6 +38,20 @@ browserOutputPolicy: maxFramesPerSecond: 10 maxFrameBytes: 65536 +browserPerformance: + animationsEnabled: true + defaultBackgroundMode: receive-only + backgroundModes: + disconnect: + label: 断开后台连接 + receive-only: + label: 只接收数据 + live: + label: 实时渲染 + deferredOutputMaxBytes: 524288 + dashboardSampleWindowSeconds: 60 + dashboardRefreshIntervalMs: 1000 + defaults: targetId: local-7683 diff --git a/scripts/src/platform-infra-webterm.ts b/scripts/src/platform-infra-webterm.ts index dd4c36c6..30b7a2a5 100644 --- a/scripts/src/platform-infra-webterm.ts +++ b/scripts/src/platform-infra-webterm.ts @@ -25,6 +25,7 @@ interface WebtermConfig { autoResumePrompt: AutoResumePrompt; browserTerminalHistory: BrowserTerminalHistory; browserOutputPolicy: BrowserOutputPolicy; + browserPerformance: BrowserPerformance; targets: WebtermTarget[]; } @@ -52,6 +53,15 @@ interface BrowserOutputPolicy { maxFrameBytes: number; } +interface BrowserPerformance { + animationsEnabled: boolean; + defaultBackgroundMode: "disconnect" | "receive-only" | "live"; + backgroundModes: Array<{ id: "disconnect" | "receive-only" | "live"; label: string }>; + deferredOutputMaxBytes: number; + dashboardSampleWindowSeconds: number; + dashboardRefreshIntervalMs: number; +} + interface WebtermTarget { id: string; enabled: boolean; @@ -74,6 +84,7 @@ interface WebtermTarget { autoResumePrompt: AutoResumePrompt; browserTerminalHistory: BrowserTerminalHistory; browserOutputPolicy: BrowserOutputPolicy; + browserPerformance: BrowserPerformance; environment: Record; }; publicExposure: { @@ -301,6 +312,7 @@ function readWebtermConfig(): WebtermConfig { 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 browserPerformance = parseBrowserPerformance(recordField(yaml, "browserPerformance", "webterm"), "webterm.browserPerformance"); const targetsRaw = yaml.targets; if (!Array.isArray(targetsRaw)) throw new Error("webterm.targets must be an array"); return { @@ -309,6 +321,7 @@ function readWebtermConfig(): WebtermConfig { autoResumePrompt, browserTerminalHistory, browserOutputPolicy, + browserPerformance, targets: targetsRaw.map((item, index) => parseTarget( asRecord(item, `webterm.targets[${index}]`), `webterm.targets[${index}]`, @@ -316,6 +329,7 @@ function readWebtermConfig(): WebtermConfig { autoResumePrompt, browserTerminalHistory, browserOutputPolicy, + browserPerformance, )), }; } @@ -327,6 +341,7 @@ function parseTarget( autoResumePrompt: AutoResumePrompt, browserTerminalHistory: BrowserTerminalHistory, browserOutputPolicy: BrowserOutputPolicy, + browserPerformance: BrowserPerformance, ): WebtermTarget { const runtime = recordField(record, "runtime", path); const exposure = recordField(record, "publicExposure", path); @@ -358,6 +373,7 @@ function parseTarget( autoResumePrompt, browserTerminalHistory, browserOutputPolicy, + browserPerformance, environment, }, publicExposure: { @@ -404,6 +420,7 @@ function renderCompose(target: WebtermTarget): string { 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), + BROWSER_PERFORMANCE_JSON: JSON.stringify(target.runtime.browserPerformance), }; const envLines = Object.entries(environment) .map(([key, value]) => ` ${key}: ${quoteYaml(value)}`) @@ -509,6 +526,7 @@ function targetSummary(target: WebtermTarget): Record { }, browserTerminalHistory: target.runtime.browserTerminalHistory, browserOutputPolicy: target.runtime.browserOutputPolicy, + browserPerformance: target.runtime.browserPerformance, sourceMounts: target.runtime.sourceMounts.map((mount) => `${mount.source}->${mount.target}${mount.readOnly ? ":ro" : ""}`), hostPort: target.runtime.hostPort, containerPort: target.runtime.containerPort, @@ -710,6 +728,29 @@ function parseBrowserOutputPolicy(record: Record, path: string) }; } +function parseBrowserPerformance(record: Record, path: string): BrowserPerformance { + const allowedModes = ["disconnect", "receive-only", "live"] as const; + const defaultBackgroundMode = stringField(record, "defaultBackgroundMode", path); + if (!allowedModes.includes(defaultBackgroundMode as typeof allowedModes[number])) { + throw new Error(`${path}.defaultBackgroundMode must be disconnect, receive-only or live`); + } + const modesRecord = recordField(record, "backgroundModes", path); + const backgroundModes = allowedModes.map((id) => ({ + id, + label: stringField(recordField(modesRecord, id, `${path}.backgroundModes`), "label", `${path}.backgroundModes.${id}`), + })); + const unknownModes = Object.keys(modesRecord).filter((id) => !allowedModes.includes(id as typeof allowedModes[number])); + if (unknownModes.length > 0) throw new Error(`${path}.backgroundModes contains unsupported modes: ${unknownModes.join(", ")}`); + return { + animationsEnabled: booleanField(record, "animationsEnabled", path), + defaultBackgroundMode: defaultBackgroundMode as BrowserPerformance["defaultBackgroundMode"], + backgroundModes, + deferredOutputMaxBytes: positiveIntegerField(record, "deferredOutputMaxBytes", path), + dashboardSampleWindowSeconds: positiveIntegerField(record, "dashboardSampleWindowSeconds", path), + dashboardRefreshIntervalMs: positiveIntegerField(record, "dashboardRefreshIntervalMs", path), + }; +} + function positiveIntegerField(record: Record, key: string, path: string): number { const value = integerField(record, key, path); if (value <= 0) throw new Error(`${path}.${key} must be positive`);