memory: gate staging tools by profile

This commit is contained in:
2026-07-25 10:56:27 +09:00
parent a44673fa6b
commit e997aa2fc6
7 changed files with 194 additions and 20 deletions
+46 -6
View File
@@ -18,9 +18,9 @@ use crate::model::{AuthRef, ModelManifest, ReasoningControl};
use crate::plugin::PluginConfig;
use crate::{
CompactionConfig, EngineManifest, FeatureConfig, FeatureFlagConfig, FileUploadLimits,
McpConfig, McpEnvValue, McpStdioCwdPolicy, MemoryConfig, ScopeConfig, SessionConfig,
SkillsConfig, TicketFeatureConfig, ToolOutputLimits, ToolPermissionConfig, ToolPermissionRule,
WebConfig, WorkerManifest, WorkerMeta,
McpConfig, McpEnvValue, McpStdioCwdPolicy, MemoryConfig, MemoryFeatureConfig, ScopeConfig,
SessionConfig, SkillsConfig, TicketFeatureConfig, ToolOutputLimits, ToolPermissionConfig,
ToolPermissionRule, WebConfig, WorkerManifest, WorkerMeta,
};
/// Partial-form Worker manifest. Every field is optional; one or more
@@ -79,7 +79,7 @@ pub struct FeatureConfigPartial {
#[serde(default)]
pub task: Option<FeatureFlagConfigPartial>,
#[serde(default)]
pub memory: Option<FeatureFlagConfigPartial>,
pub memory: Option<MemoryFeatureConfigPartial>,
#[serde(default)]
pub web: Option<FeatureFlagConfigPartial>,
#[serde(default)]
@@ -94,7 +94,7 @@ impl FeatureConfigPartial {
fn merge(self, other: Self) -> Self {
Self {
task: merge_option(self.task, other.task, FeatureFlagConfigPartial::merge),
memory: merge_option(self.memory, other.memory, FeatureFlagConfigPartial::merge),
memory: merge_option(self.memory, other.memory, MemoryFeatureConfigPartial::merge),
web: merge_option(self.web, other.web, FeatureFlagConfigPartial::merge),
workers: merge_option(self.workers, other.workers, FeatureFlagConfigPartial::merge),
ticket: merge_option(self.ticket, other.ticket, TicketFeatureConfigPartial::merge),
@@ -117,6 +117,23 @@ impl FeatureFlagConfigPartial {
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MemoryFeatureConfigPartial {
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub staging: Option<bool>,
}
impl MemoryFeatureConfigPartial {
fn merge(self, other: Self) -> Self {
Self {
enabled: other.enabled.or(self.enabled),
staging: other.staging.or(self.staging),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct TicketFeatureConfigPartial {
@@ -145,7 +162,7 @@ impl From<FeatureConfigPartial> for FeatureConfig {
task: value.task.map(FeatureFlagConfig::from).unwrap_or_default(),
memory: value
.memory
.map(FeatureFlagConfig::from)
.map(MemoryFeatureConfig::from)
.unwrap_or_default(),
web: value.web.map(FeatureFlagConfig::from).unwrap_or_default(),
workers: value
@@ -180,6 +197,24 @@ impl From<FeatureFlagConfig> for FeatureFlagConfigPartial {
}
}
impl From<MemoryFeatureConfigPartial> for MemoryFeatureConfig {
fn from(value: MemoryFeatureConfigPartial) -> Self {
Self {
enabled: value.enabled.unwrap_or_default(),
staging: value.staging.unwrap_or_default(),
}
}
}
impl From<MemoryFeatureConfig> for MemoryFeatureConfigPartial {
fn from(value: MemoryFeatureConfig) -> Self {
Self {
enabled: Some(value.enabled),
staging: Some(value.staging),
}
}
}
impl From<TicketFeatureConfigPartial> for TicketFeatureConfig {
fn from(value: TicketFeatureConfigPartial) -> Self {
Self {
@@ -1806,6 +1841,7 @@ orchestration_control = false
assert!(!manifest.feature.ticket.intake);
assert!(!manifest.feature.ticket.orchestration_control);
assert!(!manifest.feature.memory.enabled);
assert!(!manifest.feature.memory.staging);
}
#[test]
@@ -1830,6 +1866,9 @@ orchestration_control = false
thread = true
orchestration_control = true
[feature.memory]
staging = true
[feature.web]
enabled = true
"#,
@@ -1861,6 +1900,7 @@ enabled = true
.try_into()
.unwrap();
assert!(manifest.feature.memory.enabled);
assert!(manifest.feature.memory.staging);
assert!(manifest.feature.ticket.enabled);
assert!(!manifest.feature.ticket.authoring);
assert!(manifest.feature.ticket.thread);
+33 -2
View File
@@ -107,7 +107,7 @@ pub struct FeatureConfig {
#[serde(default)]
pub task: FeatureFlagConfig,
#[serde(default)]
pub memory: FeatureFlagConfig,
pub memory: MemoryFeatureConfig,
#[serde(default)]
pub web: FeatureFlagConfig,
#[serde(default)]
@@ -122,7 +122,7 @@ impl Default for FeatureConfig {
fn default() -> Self {
Self {
task: FeatureFlagConfig::disabled(),
memory: FeatureFlagConfig::disabled(),
memory: MemoryFeatureConfig::disabled(),
web: FeatureFlagConfig::disabled(),
workers: FeatureFlagConfig::disabled(),
ticket: TicketFeatureConfig::default(),
@@ -153,6 +153,37 @@ impl Default for FeatureFlagConfig {
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct MemoryFeatureConfig {
#[serde(default)]
pub enabled: bool,
/// Exposes Memory staging queue tools in addition to normal Memory CRUD/query tools.
#[serde(default)]
pub staging: bool,
}
impl MemoryFeatureConfig {
pub const fn disabled() -> Self {
Self {
enabled: false,
staging: false,
}
}
pub const fn enabled() -> Self {
Self {
enabled: true,
staging: false,
}
}
}
impl Default for MemoryFeatureConfig {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct TicketFeatureConfig {
#[serde(default)]
+34
View File
@@ -954,6 +954,17 @@ fn builtin_profile_artifact(label: &str) -> Option<serde_json::Value> {
);
Some(value)
}
"builtin:memory-consolidation" | "memory-consolidation" => {
value["slug"] = serde_json::Value::String("memory-consolidation".to_string());
value["description"] =
serde_json::Value::String("Memory staging consolidation profile.".to_string());
value["feature"]["task"] = serde_json::json!({ "enabled": false });
value["feature"]["memory"] = serde_json::json!({ "enabled": true, "staging": true });
value["feature"]["web"] = serde_json::json!({ "enabled": false });
value["feature"]["workers"] = serde_json::json!({ "enabled": false });
value["feature"]["ticket"] = serde_json::json!({ "enabled": false, "thread": false });
Some(value)
}
_ => None,
}
}
@@ -1425,6 +1436,29 @@ mod tests {
}
}
#[test]
fn builtin_memory_consolidation_profile_enables_staging_by_feature() {
let tmp = TempDir::new().unwrap();
let resolved = ProfileResolver::new()
.with_workspace_base(tmp.path())
.resolve(
&ProfileSelector::source_named(
ProfileRegistrySource::Builtin,
"memory-consolidation",
),
ProfileResolveOptions::with_worker_name("arbitrary-worker-name"),
)
.unwrap();
assert_eq!(
resolved.profile.as_ref().unwrap().name.as_deref(),
Some("memory-consolidation")
);
assert_eq!(resolved.manifest.worker.name, "arbitrary-worker-name");
assert!(resolved.manifest.feature.memory.enabled);
assert!(resolved.manifest.feature.memory.staging);
}
#[test]
fn builtin_role_profiles_preserve_role_tool_policy() {
let tmp = TempDir::new().unwrap();