use crukx_core::events::{
ActionPhase, AgentAction, AgentAdapter, AgentEvent, SessionState, EVENT_SCHEMA_VERSION,
};
use crukx_runner::{spawn, ExecutionPolicy, SpawnRequest};
use crukx_storage::events::{append_event, session_file_path, write_chain_file};
use crukx_storage::state_dir::StateDir;
use std::path::{Path, PathBuf};
pub struct RunSessionResult {
pub session_id: String,
pub event_file: PathBuf,
pub exit_code: i32,
}
pub fn run_session(
adapter: AgentAdapter,
executable: String,
arguments: Vec<String>,
cwd: &Path,
) -> RunSessionResult {
let repository_root = crukx_git::resolve_repository_root(cwd);
let state = StateDir::resolve(&repository_root);
let adapter_label = match adapter {
AgentAdapter::Claude => "claude",
AgentAdapter::Codex => "codex",
AgentAdapter::Direct => "direct",
AgentAdapter::System => "system",
};
let timestamp = chrono::Utc::now()
.format("%Y-%m-%dT%H-%M-%S%.3fZ")
.to_string();
let session_id = format!(
"{adapter_label}-{timestamp}-{}",
&uuid::Uuid::new_v4().to_string()[..8]
);
let cwd_string = cwd.to_string_lossy().into_owned();
let repository_root_string = repository_root.to_string_lossy().into_owned();
let mut sequence: u64 = 0;
let mut record = |phase: ActionPhase, action: AgentAction, error: Option<String>| {
sequence += 1;
let event = AgentEvent {
schema_version: EVENT_SCHEMA_VERSION,
event_id: uuid::Uuid::new_v4().to_string(),
session_id: session_id.clone(),
sequence,
occurred_at: chrono::Utc::now().to_rfc3339(),
adapter,
repository_root: repository_root_string.clone(),
phase,
action,
policy: None,
error,
};
let _ = append_event(&state, &session_id, &event);
};
let initial_diff = crukx_git::read_tracked_diff(&repository_root);
let base = crukx_git::read_head(&repository_root);
let shell_action = || AgentAction::ShellCommand {
executable: executable.clone(),
arguments: arguments.clone(),
cwd: cwd_string.clone(),
exit_code: None,
signal: None,
};
record(
ActionPhase::Started,
AgentAction::Session {
state: SessionState::Opened,
exit_code: None,
},
None,
);
record(ActionPhase::Proposed, shell_action(), None);
record(ActionPhase::Started, shell_action(), None);
let spawn_result = spawn(SpawnRequest {
executable: executable.clone(),
arguments: arguments.clone(),
cwd: cwd.to_path_buf(),
policy: ExecutionPolicy::DangerFullAccess,
action: shell_action(),
});
let (exit_code, spawn_error) = match spawn_result {
Ok(outcome) => (outcome.exit_code, None),
Err(err) => (127, Some(format!("{err:?}"))),
};
record(
if spawn_error.is_some() {
ActionPhase::Failed
} else {
ActionPhase::Completed
},
AgentAction::ShellCommand {
executable: executable.clone(),
arguments: arguments.clone(),
cwd: cwd_string.clone(),
exit_code: Some(exit_code),
signal: None,
},
spawn_error,
);
let final_diff = crukx_git::read_tracked_diff(&repository_root);
if final_diff != initial_diff {
record(
ActionPhase::Completed,
AgentAction::GitDiff {
base: base.clone(),
patch: final_diff.clone(),
patch_sha256: crukx_git::sha256_hex(&final_diff),
},
None,
);
}
record(
ActionPhase::Completed,
AgentAction::Session {
state: SessionState::Closed,
exit_code: Some(exit_code),
},
None,
);
let _ = write_chain_file(&state, &session_id);
RunSessionResult {
session_id: session_id.clone(),
event_file: session_file_path(&state, &session_id),
exit_code,
}
}