mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 04:10:00 +08:00
feat(hermes): add worktree session setting
This commit is contained in:
@@ -5203,6 +5203,7 @@ export function buildHermesSessionRuntimeConfigValues(config = {}) {
|
||||
atHour: parseHermesInteger(sessionReset.at_hour, 'at_hour', 4, 0, 23, false),
|
||||
groupSessionsPerUser: readHermesBool(root.group_sessions_per_user, true),
|
||||
threadSessionsPerUser: readHermesBool(root.thread_sessions_per_user, false),
|
||||
worktreeEnabled: readHermesBool(root.worktree, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5224,6 +5225,7 @@ export function mergeHermesSessionRuntimeConfig(config = {}, form = {}) {
|
||||
next.session_reset = sessionReset
|
||||
next.group_sessions_per_user = formHermesBool(form, 'groupSessionsPerUser', currentValues.groupSessionsPerUser)
|
||||
next.thread_sessions_per_user = formHermesBool(form, 'threadSessionsPerUser', currentValues.threadSessionsPerUser)
|
||||
next.worktree = formHermesBool(form, 'worktreeEnabled', currentValues.worktreeEnabled)
|
||||
return next
|
||||
}
|
||||
|
||||
|
||||
@@ -7908,6 +7908,9 @@ fn build_hermes_session_runtime_config_values(config: &serde_yaml::Value) -> Val
|
||||
let thread_sessions_per_user = root
|
||||
.and_then(|map| yaml_bool_field(map, "thread_sessions_per_user"))
|
||||
.unwrap_or(false);
|
||||
let worktree_enabled = root
|
||||
.and_then(|map| yaml_bool_field(map, "worktree"))
|
||||
.unwrap_or(false);
|
||||
|
||||
serde_json::json!({
|
||||
"sessionResetMode": mode,
|
||||
@@ -7915,6 +7918,7 @@ fn build_hermes_session_runtime_config_values(config: &serde_yaml::Value) -> Val
|
||||
"atHour": at_hour,
|
||||
"groupSessionsPerUser": group_sessions_per_user,
|
||||
"threadSessionsPerUser": thread_sessions_per_user,
|
||||
"worktreeEnabled": worktree_enabled,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7960,6 +7964,8 @@ fn merge_hermes_session_runtime_config(
|
||||
.unwrap_or_else(|| current["groupSessionsPerUser"].as_bool().unwrap_or(true));
|
||||
let thread_sessions_per_user = form_bool(form, "threadSessionsPerUser")
|
||||
.unwrap_or_else(|| current["threadSessionsPerUser"].as_bool().unwrap_or(false));
|
||||
let worktree_enabled = form_bool(form, "worktreeEnabled")
|
||||
.unwrap_or_else(|| current["worktreeEnabled"].as_bool().unwrap_or(false));
|
||||
|
||||
let root = ensure_yaml_object(config)?;
|
||||
let session_reset = yaml_child_object(root, "session_reset")?;
|
||||
@@ -7980,6 +7986,10 @@ fn merge_hermes_session_runtime_config(
|
||||
yaml_key("thread_sessions_per_user"),
|
||||
serde_yaml::Value::Bool(thread_sessions_per_user),
|
||||
);
|
||||
root.insert(
|
||||
yaml_key("worktree"),
|
||||
serde_yaml::Value::Bool(worktree_enabled),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14379,6 +14389,31 @@ mod hermes_session_runtime_config_tests {
|
||||
assert_eq!(values["atHour"], 4);
|
||||
assert_eq!(values["groupSessionsPerUser"], true);
|
||||
assert_eq!(values["threadSessionsPerUser"], false);
|
||||
assert_eq!(values["worktreeEnabled"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_runtime_values_read_worktree_flag() {
|
||||
let config: serde_yaml::Value = serde_yaml::from_str(
|
||||
r#"
|
||||
session_reset:
|
||||
mode: daily
|
||||
idle_minutes: 720
|
||||
at_hour: 3
|
||||
group_sessions_per_user: false
|
||||
thread_sessions_per_user: true
|
||||
worktree: true
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let values = build_hermes_session_runtime_config_values(&config);
|
||||
|
||||
assert_eq!(values["sessionResetMode"], "daily");
|
||||
assert_eq!(values["idleMinutes"], 720);
|
||||
assert_eq!(values["atHour"], 3);
|
||||
assert_eq!(values["groupSessionsPerUser"], false);
|
||||
assert_eq!(values["threadSessionsPerUser"], true);
|
||||
assert_eq!(values["worktreeEnabled"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -14406,6 +14441,7 @@ streaming:
|
||||
"atHour": "6",
|
||||
"groupSessionsPerUser": false,
|
||||
"threadSessionsPerUser": true,
|
||||
"worktreeEnabled": true,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -14421,6 +14457,7 @@ streaming:
|
||||
);
|
||||
assert_eq!(config["group_sessions_per_user"].as_bool(), Some(false));
|
||||
assert_eq!(config["thread_sessions_per_user"].as_bool(), Some(true));
|
||||
assert_eq!(config["worktree"].as_bool(), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -12,6 +12,7 @@ const SESSION_RUNTIME_DEFAULTS = {
|
||||
atHour: 4,
|
||||
groupSessionsPerUser: true,
|
||||
threadSessionsPerUser: false,
|
||||
worktreeEnabled: false,
|
||||
}
|
||||
|
||||
const COMPRESSION_DEFAULTS = {
|
||||
@@ -498,6 +499,10 @@ export function render() {
|
||||
<input id="hm-thread-sessions-per-user" type="checkbox" ${runtimeValues.threadSessionsPerUser ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesThreadSessionsPerUser')}</span>
|
||||
</label>
|
||||
<label class="hm-channel-check">
|
||||
<input id="hm-worktree-enabled" type="checkbox" ${runtimeValues.worktreeEnabled ? 'checked' : ''} ${disabled ? 'disabled' : ''}>
|
||||
<span>${t('engine.hermesWorktreeEnabled')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hm-channel-footnote">${t('engine.hermesSessionRuntimeFootnote')}</div>
|
||||
</div>
|
||||
@@ -2727,6 +2732,7 @@ export function render() {
|
||||
atHour: el.querySelector('#hm-session-at-hour')?.value || '4',
|
||||
groupSessionsPerUser: !!el.querySelector('#hm-group-sessions-per-user')?.checked,
|
||||
threadSessionsPerUser: !!el.querySelector('#hm-thread-sessions-per-user')?.checked,
|
||||
worktreeEnabled: !!el.querySelector('#hm-worktree-enabled')?.checked,
|
||||
}
|
||||
runtimeSaving = true
|
||||
runtimeError = null
|
||||
|
||||
@@ -497,7 +497,8 @@ export default {
|
||||
hermesSessionAtHour: _('每日换新小时', 'Daily reset hour', '每日換新小時'),
|
||||
hermesGroupSessionsPerUser: _('群聊按成员隔离会话', 'Isolate group sessions per user', '群聊依成員隔離會話'),
|
||||
hermesThreadSessionsPerUser: _('线程也按成员隔离', 'Isolate thread sessions per user', '討論串也依成員隔離'),
|
||||
hermesSessionRuntimeFootnote: _('推荐保持群聊隔离开启。关闭后,同一群/频道会共用上下文和中断状态。', 'Keeping group isolation on is recommended. Turning it off shares context and interrupt state across the same group or channel.', '建議保持群聊隔離開啟。關閉後,同一群組/頻道會共用上下文和中斷狀態。'),
|
||||
hermesWorktreeEnabled: _('CLI 会话默认使用 Git worktree 隔离', 'Use Git worktree isolation for CLI sessions by default', 'CLI 會話預設使用 Git worktree 隔離'),
|
||||
hermesSessionRuntimeFootnote: _('推荐保持群聊隔离开启;多人或多 Agent 同仓库长跑时,可开启 worktree 隔离来减少文件冲突。', 'Keeping group isolation on is recommended. For multi-user or multi-agent long runs in the same repository, enable worktree isolation to reduce file conflicts.', '建議保持群聊隔離開啟;多人或多 Agent 同倉庫長跑時,可啟用 worktree 隔離以減少檔案衝突。'),
|
||||
hermesTerminalConfigTitle: _('终端执行', 'Terminal execution', '終端執行'),
|
||||
hermesTerminalConfigDesc: _('控制 Hermes 工具命令的执行环境、工作目录、超时和容器资源,避免长任务卡死或沙箱范围误配。', 'Control command execution backend, working directory, timeouts, and container resources to avoid stuck runs or sandbox misconfiguration.', '控制 Hermes 工具命令的執行環境、工作目錄、逾時和容器資源,避免長任務卡死或沙箱範圍誤配。'),
|
||||
hermesTerminalConfigStatusReady: _('结构化配置', 'structured settings', '結構化設定'),
|
||||
|
||||
@@ -9,6 +9,20 @@ function extractEngineKeys() {
|
||||
return [...source.matchAll(/['"](engine\.[A-Za-z0-9_.-]+)['"]/g)].map(match => match[1])
|
||||
}
|
||||
|
||||
test('Hermes 配置页会暴露会话安全结构化配置字段', () => {
|
||||
for (const id of [
|
||||
'hm-runtime-save',
|
||||
'hm-session-reset-mode',
|
||||
'hm-session-idle-minutes',
|
||||
'hm-session-at-hour',
|
||||
'hm-group-sessions-per-user',
|
||||
'hm-thread-sessions-per-user',
|
||||
'hm-worktree-enabled',
|
||||
]) {
|
||||
assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('Hermes 配置页会暴露工具循环防护结构化配置字段', () => {
|
||||
for (const id of [
|
||||
'hm-tool-guardrails-save',
|
||||
@@ -430,7 +444,8 @@ test('Hermes 配置页新增结构化配置不会暴露翻译 key', () => {
|
||||
key.includes('CheckpointsConfig') ||
|
||||
key.includes('ApprovalsConfig') ||
|
||||
key.includes('CronConfig') ||
|
||||
key.includes('LoggingConfig')
|
||||
key.includes('LoggingConfig') ||
|
||||
key.includes('Worktree')
|
||||
)))
|
||||
|
||||
assert.ok(keys.size > 0, '应能提取新增结构化配置用到的 engine 翻译 key')
|
||||
|
||||
@@ -15,6 +15,7 @@ test('Hermes 会话运行时配置读取会提供稳定表单默认值', () => {
|
||||
atHour: 4,
|
||||
groupSessionsPerUser: true,
|
||||
threadSessionsPerUser: false,
|
||||
worktreeEnabled: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,6 +28,7 @@ test('Hermes 会话运行时配置读取会回显 session_reset 与隔离开关'
|
||||
},
|
||||
group_sessions_per_user: false,
|
||||
thread_sessions_per_user: true,
|
||||
worktree: true,
|
||||
})
|
||||
|
||||
assert.equal(values.sessionResetMode, 'daily')
|
||||
@@ -34,6 +36,7 @@ test('Hermes 会话运行时配置读取会回显 session_reset 与隔离开关'
|
||||
assert.equal(values.atHour, 3)
|
||||
assert.equal(values.groupSessionsPerUser, false)
|
||||
assert.equal(values.threadSessionsPerUser, true)
|
||||
assert.equal(values.worktreeEnabled, true)
|
||||
})
|
||||
|
||||
test('Hermes 会话运行时配置保存会保留无关 YAML 并写入 snake_case 字段', () => {
|
||||
@@ -51,6 +54,7 @@ test('Hermes 会话运行时配置保存会保留无关 YAML 并写入 snake_cas
|
||||
atHour: '6',
|
||||
groupSessionsPerUser: false,
|
||||
threadSessionsPerUser: true,
|
||||
worktreeEnabled: true,
|
||||
})
|
||||
|
||||
assert.deepEqual(next.model, { provider: 'anthropic', default: 'claude-sonnet-4-6' })
|
||||
@@ -61,6 +65,7 @@ test('Hermes 会话运行时配置保存会保留无关 YAML 并写入 snake_cas
|
||||
assert.equal(next.session_reset.custom_flag, 'keep-me')
|
||||
assert.equal(next.group_sessions_per_user, false)
|
||||
assert.equal(next.thread_sessions_per_user, true)
|
||||
assert.equal(next.worktree, true)
|
||||
})
|
||||
|
||||
test('Hermes 会话运行时配置保存会拒绝非法模式和范围', () => {
|
||||
|
||||
Reference in New Issue
Block a user