diff --git a/scripts/dev-api.js b/scripts/dev-api.js index 5ea3ac9..5ed498e 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -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 } diff --git a/src-tauri/src/commands/hermes.rs b/src-tauri/src/commands/hermes.rs index 1102dc9..11aee45 100644 --- a/src-tauri/src/commands/hermes.rs +++ b/src-tauri/src/commands/hermes.rs @@ -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, + strict: bool, +) -> Result { + 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, 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")); } } diff --git a/src/engines/hermes/pages/config.js b/src/engines/hermes/pages/config.js index fc4a8e1..93d58ca 100644 --- a/src/engines/hermes/pages/config.js +++ b/src/engines/hermes/pages/config.js @@ -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('')} + + + + +
${t('engine.hermesDisplayConfigFootnote')}
@@ -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 diff --git a/src/locales/modules/engine.js b/src/locales/modules/engine.js index 4cb3ce3..ae1cb04 100644 --- a/src/locales/modules/engine.js +++ b/src/locales/modules/engine.js @@ -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', '結構化設定'), diff --git a/tests/hermes-config-page-ui.test.js b/tests/hermes-config-page-ui.test.js index 2965baa..6430f00 100644 --- a/tests/hermes-config-page-ui.test.js +++ b/tests/hermes-config-page-ui.test.js @@ -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}`) } diff --git a/tests/hermes-display-config.test.js b/tests/hermes-display-config.test.js index 0b071d4..e9dffb3 100644 --- a/tests/hermes-display-config.test.js +++ b/tests/hermes-display-config.test.js @@ -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/, + ) })