mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 04:10:00 +08:00
feat(hermes): add display output controls
This commit is contained in:
@@ -3334,6 +3334,7 @@ const HERMES_DISPLAY_STREAMING_VALUES = new Set(['inherit', 'true', 'false'])
|
||||
const HERMES_DISPLAY_RESUME_VALUES = new Set(['full', 'minimal'])
|
||||
const HERMES_DISPLAY_BUSY_INPUT_MODES = new Set(['interrupt', 'queue', 'steer'])
|
||||
const HERMES_DISPLAY_BACKGROUND_PROCESS_NOTIFICATIONS = new Set(['off', 'result', 'error', 'all'])
|
||||
const HERMES_DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES = new Set(['render', 'strip', 'raw'])
|
||||
const HERMES_DISPLAY_LANGUAGE_VALUES = new Set(['en', 'zh', 'zh-hant', 'ja', 'de', 'es', 'fr', 'tr', 'uk', 'af', 'ko', 'it', 'ga', 'pt', 'ru', 'hu'])
|
||||
const HERMES_RUNTIME_FOOTER_FIELDS = new Set(['model', 'context_pct', 'cwd', 'duration', 'tokens', 'cost'])
|
||||
|
||||
@@ -3479,6 +3480,13 @@ function normalizeHermesDisplayBackgroundProcessNotifications(value, strict = fa
|
||||
return 'all'
|
||||
}
|
||||
|
||||
function normalizeHermesDisplayFinalResponseMarkdown(value, strict = false) {
|
||||
const mode = String(value ?? '').trim().toLowerCase() || 'strip'
|
||||
if (HERMES_DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES.has(mode)) return mode
|
||||
if (strict) throw new Error('display.final_response_markdown 必须是 render、strip 或 raw')
|
||||
return 'strip'
|
||||
}
|
||||
|
||||
function normalizeHermesDisplayLanguage(value, strict = false) {
|
||||
const language = String(value ?? '').trim().toLowerCase() || 'en'
|
||||
if (HERMES_DISPLAY_LANGUAGE_VALUES.has(language)) return language
|
||||
@@ -3532,6 +3540,11 @@ export function buildHermesDisplayConfigValues(config = {}) {
|
||||
displayResumeDisplay: normalizeHermesDisplayResume(display.resume_display, false),
|
||||
displayBusyInputMode: normalizeHermesDisplayBusyInputMode(display.busy_input_mode, false),
|
||||
displayBackgroundProcessNotifications: normalizeHermesDisplayBackgroundProcessNotifications(display.background_process_notifications, false),
|
||||
displayFinalResponseMarkdown: normalizeHermesDisplayFinalResponseMarkdown(display.final_response_markdown, false),
|
||||
displayTimestamps: readHermesBool(display.timestamps, false),
|
||||
displayBellOnComplete: readHermesBool(display.bell_on_complete, false),
|
||||
displayPersistentOutput: readHermesBool(display.persistent_output, true),
|
||||
displayPersistentOutputMaxLines: parseHermesInteger(display.persistent_output_max_lines, 'display.persistent_output_max_lines', 200, 0, 100000, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3556,6 +3569,11 @@ export function mergeHermesDisplayConfig(config = {}, form = {}) {
|
||||
display.resume_display = normalizeHermesDisplayResume(Object.hasOwn(form, 'displayResumeDisplay') ? form.displayResumeDisplay : currentValues.displayResumeDisplay, true)
|
||||
display.busy_input_mode = normalizeHermesDisplayBusyInputMode(Object.hasOwn(form, 'displayBusyInputMode') ? form.displayBusyInputMode : currentValues.displayBusyInputMode, true)
|
||||
display.background_process_notifications = normalizeHermesDisplayBackgroundProcessNotifications(Object.hasOwn(form, 'displayBackgroundProcessNotifications') ? form.displayBackgroundProcessNotifications : currentValues.displayBackgroundProcessNotifications, true)
|
||||
display.final_response_markdown = normalizeHermesDisplayFinalResponseMarkdown(Object.hasOwn(form, 'displayFinalResponseMarkdown') ? form.displayFinalResponseMarkdown : currentValues.displayFinalResponseMarkdown, true)
|
||||
display.timestamps = formHermesBool(form, 'displayTimestamps', currentValues.displayTimestamps)
|
||||
display.bell_on_complete = formHermesBool(form, 'displayBellOnComplete', currentValues.displayBellOnComplete)
|
||||
display.persistent_output = formHermesBool(form, 'displayPersistentOutput', currentValues.displayPersistentOutput)
|
||||
display.persistent_output_max_lines = parseHermesInteger(Object.hasOwn(form, 'displayPersistentOutputMaxLines') ? form.displayPersistentOutputMaxLines : currentValues.displayPersistentOutputMaxLines, 'display.persistent_output_max_lines', 200, 0, 100000, true)
|
||||
next.display = display
|
||||
return next
|
||||
}
|
||||
|
||||
@@ -4234,6 +4234,7 @@ const HERMES_DISPLAY_LANGUAGE_VALUES: &[&str] = &[
|
||||
|
||||
const HERMES_DISPLAY_BUSY_INPUT_MODES: &[&str] = &["interrupt", "queue", "steer"];
|
||||
const HERMES_DISPLAY_BACKGROUND_PROCESS_NOTIFICATIONS: &[&str] = &["off", "result", "error", "all"];
|
||||
const HERMES_DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES: &[&str] = &["render", "strip", "raw"];
|
||||
|
||||
const HERMES_RUNTIME_FOOTER_FIELDS: &[&str] =
|
||||
&["model", "context_pct", "cwd", "duration", "tokens", "cost"];
|
||||
@@ -4311,6 +4312,25 @@ fn normalize_hermes_display_background_process_notifications(
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_hermes_display_final_response_markdown(
|
||||
value: Option<String>,
|
||||
strict: bool,
|
||||
) -> Result<String, String> {
|
||||
let mode = value.unwrap_or_default().trim().to_ascii_lowercase();
|
||||
let mode = if mode.is_empty() {
|
||||
"strip".to_string()
|
||||
} else {
|
||||
mode
|
||||
};
|
||||
if HERMES_DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES.contains(&mode.as_str()) {
|
||||
Ok(mode)
|
||||
} else if strict {
|
||||
Err("display.final_response_markdown 必须是 render、strip 或 raw".to_string())
|
||||
} else {
|
||||
Ok("strip".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_hermes_runtime_footer_fields_text(
|
||||
value: Option<String>,
|
||||
strict: bool,
|
||||
@@ -4431,6 +4451,16 @@ fn build_hermes_display_config_values(config: &serde_yaml::Value) -> Value {
|
||||
display.and_then(|map| yaml_string_field(map, "background_process_notifications")),
|
||||
false,
|
||||
).unwrap_or_else(|_| "all".to_string()),
|
||||
"displayFinalResponseMarkdown": normalize_hermes_display_final_response_markdown(
|
||||
display.and_then(|map| yaml_string_field(map, "final_response_markdown")),
|
||||
false,
|
||||
).unwrap_or_else(|_| "strip".to_string()),
|
||||
"displayTimestamps": display.and_then(|map| yaml_bool_field(map, "timestamps")).unwrap_or(false),
|
||||
"displayBellOnComplete": display.and_then(|map| yaml_bool_field(map, "bell_on_complete")).unwrap_or(false),
|
||||
"displayPersistentOutput": display.and_then(|map| yaml_bool_field(map, "persistent_output")).unwrap_or(true),
|
||||
"displayPersistentOutputMaxLines": display
|
||||
.map(|map| bounded_hermes_i64(yaml_i64_field(map, "persistent_output_max_lines"), 200, 0, 100000))
|
||||
.unwrap_or(200),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4455,6 +4485,22 @@ fn merge_hermes_display_config(config: &mut serde_yaml::Value, form: &Value) ->
|
||||
}),
|
||||
true,
|
||||
)?;
|
||||
let final_response_markdown = normalize_hermes_display_final_response_markdown(
|
||||
form_string(form, "displayFinalResponseMarkdown").or_else(|| {
|
||||
current["displayFinalResponseMarkdown"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
}),
|
||||
true,
|
||||
)?;
|
||||
let persistent_output_max_lines = validate_hermes_i64(
|
||||
form_i64(form, "displayPersistentOutputMaxLines")
|
||||
.or_else(|| current["displayPersistentOutputMaxLines"].as_i64()),
|
||||
"display.persistent_output_max_lines",
|
||||
200,
|
||||
0,
|
||||
100000,
|
||||
)?;
|
||||
|
||||
let display = yaml_child_object(ensure_yaml_object(config)?, "display")?;
|
||||
display.insert(
|
||||
@@ -4532,6 +4578,35 @@ fn merge_hermes_display_config(config: &mut serde_yaml::Value, form: &Value) ->
|
||||
true,
|
||||
)?),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("final_response_markdown"),
|
||||
serde_yaml::Value::String(final_response_markdown),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("timestamps"),
|
||||
serde_yaml::Value::Bool(
|
||||
form_bool(form, "displayTimestamps")
|
||||
.unwrap_or_else(|| current["displayTimestamps"].as_bool().unwrap_or(false)),
|
||||
),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("bell_on_complete"),
|
||||
serde_yaml::Value::Bool(
|
||||
form_bool(form, "displayBellOnComplete")
|
||||
.unwrap_or_else(|| current["displayBellOnComplete"].as_bool().unwrap_or(false)),
|
||||
),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("persistent_output"),
|
||||
serde_yaml::Value::Bool(
|
||||
form_bool(form, "displayPersistentOutput")
|
||||
.unwrap_or_else(|| current["displayPersistentOutput"].as_bool().unwrap_or(true)),
|
||||
),
|
||||
);
|
||||
display.insert(
|
||||
yaml_key("persistent_output_max_lines"),
|
||||
serde_yaml::Value::Number(serde_yaml::Number::from(persistent_output_max_lines)),
|
||||
);
|
||||
let runtime_footer = yaml_child_object(display, "runtime_footer")?;
|
||||
runtime_footer.insert(
|
||||
yaml_key("enabled"),
|
||||
@@ -14286,6 +14361,11 @@ mod hermes_display_config_tests {
|
||||
assert_eq!(values["displayResumeDisplay"], "full");
|
||||
assert_eq!(values["displayBusyInputMode"], "interrupt");
|
||||
assert_eq!(values["displayBackgroundProcessNotifications"], "all");
|
||||
assert_eq!(values["displayFinalResponseMarkdown"], "strip");
|
||||
assert_eq!(values["displayTimestamps"], false);
|
||||
assert_eq!(values["displayBellOnComplete"], false);
|
||||
assert_eq!(values["displayPersistentOutput"], true);
|
||||
assert_eq!(values["displayPersistentOutputMaxLines"], 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -14307,6 +14387,11 @@ display:
|
||||
resume_display: minimal
|
||||
busy_input_mode: QUEUE
|
||||
background_process_notifications: ERROR
|
||||
final_response_markdown: RAW
|
||||
timestamps: true
|
||||
bell_on_complete: true
|
||||
persistent_output: false
|
||||
persistent_output_max_lines: 80
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -14324,6 +14409,11 @@ display:
|
||||
assert_eq!(values["displayResumeDisplay"], "minimal");
|
||||
assert_eq!(values["displayBusyInputMode"], "queue");
|
||||
assert_eq!(values["displayBackgroundProcessNotifications"], "error");
|
||||
assert_eq!(values["displayFinalResponseMarkdown"], "raw");
|
||||
assert_eq!(values["displayTimestamps"], true);
|
||||
assert_eq!(values["displayBellOnComplete"], true);
|
||||
assert_eq!(values["displayPersistentOutput"], false);
|
||||
assert_eq!(values["displayPersistentOutputMaxLines"], 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -14359,6 +14449,11 @@ memory:
|
||||
"displayResumeDisplay": "minimal",
|
||||
"displayBusyInputMode": "steer",
|
||||
"displayBackgroundProcessNotifications": "result",
|
||||
"displayFinalResponseMarkdown": "render",
|
||||
"displayTimestamps": true,
|
||||
"displayBellOnComplete": true,
|
||||
"displayPersistentOutput": false,
|
||||
"displayPersistentOutputMaxLines": 120,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -14410,6 +14505,20 @@ memory:
|
||||
config["display"]["background_process_notifications"].as_str(),
|
||||
Some("result")
|
||||
);
|
||||
assert_eq!(
|
||||
config["display"]["final_response_markdown"].as_str(),
|
||||
Some("render")
|
||||
);
|
||||
assert_eq!(config["display"]["timestamps"].as_bool(), Some(true));
|
||||
assert_eq!(config["display"]["bell_on_complete"].as_bool(), Some(true));
|
||||
assert_eq!(
|
||||
config["display"]["persistent_output"].as_bool(),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
config["display"]["persistent_output_max_lines"].as_i64(),
|
||||
Some(120)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -14449,6 +14558,20 @@ memory:
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("display.background_process_notifications"));
|
||||
|
||||
let err = merge_hermes_display_config(
|
||||
&mut config,
|
||||
&json!({ "displayFinalResponseMarkdown": "html" }),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("display.final_response_markdown"));
|
||||
|
||||
let err = merge_hermes_display_config(
|
||||
&mut config,
|
||||
&json!({ "displayPersistentOutputMaxLines": -1 }),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.contains("display.persistent_output_max_lines"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,11 @@ const DISPLAY_DEFAULTS = {
|
||||
displayResumeDisplay: 'full',
|
||||
displayBusyInputMode: 'interrupt',
|
||||
displayBackgroundProcessNotifications: 'all',
|
||||
displayFinalResponseMarkdown: 'strip',
|
||||
displayTimestamps: false,
|
||||
displayBellOnComplete: false,
|
||||
displayPersistentOutput: true,
|
||||
displayPersistentOutputMaxLines: 200,
|
||||
}
|
||||
|
||||
const HUMAN_DELAY_DEFAULTS = {
|
||||
@@ -195,6 +200,7 @@ const DISPLAY_LANGUAGE_VALUES = ['en', 'zh', 'zh-hant', 'ja', 'de', 'es', 'fr',
|
||||
const DISPLAY_RESUME_VALUES = ['full', 'minimal']
|
||||
const DISPLAY_BUSY_INPUT_MODES = ['interrupt', 'queue', 'steer']
|
||||
const DISPLAY_BACKGROUND_PROCESS_NOTIFICATIONS = ['off', 'result', 'error', 'all']
|
||||
const DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES = ['render', 'strip', 'raw']
|
||||
const HUMAN_DELAY_MODES = ['off', 'natural', 'custom']
|
||||
const APPROVAL_MODES = ['manual', 'smart', 'off']
|
||||
const APPROVAL_CRON_MODES = ['deny', 'approve']
|
||||
@@ -797,6 +803,16 @@ export function render() {
|
||||
${DISPLAY_BACKGROUND_PROCESS_NOTIFICATIONS.map(mode => option(`engine.hermesDisplayConfigBackgroundProcessNotifications_${mode}`, mode, displayValues.displayBackgroundProcessNotifications)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigFinalResponseMarkdown')}</span>
|
||||
<select id="hm-display-final-response-markdown" class="hm-input" ${disabled ? 'disabled' : ''}>
|
||||
${DISPLAY_FINAL_RESPONSE_MARKDOWN_VALUES.map(mode => option(`engine.hermesDisplayConfigFinalResponseMarkdown_${mode}`, mode, displayValues.displayFinalResponseMarkdown)).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigPersistentOutputMaxLines')}</span>
|
||||
<input id="hm-display-persistent-output-max-lines" class="hm-input" type="number" inputmode="numeric" min="0" max="100000" step="1" value="${esc(displayValues.displayPersistentOutputMaxLines)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesDisplayConfigRuntimeFooterFields')}</span>
|
||||
<textarea id="hm-display-runtime-footer-fields" class="hm-input" ${disabled ? 'disabled' : ''} style="min-height:96px;resize:vertical">${esc(displayValues.displayRuntimeFooterFields)}</textarea>
|
||||
@@ -819,6 +835,18 @@ export function render() {
|
||||
<input id="hm-display-file-mutation-verifier" type="checkbox" ${displayValues.displayFileMutationVerifier ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigFileMutationVerifier')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-timestamps" type="checkbox" ${displayValues.displayTimestamps ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigTimestamps')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-bell-on-complete" type="checkbox" ${displayValues.displayBellOnComplete ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigBellOnComplete')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-display-persistent-output" type="checkbox" ${displayValues.displayPersistentOutput ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesDisplayConfigPersistentOutput')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-channel-footnote">${t('engine.hermesDisplayConfigFootnote')}</div>
|
||||
</div>
|
||||
@@ -2177,6 +2205,11 @@ export function render() {
|
||||
displayResumeDisplay: el.querySelector('#hm-display-resume-display')?.value || 'full',
|
||||
displayBusyInputMode: el.querySelector('#hm-display-busy-input-mode')?.value || 'interrupt',
|
||||
displayBackgroundProcessNotifications: el.querySelector('#hm-display-background-process-notifications')?.value || 'all',
|
||||
displayFinalResponseMarkdown: el.querySelector('#hm-display-final-response-markdown')?.value || 'strip',
|
||||
displayTimestamps: !!el.querySelector('#hm-display-timestamps')?.checked,
|
||||
displayBellOnComplete: !!el.querySelector('#hm-display-bell-on-complete')?.checked,
|
||||
displayPersistentOutput: !!el.querySelector('#hm-display-persistent-output')?.checked,
|
||||
displayPersistentOutputMaxLines: el.querySelector('#hm-display-persistent-output-max-lines')?.value || '200',
|
||||
}
|
||||
displaySaving = true
|
||||
displayError = null
|
||||
|
||||
@@ -766,7 +766,7 @@ export default {
|
||||
hermesUnauthorizedDmConfigBehavior_ignore: _('静默忽略', 'Silently ignore', '靜默忽略'),
|
||||
hermesUnauthorizedDmConfigFootnote: _('pair 是默认值,会拒绝访问但在私信中回复一次性配对码;ignore 会静默丢弃陌生私信。平台级覆盖仍可在渠道配置或 raw YAML 中单独设置。', 'pair is the default: Hermes denies access but replies with a one-time pairing code in DMs. ignore silently drops unknown DMs. Platform-level overrides can still be set in channel settings or raw YAML.', 'pair 是預設值,會拒絕存取但在私訊中回覆一次性配對碼;ignore 會靜默丟棄陌生私訊。平台級覆蓋仍可在頻道設定或 raw YAML 中單獨設定。'),
|
||||
hermesDisplayConfigTitle: _('全局显示与可靠性', 'Global display and reliability', '全域顯示與可靠性'),
|
||||
hermesDisplayConfigDesc: _('控制消息平台和 CLI 的默认进度展示、忙时输入、后台进程通知、静态提示语言、运行信息页脚,以及文件写入失败校验。', 'Control default progress display, busy input handling, background process notifications, static prompt language, runtime footer, and failed file-mutation verification for messaging platforms and CLI.', '控制訊息平台和 CLI 的預設進度顯示、忙時輸入、背景程序通知、靜態提示語言、執行資訊頁腳,以及檔案寫入失敗校驗。'),
|
||||
hermesDisplayConfigDesc: _('控制消息平台和 CLI 的默认进度展示、最终回复 Markdown、时间戳、完成提醒、终端输出恢复、忙时输入、后台进程通知、静态提示语言、运行信息页脚,以及文件写入失败校验。', 'Control default progress display, final-response Markdown, timestamps, completion bell, terminal output recovery, busy input handling, background process notifications, static prompt language, runtime footer, and failed file-mutation verification for messaging platforms and CLI.', '控制訊息平台和 CLI 的預設進度顯示、最終回覆 Markdown、時間戳、完成提醒、終端輸出恢復、忙時輸入、背景程序通知、靜態提示語言、執行資訊頁腳,以及檔案寫入失敗校驗。'),
|
||||
hermesDisplayConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
hermesDisplayConfigSave: _('保存显示设置', 'Save display settings', '儲存顯示設定'),
|
||||
hermesDisplayConfigSaveSuccess: _('显示与可靠性配置已保存,建议重启 Hermes Gateway 生效', 'Display and reliability settings saved. Restart Hermes Gateway to take effect.', '顯示與可靠性設定已儲存,建議重啟 Hermes Gateway 生效'),
|
||||
@@ -811,7 +811,15 @@ export default {
|
||||
hermesDisplayConfigBackgroundProcessNotifications_result: _('仅完成结果', 'Final result only', '僅完成結果'),
|
||||
hermesDisplayConfigBackgroundProcessNotifications_error: _('仅失败结果', 'Errors only', '僅失敗結果'),
|
||||
hermesDisplayConfigBackgroundProcessNotifications_all: _('运行输出与结果', 'Running output and result', '執行輸出與結果'),
|
||||
hermesDisplayConfigFootnote: _('这里写入全局 display 配置;平台级覆盖仍在渠道页管理。忙时输入控制长跑期间新消息如何处理,后台进程通知控制 messaging watcher 噪音。display.streaming 是 CLI-only,本面板不会把它误写成 Gateway 全局流式设置。运行信息字段支持 model、context_pct、cwd、duration、tokens、cost。', 'This writes global display settings; per-platform overrides remain in channel settings. Busy input controls how new messages are handled during long runs, and background process notifications tune messaging watcher noise. display.streaming is CLI-only, so this panel does not write it as a global Gateway streaming setting. Runtime footer fields support model, context_pct, cwd, duration, tokens, and cost.', '這裡寫入全域 display 設定;平台級覆蓋仍在頻道頁管理。忙時輸入控制長跑期間新訊息如何處理,背景程序通知控制 messaging watcher 噪音。display.streaming 是 CLI-only,本面板不會把它誤寫成 Gateway 全域串流設定。執行資訊欄位支援 model、context_pct、cwd、duration、tokens、cost。'),
|
||||
hermesDisplayConfigFinalResponseMarkdown: _('最终回复 Markdown', 'Final response Markdown', '最終回覆 Markdown'),
|
||||
hermesDisplayConfigFinalResponseMarkdown_render: _('渲染 Markdown', 'Render Markdown', '渲染 Markdown'),
|
||||
hermesDisplayConfigFinalResponseMarkdown_strip: _('去除 Markdown 标记', 'Strip Markdown markers', '移除 Markdown 標記'),
|
||||
hermesDisplayConfigFinalResponseMarkdown_raw: _('保留原始内容', 'Keep raw content', '保留原始內容'),
|
||||
hermesDisplayConfigTimestamps: _('在输出中显示时间戳', 'Show timestamps in output', '在輸出中顯示時間戳'),
|
||||
hermesDisplayConfigBellOnComplete: _('任务完成时播放提示音', 'Play bell when runs complete', '任務完成時播放提示音'),
|
||||
hermesDisplayConfigPersistentOutput: _('保留终端输出用于恢复显示', 'Keep terminal output for display recovery', '保留終端輸出用於恢復顯示'),
|
||||
hermesDisplayConfigPersistentOutputMaxLines: _('保留输出最大行数', 'Max retained output lines', '保留輸出最大行數'),
|
||||
hermesDisplayConfigFootnote: _('这里写入全局 display 配置;平台级覆盖仍在渠道页管理。忙时输入控制长跑期间新消息如何处理,后台进程通知控制 messaging watcher 噪音。最终回复 Markdown、时间戳、完成铃声和持久输出影响 CLI 可读性与终端重绘恢复。display.streaming 是 CLI-only,本面板不会把它误写成 Gateway 全局流式设置。运行信息字段支持 model、context_pct、cwd、duration、tokens、cost。', 'This writes global display settings; per-platform overrides remain in channel settings. Busy input controls how new messages are handled during long runs, and background process notifications tune messaging watcher noise. Final-response Markdown, timestamps, completion bell, and persistent output affect CLI readability and terminal redraw recovery. display.streaming is CLI-only, so this panel does not write it as a global Gateway streaming setting. Runtime footer fields support model, context_pct, cwd, duration, tokens, and cost.', '這裡寫入全域 display 設定;平台級覆蓋仍在頻道頁管理。忙時輸入控制長跑期間新訊息如何處理,背景程序通知控制 messaging watcher 噪音。最終回覆 Markdown、時間戳、完成鈴聲和持久輸出會影響 CLI 可讀性與終端重繪恢復。display.streaming 是 CLI-only,本面板不會把它誤寫成 Gateway 全域串流設定。執行資訊欄位支援 model、context_pct、cwd、duration、tokens、cost。'),
|
||||
hermesHumanDelayConfigTitle: _('响应节奏', 'Response pacing', '回應節奏'),
|
||||
hermesHumanDelayConfigDesc: _('控制消息平台回复分块之间的等待时间,降低刷屏或模拟更自然发送节奏。', 'Control the wait time between reply chunks on messaging platforms to reduce flooding or mimic a more natural sending rhythm.', '控制訊息平台回覆分塊之間的等待時間,降低刷屏或模擬更自然的傳送節奏。'),
|
||||
hermesHumanDelayConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
@@ -129,6 +129,11 @@ test('Hermes 配置页会暴露全局显示与可靠性结构化配置字段', (
|
||||
'hm-display-resume-display',
|
||||
'hm-display-busy-input-mode',
|
||||
'hm-display-background-process-notifications',
|
||||
'hm-display-final-response-markdown',
|
||||
'hm-display-persistent-output-max-lines',
|
||||
'hm-display-timestamps',
|
||||
'hm-display-bell-on-complete',
|
||||
'hm-display-persistent-output',
|
||||
]) {
|
||||
assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ test('Hermes 显示配置读取会提供上游默认值', () => {
|
||||
displayResumeDisplay: 'full',
|
||||
displayBusyInputMode: 'interrupt',
|
||||
displayBackgroundProcessNotifications: 'all',
|
||||
displayFinalResponseMarkdown: 'strip',
|
||||
displayTimestamps: false,
|
||||
displayBellOnComplete: false,
|
||||
displayPersistentOutput: true,
|
||||
displayPersistentOutputMaxLines: 200,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,6 +43,11 @@ test('Hermes 显示配置读取会规范化已有字段', () => {
|
||||
resume_display: 'minimal',
|
||||
busy_input_mode: 'QUEUE',
|
||||
background_process_notifications: 'ERROR',
|
||||
final_response_markdown: 'RAW',
|
||||
timestamps: true,
|
||||
bell_on_complete: true,
|
||||
persistent_output: false,
|
||||
persistent_output_max_lines: 80,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -51,6 +61,11 @@ test('Hermes 显示配置读取会规范化已有字段', () => {
|
||||
assert.equal(values.displayResumeDisplay, 'minimal')
|
||||
assert.equal(values.displayBusyInputMode, 'queue')
|
||||
assert.equal(values.displayBackgroundProcessNotifications, 'error')
|
||||
assert.equal(values.displayFinalResponseMarkdown, 'raw')
|
||||
assert.equal(values.displayTimestamps, true)
|
||||
assert.equal(values.displayBellOnComplete, true)
|
||||
assert.equal(values.displayPersistentOutput, false)
|
||||
assert.equal(values.displayPersistentOutputMaxLines, 80)
|
||||
})
|
||||
|
||||
test('Hermes 显示配置保存会保留未知 YAML 并写入 display', () => {
|
||||
@@ -78,6 +93,11 @@ test('Hermes 显示配置保存会保留未知 YAML 并写入 display', () => {
|
||||
displayResumeDisplay: 'minimal',
|
||||
displayBusyInputMode: 'steer',
|
||||
displayBackgroundProcessNotifications: 'result',
|
||||
displayFinalResponseMarkdown: 'render',
|
||||
displayTimestamps: true,
|
||||
displayBellOnComplete: true,
|
||||
displayPersistentOutput: false,
|
||||
displayPersistentOutputMaxLines: 120,
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic' })
|
||||
@@ -95,6 +115,11 @@ test('Hermes 显示配置保存会保留未知 YAML 并写入 display', () => {
|
||||
assert.equal(next.display.resume_display, 'minimal')
|
||||
assert.equal(next.display.busy_input_mode, 'steer')
|
||||
assert.equal(next.display.background_process_notifications, 'result')
|
||||
assert.equal(next.display.final_response_markdown, 'render')
|
||||
assert.equal(next.display.timestamps, true)
|
||||
assert.equal(next.display.bell_on_complete, true)
|
||||
assert.equal(next.display.persistent_output, false)
|
||||
assert.equal(next.display.persistent_output_max_lines, 120)
|
||||
})
|
||||
|
||||
test('Hermes 显示配置保存会拒绝非法枚举和页脚字段', () => {
|
||||
@@ -122,4 +147,12 @@ test('Hermes 显示配置保存会拒绝非法枚举和页脚字段', () => {
|
||||
() => mergeHermesDisplayConfig({}, { displayBackgroundProcessNotifications: 'silent' }),
|
||||
/display\.background_process_notifications/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesDisplayConfig({}, { displayFinalResponseMarkdown: 'html' }),
|
||||
/display\.final_response_markdown/,
|
||||
)
|
||||
assert.throws(
|
||||
() => mergeHermesDisplayConfig({}, { displayPersistentOutputMaxLines: '-1' }),
|
||||
/display\.persistent_output_max_lines/,
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user