Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
72e1df6
feat(settings): define app operations model contract
Jul 22, 2026
2bef561
feat(settings): persist app operations model
Jul 22, 2026
69ff85c
fix(settings): return persisted app operations model
Jul 22, 2026
6afbc40
feat(settings): resolve app operations model
Jul 22, 2026
23a8f80
fix(settings): enforce app operations capabilities
Jul 22, 2026
4268d3a
feat(settings): expose app operations model api
Jul 22, 2026
3cc971b
feat(agent): check app operations model health
Jul 22, 2026
f774607
test(agent): verify disabled operations model skips probe
Jul 22, 2026
12d70d1
feat(memory): define backend contracts
Jul 22, 2026
b17fa44
fix(memory): tighten transport contracts
Jul 22, 2026
12770ba
test(memory): strengthen entry kind contract
Jul 22, 2026
b9b21af
feat(memory): persist durable memory state
Jul 22, 2026
65f8438
fix(memory): harden durable memory transactions
Jul 22, 2026
e3e42fe
fix(memory): preserve protected entry invariants
Jul 22, 2026
056b468
feat(memory): sanitize canonical evidence
Jul 22, 2026
9e9a13f
fix(memory): harden evidence sanitization
Jul 22, 2026
6cfa9e2
fix(memory): tighten evidence boundary
Jul 22, 2026
da6a8da
feat(memory): manage durable update jobs
Jul 22, 2026
4d9f02a
fix(memory): harden durable update jobs
Jul 22, 2026
2265f14
fix(memory): fence durable job lifecycle
Jul 22, 2026
c893a4d
fix(memory): bind jobs to canonical snapshots
Jul 22, 2026
78bc2aa
fix(memory): close durable queue admission gaps
Jul 22, 2026
631309a
fix(memory): harden reset and cancellation fences
Jul 22, 2026
572dbc0
feat(memory): reconcile update proposals
Jul 22, 2026
3769d1f
fix(memory): fence reconciliation races
Jul 22, 2026
7c02fbf
fix(memory): bind reconciliation to evidence snapshot
Jul 22, 2026
d0783c7
fix(memory): close stale reconciliation races
Jul 22, 2026
a848503
feat(memory): expose controls and library
Jul 22, 2026
fdb0210
fix(memory): preserve clear boundaries
Jul 22, 2026
9e7b609
fix(memory): fence stale reset state
Jul 22, 2026
c5ee8b4
fix(memory): fence effective policy changes
Jul 22, 2026
7689cee
feat(memory): retrieve bounded historical context
Jul 23, 2026
dbf1402
fix(memory): fence retrieval snapshots
Jul 23, 2026
3d8bcff
fix(memory): close retrieval repository bypass
Jul 23, 2026
b640bb7
fix(memory): bound canonical retrieval context
Jul 23, 2026
f554b98
fix(memory): filter retrieval eligibility before limits
Jul 23, 2026
30c358d
feat(memory): connect conversation capture and recall
Jul 23, 2026
e2f2581
fix(memory): serialize durable turn capture
Jul 23, 2026
ed53a8b
fix(memory): bound ordered capture lifecycle
Jul 23, 2026
c76115a
fix(memory): close completion lifecycle races
Jul 23, 2026
8029b6d
feat(memory): register backend capability
Jul 23, 2026
33cbe4e
fix(memory): handle backend lifecycle cleanup
Jul 23, 2026
ef00962
feat(memory): import legacy handoff summaries
Jul 23, 2026
0142d21
fix(memory): fence legacy handoff import
Jul 23, 2026
9602f0b
fix(memory): stabilize legacy import membership
Jul 23, 2026
efe2459
fix(db): make memory import migration idempotent
Jul 23, 2026
c192389
fix(db): preserve memory import sequence watermark
Jul 23, 2026
9f1dc58
fix(memory): harden backend integration
Jul 23, 2026
567644e
fix(memory): reset derived conversation state
Jul 23, 2026
52228b8
fix(memory): scrub protected entries on forget
Jul 23, 2026
59429b7
fix(memory): canonicalize conversation scopes
Jul 23, 2026
66c4801
fix(memory): make retrieval previews immutable
Jul 23, 2026
53d25c1
test(memory): cover immutable preview upgrades
Jul 23, 2026
1046f08
fix(memory): expose protected tombstones safely
Jul 23, 2026
444b1a8
fix(memory): enforce opaque tombstones
Jul 23, 2026
e54cdcf
test(memory): align service tests with tombstones
Jul 23, 2026
b6dbfea
fix(memory): clear tombstone lineage
Jul 23, 2026
06b88fd
fix(memory): validate tombstone response states
Jul 23, 2026
7517dff
fix(memory): strip tombstone lineage responses
Jul 23, 2026
e1af25a
fix(memory): defer queue-full retries durably
Jul 23, 2026
828239b
fix(memory): preserve queue-full retry deadlines
Jul 23, 2026
eca0db8
chore(memory): integrate upstream main
Jul 29, 2026
cbf995d
test(memory): update conversation fixture fields
Jul 29, 2026
a02c027
test(memory): stabilize expired lease recovery
Jul 29, 2026
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
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ members = [
"crates/aionui-project",
"crates/aionui-cron",
"crates/aionui-assistant",
"crates/aionui-memory",
"crates/aionui-app",
]

Expand Down Expand Up @@ -57,6 +58,7 @@ aionui-team = { path = "crates/aionui-team" }
aionui-project = { path = "crates/aionui-project" }
aionui-cron = { path = "crates/aionui-cron" }
aionui-assistant = { path = "crates/aionui-assistant" }
aionui-memory = { path = "crates/aionui-memory" }
aionui-app = { path = "crates/aionui-app" }

aion-agent = { git = "https://github.com/iOfficeAI/aionrs.git", tag = "v0.2.8" }
Expand All @@ -77,6 +79,7 @@ http = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
unicode-normalization = "0.1"

# Async utilities
futures-util = "0.3"
Expand Down
1 change: 1 addition & 0 deletions crates/aionui-ai-agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub use runtime_token::{
};
pub use services::AgentAvailabilityFeedbackPort;
pub use services::AgentService;
pub use services::ProviderHealthChecker;
pub use services::RemoteAgentService;
pub use session_context::{
AcpSessionBuildContext, AgentSessionContext, AgentSessionKind, AionrsSessionBuildContext, ConversationContext,
Expand Down
20 changes: 17 additions & 3 deletions crates/aionui-ai-agent/src/routes/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use axum::extract::{Extension, Json, Path, State};
use axum::routing::{get, patch, post, put};

use aionui_api_types::{
AgentLogoEntry, AgentManagementRow, AgentMetadata, AgentOverridesResponse, ApiResponse, CustomAgentUpsertRequest,
DeleteCustomAgentResponse, ProviderHealthCheckRequest, ProviderHealthCheckResponse, SetAgentOverridesRequest,
SetEnabledRequest, TryConnectCustomAgentRequest, TryConnectCustomAgentResponse,
AgentLogoEntry, AgentManagementRow, AgentMetadata, AgentOverridesResponse, ApiResponse, AppOperationsModelResponse,
CustomAgentUpsertRequest, DeleteCustomAgentResponse, ProviderHealthCheckRequest, ProviderHealthCheckResponse,
SetAgentOverridesRequest, SetEnabledRequest, TryConnectCustomAgentRequest, TryConnectCustomAgentResponse,
};
use aionui_auth::CurrentUser;
use aionui_common::ApiError;
Expand All @@ -29,6 +29,7 @@ pub fn agent_routes(state: AgentRouterState) -> Router {
.route("/api/agents/management", get(list_management_agents))
.route("/api/agents/{id}/health-check", post(health_check_by_id))
.route("/api/agents/provider-health-check", post(provider_health_check))
.route("/api/app-operations/model/check", post(check_app_operations_model))
.route("/api/agents/{id}/enabled", patch(set_agent_enabled))
.route(
"/api/agents/{id}/overrides",
Expand All @@ -40,6 +41,19 @@ pub fn agent_routes(state: AgentRouterState) -> Router {
.with_state(state)
}

async fn check_app_operations_model(
State(state): State<AgentRouterState>,
Extension(_user): Extension<CurrentUser>,
) -> Result<Json<ApiResponse<AppOperationsModelResponse>>, ApiError> {
Ok(Json(ApiResponse::ok(
state
.service
.check_app_operations_model()
.await
.map_err(agent_error_to_api_error)?,
)))
}

async fn list_agent_logos(
State(state): State<AgentRouterState>,
Extension(_user): Extension<CurrentUser>,
Expand Down
95 changes: 91 additions & 4 deletions crates/aionui-ai-agent/src/services/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,28 @@

use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;

use aionui_api_types::{AgentLogoEntry, AgentManagementRow, ProviderHealthCheckRequest, ProviderHealthCheckResponse};
use aionui_api_types::{
AgentLogoEntry, AgentManagementRow, AppOperationsModelHealth, AppOperationsModelResponse, HealthStatus,
ProviderHealthCheckRequest, ProviderHealthCheckResponse,
};
use aionui_common::now_ms;
use aionui_db::IProviderRepository;
use aionui_realtime::EventBroadcaster;
use aionui_system::SettingsService;
use tracing::info;

use super::availability::{AgentAvailabilityFeedbackPort, AgentAvailabilityService};
use super::provider_health::ProviderHealthCheckService;
use super::provider_health::{ProviderHealthCheckService, ProviderHealthChecker};
use crate::error::AgentError;
use crate::registry::AgentRegistry;

pub struct AgentService {
registry: Arc<AgentRegistry>,
broadcaster: Arc<dyn EventBroadcaster>,
provider_health: ProviderHealthCheckService,
provider_health: Arc<dyn ProviderHealthChecker>,
app_operations_settings: SettingsService,
availability: AgentAvailabilityService,
}

Expand All @@ -38,17 +46,34 @@ impl AgentService {
provider_repo: Arc<dyn IProviderRepository>,
encryption_key: [u8; 32],
data_dir: PathBuf,
app_operations_settings: SettingsService,
) -> Arc<Self> {
let provider_health = ProviderHealthCheckService::new(provider_repo.clone(), encryption_key, data_dir.clone());
let provider_health = Arc::new(ProviderHealthCheckService::new(
provider_repo.clone(),
encryption_key,
data_dir.clone(),
));
let availability = AgentAvailabilityService::new(registry.clone(), provider_repo);
Arc::new(Self {
registry,
broadcaster,
provider_health,
app_operations_settings,
availability,
})
}

#[doc(hidden)]
pub fn with_provider_health_checker(&self, provider_health: Arc<dyn ProviderHealthChecker>) -> Arc<Self> {
Arc::new(Self {
registry: self.registry.clone(),
broadcaster: self.broadcaster.clone(),
provider_health,
app_operations_settings: self.app_operations_settings.clone(),
availability: self.availability.clone(),
})
}

/// Registry accessor consumed by the `services::custom` submodule
/// for direct repository access (upsert / delete / enable toggle).
pub(crate) fn registry(&self) -> &Arc<AgentRegistry> {
Expand Down Expand Up @@ -113,6 +138,59 @@ impl AgentService {
self.provider_health.health_check(req).await
}

pub async fn check_app_operations_model(&self) -> Result<AppOperationsModelResponse, AgentError> {
let started = Instant::now();
let result = self.check_app_operations_model_inner().await;
let status = result
.as_ref()
.map(|response| app_operations_health_label(response.health))
.unwrap_or("error");
info!(
status,
duration_ms = started.elapsed().as_millis(),
"App Operations model health check completed"
);
result
}

async fn check_app_operations_model_inner(&self) -> Result<AppOperationsModelResponse, AgentError> {
let current = self
.app_operations_settings
.get_app_operations_model()
.await
.map_err(|_| AgentError::internal("Failed to resolve App Operations model"))?;
let Some(resolved) = current.resolved_model else {
return Ok(current);
};

let response = self
.provider_health
.health_check(ProviderHealthCheckRequest {
provider_id: resolved.provider_id.clone(),
model: resolved.model_id.clone(),
})
.await?;
let status = match response.status {
HealthStatus::Healthy => HealthStatus::Healthy,
HealthStatus::Unhealthy | HealthStatus::Unknown => HealthStatus::Unhealthy,
};
self.app_operations_settings
.record_app_operations_health(
&resolved.provider_id,
&resolved.model_id,
status,
now_ms(),
response.elapsed_ms.try_into().unwrap_or(i64::MAX),
)
.await
.map_err(|_| AgentError::internal("Failed to record App Operations model health"))?;

self.app_operations_settings
.get_app_operations_model()
.await
.map_err(|_| AgentError::internal("Failed to refresh App Operations model health"))
}

pub async fn set_agent_overrides(
&self,
id: &str,
Expand Down Expand Up @@ -192,6 +270,15 @@ impl AgentService {
}
}

fn app_operations_health_label(health: AppOperationsModelHealth) -> &'static str {
match health {
AppOperationsModelHealth::Ready => "ready",
AppOperationsModelHealth::Checking => "checking",
AppOperationsModelHealth::SetupRequired => "setup_required",
AppOperationsModelHealth::Unavailable => "unavailable",
}
}

/// True when the row is launched through a bridge binary (e.g. `npx`) rather
/// than a direct CLI. Such rows store the bridge's own arguments in `args`
/// (e.g. `-y <package> acp`), so replacing `command` with a launch path would
Expand Down
1 change: 1 addition & 0 deletions crates/aionui-ai-agent/src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pub mod remote;

pub use agent::AgentService;
pub use availability::AgentAvailabilityFeedbackPort;
pub use provider_health::ProviderHealthChecker;
pub use remote::RemoteAgentService;
18 changes: 18 additions & 0 deletions crates/aionui-ai-agent/src/services/provider_health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ pub struct ProviderHealthCheckService {
data_dir: PathBuf,
}

#[async_trait::async_trait]
pub trait ProviderHealthChecker: Send + Sync {
async fn health_check(
&self,
request: ProviderHealthCheckRequest,
) -> Result<ProviderHealthCheckResponse, AgentError>;
}

impl ProviderHealthCheckService {
pub fn new(provider_repo: Arc<dyn IProviderRepository>, encryption_key: [u8; 32], data_dir: PathBuf) -> Self {
Self {
Expand Down Expand Up @@ -108,6 +116,16 @@ impl ProviderHealthCheckService {
}
}

#[async_trait::async_trait]
impl ProviderHealthChecker for ProviderHealthCheckService {
async fn health_check(
&self,
request: ProviderHealthCheckRequest,
) -> Result<ProviderHealthCheckResponse, AgentError> {
ProviderHealthCheckService::health_check(self, request).await
}
}

async fn run_probe(
provider_id: String,
platform: String,
Expand Down
Loading