mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-08 17:14:49 +08:00
feat(config): add trustedRoots to RuntimeConfig
Closes the startup-friction gap filed in ROADMAP (dd97c49).
WorkerCreate required trusted_roots on every call with no config-level
default. Any batch script that omitted the field stalled all workers at
TrustRequired with no auto-recovery path.
Changes:
- RuntimeFeatureConfig: add trusted_roots: Vec<String> field
- ConfigLoader: wire parse_optional_trusted_roots() for 'trustedRoots' key
- RuntimeConfig / RuntimeFeatureConfig: expose trusted_roots() accessor
- config_validate: add trustedRoots to TOP_LEVEL_FIELDS schema (StringArray)
- Tests: parses_trusted_roots_from_settings + trusted_roots_default_is_empty_when_unset
Callers can now set trusted_roots in .claw/settings.json:
{ "trustedRoots": ["/tmp/worktrees"] }
WorkerRegistry::spawn_worker() callers should merge config.trusted_roots()
with any per-call overrides (wiring left for follow-up).
This commit is contained in:
@@ -85,6 +85,7 @@ pub struct RuntimeFeatureConfig {
|
|||||||
permission_rules: RuntimePermissionRuleConfig,
|
permission_rules: RuntimePermissionRuleConfig,
|
||||||
sandbox: SandboxConfig,
|
sandbox: SandboxConfig,
|
||||||
provider_fallbacks: ProviderFallbackConfig,
|
provider_fallbacks: ProviderFallbackConfig,
|
||||||
|
trusted_roots: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ordered chain of fallback model identifiers used when the primary
|
/// Ordered chain of fallback model identifiers used when the primary
|
||||||
@@ -334,6 +335,7 @@ impl ConfigLoader {
|
|||||||
permission_rules: parse_optional_permission_rules(&merged_value)?,
|
permission_rules: parse_optional_permission_rules(&merged_value)?,
|
||||||
sandbox: parse_optional_sandbox_config(&merged_value)?,
|
sandbox: parse_optional_sandbox_config(&merged_value)?,
|
||||||
provider_fallbacks: parse_optional_provider_fallbacks(&merged_value)?,
|
provider_fallbacks: parse_optional_provider_fallbacks(&merged_value)?,
|
||||||
|
trusted_roots: parse_optional_trusted_roots(&merged_value)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(RuntimeConfig {
|
Ok(RuntimeConfig {
|
||||||
@@ -428,6 +430,11 @@ impl RuntimeConfig {
|
|||||||
pub fn provider_fallbacks(&self) -> &ProviderFallbackConfig {
|
pub fn provider_fallbacks(&self) -> &ProviderFallbackConfig {
|
||||||
&self.feature_config.provider_fallbacks
|
&self.feature_config.provider_fallbacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn trusted_roots(&self) -> &[String] {
|
||||||
|
&self.feature_config.trusted_roots
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuntimeFeatureConfig {
|
impl RuntimeFeatureConfig {
|
||||||
@@ -492,6 +499,11 @@ impl RuntimeFeatureConfig {
|
|||||||
pub fn provider_fallbacks(&self) -> &ProviderFallbackConfig {
|
pub fn provider_fallbacks(&self) -> &ProviderFallbackConfig {
|
||||||
&self.provider_fallbacks
|
&self.provider_fallbacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn trusted_roots(&self) -> &[String] {
|
||||||
|
&self.trusted_roots
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProviderFallbackConfig {
|
impl ProviderFallbackConfig {
|
||||||
@@ -913,6 +925,14 @@ fn parse_optional_provider_fallbacks(
|
|||||||
Ok(ProviderFallbackConfig { primary, fallbacks })
|
Ok(ProviderFallbackConfig { primary, fallbacks })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_optional_trusted_roots(root: &JsonValue) -> Result<Vec<String>, ConfigError> {
|
||||||
|
let Some(object) = root.as_object() else {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
};
|
||||||
|
Ok(optional_string_array(object, "trustedRoots", "merged settings.trustedRoots")?
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_filesystem_mode_label(value: &str) -> Result<FilesystemIsolationMode, ConfigError> {
|
fn parse_filesystem_mode_label(value: &str) -> Result<FilesystemIsolationMode, ConfigError> {
|
||||||
match value {
|
match value {
|
||||||
"off" => Ok(FilesystemIsolationMode::Off),
|
"off" => Ok(FilesystemIsolationMode::Off),
|
||||||
@@ -1465,6 +1485,53 @@ mod tests {
|
|||||||
fs::remove_dir_all(root).expect("cleanup temp dir");
|
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_trusted_roots_from_settings() {
|
||||||
|
// given
|
||||||
|
let root = temp_dir();
|
||||||
|
let cwd = root.join("project");
|
||||||
|
let home = root.join("home").join(".claw");
|
||||||
|
fs::create_dir_all(&home).expect("home config dir");
|
||||||
|
fs::create_dir_all(&cwd).expect("project dir");
|
||||||
|
fs::write(
|
||||||
|
home.join("settings.json"),
|
||||||
|
r#"{"trustedRoots": ["/tmp/worktrees", "/home/user/projects"]}"#,
|
||||||
|
)
|
||||||
|
.expect("write settings");
|
||||||
|
|
||||||
|
// when
|
||||||
|
let loaded = ConfigLoader::new(&cwd, &home)
|
||||||
|
.load()
|
||||||
|
.expect("config should load");
|
||||||
|
|
||||||
|
// then
|
||||||
|
let roots = loaded.trusted_roots();
|
||||||
|
assert_eq!(roots, ["/tmp/worktrees", "/home/user/projects"]);
|
||||||
|
|
||||||
|
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trusted_roots_default_is_empty_when_unset() {
|
||||||
|
// given
|
||||||
|
let root = temp_dir();
|
||||||
|
let cwd = root.join("project");
|
||||||
|
let home = root.join("home").join(".claw");
|
||||||
|
fs::create_dir_all(&home).expect("home config dir");
|
||||||
|
fs::create_dir_all(&cwd).expect("project dir");
|
||||||
|
fs::write(home.join("settings.json"), "{}").expect("write empty settings");
|
||||||
|
|
||||||
|
// when
|
||||||
|
let loaded = ConfigLoader::new(&cwd, &home)
|
||||||
|
.load()
|
||||||
|
.expect("config should load");
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert!(loaded.trusted_roots().is_empty());
|
||||||
|
|
||||||
|
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_typed_mcp_and_oauth_config() {
|
fn parses_typed_mcp_and_oauth_config() {
|
||||||
let root = temp_dir();
|
let root = temp_dir();
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ const TOP_LEVEL_FIELDS: &[FieldSpec] = &[
|
|||||||
name: "providerFallbacks",
|
name: "providerFallbacks",
|
||||||
expected: FieldType::Object,
|
expected: FieldType::Object,
|
||||||
},
|
},
|
||||||
|
FieldSpec {
|
||||||
|
name: "trustedRoots",
|
||||||
|
expected: FieldType::StringArray,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const HOOKS_FIELDS: &[FieldSpec] = &[
|
const HOOKS_FIELDS: &[FieldSpec] = &[
|
||||||
|
|||||||
Reference in New Issue
Block a user