Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/ov_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ pub mod skills;
pub mod snapshot;
pub mod system;
pub mod task;
pub mod user_settings;
pub mod watch;
40 changes: 40 additions & 0 deletions crates/ov_cli/src/commands/user_settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::client::HttpClient;
use crate::error::{Error, Result};
use crate::output::{OutputFormat, output_success};
use serde_json::{Map, Value};

pub async fn get_memory(
client: &HttpClient,
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
let response: Value = client.get("/api/v1/user-settings/memory", &[]).await?;
output_success(&response, output_format, compact);
Ok(())
}

pub async fn patch_memory(
client: &HttpClient,
agent_evolution_enabled: Option<bool>,
clear_agent_evolution_enabled: bool,
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
let mut body = Map::new();
if clear_agent_evolution_enabled {
body.insert("agent_evolution_enabled".into(), Value::Null);
} else if let Some(enabled) = agent_evolution_enabled {
body.insert("agent_evolution_enabled".into(), Value::Bool(enabled));
}
if body.is_empty() {
return Err(Error::Client(
"set-memory requires at least one setting or clear flag".to_string(),
));
}

let response: Value = client
.patch("/api/v1/user-settings/memory", &Value::Object(body), &[])
.await?;
output_success(&response, output_format, compact);
Ok(())
}
45 changes: 43 additions & 2 deletions crates/ov_cli/src/help_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,14 @@ const CONFIG_STATUS: &[HelpCommand] = help_commands![
];

const IMPORT_EXPORT_SESSIONS: &[HelpCommand] = help_commands![
"import", "export", "backup", "restore", "snapshot", "session", "privacy"
"import",
"export",
"backup",
"restore",
"snapshot",
"session",
"user-settings",
"privacy"
];

const INTERACTIVE_ADMIN: &[HelpCommand] = help_commands![
Expand Down Expand Up @@ -525,6 +532,24 @@ const COMMAND_HELP_SPECS: &[CommandHelpSpec] = &[
description: "Show exact arguments for a session operation.",
}],
},
CommandHelpSpec {
path: &["user-settings"],
purpose: "Manage current-user memory extraction and Agent Evolution settings.",
examples: &[
HelpItem {
label: "ov user-settings memory",
description: "Show user overrides and effective memory settings.",
},
HelpItem {
label: "ov user-settings set-memory --agent-evolution-enabled true",
description: "Enable future trajectory and experience production.",
},
],
next_steps: &[HelpItem {
label: "ov user-settings set-memory --help",
description: "Show Agent Evolution override options.",
}],
},
CommandHelpSpec {
path: &["snapshot"],
purpose: "Manage workspace snapshots: commit, restore, show, and walk history.",
Expand Down Expand Up @@ -2328,6 +2353,7 @@ fn localized_command_description<'a>(
"task" => "查看异步任务",
"observer" => "观察服务器组件",
"session" => "管理会话",
"user-settings" => "管理当前用户记忆配置",
"import" => "导入 .ovpack",
"export" => "导出为 .ovpack",
"backup" => "创建仅恢复备份",
Expand Down Expand Up @@ -2549,7 +2575,15 @@ fn version() -> String {
fn is_bare_group_help_command(command: &str) -> bool {
matches!(
command,
"task" | "skills" | "session" | "snapshot" | "privacy" | "admin" | "system" | "observer"
"task"
| "skills"
| "session"
| "user-settings"
| "snapshot"
| "privacy"
| "admin"
| "system"
| "observer"
)
}

Expand Down Expand Up @@ -3089,6 +3123,13 @@ mod tests {
assert!(session.contains("add-message <session-id>"));
assert!(session.contains("add-messages <session-id> <messages-json>"));

let user_settings = strip_ansi(
&render_command_help_request(&os_args(&["ov", "user-settings", "--help"]))
.expect("user settings help should render"),
);
assert!(user_settings.contains("memory"));
assert!(user_settings.contains("set-memory"));

let watch = strip_ansi(
&render_command_help_request(&os_args(&["ov", "task", "watch", "--help"]))
.expect("watch help should render"),
Expand Down
111 changes: 106 additions & 5 deletions crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,11 @@ enum Commands {
#[command(subcommand)]
action: SessionCommands,
},
/// [Data] Manage current-user memory and Agent Evolution settings
UserSettings {
#[command(subcommand)]
action: UserSettingsCommands,
},
/// [Experimental][Data] Add memory in one shot (creates session, adds messages, commits)
AddMemory {
/// Content to memorize. Plain string (treated as user message),
Expand Down Expand Up @@ -1559,6 +1564,21 @@ enum PrivacyCommands {
},
}

#[derive(Subcommand)]
enum UserSettingsCommands {
/// Show current-user memory setting overrides and effective values
Memory,
/// Partially update current-user memory settings
SetMemory {
/// Enable or disable Agent Evolution memory production
#[arg(long, conflicts_with = "clear_agent_evolution_enabled")]
agent_evolution_enabled: Option<bool>,
/// Clear the user Agent Evolution override
#[arg(long, conflicts_with = "agent_evolution_enabled")]
clear_agent_evolution_enabled: bool,
},
}

#[derive(Subcommand)]
enum AdminCommands {
/// Create a new account with its first admin user
Expand Down Expand Up @@ -2028,7 +2048,15 @@ fn allows_plain_help_as_user_input(command: &str) -> bool {
fn is_plain_help_group(command: &str) -> bool {
matches!(
command,
"config" | "task" | "admin" | "system" | "session" | "privacy" | "observer" | "skills"
"config"
| "task"
| "admin"
| "system"
| "session"
| "user-settings"
| "privacy"
| "observer"
| "skills"
)
}

Expand Down Expand Up @@ -2205,6 +2233,7 @@ fn is_top_level_server_command(command: &str) -> bool {
| "grep"
| "glob"
| "add-memory"
| "user-settings"
| "relations"
| "link"
| "unlink"
Expand Down Expand Up @@ -2990,6 +3019,28 @@ async fn main() {
Commands::System { action } => handlers::handle_system(action, ctx).await,
Commands::Observer { action } => handlers::handle_observer(action, ctx).await,
Commands::Session { action } => handlers::handle_session(action, ctx).await,
Commands::UserSettings { action } => {
let client = ctx.get_client();
match action {
UserSettingsCommands::Memory => {
commands::user_settings::get_memory(&client, ctx.output_format, ctx.compact)
.await
}
UserSettingsCommands::SetMemory {
agent_evolution_enabled,
clear_agent_evolution_enabled,
} => {
commands::user_settings::patch_memory(
&client,
agent_evolution_enabled,
clear_agent_evolution_enabled,
ctx.output_format,
ctx.compact,
)
.await
}
}
}
Commands::Admin { action } => handlers::handle_admin(action, ctx).await,
Commands::Privacy { action } => handlers::handle_privacy(action, ctx).await,
Commands::Ls {
Expand Down Expand Up @@ -3217,10 +3268,11 @@ async fn main() {
mod tests {
use super::{
Cli, CliContext, Commands, ConfigAddTarget, ConfigCommands, LanguageGateAction,
PrivacyCommands, SkillCommands, UploadCliOptions, find_command_index, first_command_token,
is_language_command_request, language_command_can_run_picker, language_gate_action,
language_required_message, legacy_upload_option_error, plain_help_misuse,
pre_parse_requires_cli_config_file, preprocess_cli_args, preprocess_privacy_args,
PrivacyCommands, SkillCommands, UploadCliOptions, UserSettingsCommands, find_command_index,
first_command_token, is_language_command_request, language_command_can_run_picker,
language_gate_action, language_required_message, legacy_upload_option_error,
plain_help_misuse, pre_parse_requires_cli_config_file, preprocess_cli_args,
preprocess_privacy_args,
};
use crate::config::{Config, DEFAULT_CUSTOM_URL};
use crate::output::OutputFormat;
Expand Down Expand Up @@ -3307,6 +3359,55 @@ mod tests {
}
}

#[test]
fn cli_parses_user_memory_settings_commands() {
assert!(pre_parse_requires_cli_config_file(&os_args(&[
"ov",
"user-settings",
"memory",
])));
let get = Cli::try_parse_from(["ov", "user-settings", "memory"])
.expect("user memory settings get should parse");
assert!(matches!(
get.command,
Commands::UserSettings {
action: UserSettingsCommands::Memory
}
));

let set = Cli::try_parse_from([
"ov",
"user-settings",
"set-memory",
"--agent-evolution-enabled",
"true",
])
.expect("user memory settings patch should parse");
match set.command {
Commands::UserSettings {
action:
UserSettingsCommands::SetMemory {
agent_evolution_enabled,
..
},
} => {
assert_eq!(agent_evolution_enabled, Some(true));
}
_ => panic!("expected user-settings set-memory command"),
}

assert!(
Cli::try_parse_from([
"ov",
"user-settings",
"set-memory",
"--memory-types",
"profile,events",
])
.is_err()
);
}

#[test]
fn cli_parses_search_context_type() {
let cli = Cli::try_parse_from(["ov", "search", "invoice", "--context-type", "skill"])
Expand Down
Loading