@@ -4352,6 +4352,55 @@ pub async fn agent_budget_ranking(State(state): State<Arc<AppState>>) -> impl In
43524352 Json ( serde_json:: json!( { "agents" : agents, "total" : agents. len( ) } ) )
43534353}
43544354
4355+ /// PUT /api/budget/agents/{id} — Update per-agent budget limits at runtime.
4356+ pub async fn update_agent_budget (
4357+ State ( state) : State < Arc < AppState > > ,
4358+ Path ( id) : Path < String > ,
4359+ Json ( body) : Json < serde_json:: Value > ,
4360+ ) -> impl IntoResponse {
4361+ let agent_id: AgentId = match id. parse ( ) {
4362+ Ok ( id) => id,
4363+ Err ( _) => {
4364+ return (
4365+ StatusCode :: BAD_REQUEST ,
4366+ Json ( serde_json:: json!( { "error" : "Invalid agent ID" } ) ) ,
4367+ )
4368+ }
4369+ } ;
4370+
4371+ let hourly = body[ "max_cost_per_hour_usd" ] . as_f64 ( ) ;
4372+ let daily = body[ "max_cost_per_day_usd" ] . as_f64 ( ) ;
4373+ let monthly = body[ "max_cost_per_month_usd" ] . as_f64 ( ) ;
4374+
4375+ if hourly. is_none ( ) && daily. is_none ( ) && monthly. is_none ( ) {
4376+ return (
4377+ StatusCode :: BAD_REQUEST ,
4378+ Json ( serde_json:: json!( { "error" : "Provide at least one of: max_cost_per_hour_usd, max_cost_per_day_usd, max_cost_per_month_usd" } ) ) ,
4379+ ) ;
4380+ }
4381+
4382+ match state
4383+ . kernel
4384+ . registry
4385+ . update_resources ( agent_id, hourly, daily, monthly)
4386+ {
4387+ Ok ( ( ) ) => {
4388+ // Persist updated entry
4389+ if let Some ( entry) = state. kernel . registry . get ( agent_id) {
4390+ let _ = state. kernel . memory . save_agent ( & entry) ;
4391+ }
4392+ (
4393+ StatusCode :: OK ,
4394+ Json ( serde_json:: json!( { "status" : "ok" , "message" : "Agent budget updated" } ) ) ,
4395+ )
4396+ }
4397+ Err ( e) => (
4398+ StatusCode :: NOT_FOUND ,
4399+ Json ( serde_json:: json!( { "error" : format!( "{e}" ) } ) ) ,
4400+ ) ,
4401+ }
4402+ }
4403+
43554404// ---------------------------------------------------------------------------
43564405// Session listing endpoints
43574406// ---------------------------------------------------------------------------
@@ -5632,6 +5681,32 @@ pub async fn reset_session(
56325681 }
56335682}
56345683
5684+ /// DELETE /api/agents/{id}/history — Clear ALL conversation history for an agent.
5685+ pub async fn clear_agent_history (
5686+ State ( state) : State < Arc < AppState > > ,
5687+ Path ( id) : Path < String > ,
5688+ ) -> impl IntoResponse {
5689+ let agent_id: AgentId = match id. parse ( ) {
5690+ Ok ( id) => id,
5691+ Err ( _) => {
5692+ return (
5693+ StatusCode :: BAD_REQUEST ,
5694+ Json ( serde_json:: json!( { "error" : "Invalid agent ID" } ) ) ,
5695+ )
5696+ }
5697+ } ;
5698+ match state. kernel . clear_agent_history ( agent_id) {
5699+ Ok ( ( ) ) => (
5700+ StatusCode :: OK ,
5701+ Json ( serde_json:: json!( { "status" : "ok" , "message" : "All history cleared" } ) ) ,
5702+ ) ,
5703+ Err ( e) => (
5704+ StatusCode :: INTERNAL_SERVER_ERROR ,
5705+ Json ( serde_json:: json!( { "error" : format!( "{e}" ) } ) ) ,
5706+ ) ,
5707+ }
5708+ }
5709+
56355710/// POST /api/agents/{id}/session/compact — Trigger LLM session compaction.
56365711pub async fn compact_session (
56375712 State ( state) : State < Arc < AppState > > ,
@@ -9253,39 +9328,46 @@ fn audit_to_comms_event(
92539328 } ;
92549329
92559330 let action_str = format ! ( "{:?}" , entry. action) ;
9256- let ( kind, detail) = match action_str. as_str ( ) {
9257- "AgentMessage" => (
9258- CommsEventKind :: AgentMessage ,
9259- openfang_types:: truncate_str ( & entry. detail , 200 ) . to_string ( ) ,
9260- ) ,
9331+ let ( kind, detail, target_label) = match action_str. as_str ( ) {
9332+ "AgentMessage" => {
9333+ // Format detail: "tokens_in=X, tokens_out=Y" → readable summary
9334+ let detail = if entry. detail . starts_with ( "tokens_in=" ) {
9335+ let parts: Vec < & str > = entry. detail . split ( ", " ) . collect ( ) ;
9336+ let in_tok = parts. first ( ) . and_then ( |p| p. strip_prefix ( "tokens_in=" ) ) . unwrap_or ( "?" ) ;
9337+ let out_tok = parts. get ( 1 ) . and_then ( |p| p. strip_prefix ( "tokens_out=" ) ) . unwrap_or ( "?" ) ;
9338+ if entry. outcome == "ok" {
9339+ format ! ( "{} in / {} out tokens" , in_tok, out_tok)
9340+ } else {
9341+ format ! ( "{} in / {} out — {}" , in_tok, out_tok, openfang_types:: truncate_str( & entry. outcome, 80 ) )
9342+ }
9343+ } else if entry. outcome != "ok" {
9344+ format ! ( "{} — {}" , openfang_types:: truncate_str( & entry. detail, 80 ) , openfang_types:: truncate_str( & entry. outcome, 80 ) )
9345+ } else {
9346+ openfang_types:: truncate_str ( & entry. detail , 200 ) . to_string ( )
9347+ } ;
9348+ ( CommsEventKind :: AgentMessage , detail, "user" )
9349+ }
92619350 "AgentSpawn" => (
92629351 CommsEventKind :: AgentSpawned ,
92639352 format ! ( "Agent spawned: {}" , openfang_types:: truncate_str( & entry. detail, 100 ) ) ,
9353+ "" ,
92649354 ) ,
92659355 "AgentKill" => (
92669356 CommsEventKind :: AgentTerminated ,
92679357 format ! ( "Agent killed: {}" , openfang_types:: truncate_str( & entry. detail, 100 ) ) ,
9358+ "" ,
92689359 ) ,
9269- "ToolInvoke" => return None ,
9270- "CapabilityCheck" => return None ,
9271- "MemoryAccess" => return None ,
9272- "FileAccess" => return None ,
9273- "NetworkAccess" => return None ,
9274- "ShellExec" => return None ,
9275- "AuthAttempt" => return None ,
9276- "WireConnect" => return None ,
9277- "ConfigChange" => return None ,
92789360 _ => return None ,
92799361 } ;
92809362
92819363 Some ( CommsEvent {
9282- id : entry. seq . to_string ( ) ,
9364+ id : format ! ( "audit-{}" , entry. seq) ,
92839365 timestamp : entry. timestamp . clone ( ) ,
92849366 kind,
92859367 source_id : entry. agent_id . clone ( ) ,
92869368 source_name : resolve_name ( & entry. agent_id ) ,
9287- target_id : String :: new ( ) ,
9288- target_name : String :: new ( ) ,
9369+ target_id : if target_label . is_empty ( ) { String :: new ( ) } else { target_label . to_string ( ) } ,
9370+ target_name : if target_label . is_empty ( ) { String :: new ( ) } else { target_label . to_string ( ) } ,
92899371 detail,
92909372 } )
92919373}
0 commit comments