fix: inherit parent profile for default subworkers
This commit is contained in:
@@ -45,9 +45,9 @@ use protocol::Method;
|
||||
struct SubWorkerSpawnInput {
|
||||
/// Identifier for the spawned Internal SubWorker. Must be unique among this Worker's direct children.
|
||||
name: String,
|
||||
/// Profile selector for child role configuration. Omit or use `default`
|
||||
/// for the effective child default profile, use `inherit` to derive
|
||||
/// reusable config from the spawner, or use a registry selector such as
|
||||
/// Profile selector for child role configuration. Omit or use `inherit`
|
||||
/// to derive reusable config from the spawner, use `default` for the
|
||||
/// effective registry default profile, or use a registry selector such as
|
||||
/// `project:coder`, `project:reviewer`, `builtin:companion`, or an
|
||||
/// unambiguous profile slug. Raw/path selectors are rejected.
|
||||
#[serde(default)]
|
||||
@@ -204,7 +204,7 @@ enum SpawnProfileSelector {
|
||||
|
||||
fn parse_spawn_profile_selector(raw: Option<&str>) -> Result<SpawnProfileSelector, String> {
|
||||
let Some(raw) = raw.map(str::trim).filter(|s| !s.is_empty()) else {
|
||||
return Ok(SpawnProfileSelector::Default);
|
||||
return Ok(SpawnProfileSelector::Inherit);
|
||||
};
|
||||
if raw == "default" {
|
||||
return Ok(SpawnProfileSelector::Default);
|
||||
@@ -2298,7 +2298,7 @@ max_tokens = 3333
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omitted_profile_resolves_effective_registry_default() {
|
||||
fn explicit_default_profile_resolves_effective_registry_default() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let project = tmp.path().join("project");
|
||||
let delegated = tmp.path().join("delegated");
|
||||
@@ -2322,7 +2322,7 @@ max_tokens = 3333
|
||||
"child-default",
|
||||
None,
|
||||
&scope,
|
||||
SpawnProfileSelector::Default,
|
||||
parse_spawn_profile_selector(Some("default")).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(config.worker.name.as_deref(), Some("child-default"));
|
||||
@@ -2373,6 +2373,44 @@ max_tokens = 3333
|
||||
assert!(config.scope.deny.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omitted_profile_inherits_parent_ticket_features_instead_of_registry_default() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let mut parent = parent_manifest(tmp.path(), None);
|
||||
let scope = vec![abs_rule(tmp.path(), Permission::Read)];
|
||||
let available = AvailableProfiles {
|
||||
registry: Some(
|
||||
ProfileDiscovery::with_sources(None, None)
|
||||
.discover()
|
||||
.unwrap(),
|
||||
),
|
||||
diagnostic: None,
|
||||
};
|
||||
let input: SubWorkerSpawnInput = serde_json::from_value(serde_json::json!({
|
||||
"name": "child", "task": "inspect", "scope": []
|
||||
}))
|
||||
.unwrap();
|
||||
for enabled in [false, true] {
|
||||
parent.feature.ticket.enabled = enabled;
|
||||
parent.feature.ticket.thread = enabled;
|
||||
let config = child_config_from_profile(
|
||||
&parent,
|
||||
&available,
|
||||
tmp.path(),
|
||||
"child",
|
||||
None,
|
||||
&scope,
|
||||
parse_spawn_profile_selector(input.profile.as_deref()).unwrap(),
|
||||
);
|
||||
assert_eq!(config.model.model_id.as_deref(), Some("parent-model"));
|
||||
assert_eq!(config.engine.language.as_deref(), Some("Parentish"));
|
||||
let ticket = config.feature.ticket.unwrap();
|
||||
assert_eq!(ticket.enabled, Some(enabled));
|
||||
assert_eq!(ticket.thread, Some(enabled));
|
||||
assert_eq!(config.scope.allow, scope);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inherit_copies_reusable_parent_fields_and_replaces_runtime_authority() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
@@ -2581,14 +2619,16 @@ max_tokens = 3333
|
||||
|
||||
#[test]
|
||||
fn spawn_profile_selector_accepts_default_inherit_and_registry() {
|
||||
for raw in [None, Some(""), Some(" "), Some("inherit")] {
|
||||
assert_eq!(
|
||||
parse_spawn_profile_selector(raw).unwrap(),
|
||||
SpawnProfileSelector::Inherit
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
parse_spawn_profile_selector(None).unwrap(),
|
||||
parse_spawn_profile_selector(Some("default")).unwrap(),
|
||||
SpawnProfileSelector::Default
|
||||
);
|
||||
assert_eq!(
|
||||
parse_spawn_profile_selector(Some("inherit")).unwrap(),
|
||||
SpawnProfileSelector::Inherit
|
||||
);
|
||||
assert_eq!(
|
||||
parse_spawn_profile_selector(Some("project:reviewer")).unwrap(),
|
||||
SpawnProfileSelector::Registry(ProfileSelector::source_named(
|
||||
|
||||
@@ -2,9 +2,10 @@ Spawn a parent-owned Internal SubWorker session to split context for a delegated
|
||||
|
||||
Optional `cwd`: when provided, the spawned SubWorker's tool default working directory only. It must be a Workdir-relative existing directory covered by the child's readable scope, and it does not change workspace/Profile/memory/Ticket roots or grant authority. `name` must be unique among this Worker's direct children.
|
||||
|
||||
Profile selection: `profile` may be omitted or set to `default` to use the effective child default profile, set to `inherit` to derive reusable child configuration from this Worker, or set to one of the registry selectors below. Raw/path profile selectors are not accepted by SubWorkerSpawn. `scope` is the child's only filesystem capability and replaces profile scope. `command` is a separate explicit grant, defaults to false, and is accepted only with a writable scope; writable scope alone does not grant command execution.
|
||||
Profile selection: omit `profile` or set it to `inherit` to derive reusable child configuration from this Worker. Set it to `default` to use the effective registry default profile, or to one of the registry selectors below. Raw/path profile selectors are not accepted by SubWorkerSpawn. `scope` is the child's only filesystem capability and replaces profile scope. `command` is a separate explicit grant, defaults to false, and is accepted only with a writable scope; writable scope alone does not grant command execution.
|
||||
|
||||
Default profile: {{ default_profile }}
|
||||
Default when omitted: inherit.
|
||||
Registry default (`profile = "default"`): {{ default_profile }}
|
||||
Special selector: inherit — derive reusable model/worker/tool policy from the spawner while replacing worker.name and scope.
|
||||
Available registry profiles:
|
||||
{{ available_profiles }}{% if profile_diagnostic %}
|
||||
|
||||
Reference in New Issue
Block a user