feat: add explicit Worker WIP mode

This commit is contained in:
2026-10-04 00:26:15 +09:00
parent 85a8944a6c
commit 4a05aad508
15 changed files with 2546 additions and 18 deletions
+1
View File
@@ -3,6 +3,7 @@ name = "manifest"
version = "0.1.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
arc-swap = "1"
+34 -1
View File
@@ -590,6 +590,8 @@ impl From<FeatureConfig> for FeatureConfigPartial {
pub struct WorkerMetaConfig {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub mode: Option<crate::WorkerMode>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -925,6 +927,7 @@ impl WorkerMetaConfig {
fn merge(self, upper: Self) -> Self {
Self {
name: upper.name.or(self.name),
mode: upper.mode.or(self.mode),
}
}
}
@@ -1356,7 +1359,10 @@ impl TryFrom<WorkerManifestConfig> for WorkerManifest {
validate_mcp_config(&cfg.mcp)?;
Ok(WorkerManifest {
worker: WorkerMeta { name },
worker: WorkerMeta {
name,
mode: cfg.worker.mode.unwrap_or_default(),
},
model: cfg.model,
engine,
scope: cfg.scope,
@@ -1391,6 +1397,7 @@ mod tests {
WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some("test".into()),
mode: None,
},
model: ModelManifest {
scheme: Some(SchemeKind::Anthropic),
@@ -1498,10 +1505,31 @@ mod tests {
fn resolve_minimal_succeeds() {
let manifest: WorkerManifest = minimal_valid().try_into().unwrap();
assert_eq!(manifest.worker.name, "test");
assert_eq!(manifest.worker.mode, crate::WorkerMode::Tools);
assert_eq!(manifest.model.scheme, Some(SchemeKind::Anthropic));
assert!(manifest.permissions.is_none());
}
#[test]
fn worker_mode_is_explicit_opt_in_and_persists_in_snapshots() {
let mut configured = minimal_valid();
configured.worker.mode = Some(crate::WorkerMode::Wip);
let manifest: WorkerManifest = configured.try_into().unwrap();
assert_eq!(manifest.worker.mode, crate::WorkerMode::Wip);
let snapshot = crate::write_persisted_worker_manifest_snapshot(&manifest).unwrap();
let restored = crate::read_persisted_worker_manifest_snapshot(snapshot.clone()).unwrap();
assert_eq!(restored.worker.mode, crate::WorkerMode::Wip);
let mut legacy = snapshot;
legacy["manifest"]["worker"]
.as_object_mut()
.unwrap()
.remove("mode");
let restored = crate::read_persisted_worker_manifest_snapshot(legacy).unwrap();
assert_eq!(restored.worker.mode, crate::WorkerMode::Tools);
}
#[test]
fn resolve_mcp_stdio_config_preserves_explicit_policy() {
let mut cfg = minimal_valid();
@@ -1784,6 +1812,7 @@ mod tests {
let lower = WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some("lower".into()),
mode: None,
},
model: ModelManifest {
model_id: Some("lower-model".into()),
@@ -1794,6 +1823,7 @@ mod tests {
let upper = WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some("upper".into()),
mode: None,
},
..Default::default()
};
@@ -2248,6 +2278,7 @@ enabled = false
.merge(WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some("feature-test".into()),
mode: None,
},
model: ModelManifest {
scheme: Some(SchemeKind::Anthropic),
@@ -2351,6 +2382,7 @@ enabled = true
.merge(WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some("feature-merge-test".into()),
mode: None,
},
model: ModelManifest {
scheme: Some(SchemeKind::Anthropic),
@@ -2428,6 +2460,7 @@ permission = "write"
let overlay = WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some("x".into()),
mode: None,
},
model: ModelManifest {
scheme: Some(SchemeKind::Anthropic),
+15
View File
@@ -670,6 +670,21 @@ pub struct WorkspaceMemorySettingsSnapshot {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerMeta {
pub name: String,
/// Model-facing capability surface. Existing manifests default to ordinary tools.
#[serde(default)]
pub mode: WorkerMode,
}
/// Model-facing Worker capability surface.
///
/// WIP is explicit opt-in: it replaces individual model-visible tools with the
/// discover/inspect/call Worldspace client while retaining the same host authority.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkerMode {
#[default]
Tools,
Wip,
}
/// When the engine may begin executing provider-confirmed tool calls.
+21 -2
View File
@@ -626,6 +626,7 @@ fn resolve_profile_value(
let config = WorkerManifestConfig {
worker: WorkerMetaConfig {
name: Some(worker_name),
mode: profile.worker.and_then(|worker| worker.mode),
},
model: profile.model.unwrap_or_default(),
engine: profile.engine.unwrap_or_default(),
@@ -668,6 +669,8 @@ struct ProfileConfig {
#[serde(default)]
description: Option<String>,
#[serde(default)]
worker: Option<ProfileWorkerConfig>,
#[serde(default)]
model: Option<ModelManifest>,
#[serde(default)]
engine: Option<EngineManifestConfig>,
@@ -691,6 +694,13 @@ struct ProfileConfig {
skills: Option<SkillsConfig>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct ProfileWorkerConfig {
#[serde(default)]
mode: Option<crate::WorkerMode>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum ProfileScopeConfig {
@@ -864,8 +874,15 @@ fn reject_manifest_shaped_profile(value: &serde_json::Value) -> Result<(), Profi
)));
}
}
if map.contains_key("worker") {
return Err(ProfileError::InvalidProfile("field `worker` is runtime-bound and is not allowed in reusable Profiles; pass the Worker name via CLI/TUI runtime inputs".into()));
if let Some(worker) = map.get("worker") {
let Some(worker) = worker.as_object() else {
return Err(ProfileError::InvalidProfile(
"field `worker` must be an object containing only reusable `mode` policy".into(),
));
};
if worker.contains_key("name") {
return Err(ProfileError::InvalidProfile("field `worker.name` is runtime-bound and is not allowed in reusable Profiles; pass the Worker name via CLI/TUI runtime inputs".into()));
}
}
if let Some(scope) = map.get("scope").and_then(|v| v.as_object()) {
for key in ["allow", "deny"] {
@@ -1691,6 +1708,7 @@ mod tests {
r#"
slug = "coder"
scope = "workspace_read"
worker = { mode = "wip" }
[model]
scheme = "anthropic"
@@ -1710,6 +1728,7 @@ reasoning = "high"
)
.unwrap();
assert_eq!(resolved.manifest.worker.name, "runtime-worker");
assert_eq!(resolved.manifest.worker.mode, crate::WorkerMode::Wip);
assert_eq!(resolved.manifest.model.scheme, Some(SchemeKind::Anthropic));
assert_eq!(
resolved.manifest.engine.reasoning,