mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 04:10:00 +08:00
feat(hermes): add delegation model override config
This commit is contained in:
@@ -4880,6 +4880,8 @@ export function buildHermesExecutionLimitsConfigValues(config = {}) {
|
||||
delegationOrchestratorEnabled: readHermesBool(delegation.orchestrator_enabled, true),
|
||||
delegationSubagentAutoApprove: readHermesBool(delegation.subagent_auto_approve, false),
|
||||
delegationInheritMcpToolsets: readHermesBool(delegation.inherit_mcp_toolsets, true),
|
||||
delegationModel: typeof delegation.model === 'string' ? delegation.model.trim() : '',
|
||||
delegationProvider: typeof delegation.provider === 'string' ? delegation.provider.trim() : '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5198,6 +5200,12 @@ export function mergeHermesExecutionLimitsConfig(config = {}, form = {}) {
|
||||
delegation.orchestrator_enabled = formHermesBool(form, 'delegationOrchestratorEnabled', currentValues.delegationOrchestratorEnabled)
|
||||
delegation.subagent_auto_approve = formHermesBool(form, 'delegationSubagentAutoApprove', currentValues.delegationSubagentAutoApprove)
|
||||
delegation.inherit_mcp_toolsets = formHermesBool(form, 'delegationInheritMcpToolsets', currentValues.delegationInheritMcpToolsets)
|
||||
const delegationModel = normalizeHermesModelConfigString(Object.hasOwn(form, 'delegationModel') ? form.delegationModel : currentValues.delegationModel, 'delegation.model')
|
||||
if (delegationModel) delegation.model = delegationModel
|
||||
else delete delegation.model
|
||||
const delegationProvider = normalizeHermesModelConfigString(Object.hasOwn(form, 'delegationProvider') ? form.delegationProvider : currentValues.delegationProvider, 'delegation.provider')
|
||||
if (delegationProvider) delegation.provider = delegationProvider
|
||||
else delete delegation.provider
|
||||
next.code_execution = codeExecution
|
||||
next.delegation = delegation
|
||||
return next
|
||||
|
||||
@@ -6878,6 +6878,16 @@ fn build_hermes_execution_limits_config_values(config: &serde_yaml::Value) -> Va
|
||||
let delegation_inherit_mcp_toolsets = delegation
|
||||
.and_then(|map| yaml_bool_field(map, "inherit_mcp_toolsets"))
|
||||
.unwrap_or(true);
|
||||
let delegation_model = delegation
|
||||
.and_then(|map| yaml_string_field(map, "model"))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_default();
|
||||
let delegation_provider = delegation
|
||||
.and_then(|map| yaml_string_field(map, "provider"))
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_default();
|
||||
|
||||
serde_json::json!({
|
||||
"codeExecutionMode": code_execution_mode,
|
||||
@@ -6890,6 +6900,8 @@ fn build_hermes_execution_limits_config_values(config: &serde_yaml::Value) -> Va
|
||||
"delegationOrchestratorEnabled": delegation_orchestrator_enabled,
|
||||
"delegationSubagentAutoApprove": delegation_subagent_auto_approve,
|
||||
"delegationInheritMcpToolsets": delegation_inherit_mcp_toolsets,
|
||||
"delegationModel": delegation_model,
|
||||
"delegationProvider": delegation_provider,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7733,6 +7745,20 @@ fn merge_hermes_execution_limits_config(
|
||||
.as_bool()
|
||||
.unwrap_or(true)
|
||||
});
|
||||
let delegation_model = form_string(form, "delegationModel")
|
||||
.or_else(|| current["delegationModel"].as_str().map(ToString::to_string))
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
let delegation_provider = form_string(form, "delegationProvider")
|
||||
.or_else(|| {
|
||||
current["delegationProvider"]
|
||||
.as_str()
|
||||
.map(ToString::to_string)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let root = ensure_yaml_object(config)?;
|
||||
let code_execution = yaml_child_object(root, "code_execution")?;
|
||||
@@ -7778,6 +7804,22 @@ fn merge_hermes_execution_limits_config(
|
||||
yaml_key("inherit_mcp_toolsets"),
|
||||
serde_yaml::Value::Bool(delegation_inherit_mcp_toolsets),
|
||||
);
|
||||
if delegation_model.is_empty() {
|
||||
delegation.remove(yaml_key("model"));
|
||||
} else {
|
||||
delegation.insert(
|
||||
yaml_key("model"),
|
||||
serde_yaml::Value::String(delegation_model),
|
||||
);
|
||||
}
|
||||
if delegation_provider.is_empty() {
|
||||
delegation.remove(yaml_key("provider"));
|
||||
} else {
|
||||
delegation.insert(
|
||||
yaml_key("provider"),
|
||||
serde_yaml::Value::String(delegation_provider),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15465,6 +15507,8 @@ mod hermes_execution_limits_config_tests {
|
||||
assert_eq!(values["delegationOrchestratorEnabled"], true);
|
||||
assert_eq!(values["delegationSubagentAutoApprove"], false);
|
||||
assert_eq!(values["delegationInheritMcpToolsets"], true);
|
||||
assert_eq!(values["delegationModel"], "");
|
||||
assert_eq!(values["delegationProvider"], "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -15483,6 +15527,8 @@ delegation:
|
||||
orchestrator_enabled: false
|
||||
subagent_auto_approve: true
|
||||
inherit_mcp_toolsets: false
|
||||
model: google/gemini-3-flash-preview
|
||||
provider: openrouter
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -15497,6 +15543,8 @@ delegation:
|
||||
assert_eq!(values["delegationOrchestratorEnabled"], false);
|
||||
assert_eq!(values["delegationSubagentAutoApprove"], true);
|
||||
assert_eq!(values["delegationInheritMcpToolsets"], false);
|
||||
assert_eq!(values["delegationModel"], "google/gemini-3-flash-preview");
|
||||
assert_eq!(values["delegationProvider"], "openrouter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -15531,6 +15579,8 @@ streaming:
|
||||
"delegationOrchestratorEnabled": false,
|
||||
"delegationSubagentAutoApprove": true,
|
||||
"delegationInheritMcpToolsets": false,
|
||||
"delegationModel": "anthropic/claude-haiku-4.6",
|
||||
"delegationProvider": "anthropic",
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -15569,11 +15619,40 @@ streaming:
|
||||
config["delegation"]["inherit_mcp_toolsets"].as_bool(),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(config["delegation"]["model"].as_str(), Some("child-model"));
|
||||
assert_eq!(
|
||||
config["delegation"]["provider"].as_str(),
|
||||
Some("openrouter")
|
||||
config["delegation"]["model"].as_str(),
|
||||
Some("anthropic/claude-haiku-4.6")
|
||||
);
|
||||
assert_eq!(config["delegation"]["provider"].as_str(), Some("anthropic"));
|
||||
assert_eq!(
|
||||
config["delegation"]["custom_flag"].as_str(),
|
||||
Some("keep-delegation")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_execution_limits_config_removes_empty_child_model_overrides() {
|
||||
let mut config: serde_yaml::Value = serde_yaml::from_str(
|
||||
r#"
|
||||
delegation:
|
||||
model: child-model
|
||||
provider: openrouter
|
||||
custom_flag: keep-delegation
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
merge_hermes_execution_limits_config(
|
||||
&mut config,
|
||||
&json!({
|
||||
"delegationModel": " ",
|
||||
"delegationProvider": "",
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(config["delegation"]["model"].is_null());
|
||||
assert!(config["delegation"]["provider"].is_null());
|
||||
assert_eq!(
|
||||
config["delegation"]["custom_flag"].as_str(),
|
||||
Some("keep-delegation")
|
||||
|
||||
@@ -192,6 +192,8 @@ const EXECUTION_LIMITS_DEFAULTS = {
|
||||
delegationOrchestratorEnabled: true,
|
||||
delegationSubagentAutoApprove: false,
|
||||
delegationInheritMcpToolsets: true,
|
||||
delegationModel: '',
|
||||
delegationProvider: '',
|
||||
}
|
||||
|
||||
const IO_SAFETY_DEFAULTS = {
|
||||
@@ -1548,6 +1550,14 @@ export function render() {
|
||||
<span class="hm-field-label">${t('engine.hermesExecutionLimitsDelegationMaxSpawnDepth')}</span>
|
||||
<input id="hm-delegation-max-spawn-depth" class="hm-input" type="number" inputmode="numeric" min="1" max="3" step="1" value="${esc(executionLimitsValues.delegationMaxSpawnDepth)}" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesExecutionLimitsDelegationModel')}</span>
|
||||
<input id="hm-delegation-model" class="hm-input" value="${esc(executionLimitsValues.delegationModel)}" placeholder="google/gemini-3-flash-preview" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
<label class="hm-field">
|
||||
<span class="hm-field-label">${t('engine.hermesExecutionLimitsDelegationProvider')}</span>
|
||||
<input id="hm-delegation-provider" class="hm-input" value="${esc(executionLimitsValues.delegationProvider)}" placeholder="openrouter" ${disabled ? 'disabled' : ''}>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-config-check-grid">
|
||||
<label class="hm-channel-check">
|
||||
@@ -3402,6 +3412,8 @@ export function render() {
|
||||
delegationChildTimeoutSeconds: el.querySelector('#hm-delegation-child-timeout-seconds')?.value || '600',
|
||||
delegationMaxConcurrentChildren: el.querySelector('#hm-delegation-max-concurrent-children')?.value || '3',
|
||||
delegationMaxSpawnDepth: el.querySelector('#hm-delegation-max-spawn-depth')?.value || '1',
|
||||
delegationModel: el.querySelector('#hm-delegation-model')?.value || '',
|
||||
delegationProvider: el.querySelector('#hm-delegation-provider')?.value || '',
|
||||
delegationOrchestratorEnabled: !!el.querySelector('#hm-delegation-orchestrator-enabled')?.checked,
|
||||
delegationSubagentAutoApprove: !!el.querySelector('#hm-delegation-subagent-auto-approve')?.checked,
|
||||
delegationInheritMcpToolsets: !!el.querySelector('#hm-delegation-inherit-mcp-toolsets')?.checked,
|
||||
|
||||
@@ -561,10 +561,12 @@ export default {
|
||||
hermesExecutionLimitsDelegationChildTimeout: _('每个子任务超时(秒)', 'Child timeout (s)', '每個子任務逾時(秒)'),
|
||||
hermesExecutionLimitsDelegationMaxConcurrent: _('最大并发子任务', 'Max concurrent children', '最大並發子任務'),
|
||||
hermesExecutionLimitsDelegationMaxSpawnDepth: _('委派深度上限', 'Spawn depth limit', '委派深度上限'),
|
||||
hermesExecutionLimitsDelegationModel: _('子 Agent 模型覆盖(可选)', 'Child model override (optional)', '子 Agent 模型覆蓋(可選)'),
|
||||
hermesExecutionLimitsDelegationProvider: _('子 Agent Provider 覆盖(可选)', 'Child provider override (optional)', '子 Agent Provider 覆蓋(可選)'),
|
||||
hermesExecutionLimitsDelegationOrchestratorEnabled: _('允许中间协调 Agent', 'Allow orchestrator children', '允許中間協調 Agent'),
|
||||
hermesExecutionLimitsDelegationInheritMcp: _('保留父任务 MCP 工具集', 'Inherit parent MCP toolsets', '保留父任務 MCP 工具集'),
|
||||
hermesExecutionLimitsDelegationAutoApprove: _('自动批准子任务危险命令', 'Auto-approve child dangerous commands', '自動批准子任務危險命令'),
|
||||
hermesExecutionLimitsFootnote: _('默认会拒绝子任务危险命令审批,适合交互式和长跑任务。只有在完全信任无人值守环境时才开启自动批准。', 'By default, dangerous-command approvals from child agents are auto-denied, which fits interactive and long-running tasks. Enable auto-approval only in fully trusted unattended environments.', '預設會拒絕子任務危險命令審批,適合互動式和長跑任務。只有在完全信任無人值守環境時才啟用自動批准。'),
|
||||
hermesExecutionLimitsFootnote: _('子 Agent 模型和 Provider 留空时继承父任务;只在需要降低成本、隔离慢模型或固定子任务路由时填写。默认会拒绝子任务危险命令审批,适合交互式和长跑任务。只有在完全信任无人值守环境时才开启自动批准。', 'Leave child model and provider blank to inherit the parent task. Fill them only to reduce cost, isolate slower models, or pin child-task routing. By default, dangerous-command approvals from child agents are auto-denied, which fits interactive and long-running tasks. Enable auto-approval only in fully trusted unattended environments.', '子 Agent 模型和 Provider 留空時繼承父任務;只在需要降低成本、隔離慢模型或固定子任務路由時填寫。預設會拒絕子任務危險命令審批,適合互動式和長跑任務。只有在完全信任無人值守環境時才啟用自動批准。'),
|
||||
hermesIoSafetyTitle: _('输入输出保护', 'Input and output safety', '輸入輸出保護'),
|
||||
hermesIoSafetyDesc: _('限制单次文件读取和工具输出体量,避免大文件或长日志一次性挤爆上下文。', 'Limit single file reads and tool output size so large files or long logs do not flood the context.', '限制單次檔案讀取和工具輸出體量,避免大型檔案或長日誌一次性擠爆上下文。'),
|
||||
hermesIoSafetyStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
@@ -288,6 +288,8 @@ test('Hermes 配置页会暴露执行与委派限制结构化配置字段', () =
|
||||
'hm-delegation-child-timeout-seconds',
|
||||
'hm-delegation-max-concurrent-children',
|
||||
'hm-delegation-max-spawn-depth',
|
||||
'hm-delegation-model',
|
||||
'hm-delegation-provider',
|
||||
'hm-delegation-orchestrator-enabled',
|
||||
'hm-delegation-subagent-auto-approve',
|
||||
'hm-delegation-inherit-mcp-toolsets',
|
||||
|
||||
@@ -20,6 +20,8 @@ test('Hermes 执行与委派限制读取会提供上游默认值', () => {
|
||||
delegationOrchestratorEnabled: true,
|
||||
delegationSubagentAutoApprove: false,
|
||||
delegationInheritMcpToolsets: true,
|
||||
delegationModel: '',
|
||||
delegationProvider: '',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,6 +40,8 @@ test('Hermes 执行与委派限制读取会回显 YAML 字段', () => {
|
||||
orchestrator_enabled: false,
|
||||
subagent_auto_approve: true,
|
||||
inherit_mcp_toolsets: false,
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
provider: 'openrouter',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -51,6 +55,8 @@ test('Hermes 执行与委派限制读取会回显 YAML 字段', () => {
|
||||
assert.equal(values.delegationOrchestratorEnabled, false)
|
||||
assert.equal(values.delegationSubagentAutoApprove, true)
|
||||
assert.equal(values.delegationInheritMcpToolsets, false)
|
||||
assert.equal(values.delegationModel, 'google/gemini-3-flash-preview')
|
||||
assert.equal(values.delegationProvider, 'openrouter')
|
||||
})
|
||||
|
||||
test('Hermes 执行与委派限制保存会保留未知字段并写入上游结构', () => {
|
||||
@@ -77,6 +83,8 @@ test('Hermes 执行与委派限制保存会保留未知字段并写入上游结
|
||||
delegationOrchestratorEnabled: false,
|
||||
delegationSubagentAutoApprove: true,
|
||||
delegationInheritMcpToolsets: false,
|
||||
delegationModel: 'anthropic/claude-haiku-4.6',
|
||||
delegationProvider: 'anthropic',
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic' })
|
||||
@@ -92,8 +100,25 @@ test('Hermes 执行与委派限制保存会保留未知字段并写入上游结
|
||||
assert.equal(next.delegation.orchestrator_enabled, false)
|
||||
assert.equal(next.delegation.subagent_auto_approve, true)
|
||||
assert.equal(next.delegation.inherit_mcp_toolsets, false)
|
||||
assert.equal(next.delegation.model, 'child-model')
|
||||
assert.equal(next.delegation.provider, 'openrouter')
|
||||
assert.equal(next.delegation.model, 'anthropic/claude-haiku-4.6')
|
||||
assert.equal(next.delegation.provider, 'anthropic')
|
||||
assert.equal(next.delegation.custom_flag, 'keep-delegation')
|
||||
})
|
||||
|
||||
test('Hermes 执行与委派限制保存空子 Agent 模型覆盖会删除对应字段', () => {
|
||||
const next = mergeHermesExecutionLimitsConfig({
|
||||
delegation: {
|
||||
model: 'child-model',
|
||||
provider: 'openrouter',
|
||||
custom_flag: 'keep-delegation',
|
||||
},
|
||||
}, {
|
||||
delegationModel: ' ',
|
||||
delegationProvider: '',
|
||||
})
|
||||
|
||||
assert.equal(Object.hasOwn(next.delegation, 'model'), false)
|
||||
assert.equal(Object.hasOwn(next.delegation, 'provider'), false)
|
||||
assert.equal(next.delegation.custom_flag, 'keep-delegation')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user