diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index 4479a82a..1aadc69f 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -18,6 +18,17 @@ sessionStartModes: label: oncx command: oncx +autoResumePrompt: + enabled: true + prompt: "[自动提醒]如果有未完成任务则继续,没有则直接结束" + sendEnter: true + enterDelayMs: 500 + readiness: + minDelayMs: 2000 + minOutputBytes: 64 + quietPeriodMs: 1500 + timeoutMs: 60000 + defaults: targetId: local-7683 @@ -53,11 +64,13 @@ targets: command: mycx resume 019f4937-9820-7b31-9178-344ae93d5a4c cols: 100 rows: 30 + resumePrompt: true - title: CONSTAR cwd: /root/unidesk command: mycx resume 019f4b5f-b5ce-76b3-8ccb-508790e9662d cols: 100 rows: 30 + resumePrompt: true environment: HOST: 0.0.0.0 PORT: "7681" diff --git a/scripts/src/platform-infra-webterm.ts b/scripts/src/platform-infra-webterm.ts index 3830cdbc..f2374746 100644 --- a/scripts/src/platform-infra-webterm.ts +++ b/scripts/src/platform-infra-webterm.ts @@ -22,6 +22,7 @@ const serviceName = "webterm"; interface WebtermConfig { defaults: { targetId: string }; sessionStartModes: SessionStartModes; + autoResumePrompt: AutoResumePrompt; targets: WebtermTarget[]; } @@ -30,6 +31,14 @@ interface SessionStartModes { options: Array<{ id: string; label: string; command: string }>; } +interface AutoResumePrompt { + enabled: boolean; + prompt: string; + sendEnter: boolean; + enterDelayMs: number; + readiness: { minDelayMs: number; minOutputBytes: number; quietPeriodMs: number; timeoutMs: number }; +} + interface WebtermTarget { id: string; enabled: boolean; @@ -47,8 +56,9 @@ interface WebtermTarget { privileged: boolean; pid: "host" | "container"; sourceMounts: Array<{ source: string; target: string; readOnly: boolean }>; - autoStartSessions: Array<{ title: string; cwd: string; command: string; cols: number; rows: number }>; + autoStartSessions: Array<{ title: string; cwd: string; command: string; cols: number; rows: number; resumePrompt: boolean }>; sessionStartModes: SessionStartModes; + autoResumePrompt: AutoResumePrompt; environment: Record; }; publicExposure: { @@ -245,16 +255,18 @@ function readWebtermConfig(): WebtermConfig { const yaml = readYamlRecord(configPath, "platform-infra-webterm"); 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 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, - targets: targetsRaw.map((item, index) => parseTarget(asRecord(item, `webterm.targets[${index}]`), `webterm.targets[${index}]`, sessionStartModes)), + autoResumePrompt, + targets: targetsRaw.map((item, index) => parseTarget(asRecord(item, `webterm.targets[${index}]`), `webterm.targets[${index}]`, sessionStartModes, autoResumePrompt)), }; } -function parseTarget(record: Record, path: string, sessionStartModes: SessionStartModes): WebtermTarget { +function parseTarget(record: Record, path: string, sessionStartModes: SessionStartModes, autoResumePrompt: AutoResumePrompt): WebtermTarget { const runtime = recordField(record, "runtime", path); const exposure = recordField(record, "publicExposure", path); const dns = recordField(exposure, "dns", `${path}.publicExposure`); @@ -282,6 +294,7 @@ function parseTarget(record: Record, path: string, sessionStart sourceMounts, autoStartSessions, sessionStartModes, + autoResumePrompt, environment, }, publicExposure: { @@ -324,6 +337,7 @@ function renderCompose(target: WebtermTarget): string { AUTO_START_SESSIONS_JSON: JSON.stringify(target.runtime.autoStartSessions), 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), }; const envLines = Object.entries(environment) .map(([key, value]) => ` ${key}: ${quoteYaml(value)}`) @@ -402,6 +416,14 @@ function targetSummary(target: WebtermTarget): Record { defaultMode: target.runtime.sessionStartModes.defaultMode, options: target.runtime.sessionStartModes.options.map((mode) => mode.id), }, + autoResumePrompt: { + enabled: target.runtime.autoResumePrompt.enabled, + taggedSessions: target.runtime.autoStartSessions.filter((session) => session.resumePrompt).map((session) => session.title), + readiness: target.runtime.autoResumePrompt.readiness, + promptBytes: Buffer.byteLength(target.runtime.autoResumePrompt.prompt), + sendEnter: target.runtime.autoResumePrompt.sendEnter, + enterDelayMs: target.runtime.autoResumePrompt.enterDelayMs, + }, sourceMounts: target.runtime.sourceMounts.map((mount) => `${mount.source}->${mount.target}${mount.readOnly ? ":ro" : ""}`), hostPort: target.runtime.hostPort, containerPort: target.runtime.containerPort, @@ -516,6 +538,7 @@ function parseAutoStartSessions(value: unknown, path: string): WebtermTarget["ru command: stringField(record, "command", itemPath), cols: integerField(record, "cols", itemPath), rows: integerField(record, "rows", itemPath), + resumePrompt: booleanField(record, "resumePrompt", itemPath), }; }); } @@ -543,6 +566,22 @@ function commandField(record: Record, path: string): string { return value.trim(); } +function parseAutoResumePrompt(record: Record, path: string): AutoResumePrompt { + const readiness = recordField(record, "readiness", path); + return { + enabled: booleanField(record, "enabled", path), + prompt: stringField(record, "prompt", path), + sendEnter: booleanField(record, "sendEnter", path), + enterDelayMs: integerField(record, "enterDelayMs", path), + readiness: { + minDelayMs: integerField(readiness, "minDelayMs", `${path}.readiness`), + minOutputBytes: integerField(readiness, "minOutputBytes", `${path}.readiness`), + quietPeriodMs: integerField(readiness, "quietPeriodMs", `${path}.readiness`), + timeoutMs: integerField(readiness, "timeoutMs", `${path}.readiness`), + }, + }; +} + function parseLiteral(value: string, expected: T, path: string): T { if (value !== expected) throw new Error(`${path} must be ${expected}`); return expected;