feat(hermes): add worktree session setting

This commit is contained in:
晴天
2026-05-26 05:50:12 +08:00
parent 51be3ab4ca
commit 975613416d
6 changed files with 68 additions and 2 deletions

View File

@@ -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
}

View File

@@ -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]

View File

@@ -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

View File

@@ -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', '結構化設定'),

View File

@@ -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')

View File

@@ -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 会话运行时配置保存会拒绝非法模式和范围', () => {