feat(hermes): add browser camofox controls

This commit is contained in:
晴天
2026-05-27 06:43:41 +08:00
parent 3c29431be0
commit b51bde929e
6 changed files with 327 additions and 4 deletions

View File

@@ -4770,6 +4770,17 @@ function normalizeHermesOptionalString(value, key) {
return value.trim()
}
function normalizeHermesCamofoxIdentity(value, key) {
if (value == null || value === '') return ''
if (typeof value !== 'string') throw new Error(`${key} 必须是字符串`)
const text = value.trim()
if (!text) return ''
if (!/^[A-Za-z0-9_.:@+-]+$/.test(text)) {
throw new Error(`${key} 只能包含字母、数字、下划线、点、冒号、@、加号和短横线`)
}
return text
}
export function buildHermesModelConfigValues(config = {}) {
const root = config && typeof config === 'object' && !Array.isArray(config) ? config : {}
const model = root.model && typeof root.model === 'object' && !Array.isArray(root.model) ? root.model : {}
@@ -5639,6 +5650,9 @@ export function buildHermesBrowserConfigValues(config = {}) {
const browser = root.browser && typeof root.browser === 'object' && !Array.isArray(root.browser)
? root.browser
: {}
const camofox = browser.camofox && typeof browser.camofox === 'object' && !Array.isArray(browser.camofox)
? browser.camofox
: {}
return {
browserInactivityTimeout: parseHermesInteger(browser.inactivity_timeout, 'browser.inactivity_timeout', 120, 1, 86400, false),
browserCommandTimeout: parseHermesInteger(browser.command_timeout, 'browser.command_timeout', 30, 5, 3600, false),
@@ -5647,6 +5661,10 @@ export function buildHermesBrowserConfigValues(config = {}) {
browserAllowPrivateUrls: readHermesBool(browser.allow_private_urls, false),
browserAutoLocalForPrivateUrls: readHermesBool(browser.auto_local_for_private_urls, true),
browserCdpUrl: normalizeHermesOptionalString(browser.cdp_url, 'browser.cdp_url'),
browserCamofoxManagedPersistence: readHermesBool(camofox.managed_persistence, false),
browserCamofoxUserId: normalizeHermesCamofoxIdentity(camofox.user_id, 'browser.camofox.user_id'),
browserCamofoxSessionKey: normalizeHermesCamofoxIdentity(camofox.session_key, 'browser.camofox.session_key'),
browserCamofoxAdoptExistingTab: readHermesBool(camofox.adopt_existing_tab, false),
browserDialogPolicy: normalizeHermesBrowserDialogPolicy(browser.dialog_policy, false),
browserDialogTimeout: parseHermesInteger(browser.dialog_timeout_s, 'browser.dialog_timeout_s', 300, 1, 86400, false),
}
@@ -5667,6 +5685,18 @@ export function mergeHermesBrowserConfig(config = {}, form = {}) {
const cdpUrl = normalizeHermesOptionalString(Object.hasOwn(form, 'browserCdpUrl') ? form.browserCdpUrl : currentValues.browserCdpUrl, 'browser.cdp_url')
if (cdpUrl) browser.cdp_url = cdpUrl
else delete browser.cdp_url
const camofox = browser.camofox && typeof browser.camofox === 'object' && !Array.isArray(browser.camofox)
? mergeConfigsPreservingFields(browser.camofox, {})
: {}
camofox.managed_persistence = formHermesBool(form, 'browserCamofoxManagedPersistence', currentValues.browserCamofoxManagedPersistence)
const camofoxUserId = normalizeHermesCamofoxIdentity(Object.hasOwn(form, 'browserCamofoxUserId') ? form.browserCamofoxUserId : currentValues.browserCamofoxUserId, 'browser.camofox.user_id')
if (camofoxUserId) camofox.user_id = camofoxUserId
else delete camofox.user_id
const camofoxSessionKey = normalizeHermesCamofoxIdentity(Object.hasOwn(form, 'browserCamofoxSessionKey') ? form.browserCamofoxSessionKey : currentValues.browserCamofoxSessionKey, 'browser.camofox.session_key')
if (camofoxSessionKey) camofox.session_key = camofoxSessionKey
else delete camofox.session_key
camofox.adopt_existing_tab = formHermesBool(form, 'browserCamofoxAdoptExistingTab', currentValues.browserCamofoxAdoptExistingTab)
browser.camofox = camofox
browser.dialog_policy = normalizeHermesBrowserDialogPolicy(Object.hasOwn(form, 'browserDialogPolicy') ? form.browserDialogPolicy : currentValues.browserDialogPolicy, true)
browser.dialog_timeout_s = parseHermesInteger(Object.hasOwn(form, 'browserDialogTimeout') ? form.browserDialogTimeout : currentValues.browserDialogTimeout, 'browser.dialog_timeout_s', 300, 1, 86400, true)
next.browser = browser

View File

@@ -2603,6 +2603,23 @@ fn set_optional_yaml_string(map: &mut serde_yaml::Mapping, key: &str, value: Str
}
}
fn normalize_hermes_camofox_identity(value: Option<String>, key: &str) -> Result<String, String> {
let text = value.unwrap_or_default().trim().to_string();
if text.is_empty() {
return Ok(String::new());
}
if text
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '.' | ':' | '@' | '+' | '-'))
{
Ok(text)
} else {
Err(format!(
"{key} 只能包含字母、数字、下划线、点、冒号、@、加号和短横线"
))
}
}
fn yaml_string_sequence_field(map: &serde_yaml::Mapping, key: &str) -> Vec<String> {
yaml_get(map, key)
.and_then(|value| value.as_sequence())
@@ -8583,6 +8600,23 @@ fn build_hermes_browser_config_values(config: &serde_yaml::Value) -> Value {
let browser_cdp_url = browser
.and_then(|map| yaml_string_field(map, "cdp_url"))
.unwrap_or_default();
let camofox = browser.and_then(|map| yaml_get_mapping(map, "camofox"));
let browser_camofox_managed_persistence = camofox
.and_then(|map| yaml_bool_field(map, "managed_persistence"))
.unwrap_or(false);
let browser_camofox_user_id = normalize_hermes_camofox_identity(
camofox.and_then(|map| yaml_string_field(map, "user_id")),
"browser.camofox.user_id",
)
.unwrap_or_default();
let browser_camofox_session_key = normalize_hermes_camofox_identity(
camofox.and_then(|map| yaml_string_field(map, "session_key")),
"browser.camofox.session_key",
)
.unwrap_or_default();
let browser_camofox_adopt_existing_tab = camofox
.and_then(|map| yaml_bool_field(map, "adopt_existing_tab"))
.unwrap_or(false);
let browser_dialog_policy = normalize_hermes_browser_dialog_policy(
browser.and_then(|map| yaml_string_field(map, "dialog_policy")),
false,
@@ -8600,6 +8634,10 @@ fn build_hermes_browser_config_values(config: &serde_yaml::Value) -> Value {
"browserAllowPrivateUrls": browser_allow_private_urls,
"browserAutoLocalForPrivateUrls": browser_auto_local_for_private_urls,
"browserCdpUrl": browser_cdp_url,
"browserCamofoxManagedPersistence": browser_camofox_managed_persistence,
"browserCamofoxUserId": browser_camofox_user_id,
"browserCamofoxSessionKey": browser_camofox_session_key,
"browserCamofoxAdoptExistingTab": browser_camofox_adopt_existing_tab,
"browserDialogPolicy": browser_dialog_policy,
"browserDialogTimeout": browser_dialog_timeout,
})
@@ -8663,6 +8701,44 @@ fn merge_hermes_browser_config(config: &mut serde_yaml::Value, form: &Value) ->
.trim()
.to_string()
};
let browser_camofox_managed_persistence = form_bool(form, "browserCamofoxManagedPersistence")
.unwrap_or_else(|| {
current["browserCamofoxManagedPersistence"]
.as_bool()
.unwrap_or(false)
});
let browser_camofox_user_id = normalize_hermes_camofox_identity(
if form.get("browserCamofoxUserId").is_some() {
Some(
form_string(form, "browserCamofoxUserId")
.ok_or_else(|| "browser.camofox.user_id 必须是字符串".to_string())?,
)
} else {
current["browserCamofoxUserId"]
.as_str()
.map(ToString::to_string)
},
"browser.camofox.user_id",
)?;
let browser_camofox_session_key = normalize_hermes_camofox_identity(
if form.get("browserCamofoxSessionKey").is_some() {
Some(
form_string(form, "browserCamofoxSessionKey")
.ok_or_else(|| "browser.camofox.session_key 必须是字符串".to_string())?,
)
} else {
current["browserCamofoxSessionKey"]
.as_str()
.map(ToString::to_string)
},
"browser.camofox.session_key",
)?;
let browser_camofox_adopt_existing_tab = form_bool(form, "browserCamofoxAdoptExistingTab")
.unwrap_or_else(|| {
current["browserCamofoxAdoptExistingTab"]
.as_bool()
.unwrap_or(false)
});
let browser_dialog_policy = normalize_hermes_browser_dialog_policy(
if form.get("browserDialogPolicy").is_some() {
form_string(form, "browserDialogPolicy")
@@ -8712,6 +8788,17 @@ fn merge_hermes_browser_config(config: &mut serde_yaml::Value, form: &Value) ->
serde_yaml::Value::Bool(browser_auto_local_for_private_urls),
);
set_optional_yaml_string(browser, "cdp_url", browser_cdp_url);
let camofox = yaml_child_object(browser, "camofox")?;
camofox.insert(
yaml_key("managed_persistence"),
serde_yaml::Value::Bool(browser_camofox_managed_persistence),
);
set_optional_yaml_string(camofox, "user_id", browser_camofox_user_id);
set_optional_yaml_string(camofox, "session_key", browser_camofox_session_key);
camofox.insert(
yaml_key("adopt_existing_tab"),
serde_yaml::Value::Bool(browser_camofox_adopt_existing_tab),
);
browser.insert(
yaml_key("dialog_policy"),
serde_yaml::Value::String(browser_dialog_policy),
@@ -18257,6 +18344,10 @@ mod hermes_browser_config_tests {
assert_eq!(values["browserAllowPrivateUrls"], false);
assert_eq!(values["browserAutoLocalForPrivateUrls"], true);
assert_eq!(values["browserCdpUrl"], "");
assert_eq!(values["browserCamofoxManagedPersistence"], false);
assert_eq!(values["browserCamofoxUserId"], "");
assert_eq!(values["browserCamofoxSessionKey"], "");
assert_eq!(values["browserCamofoxAdoptExistingTab"], false);
assert_eq!(values["browserDialogPolicy"], "must_respond");
assert_eq!(values["browserDialogTimeout"], 300);
}
@@ -18273,6 +18364,11 @@ browser:
allow_private_urls: true
auto_local_for_private_urls: false
cdp_url: ws://127.0.0.1:9222/devtools/browser/demo
camofox:
managed_persistence: true
user_id: shared-camofox-user
session_key: shared-session-key
adopt_existing_tab: true
dialog_policy: auto_accept
dialog_timeout_s: 120
"#,
@@ -18289,6 +18385,10 @@ browser:
values["browserCdpUrl"],
"ws://127.0.0.1:9222/devtools/browser/demo"
);
assert_eq!(values["browserCamofoxManagedPersistence"], true);
assert_eq!(values["browserCamofoxUserId"], "shared-camofox-user");
assert_eq!(values["browserCamofoxSessionKey"], "shared-session-key");
assert_eq!(values["browserCamofoxAdoptExistingTab"], true);
assert_eq!(values["browserDialogPolicy"], "auto_accept");
assert_eq!(values["browserDialogTimeout"], 120);
}
@@ -18306,7 +18406,11 @@ browser:
engine: auto
cdp_url: ws://127.0.0.1:9222/devtools/browser/demo
camofox:
managed_persistence: true
managed_persistence: false
user_id: old-user
session_key: old-session
adopt_existing_tab: false
custom_flag: keep-camofox
custom_flag: keep-browser
streaming:
enabled: true
@@ -18324,6 +18428,10 @@ streaming:
"browserAllowPrivateUrls": true,
"browserAutoLocalForPrivateUrls": false,
"browserCdpUrl": "http://127.0.0.1:9222",
"browserCamofoxManagedPersistence": true,
"browserCamofoxUserId": "shared-camofox-user",
"browserCamofoxSessionKey": "shared-session-key",
"browserCamofoxAdoptExistingTab": true,
"browserDialogPolicy": "auto_dismiss",
"browserDialogTimeout": "45",
}),
@@ -18357,6 +18465,69 @@ streaming:
config["browser"]["camofox"]["managed_persistence"].as_bool(),
Some(true)
);
assert_eq!(
config["browser"]["camofox"]["user_id"].as_str(),
Some("shared-camofox-user")
);
assert_eq!(
config["browser"]["camofox"]["session_key"].as_str(),
Some("shared-session-key")
);
assert_eq!(
config["browser"]["camofox"]["adopt_existing_tab"].as_bool(),
Some(true)
);
assert_eq!(
config["browser"]["camofox"]["custom_flag"].as_str(),
Some("keep-camofox")
);
assert_eq!(
config["browser"]["custom_flag"].as_str(),
Some("keep-browser")
);
}
#[test]
fn merge_browser_config_removes_empty_camofox_identity_fields() {
let mut config: serde_yaml::Value = serde_yaml::from_str(
r#"
browser:
camofox:
managed_persistence: true
user_id: old-user
session_key: old-session
adopt_existing_tab: true
custom_flag: keep-camofox
custom_flag: keep-browser
"#,
)
.unwrap();
merge_hermes_browser_config(
&mut config,
&json!({
"browserCamofoxManagedPersistence": false,
"browserCamofoxUserId": " ",
"browserCamofoxSessionKey": "",
"browserCamofoxAdoptExistingTab": false,
}),
)
.unwrap();
assert_eq!(
config["browser"]["camofox"]["managed_persistence"].as_bool(),
Some(false)
);
assert!(config["browser"]["camofox"]["user_id"].is_null());
assert!(config["browser"]["camofox"]["session_key"].is_null());
assert_eq!(
config["browser"]["camofox"]["adopt_existing_tab"].as_bool(),
Some(false)
);
assert_eq!(
config["browser"]["camofox"]["custom_flag"].as_str(),
Some("keep-camofox")
);
assert_eq!(
config["browser"]["custom_flag"].as_str(),
Some("keep-browser")
@@ -18406,6 +18577,21 @@ browser:
let err =
merge_hermes_browser_config(&mut config, &json!({ "browserCdpUrl": 123 })).unwrap_err();
assert!(err.contains("browser.cdp_url"));
let err = merge_hermes_browser_config(&mut config, &json!({ "browserCamofoxUserId": 123 }))
.unwrap_err();
assert!(err.contains("browser.camofox.user_id"));
let err = merge_hermes_browser_config(
&mut config,
&json!({ "browserCamofoxUserId": "bad user" }),
)
.unwrap_err();
assert!(err.contains("browser.camofox.user_id"));
let err = merge_hermes_browser_config(
&mut config,
&json!({ "browserCamofoxSessionKey": "bad session" }),
)
.unwrap_err();
assert!(err.contains("browser.camofox.session_key"));
}
}

View File

@@ -309,6 +309,10 @@ const BROWSER_DEFAULTS = {
browserAllowPrivateUrls: false,
browserAutoLocalForPrivateUrls: true,
browserCdpUrl: '',
browserCamofoxManagedPersistence: false,
browserCamofoxUserId: '',
browserCamofoxSessionKey: '',
browserCamofoxAdoptExistingTab: false,
browserDialogPolicy: 'must_respond',
browserDialogTimeout: 300,
}
@@ -2411,6 +2415,27 @@ export function render() {
<span>${t('engine.hermesBrowserConfigAutoLocalForPrivateUrls')}</span>
</label>
</div>
<div class="hm-config-subtitle">${t('engine.hermesBrowserConfigCamofoxTitle')}</div>
<div class="hm-config-runtime-grid hm-config-browser-camofox-grid">
<label class="hm-field">
<span class="hm-field-label">${t('engine.hermesBrowserConfigCamofoxUserId')}</span>
<input id="hm-browser-camofox-user-id" class="hm-input" type="text" autocomplete="off" spellcheck="false" value="${esc(browserValues.browserCamofoxUserId)}" ${disabled ? 'disabled' : ''}>
</label>
<label class="hm-field">
<span class="hm-field-label">${t('engine.hermesBrowserConfigCamofoxSessionKey')}</span>
<input id="hm-browser-camofox-session-key" class="hm-input" type="text" autocomplete="off" spellcheck="false" value="${esc(browserValues.browserCamofoxSessionKey)}" ${disabled ? 'disabled' : ''}>
</label>
</div>
<div class="hm-config-check-grid">
<label class="hm-channel-check">
<input id="hm-browser-camofox-managed-persistence" type="checkbox" ${browserValues.browserCamofoxManagedPersistence ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
<span>${t('engine.hermesBrowserConfigCamofoxManagedPersistence')}</span>
</label>
<label class="hm-channel-check">
<input id="hm-browser-camofox-adopt-existing-tab" type="checkbox" ${browserValues.browserCamofoxAdoptExistingTab ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
<span>${t('engine.hermesBrowserConfigCamofoxAdoptExistingTab')}</span>
</label>
</div>
<div class="hm-channel-footnote">${t('engine.hermesBrowserConfigFootnote')}</div>
</div>
</div>
@@ -4816,6 +4841,10 @@ export function render() {
browserAllowPrivateUrls: !!el.querySelector('#hm-browser-allow-private-urls')?.checked,
browserAutoLocalForPrivateUrls: !!el.querySelector('#hm-browser-auto-local-for-private-urls')?.checked,
browserCdpUrl: el.querySelector('#hm-browser-cdp-url')?.value || '',
browserCamofoxManagedPersistence: !!el.querySelector('#hm-browser-camofox-managed-persistence')?.checked,
browserCamofoxUserId: el.querySelector('#hm-browser-camofox-user-id')?.value || '',
browserCamofoxSessionKey: el.querySelector('#hm-browser-camofox-session-key')?.value || '',
browserCamofoxAdoptExistingTab: !!el.querySelector('#hm-browser-camofox-adopt-existing-tab')?.checked,
browserDialogPolicy: el.querySelector('#hm-browser-dialog-policy')?.value || 'must_respond',
browserDialogTimeout: el.querySelector('#hm-browser-dialog-timeout')?.value || '300',
}

View File

@@ -714,12 +714,17 @@ export default {
hermesBrowserConfigAutoLocalForPrivateUrls: _('私网地址自动切到本地浏览器', 'Use local browser automatically for private URLs', '私網位址自動切到本機瀏覽器'),
hermesBrowserConfigCdpUrl: _('CDP 连接地址', 'CDP endpoint URL', 'CDP 連線位址'),
hermesBrowserConfigCdpUrlPlaceholder: _('留空则自动创建浏览器', 'Leave empty to launch browser automatically', '留空則自動建立瀏覽器'),
hermesBrowserConfigCamofoxTitle: _('Camofox 高级身份', 'Camofox advanced identity', 'Camofox 進階身分'),
hermesBrowserConfigCamofoxManagedPersistence: _('启用 Camofox 托管持久化', 'Enable Camofox managed persistence', '啟用 Camofox 託管持久化'),
hermesBrowserConfigCamofoxUserId: _('Camofox 用户 ID', 'Camofox user ID', 'Camofox 使用者 ID'),
hermesBrowserConfigCamofoxSessionKey: _('Camofox 会话 Key', 'Camofox session key', 'Camofox 工作階段 Key'),
hermesBrowserConfigCamofoxAdoptExistingTab: _('接管已有 Camofox 标签页', 'Adopt existing Camofox tab', '接管既有 Camofox 分頁'),
hermesBrowserConfigDialogPolicy: _('弹窗处理策略', 'Dialog handling policy', '彈窗處理策略'),
hermesBrowserConfigDialogPolicy_must_respond: _('等待用户处理', 'Wait for user response', '等待使用者處理'),
hermesBrowserConfigDialogPolicy_auto_dismiss: _('自动取消弹窗', 'Auto dismiss dialogs', '自動取消彈窗'),
hermesBrowserConfigDialogPolicy_auto_accept: _('自动确认弹窗', 'Auto accept dialogs', '自動確認彈窗'),
hermesBrowserConfigDialogTimeout: _('弹窗等待超时秒数', 'Dialog wait timeout seconds', '彈窗等待逾時秒數'),
hermesBrowserConfigFootnote: _('Lightpanda 导航更快但不支持截图;录制会把 WebM 写入 Hermes browser_recordings 目录,请只在需要审计时开启。允许私网地址会放开 localhost / 192.168 等目标;CDP 地址留空表示自动创建浏览器Camofox 嵌套高级字段会保留在 raw YAML 中。', 'Lightpanda navigates faster but does not support screenshots. Recording writes WebM files into the Hermes browser_recordings directory, so enable it only for audits. Allowing private URLs opens localhost / 192.168-style targets. Empty CDP URL means Hermes launches a browser automatically. Nested Camofox advanced fields stay in raw YAML.', 'Lightpanda 導覽更快但不支援截圖;錄製會把 WebM 寫入 Hermes browser_recordings 目錄,請只在需要稽核時開啟。允許私網位址會放開 localhost / 192.168 等目標;CDP 位址留空表示自動建立瀏覽器Camofox 巢狀進階欄位會保留在 raw YAML 中。'),
hermesBrowserConfigFootnote: _('Lightpanda 导航更快但不支持截图;录制会把 WebM 写入 Hermes browser_recordings 目录请只在需要审计时开启。CDP 地址留空表示自动创建浏览器Camofox 身份字段只影响 Camofox 后端user_id / session_key 会复用远端 profile 或 session不保存密码或 API Key接管已有标签页仅在 Camofox 服务返回 tabs 时生效。', 'Lightpanda navigates faster but does not support screenshots. Recording writes WebM files into the Hermes browser_recordings directory, so enable it only for audits. Empty CDP URL means Hermes launches a browser automatically. Camofox identity fields only affect the Camofox backend; user_id / session_key reuse a remote profile or session and do not store passwords or API keys. Existing-tab adoption only works when the Camofox service returns tabs.', 'Lightpanda 導覽更快但不支援截圖;錄製會把 WebM 寫入 Hermes browser_recordings 目錄請只在需要稽核時開啟。CDP 位址留空表示自動建立瀏覽器Camofox 身分欄位只影響 Camofox 後端user_id / session_key 會複用遠端 profile 或 session不儲存密碼或 API Key接管既有分頁僅在 Camofox 服務返回 tabs 時生效。'),
hermesWebConfigTitle: _('Web 工具后端', 'Web tool backends', 'Web 工具後端'),
hermesWebConfigDesc: _('控制 web_search / web_extract 的默认和分能力后端,只保存后端选择,不保存 API Key。', 'Control default and per-capability backends for web_search / web_extract. This stores backend choices only, not API keys.', '控制 web_search / web_extract 的預設和分能力後端,只儲存後端選擇,不儲存 API Key。'),
hermesWebConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),

View File

@@ -17,6 +17,10 @@ test('Hermes 浏览器配置读取会提供上游默认值', () => {
browserAllowPrivateUrls: false,
browserAutoLocalForPrivateUrls: true,
browserCdpUrl: '',
browserCamofoxManagedPersistence: false,
browserCamofoxUserId: '',
browserCamofoxSessionKey: '',
browserCamofoxAdoptExistingTab: false,
browserDialogPolicy: 'must_respond',
browserDialogTimeout: 300,
})
@@ -32,6 +36,12 @@ test('Hermes 浏览器配置读取会回显 YAML 字段', () => {
allow_private_urls: true,
auto_local_for_private_urls: false,
cdp_url: 'ws://127.0.0.1:9222/devtools/browser/demo',
camofox: {
managed_persistence: true,
user_id: 'shared-camofox-user',
session_key: 'shared-session-key',
adopt_existing_tab: true,
},
dialog_policy: 'auto_accept',
dialog_timeout_s: 120,
},
@@ -44,6 +54,10 @@ test('Hermes 浏览器配置读取会回显 YAML 字段', () => {
assert.equal(values.browserAllowPrivateUrls, true)
assert.equal(values.browserAutoLocalForPrivateUrls, false)
assert.equal(values.browserCdpUrl, 'ws://127.0.0.1:9222/devtools/browser/demo')
assert.equal(values.browserCamofoxManagedPersistence, true)
assert.equal(values.browserCamofoxUserId, 'shared-camofox-user')
assert.equal(values.browserCamofoxSessionKey, 'shared-session-key')
assert.equal(values.browserCamofoxAdoptExistingTab, true)
assert.equal(values.browserDialogPolicy, 'auto_accept')
assert.equal(values.browserDialogTimeout, 120)
})
@@ -57,7 +71,13 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', (
record_sessions: false,
engine: 'auto',
cdp_url: 'ws://127.0.0.1:9222/devtools/browser/demo',
camofox: { managed_persistence: true },
camofox: {
managed_persistence: false,
user_id: 'old-user',
session_key: 'old-session',
adopt_existing_tab: false,
custom_flag: 'keep-camofox',
},
custom_flag: 'keep-browser',
},
streaming: { enabled: true },
@@ -69,6 +89,10 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', (
browserAllowPrivateUrls: true,
browserAutoLocalForPrivateUrls: false,
browserCdpUrl: 'http://127.0.0.1:9222',
browserCamofoxManagedPersistence: true,
browserCamofoxUserId: 'shared-camofox-user',
browserCamofoxSessionKey: 'shared-session-key',
browserCamofoxAdoptExistingTab: true,
browserDialogPolicy: 'auto_dismiss',
browserDialogTimeout: '45',
})
@@ -84,7 +108,40 @@ test('Hermes 浏览器配置保存会保留未知字段并写入上游结构', (
assert.equal(next.browser.cdp_url, 'http://127.0.0.1:9222')
assert.equal(next.browser.dialog_policy, 'auto_dismiss')
assert.equal(next.browser.dialog_timeout_s, 45)
assert.deepEqual(next.browser.camofox, { managed_persistence: true })
assert.deepEqual(next.browser.camofox, {
managed_persistence: true,
user_id: 'shared-camofox-user',
session_key: 'shared-session-key',
adopt_existing_tab: true,
custom_flag: 'keep-camofox',
})
assert.equal(next.browser.custom_flag, 'keep-browser')
})
test('Hermes 浏览器配置保存空 Camofox 身份字段会删除对应覆盖', () => {
const next = mergeHermesBrowserConfig({
browser: {
camofox: {
managed_persistence: true,
user_id: 'old-user',
session_key: 'old-session',
adopt_existing_tab: true,
custom_flag: 'keep-camofox',
},
custom_flag: 'keep-browser',
},
}, {
browserCamofoxManagedPersistence: false,
browserCamofoxUserId: ' ',
browserCamofoxSessionKey: '',
browserCamofoxAdoptExistingTab: false,
})
assert.equal(next.browser.camofox.managed_persistence, false)
assert.equal(Object.hasOwn(next.browser.camofox, 'user_id'), false)
assert.equal(Object.hasOwn(next.browser.camofox, 'session_key'), false)
assert.equal(next.browser.camofox.adopt_existing_tab, false)
assert.equal(next.browser.camofox.custom_flag, 'keep-camofox')
assert.equal(next.browser.custom_flag, 'keep-browser')
})
@@ -127,4 +184,16 @@ test('Hermes 浏览器配置保存会拒绝非法引擎和越界值', () => {
() => mergeHermesBrowserConfig({}, { browserCdpUrl: 123 }),
/browser\.cdp_url/,
)
assert.throws(
() => mergeHermesBrowserConfig({}, { browserCamofoxUserId: 123 }),
/browser\.camofox\.user_id/,
)
assert.throws(
() => mergeHermesBrowserConfig({}, { browserCamofoxUserId: 'bad user' }),
/browser\.camofox\.user_id/,
)
assert.throws(
() => mergeHermesBrowserConfig({}, { browserCamofoxSessionKey: 'bad session' }),
/browser\.camofox\.session_key/,
)
})

View File

@@ -458,6 +458,10 @@ test('Hermes 配置页会暴露浏览器基础结构化配置字段', () => {
'hm-browser-allow-private-urls',
'hm-browser-auto-local-for-private-urls',
'hm-browser-cdp-url',
'hm-browser-camofox-managed-persistence',
'hm-browser-camofox-user-id',
'hm-browser-camofox-session-key',
'hm-browser-camofox-adopt-existing-tab',
'hm-browser-dialog-policy',
'hm-browser-dialog-timeout',
]) {