feat: 配置 Webterm 自动恢复提示

This commit is contained in:
Codex
2026-07-11 04:59:39 +02:00
parent fdd55ade33
commit c5bfd97656
2 changed files with 55 additions and 3 deletions
+13
View File
@@ -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"
+42 -3
View File
@@ -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<string, string>;
};
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<string, unknown>, path: string, sessionStartModes: SessionStartModes): WebtermTarget {
function parseTarget(record: Record<string, unknown>, 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<string, unknown>, 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<string, unknown> {
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<string, unknown>, path: string): string {
return value.trim();
}
function parseAutoResumePrompt(record: Record<string, unknown>, 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<T extends string>(value: string, expected: T, path: string): T {
if (value !== expected) throw new Error(`${path} must be ${expected}`);
return expected;