mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat(profiles): top-level agent profiles — soul, memory, skills, MCP, connectors (#3632)
This commit is contained in:
@@ -451,11 +451,11 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
|
||||
let profiles_initial = rpc(
|
||||
&harness.rpc_base,
|
||||
31_001,
|
||||
"openhuman.agent_profiles_list",
|
||||
"openhuman.profiles_list",
|
||||
json!({}),
|
||||
)
|
||||
.await;
|
||||
let initial = ok(&profiles_initial, "agent_profiles_list initial");
|
||||
let initial = ok(&profiles_initial, "profiles_list initial");
|
||||
assert_eq!(
|
||||
initial.get("activeProfileId").and_then(Value::as_str),
|
||||
Some("default")
|
||||
@@ -464,7 +464,7 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
|
||||
let upsert_profile = rpc(
|
||||
&harness.rpc_base,
|
||||
31_002,
|
||||
"openhuman.agent_profile_upsert",
|
||||
"openhuman.profiles_upsert",
|
||||
json!({
|
||||
"profile": {
|
||||
"id": "E2E Planner",
|
||||
@@ -480,7 +480,7 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let upserted = ok(&upsert_profile, "agent_profile_upsert");
|
||||
let upserted = ok(&upsert_profile, "profiles_upsert");
|
||||
assert!(
|
||||
upserted
|
||||
.get("profiles")
|
||||
@@ -494,12 +494,12 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
|
||||
let select_profile = rpc(
|
||||
&harness.rpc_base,
|
||||
31_003,
|
||||
"openhuman.agent_profile_select",
|
||||
"openhuman.profiles_select",
|
||||
json!({ "profile_id": "e2e-planner" }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
ok(&select_profile, "agent_profile_select")
|
||||
ok(&select_profile, "profiles_select")
|
||||
.get("activeProfileId")
|
||||
.and_then(Value::as_str),
|
||||
Some("e2e-planner")
|
||||
@@ -508,11 +508,11 @@ async fn config_agent_tools_and_threads_mutation_paths_round_trip() {
|
||||
let delete_profile = rpc(
|
||||
&harness.rpc_base,
|
||||
31_004,
|
||||
"openhuman.agent_profile_delete",
|
||||
"openhuman.profiles_delete",
|
||||
json!({ "profile_id": "e2e-planner" }),
|
||||
)
|
||||
.await;
|
||||
let deleted = ok(&delete_profile, "agent_profile_delete");
|
||||
let deleted = ok(&delete_profile, "profiles_delete");
|
||||
assert!(
|
||||
deleted
|
||||
.get("profiles")
|
||||
|
||||
@@ -59,18 +59,10 @@ use openhuman_core::openhuman::agent::multimodal::{
|
||||
contains_image_markers, count_image_markers, extract_ollama_image_payload, parse_image_markers,
|
||||
prepare_messages_for_provider, MultimodalError,
|
||||
};
|
||||
use openhuman_core::openhuman::agent::personality_paths::{
|
||||
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
|
||||
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
|
||||
HasToolkit, PersonalityContext,
|
||||
};
|
||||
use openhuman_core::openhuman::agent::pformat::{
|
||||
build_registry, parse_call as parse_pformat_call, render_signature, render_signature_from_tool,
|
||||
PFormatParamType, PFormatRegistry, PFormatToolParams,
|
||||
};
|
||||
use openhuman_core::openhuman::agent::profiles::{
|
||||
AgentProfile, AgentProfileStore, AgentProfilesState, DEFAULT_PROFILE_ID,
|
||||
};
|
||||
use openhuman_core::openhuman::agent::prompts::{
|
||||
render_ambient_environment, render_subagent_system_prompt, render_tools, ConnectedIntegration,
|
||||
GatedIntegrationTool, LearnedContextData, NamespaceSummary, PersonalityRosterEntry,
|
||||
@@ -176,6 +168,17 @@ use openhuman_core::openhuman::inference::{
|
||||
DeviceProfile,
|
||||
};
|
||||
use openhuman_core::openhuman::memory::{Memory, MemoryCategory, MemoryEntry, RecallOpts};
|
||||
use openhuman_core::openhuman::profiles::{
|
||||
all_profiles_controller_schemas, all_profiles_registered_controllers,
|
||||
};
|
||||
use openhuman_core::openhuman::profiles::{
|
||||
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
|
||||
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
|
||||
HasToolkit, PersonalityContext,
|
||||
};
|
||||
use openhuman_core::openhuman::profiles::{
|
||||
AgentProfile, AgentProfileStore, AgentProfilesState, DEFAULT_PROFILE_ID,
|
||||
};
|
||||
use openhuman_core::openhuman::security::SecurityPolicy;
|
||||
use openhuman_core::openhuman::todos::ops::BoardLocation;
|
||||
use openhuman_core::openhuman::tools::{Tool, ToolResult, ToolSpec};
|
||||
@@ -1116,6 +1119,14 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
.iter()
|
||||
.all(|controller| controller.rpc_method_name().starts_with("openhuman.agent_")));
|
||||
|
||||
// Profiles moved to their own top-level domain (`openhuman.profiles_*`).
|
||||
let profile_schemas = all_profiles_controller_schemas();
|
||||
let profiles = all_profiles_registered_controllers();
|
||||
assert_eq!(profile_schemas.len(), profiles.len());
|
||||
assert!(profiles.iter().all(|controller| controller
|
||||
.rpc_method_name()
|
||||
.starts_with("openhuman.profiles_")));
|
||||
|
||||
let status = call(controller(®istered, "server_status"), json!({}))
|
||||
.await
|
||||
.expect("server status");
|
||||
@@ -1155,7 +1166,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
assert_eq!(reload.pointer("/status"), Some(&json!("noop")));
|
||||
assert_eq!(reload.pointer("/registry_initialised"), Some(&json!(true)));
|
||||
|
||||
let list = call(controller(®istered, "profiles_list"), json!({}))
|
||||
let list = call(controller(&profiles, "list"), json!({}))
|
||||
.await
|
||||
.expect("profiles list");
|
||||
assert_eq!(
|
||||
@@ -1170,7 +1181,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
.any(|profile| profile.pointer("/id") == Some(&json!("research"))));
|
||||
|
||||
let unknown_agent = call(
|
||||
controller(®istered, "profile_upsert"),
|
||||
controller(&profiles, "upsert"),
|
||||
json!({
|
||||
"profile": {
|
||||
"id": "Bad Agent",
|
||||
@@ -1185,7 +1196,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
assert!(unknown_agent.contains("agent definition 'unknown-agent-id' not found"));
|
||||
|
||||
let upserted = call(
|
||||
controller(®istered, "profile_upsert"),
|
||||
controller(&profiles, "upsert"),
|
||||
json!({
|
||||
"profile": {
|
||||
"id": " My Research Profile ",
|
||||
@@ -1220,7 +1231,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
);
|
||||
|
||||
let selected = call(
|
||||
controller(®istered, "profile_select"),
|
||||
controller(&profiles, "select"),
|
||||
json!({ "profile_id": "my-research-profile" }),
|
||||
)
|
||||
.await
|
||||
@@ -1231,7 +1242,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
);
|
||||
|
||||
let missing_select = call(
|
||||
controller(®istered, "profile_select"),
|
||||
controller(&profiles, "select"),
|
||||
json!({ "profile_id": "missing-profile" }),
|
||||
)
|
||||
.await
|
||||
@@ -1239,7 +1250,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
assert!(missing_select.contains("agent profile 'missing-profile' not found"));
|
||||
|
||||
let delete_builtin = call(
|
||||
controller(®istered, "profile_delete"),
|
||||
controller(&profiles, "delete"),
|
||||
json!({ "profile_id": DEFAULT_PROFILE_ID }),
|
||||
)
|
||||
.await
|
||||
@@ -1247,7 +1258,7 @@ async fn agent_registry_and_profile_controllers_cover_success_and_errors() {
|
||||
assert!(delete_builtin.contains("built-in agent profile"));
|
||||
|
||||
let deleted = call(
|
||||
controller(®istered, "profile_delete"),
|
||||
controller(&profiles, "delete"),
|
||||
json!({ "profile_id": "my-research-profile" }),
|
||||
)
|
||||
.await
|
||||
@@ -1359,6 +1370,10 @@ fn agent_profile_store_and_personality_helpers_cover_normalisation_edges() {
|
||||
soul_md: Some(" inline soul ".to_string()),
|
||||
soul_md_path: None,
|
||||
composio_integrations: Some(vec![" gmail ".to_string(), String::new()]),
|
||||
memory_sources: None,
|
||||
include_agent_conversations: true,
|
||||
allowed_skills: None,
|
||||
allowed_mcp_servers: None,
|
||||
memory_dir_suffix: None,
|
||||
is_master: true,
|
||||
sort_order: Some(50),
|
||||
@@ -1393,6 +1408,10 @@ fn agent_profile_store_and_personality_helpers_cover_normalisation_edges() {
|
||||
soul_md: None,
|
||||
soul_md_path: None,
|
||||
composio_integrations: Some(vec![]),
|
||||
memory_sources: None,
|
||||
include_agent_conversations: true,
|
||||
allowed_skills: None,
|
||||
allowed_mcp_servers: None,
|
||||
memory_dir_suffix: None,
|
||||
is_master: false,
|
||||
sort_order: None,
|
||||
@@ -1410,6 +1429,10 @@ fn agent_profile_store_and_personality_helpers_cover_normalisation_edges() {
|
||||
|
||||
let reused = store
|
||||
.upsert(AgentProfile {
|
||||
memory_sources: None,
|
||||
include_agent_conversations: true,
|
||||
allowed_skills: None,
|
||||
allowed_mcp_servers: None,
|
||||
memory_dir_suffix: None,
|
||||
description: "updated".to_string(),
|
||||
..second_profile.clone()
|
||||
@@ -1719,6 +1742,10 @@ fn agent_personality_paths_cover_safe_fallbacks_and_integration_filters() {
|
||||
soul_md: Some("inline soul".into()),
|
||||
soul_md_path: Some("personality-soul.md".into()),
|
||||
composio_integrations: Some(vec!["gmail".into(), "slack".into()]),
|
||||
memory_sources: None,
|
||||
include_agent_conversations: true,
|
||||
allowed_skills: None,
|
||||
allowed_mcp_servers: None,
|
||||
memory_dir_suffix: Some("-7".into()),
|
||||
is_master: false,
|
||||
sort_order: Some(10),
|
||||
@@ -3471,7 +3498,7 @@ async fn agent_public_tools_cover_validation_and_metadata_paths() {
|
||||
assert!(clarification.output().contains("Which target?"));
|
||||
assert!(clarification.output().contains("unit, coverage"));
|
||||
|
||||
let run_workflow = RunWorkflowTool;
|
||||
let run_workflow = RunWorkflowTool::new();
|
||||
assert_eq!(run_workflow.name(), RUN_WORKFLOW_TOOL_NAME);
|
||||
assert_eq!(
|
||||
run_workflow.parameters_schema().pointer("/required/0"),
|
||||
|
||||
@@ -18,14 +18,6 @@ use std::sync::Arc;
|
||||
use serde_json::json;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use openhuman_core::openhuman::agent::personality_paths::{
|
||||
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
|
||||
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
|
||||
HasToolkit, PersonalityContext,
|
||||
};
|
||||
use openhuman_core::openhuman::agent::profiles::{
|
||||
built_in_profiles, AgentProfile, AgentProfileStore, DEFAULT_PROFILE_ID,
|
||||
};
|
||||
use openhuman_core::openhuman::agent::prompts::types::LearnedContextData;
|
||||
use openhuman_core::openhuman::agent::prompts::{
|
||||
IdentitySection, PersonalityRosterEntry, PersonalityRosterSection, PromptContext,
|
||||
@@ -36,6 +28,14 @@ use openhuman_core::openhuman::memory::{NamespaceDocumentInput, UnifiedMemory};
|
||||
use openhuman_core::openhuman::memory_conversations::{
|
||||
ensure_thread, list_threads, update_thread_title, ConversationStore, CreateConversationThread,
|
||||
};
|
||||
use openhuman_core::openhuman::profiles::{
|
||||
built_in_profiles, AgentProfile, AgentProfileStore, DEFAULT_PROFILE_ID,
|
||||
};
|
||||
use openhuman_core::openhuman::profiles::{
|
||||
filter_integrations, memory_subdir_for_suffix, memory_tree_subdir_for_suffix,
|
||||
resolve_personality_memory_md, resolve_personality_soul, session_raw_subdir_for_suffix,
|
||||
HasToolkit, PersonalityContext,
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Test helpers
|
||||
@@ -57,6 +57,10 @@ fn make_profile(id: &str, name: &str) -> AgentProfile {
|
||||
soul_md: None,
|
||||
soul_md_path: None,
|
||||
composio_integrations: None,
|
||||
memory_sources: None,
|
||||
include_agent_conversations: true,
|
||||
allowed_skills: None,
|
||||
allowed_mcp_servers: None,
|
||||
memory_dir_suffix: None,
|
||||
is_master: false,
|
||||
sort_order: None,
|
||||
|
||||
@@ -257,10 +257,10 @@ async fn worker_b_schema_catalog_exposes_all_controller_methods() {
|
||||
"openhuman.agent_get_definition",
|
||||
"openhuman.agent_reload_definitions",
|
||||
"openhuman.agent_triage_evaluate",
|
||||
"openhuman.agent_profiles_list",
|
||||
"openhuman.agent_profile_select",
|
||||
"openhuman.agent_profile_upsert",
|
||||
"openhuman.agent_profile_delete",
|
||||
"openhuman.profiles_list",
|
||||
"openhuman.profiles_select",
|
||||
"openhuman.profiles_upsert",
|
||||
"openhuman.profiles_delete",
|
||||
"openhuman.tools_composio_execute",
|
||||
"openhuman.tools_web_search",
|
||||
"openhuman.tools_seltz_search",
|
||||
@@ -487,7 +487,7 @@ async fn agent_definitions_profiles_and_validation_paths_are_reachable() {
|
||||
"not found",
|
||||
),
|
||||
(
|
||||
"openhuman.agent_profile_upsert",
|
||||
"openhuman.profiles_upsert",
|
||||
json!({
|
||||
"profile": {
|
||||
"id": "bad-worker-b-profile",
|
||||
@@ -501,7 +501,7 @@ async fn agent_definitions_profiles_and_validation_paths_are_reachable() {
|
||||
"not found",
|
||||
),
|
||||
(
|
||||
"openhuman.agent_profile_select",
|
||||
"openhuman.profiles_select",
|
||||
json!({ "profile_id": "missing-worker-b-profile" }),
|
||||
"not found",
|
||||
),
|
||||
@@ -539,12 +539,12 @@ async fn agent_definitions_profiles_and_validation_paths_are_reachable() {
|
||||
let profiles = rpc(
|
||||
&harness.rpc_base,
|
||||
20_200,
|
||||
"openhuman.agent_profiles_list",
|
||||
"openhuman.profiles_list",
|
||||
json!({}),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
ok(&profiles, "agent_profiles_list")
|
||||
ok(&profiles, "profiles_list")
|
||||
.get("activeProfileId")
|
||||
.and_then(Value::as_str),
|
||||
Some("default")
|
||||
|
||||
@@ -496,7 +496,7 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
|
||||
let upsert = rpc(
|
||||
&harness.rpc_base,
|
||||
301,
|
||||
"openhuman.agent_profile_upsert",
|
||||
"openhuman.profiles_upsert",
|
||||
json!({
|
||||
"profile": {
|
||||
"id": "worker-b-custom",
|
||||
@@ -517,7 +517,7 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let profiles = ok(&upsert, "agent_profile_upsert")
|
||||
let profiles = ok(&upsert, "profiles_upsert")
|
||||
.get("profiles")
|
||||
.and_then(Value::as_array)
|
||||
.expect("profiles after upsert");
|
||||
@@ -534,12 +534,12 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
|
||||
let select = rpc(
|
||||
&harness.rpc_base,
|
||||
302,
|
||||
"openhuman.agent_profile_select",
|
||||
"openhuman.profiles_select",
|
||||
json!({ "profile_id": "worker-b-custom" }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
ok(&select, "agent_profile_select")
|
||||
ok(&select, "profiles_select")
|
||||
.get("activeProfileId")
|
||||
.and_then(Value::as_str),
|
||||
Some("worker-b-custom")
|
||||
@@ -548,7 +548,7 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
|
||||
let delete_default = rpc(
|
||||
&harness.rpc_base,
|
||||
303,
|
||||
"openhuman.agent_profile_delete",
|
||||
"openhuman.profiles_delete",
|
||||
json!({ "profile_id": "default" }),
|
||||
)
|
||||
.await;
|
||||
@@ -560,12 +560,12 @@ async fn agent_profile_lifecycle_persists_custom_profile_and_validates_delete()
|
||||
let delete_custom = rpc(
|
||||
&harness.rpc_base,
|
||||
304,
|
||||
"openhuman.agent_profile_delete",
|
||||
"openhuman.profiles_delete",
|
||||
json!({ "profile_id": "worker-b-custom" }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
ok(&delete_custom, "agent_profile_delete")
|
||||
ok(&delete_custom, "profiles_delete")
|
||||
.get("activeProfileId")
|
||||
.and_then(Value::as_str),
|
||||
Some("default"),
|
||||
|
||||
Reference in New Issue
Block a user