Skip to content
Merged
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
6 changes: 3 additions & 3 deletions scripts/i18n-governance-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"maxTotal": 0
},
"sharedTermDuplicates": {
"maxTotal": 176,
"maxTotal": 174,
"bySurface": {
"core": 15,
"installer": 0,
"mobile-web": 0,
"relay-static-homepage": 0,
"web-ui": 161
"web-ui": 159
},
"bySharedKey": {
"agents.claw": 3,
Expand All @@ -34,7 +34,7 @@
"statuses.done": 24,
"statuses.failed": 42,
"statuses.loading": 5,
"statuses.running": 12,
"statuses.running": 10,
"tools.edit": 33,
"tools.explore": 2,
"tools.search": 12,
Expand Down
13 changes: 12 additions & 1 deletion src/apps/desktop/src/api/agentic_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,13 @@ impl From<ControlDeepReviewQueueActionDTO> for DeepReviewQueueControlAction {
#[serde(rename_all = "camelCase")]
pub struct CancelSessionRequest {
pub session_id: String,
/// Tree cancellation opts out so a parent session does not stop its children.
#[serde(default = "default_cancel_descendants")]
pub cancel_descendants: bool,
}

fn default_cancel_descendants() -> bool {
true
}

fn sanitize_create_session_review_metadata(request: &mut CreateSessionRequest) {
Expand Down Expand Up @@ -2595,7 +2602,11 @@ pub async fn cancel_session(
request: CancelSessionRequest,
) -> Result<CancelSessionResponse, String> {
let dialog_turn_id = coordinator
.cancel_active_turn_for_session(&request.session_id, std::time::Duration::from_secs(5))
.cancel_active_turn_for_session_with_descendant_policy(
&request.session_id,
std::time::Duration::from_secs(5),
request.cancel_descendants,
)
.await
.map_err(|e| {
log::error!(
Expand Down
1 change: 1 addition & 0 deletions src/apps/desktop/src/api/remote_workspace_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ pub const REMOTE_WORKSPACE_COMMAND_POLICIES: &[(&str, RemoteWorkspacePolicy)] =
"get_session_file_diff_stats",
RemoteWorkspacePolicy::LegacyUnaudited,
),
("get_session_lineage", RemoteWorkspacePolicy::RemoteRouted),
("get_session_files", RemoteWorkspacePolicy::LegacyUnaudited),
(
"get_session_operations",
Expand Down
38 changes: 37 additions & 1 deletion src/apps/desktop/src/api/session_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::runtime::{
};
use crate::startup_trace::DesktopStartupTrace;
use bitfun_core::agentic::coordination::get_global_scheduler;
use bitfun_core::agentic::persistence::{SessionBranchResult, SessionMetadataPage};
use bitfun_core::agentic::persistence::{
SessionBranchResult, SessionLineageSnapshot, SessionMetadataPage,
};
use bitfun_core::service::remote_ssh::normalize_remote_workspace_path;
use bitfun_core::service::session::{
DialogTurnData, SessionKind, SessionMetadata, SessionStatus, SessionTranscriptExport,
Expand Down Expand Up @@ -56,6 +58,16 @@ pub struct ListPersistedSessionsPageRequest {
pub remote_ssh_host: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetSessionLineageRequest {
pub session_id: String,
pub workspace_path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub remote_connection_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub remote_ssh_host: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadSessionTurnsRequest {
pub session_id: String,
Expand Down Expand Up @@ -359,6 +371,30 @@ pub async fn list_persisted_sessions_page(
result
}

#[tauri::command]
pub async fn get_session_lineage(
request: GetSessionLineageRequest,
runtime: State<'_, DesktopRuntimeContext>,
) -> Result<Option<SessionLineageSnapshot>, String> {
runtime
.session_application()
.get_session_lineage(
desktop_session_scope(
request.workspace_path,
request.remote_connection_id,
request.remote_ssh_host,
),
&request.session_id,
)
.await
.map_err(|error| {
format!(
"Failed to load session lineage: {}",
desktop_session_error(error)
)
})
}

#[tauri::command]
pub async fn load_session_turns(
request: LoadSessionTurnsRequest,
Expand Down
1 change: 1 addition & 0 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,7 @@ pub async fn run() {
list_persisted_sessions,
search_referenceable_sessions,
list_persisted_sessions_page,
get_session_lineage,
load_session_turns,
get_session_usage_report,
save_session_turn,
Expand Down
17 changes: 16 additions & 1 deletion src/apps/desktop/src/runtime/session_application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use bitfun_agent_runtime::sdk::{
};
use bitfun_core::agentic::coordination::{ConversationCoordinator, DialogScheduler};
use bitfun_core::agentic::core::Session;
use bitfun_core::agentic::persistence::{SessionBranchResult, SessionMetadataPage};
use bitfun_core::agentic::persistence::{
SessionBranchResult, SessionLineageSnapshot, SessionMetadataPage,
};
use bitfun_core::agentic::session::SessionViewRestoreTiming;
use bitfun_core::product_runtime::{CoreAgentRuntimeCompatibility, CoreProductAgentRuntime};
use bitfun_core::service::remote_ssh::workspace_state::{
Expand Down Expand Up @@ -392,6 +394,19 @@ impl DesktopSessionApplication {
.map_err(|error| DesktopSessionApplicationError::Core(error.to_string()))
}

pub(crate) async fn get_session_lineage(
&self,
request: DesktopSessionScopeRequest,
anchor_session_id: &str,
) -> DesktopSessionApplicationResult<Option<SessionLineageSnapshot>> {
let scope = self.resolved_scope(request).await;
let storage_path = self.storage_path(&scope);
self.compatibility
.get_persisted_session_lineage(&storage_path, anchor_session_id)
.await
.map_err(|error| DesktopSessionApplicationError::Core(error.to_string()))
}

pub(crate) async fn list_archived_sessions(
&self,
request: DesktopSessionScopeRequest,
Expand Down
10 changes: 9 additions & 1 deletion src/apps/server/src/rpc_dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,17 @@ pub async fn dispatch(
"cancel_session" => {
let request = extract_request(&params)?;
let session_id = get_string(&request, "sessionId")?;
let cancel_descendants = request
.get("cancelDescendants")
.and_then(serde_json::Value::as_bool)
.unwrap_or(true);
let dialog_turn_id = state
.coordinator
.cancel_active_turn_for_session(&session_id, Duration::from_secs(5))
.cancel_active_turn_for_session_with_descendant_policy(
&session_id,
Duration::from_secs(5),
cancel_descendants,
)
.await
.map_err(|e| anyhow!("{}", e))?;
Ok(serde_json::json!({
Expand Down
39 changes: 33 additions & 6 deletions src/crates/assembly/core/src/agentic/coordination/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5918,10 +5918,20 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
&self,
session_id: &str,
dialog_turn_id: &str,
) -> BitFunResult<()> {
self.cancel_dialog_turn_with_descendant_policy(session_id, dialog_turn_id, true)
.await
}

async fn cancel_dialog_turn_with_descendant_policy(
&self,
session_id: &str,
dialog_turn_id: &str,
cancel_descendants: bool,
) -> BitFunResult<()> {
info!(
"Received cancel request: dialog_turn_id={}, session_id={}",
dialog_turn_id, session_id
"Received cancel request: dialog_turn_id={}, session_id={}, cancel_descendants={}",
dialog_turn_id, session_id, cancel_descendants
);

if let Some(control) = self.manual_compaction_controls.get(dialog_turn_id) {
Expand Down Expand Up @@ -6000,8 +6010,10 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
warn!("Failed to cancel tool execution: {}", e);
}

self.cancel_active_subagents_for_parent_turn(session_id, dialog_turn_id)
.await;
if cancel_descendants {
self.cancel_active_subagents_for_parent_turn(session_id, dialog_turn_id)
.await;
}

// Step 4: Wait briefly for the spawn task that owns this turn to drain
// its in-memory message writes before returning. Capped so the RPC
Expand Down Expand Up @@ -6029,6 +6041,17 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
&self,
session_id: &str,
wait_timeout: Duration,
) -> BitFunResult<Option<String>> {
self.cancel_active_turn_for_session_with_descendant_policy(session_id, wait_timeout, true)
.await
}

/// Cancel only the target session when `cancel_descendants` is false.
pub async fn cancel_active_turn_for_session_with_descendant_policy(
&self,
session_id: &str,
wait_timeout: Duration,
cancel_descendants: bool,
) -> BitFunResult<Option<String>> {
abort_thread_goal_continuation_for_session(session_id);

Expand All @@ -6043,8 +6066,12 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
return Ok(None);
};

self.cancel_dialog_turn(session_id, &current_turn_id)
.await?;
self.cancel_dialog_turn_with_descendant_policy(
session_id,
&current_turn_id,
cancel_descendants,
)
.await?;

let deadline = Instant::now() + wait_timeout;
while self.execution_engine.has_active_turn(&current_turn_id) {
Expand Down
2 changes: 1 addition & 1 deletion src/crates/assembly/core/src/agentic/persistence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ pub mod session_branch;

pub use bitfun_runtime_ports::SessionTurnLoadTiming;
pub use bitfun_services_core::session::{
SessionBranchRequest, SessionBranchResult, SessionMetadataPage,
SessionBranchRequest, SessionBranchResult, SessionLineageSnapshot, SessionMetadataPage,
};
pub use manager::{MaterializedSessionReferenceTranscript, PersistenceManager};
16 changes: 15 additions & 1 deletion src/crates/assembly/core/src/product_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ use bitfun_runtime_ports::{
};
use bitfun_runtime_services::RuntimeServices;
use bitfun_services_core::permission_store::ProjectPermissionSqliteStore;
use bitfun_services_core::session::SessionBranchBoundary;
use bitfun_services_core::session::{
build_session_lineage_snapshot, SessionBranchBoundary, SessionLineageSnapshot,
};

use crate::agentic::coordination::{
ConversationCoordinator, DialogScheduler, SessionMaintenancePermit,
Expand Down Expand Up @@ -853,6 +855,18 @@ impl CoreAgentRuntimeCompatibility {
.await
}

pub async fn get_persisted_session_lineage(
&self,
workspace_path: &Path,
anchor_session_id: &str,
) -> BitFunResult<Option<SessionLineageSnapshot>> {
let metadata = self
.persistence
.list_session_metadata_including_internal(workspace_path)
.await?;
Ok(build_session_lineage_snapshot(metadata, anchor_session_id))
}

pub async fn load_persisted_session_metadata(
&self,
workspace_path: &Path,
Expand Down
Loading
Loading