use std::collections::VecDeque;
use std::fmt::Write as FmtWrite;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex as StdMutex, OnceLock};
use std::time::Instant;
use tokio::sync::Mutex;
use tracing::{info, instrument, warn};
use crate::errors::{Result, WinxError};
use crate::runtime::{EmbeddedShellRuntime, ShellRuntime, ShellSessionTransition};
use crate::state::bash_state::{generate_thread_id, BashState};
use crate::types::{
normalize_thread_id, AllowedCommands, AllowedGlobs, BashCommandMode, BashMode,
CodeWriterConfig, FileEditMode, Initialize, InitializeType, Modes, WriteIfEmptyMode,
};
use crate::utils::mmap::read_file_to_string;
use crate::utils::path::{ensure_directory_exists, expand_user, validate_path_in_workspace};
const POLICY_WARNING_CACHE_CAPACITY: usize = 128;
static WARNED_POLICY_CONFIGURATIONS: OnceLock<StdMutex<VecDeque<Vec<String>>>> = OnceLock::new();
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum InitializeTransition {
Created,
AttachedExisting,
ModeChanged,
ShellReset,
ResetSkippedHealthy,
WorkspaceChanged,
Status,
}
impl InitializeTransition {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Created => "created",
Self::AttachedExisting => "attached_existing",
Self::ModeChanged => "mode_changed",
Self::ShellReset => "shell_reset",
Self::ResetSkippedHealthy => "reset_skipped_healthy",
Self::WorkspaceChanged => "workspace_changed",
Self::Status => "status",
}
}
pub(crate) const fn reused(self) -> bool {
matches!(self, Self::AttachedExisting | Self::ResetSkippedHealthy)
}
}
#[derive(Debug)]
pub(crate) struct InitializeOutcome {
pub(crate) text: String,
pub(crate) transition: InitializeTransition,
pub(crate) recovered_missing_session: bool,
pub(crate) context_bytes: usize,
pub(crate) guidelines_bytes: usize,
pub(crate) initial_files_count: usize,
pub(crate) compact_response: bool,
pub(crate) code_writer_policy_strength: Option<&'static str>,
pub(crate) shell_spawners_present: bool,
pub(crate) shell_reset_retry_after_seconds: Option<u64>,
pub(crate) temporary_artifact_dir: PathBuf,
pub(crate) temporary_artifact_ttl_seconds: u64,
pub(crate) temporary_artifact_max_bytes: u64,
pub(crate) temporary_artifact_max_session_bytes: u64,
pub(crate) temporary_artifact_max_file_bytes: u64,
pub(crate) temporary_artifact_max_files: usize,
pub(crate) temporary_artifact_stale_pruned_files: usize,
pub(crate) temporary_artifact_stale_pruned_bytes: u64,
pub(crate) temporary_code_map_max_calls: usize,
pub(crate) temporary_code_map_max_unique_files: usize,
pub(crate) session_status: Option<crate::runtime::ShellSessionStatus>,
}
fn create_playground_dir(thread_id: &str) -> Result<PathBuf> {
#[cfg(unix)]
let owner = crate::os::unix::effective_uid().to_string();
#[cfg(not(unix))]
let owner = std::process::id().to_string();
let dir = std::env::temp_dir().join(format!("winx-playground-{owner}-{thread_id}"));
ensure_directory_exists(&dir)?;
Ok(dir)
}
fn command_exists(cmd: &str) -> bool {
crate::utils::executable::is_available(cmd)
}
fn code_writer_state(
config: &CodeWriterConfig,
workspace_root: &Path,
) -> (BashCommandMode, FileEditMode, WriteIfEmptyMode) {
let mut config = config.clone();
config.allowed_globs.normalize();
config.allowed_commands.normalize();
config.update_relative_globs(&workspace_root.to_string_lossy());
(
BashCommandMode {
bash_mode: BashMode::NormalMode,
allowed_commands: config.allowed_commands,
},
FileEditMode { allowed_globs: config.allowed_globs.clone() },
WriteIfEmptyMode { allowed_globs: config.allowed_globs },
)
}
fn mode_to_state(
mode: Modes,
config: Option<&CodeWriterConfig>,
workspace_root: &Path,
) -> Result<(BashCommandMode, FileEditMode, WriteIfEmptyMode)> {
match mode {
Modes::Wcgw => Ok((
BashCommandMode {
bash_mode: BashMode::NormalMode,
allowed_commands: AllowedCommands::All("all".to_string()),
},
FileEditMode { allowed_globs: AllowedGlobs::All("all".to_string()) },
WriteIfEmptyMode { allowed_globs: AllowedGlobs::All("all".to_string()) },
)),
Modes::Architect => Ok((
BashCommandMode {
bash_mode: BashMode::RestrictedMode,
allowed_commands: AllowedCommands::List(
crate::utils::bash_parser::architect_allowed_commands(),
),
},
FileEditMode { allowed_globs: AllowedGlobs::List(vec![]) },
WriteIfEmptyMode { allowed_globs: AllowedGlobs::List(vec![]) },
)),
Modes::CodeWriter => {
let config = config.ok_or_else(|| {
WinxError::ArgumentParseError(
"code_writer_config is required when mode_name is code_writer.".to_string(),
)
})?;
Ok(code_writer_state(config, workspace_root))
}
}
}
fn active_code_writer_config(state: &BashState) -> Option<CodeWriterConfig> {
(state.mode == Modes::CodeWriter).then(|| CodeWriterConfig {
allowed_globs: state.file_edit_mode.allowed_globs.clone(),
allowed_commands: state.bash_command_mode.allowed_commands.clone(),
})
}
fn active_code_writer_policy(state: &BashState) -> (Option<&'static str>, Vec<String>) {
if state.mode != Modes::CodeWriter {
return (None, Vec::new());
}
match &state.bash_command_mode.allowed_commands {
AllowedCommands::All(value) if value == "all" => (Some("unrestricted"), Vec::new()),
AllowedCommands::All(_) => (Some("restricted"), Vec::new()),
AllowedCommands::List(commands) => {
let bypass = crate::utils::bash_parser::detect_allowlist_bypass(commands);
let strength = if bypass.is_empty() { "restricted" } else { "weak" };
(Some(strength), bypass)
}
}
}
fn warn_code_writer_policy_once(state: &BashState, bypass: &[String]) {
if bypass.is_empty() {
return;
}
let AllowedCommands::List(commands) = &state.bash_command_mode.allowed_commands else {
return;
};
let cache = WARNED_POLICY_CONFIGURATIONS
.get_or_init(|| StdMutex::new(VecDeque::with_capacity(POLICY_WARNING_CACHE_CAPACITY)));
let mut cache = cache.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
if cache.iter().any(|seen| seen == commands) {
return;
}
if cache.len() == POLICY_WARNING_CACHE_CAPACITY {
cache.pop_front();
}
cache.push_back(commands.clone());
drop(cache);
warn!(
commands = ?bypass,
"code_writer allowlist includes shell-spawning commands; the command allowlist is \
effectively bypassable and does not sandbox the agent"
);
}
fn append_code_writer_policy_warning(response: &mut String, bypass: &[String]) {
if bypass.is_empty() {
return;
}
let _ = writeln!(
response,
"\n⚠️ SECURITY: code_writer allowlist includes shell/eval commands ({}). \
They execute arbitrary code from string arguments (e.g. `bash -c …`, \
`find -exec …`), so the command allowlist is effectively bypassable and \
does NOT sandbox the agent. Drop them if you intended a hard restriction.",
bypass.join(", ")
);
}
fn read_initial_files_simple(files: &[String], workspace: &std::path::Path) -> String {
let mut output = String::new();
for file_path in files {
let expanded = expand_user(file_path);
let path = if std::path::Path::new(&expanded).is_absolute() {
PathBuf::from(&expanded)
} else {
workspace.join(&expanded)
};
if let Ok(validated) = validate_path_in_workspace(&path, workspace) {
if validated.exists() && validated.is_file() {
if let Ok(content) = read_file_to_string(&validated, 10_000_000) {
let _ = write!(output, "\n{file_path}\n```\n{content}\n```\n");
}
}
}
}
output
}
fn prepare_workspace(
initialize: &Initialize,
thread_id: &str,
response: &mut String,
) -> Result<PathBuf> {
let workspace_path_str = expand_user(&initialize.any_workspace_path);
if workspace_path_str.is_empty() {
let playground = create_playground_dir(thread_id)?;
let _ = writeln!(
response,
"No workspace path provided; created a playground at {}",
playground.display()
);
return Ok(playground);
}
let workspace_path = PathBuf::from(&workspace_path_str);
let mut folder_to_start = workspace_path.clone();
if workspace_path.exists() {
if workspace_path.is_file() {
folder_to_start = workspace_path.parent().unwrap_or(&workspace_path).to_path_buf();
let _ =
writeln!(response, "Using parent directory of file: {}", folder_to_start.display());
} else if workspace_path.is_dir() {
let _ = writeln!(response, "Using workspace directory: {}", folder_to_start.display());
}
} else if workspace_path.is_absolute() {
ensure_directory_exists(&workspace_path).map_err(|e| {
WinxError::WorkspacePathError(format!("Failed to create workspace: {e}"))
})?;
let _ = writeln!(response, "Created workspace directory: {}", workspace_path.display());
}
if folder_to_start.exists() {
if let Ok(canonical) = folder_to_start.canonicalize().map(crate::utils::path::simplified) {
folder_to_start = canonical;
}
}
Ok(folder_to_start)
}
fn initialize_thread_id(initialize: &Initialize) -> String {
let thread_id = normalize_thread_id(&initialize.thread_id);
if thread_id.is_empty() {
generate_thread_id()
} else {
thread_id
}
}
fn validate_thread_id(initialize: &Initialize) -> Result<()> {
if initialize.init_type != InitializeType::FirstCall
&& normalize_thread_id(&initialize.thread_id).is_empty()
{
return Err(WinxError::ThreadIdMismatch(
"Thread id should be provided if type != 'first_call', including when resetting."
.to_string(),
));
}
Ok(())
}
fn load_guidelines(workspace: &Path) -> String {
let mut output = String::new();
let mut candidates = Vec::new();
if let Some(home) = home::home_dir() {
candidates.push(home.join(".winx").join("AGENTS.md"));
candidates.push(home.join(".winx").join("CLAUDE.md"));
candidates.push(home.join(".wcgw").join("AGENTS.md"));
candidates.push(home.join(".wcgw").join("CLAUDE.md"));
}
candidates.push(workspace.join("AGENTS.md"));
candidates.push(workspace.join("CLAUDE.md"));
for path in candidates {
if path.is_file() {
if let Ok(content) = fs::read_to_string(&path) {
let _ = writeln!(output, "\n## {}\n{}", path.display(), content);
}
}
}
output
}
pub async fn handle_tool_call(
bash_state_arc: &Arc<Mutex<Option<BashState>>>,
initialize: Initialize,
) -> Result<String> {
handle_tool_call_with_runtime(&EmbeddedShellRuntime, bash_state_arc, initialize).await
}
pub async fn handle_tool_call_with_runtime(
runtime: &dyn ShellRuntime,
bash_state_arc: &Arc<Mutex<Option<BashState>>>,
initialize: Initialize,
) -> Result<String> {
Ok(handle_tool_call_with_runtime_detailed(runtime, bash_state_arc, initialize).await?.text)
}
#[instrument(level = "info", skip(runtime, bash_state_arc, initialize))]
#[allow(clippy::too_many_lines)]
pub(crate) async fn handle_tool_call_with_runtime_detailed(
runtime: &dyn ShellRuntime,
bash_state_arc: &Arc<Mutex<Option<BashState>>>,
mut initialize: Initialize,
) -> Result<InitializeOutcome> {
let mut response = String::new();
let mut context_bytes = 0;
let mut guidelines_bytes = 0;
let initial_files_count = initialize.initial_files_to_read.len();
info!("Initialize called for workspace: {}", initialize.any_workspace_path);
validate_thread_id(&initialize)?;
let thread_id = initialize_thread_id(&initialize);
if initialize.init_type == InitializeType::Status {
return describe_session_status(runtime, bash_state_arc, &thread_id).await;
}
let mut bash_state_guard = bash_state_arc.lock().await;
let recovered_request = if bash_state_guard.is_none() {
match initialize.init_type {
InitializeType::UserAskedModeChange => Some("user_asked_mode_change"),
InitializeType::ResetShell => Some("reset_shell"),
InitializeType::FirstCall
| InitializeType::UserAskedChangeWorkspace
| InitializeType::Status => None,
}
} else {
None
};
if let Some(requested_transition) = recovered_request {
info!(requested_transition, "Initialize recovered a missing live session as first_call");
initialize.init_type = InitializeType::FirstCall;
}
if initialize.init_type != InitializeType::FirstCall && bash_state_guard.is_none() {
return Err(WinxError::BashStateNotInitialized);
}
let local_attach = initialize.init_type == InitializeType::FirstCall
&& bash_state_guard
.as_ref()
.is_some_and(|state| state.initialized && state.current_thread_id == thread_id);
let folder_to_start = if local_attach {
bash_state_guard
.as_ref()
.map_or_else(|| PathBuf::from("/tmp"), |state| state.workspace_root.clone())
} else {
prepare_workspace(&initialize, &thread_id, &mut response)?
};
let requested_mode = Modes::from(&initialize.mode_name);
let preserve_active_mode =
local_attach || initialize.init_type == InitializeType::UserAskedChangeWorkspace;
let (mode, bash_command_mode, file_edit_mode, write_if_empty_mode) = if preserve_active_mode {
let Some(state) = bash_state_guard.as_ref() else {
return Err(WinxError::BashStateNotInitialized);
};
(
state.mode,
state.bash_command_mode.clone(),
state.file_edit_mode.clone(),
state.write_if_empty_mode.clone(),
)
} else {
let (bash_command_mode, file_edit_mode, write_if_empty_mode) = mode_to_state(
requested_mode,
initialize.code_writer_config.as_ref(),
&folder_to_start,
)?;
(requested_mode, bash_command_mode, file_edit_mode, write_if_empty_mode)
};
let transition;
let mut compact_response = false;
let mut shell_reset_retry_after_seconds = None;
match initialize.init_type {
InitializeType::Status => {
return Err(WinxError::ArgumentParseError(
"status probes are answered before any session transition".to_string(),
));
}
InitializeType::FirstCall => {
let configured = if local_attach {
let Some(state) = bash_state_guard.as_mut() else {
return Err(WinxError::BashStateNotInitialized);
};
let mut configured = runtime.attach_session(state).await?;
configured.attached_existing = true;
configured
} else {
let mut new_bash_state = BashState::new();
new_bash_state.current_thread_id.clone_from(&thread_id);
new_bash_state.mode = mode;
new_bash_state.bash_command_mode = bash_command_mode;
new_bash_state.file_edit_mode = file_edit_mode;
new_bash_state.write_if_empty_mode = write_if_empty_mode;
new_bash_state.initialized = true;
let resumed_context = if initialize.task_id_to_resume.trim().is_empty() {
None
} else {
crate::tools::context_save::load_saved_context(&initialize.task_id_to_resume)?
};
if let Some((memory_data, snapshot)) = &resumed_context {
if let Some(snapshot) = snapshot {
new_bash_state.apply_snapshot(snapshot);
new_bash_state.current_thread_id.clone_from(&thread_id);
}
let _ = writeln!(
response,
"\n# Resumed task {}\nFollowing is the retrieved task context:\n{}",
initialize.task_id_to_resume, memory_data
);
}
if resumed_context.as_ref().and_then(|(_, snapshot)| snapshot.as_ref()).is_none() {
let resumed_root = resumed_context
.as_ref()
.and_then(|(memory, _)| {
crate::tools::context_save::extract_project_root(memory)
})
.filter(|root| root.exists());
let target = resumed_root.as_deref().unwrap_or(folder_to_start.as_path());
if target.exists() {
new_bash_state.update_cwd(target)?;
new_bash_state.update_workspace_root(target)?;
}
}
let configured = runtime
.configure_session(&mut new_bash_state, ShellSessionTransition::FirstCall)
.await?;
*bash_state_guard = Some(new_bash_state);
configured
};
transition = if configured.attached_existing {
InitializeTransition::AttachedExisting
} else {
InitializeTransition::Created
};
compact_response = configured.attached_existing
&& initialize.initial_files_to_read.is_empty()
&& initialize.task_id_to_resume.is_empty();
if compact_response {
response.clear();
let state = bash_state_guard.as_ref().ok_or(WinxError::BashStateNotInitialized)?;
let _ = writeln!(
response,
"Attached to the existing durable Winx session; PTY, cwd, mode, and prior \
context were preserved.\ncwd={}\nmode={}",
state.cwd.display(),
state.mode
);
if let Some(attach_hint) = configured.attach_hint.as_deref() {
let _ = writeln!(response, "Attach terminal: {attach_hint}");
}
response.push_str(
"Context and instructions are unchanged. Continue with the existing thread; \
use an explicit mode/workspace change or reset only when intended.\n",
);
} else {
if configured.attached_existing {
response.push_str(
"\nAttached to the existing durable session for this principal/workspace; \
the guardian-owned PTY and cwd were preserved. Use `reset_shell` or an \
explicit workspace/mode change when replacement is intended.\n",
);
}
let _ = write!(
response,
"\n# Environment\nSystem: {}\nMachine: {}\nInitialized in directory: {}\n",
std::env::consts::OS,
std::env::consts::ARCH,
bash_state_guard
.as_ref()
.map_or(folder_to_start.as_path(), |state| state.cwd.as_path())
.display()
);
if command_exists("rg") {
let _ = writeln!(
response,
"\n# Available commands\nUse ripgrep `rg` instead of `grep`/`find -name` — \
it's much faster and respects .gitignore."
);
}
if let Some(attach_hint) = configured.attach_hint.as_deref() {
let _ = writeln!(response, "\nAttach terminal: {attach_hint}");
}
let active_config = bash_state_guard.as_ref().and_then(active_code_writer_config);
let active_mode = bash_state_guard.as_ref().map_or(mode, |state| state.mode);
let _ = writeln!(
response,
"\n{}",
crate::utils::mode_prompts::mode_prompt(active_mode, active_config.as_ref())
);
let active_workspace = bash_state_guard
.as_ref()
.map_or(folder_to_start.as_path(), |state| state.workspace_root.as_path());
let guidelines = load_guidelines(active_workspace);
guidelines_bytes = guidelines.len();
if !guidelines.is_empty() {
let _ = writeln!(response, "\n# Agent guidelines\n{guidelines}");
}
if let Ok((repo_context, _)) =
crate::utils::repo::get_repo_context(active_workspace)
{
context_bytes = repo_context.len();
let _ = writeln!(response, "\n# Workspace structure\n{repo_context}");
}
if !initialize.initial_files_to_read.is_empty() {
let content = read_initial_files_simple(
&initialize.initial_files_to_read,
active_workspace,
);
if !content.is_empty() {
let _ = writeln!(response, "\n# Requested files\n{content}");
}
}
}
if let Some(requested_transition) = recovered_request {
let _ = writeln!(
response,
"\nRecovered missing live session: `{requested_transition}` arrived after \
the adapter no longer had session state. Winx safely performed `first_call` \
with the same thread and intended workspace; continue with the returned \
thread_id/workspace_root pair."
);
}
}
InitializeType::UserAskedModeChange => {
transition = InitializeTransition::ModeChanged;
if let Some(state) = bash_state_guard.as_mut() {
state.mode = mode;
state.bash_command_mode = bash_command_mode;
state.file_edit_mode = file_edit_mode;
state.write_if_empty_mode = write_if_empty_mode;
runtime.configure_session(state, ShellSessionTransition::ModeChange).await?;
let _ = writeln!(response, "Changed mode to: {mode:?}");
let active_config = active_code_writer_config(state);
let _ = writeln!(
response,
"\n{}",
crate::utils::mode_prompts::mode_prompt(mode, active_config.as_ref())
);
} else {
return Err(WinxError::BashStateNotInitialized);
}
}
InitializeType::ResetShell => {
if let Some(state) = bash_state_guard.as_mut() {
if let Some(retry_after) = state.shell_reset_retry_after(Instant::now()) {
transition = InitializeTransition::ResetSkippedHealthy;
compact_response = true;
let retry_after_seconds = retry_after.as_secs().max(1);
shell_reset_retry_after_seconds = Some(retry_after_seconds);
let _ = writeln!(
response,
"Skipped redundant reset_shell: the PTY was replaced recently and no \
shell-runtime failure has occurred since. The existing PTY, cwd, mode, \
and context were preserved. Continue with BashCommand; retry reset_shell \
only after a shell-runtime failure or in {retry_after_seconds} seconds."
);
} else {
state.mode = mode;
state.bash_command_mode = bash_command_mode;
state.file_edit_mode = file_edit_mode;
state.write_if_empty_mode = write_if_empty_mode;
if let Err(error) =
runtime.configure_session(state, ShellSessionTransition::Reset).await
{
state.record_shell_runtime_failure();
return Err(error);
}
state.record_shell_reset(Instant::now());
transition = InitializeTransition::ShellReset;
response.push_str("Reset shell (new PTY created)\n");
}
} else {
return Err(WinxError::BashStateNotInitialized);
}
}
InitializeType::UserAskedChangeWorkspace => {
transition = InitializeTransition::WorkspaceChanged;
if let Some(state) = bash_state_guard.as_mut() {
if folder_to_start.exists() {
state.update_cwd(&folder_to_start)?;
state.update_workspace_root(&folder_to_start)?;
runtime
.configure_session(state, ShellSessionTransition::WorkspaceChange)
.await?;
let _ =
writeln!(response, "Changed workspace to: {}", folder_to_start.display());
} else {
let _ = writeln!(
response,
"Warning: Workspace path {} does not exist",
folder_to_start.display()
);
}
} else {
return Err(WinxError::BashStateNotInitialized);
}
}
}
let state = bash_state_guard.as_ref().ok_or(WinxError::BashStateNotInitialized)?;
let temporary_artifact =
crate::utils::agent_temp::prepare_session(&state.workspace_root, &state.current_thread_id);
let temporary_artifact_instruction = if compact_response {
format!(
"Use temporary_artifact_dir={} for a small stable set of session-local derived \
helpers; BashCommand exports the same path as WINX_TEMP_DIR. Reuse helpers instead \
of creating CodeMap carriers; helper maps accept one existing file and are limited \
to {} unique files / {} calls. Near its quota Winx reclaims only helpers inactive for \
the advertised TTL; if it remains over budget, explicitly remove obsolete helpers \
before running ordinary commands.",
temporary_artifact.directory.display(),
crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_UNIQUE_FILES,
crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_CALLS,
)
} else {
format!(
"Use temporary_artifact_dir={} for session-local derived helpers only; keep names \
short, reuse one stable file per purpose, preserve source-path/line provenance, and \
treat helpers as non-canonical. Never create a carrier solely for CodeMap. The \
directory is created on demand, limited to {} files / {} bytes for this session, and \
expired after {} seconds of inactivity. Every BashCommand PTY exports the same path \
as WINX_TEMP_DIR; shell-generated helpers must stay beneath it. Winx audits actual \
usage after Bash. At a high-water mark it reclaims only helpers inactive for the full \
TTL, never fresh artifacts; if the hard limit remains exceeded, explicitly inspect and \
remove obsolete helpers before ordinary commands can continue. \
Helper CodeMap accepts one existing file and is limited to {} unique files / {} calls \
in this live session.",
temporary_artifact.directory.display(),
temporary_artifact.max_session_files,
temporary_artifact.max_session_bytes,
temporary_artifact.ttl_seconds,
crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_UNIQUE_FILES,
crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_CALLS,
)
};
let _ = writeln!(
response,
"\nUse thread_id={thread_id} for all winx tool calls.\nUse workspace_root={} for all winx tool calls.\n{temporary_artifact_instruction}\nBefore every reuse, confirm workspace_root still matches the user's current project. Keep this exact pair together. workspace_root identifies this project session; it does not restrict target paths allowed by policy.",
state.workspace_root.display(),
);
let (code_writer_policy_strength, bypass) =
bash_state_guard.as_ref().map_or((None, Vec::new()), active_code_writer_policy);
if let Some(state) = bash_state_guard.as_ref() {
warn_code_writer_policy_once(state, &bypass);
}
if compact_response {
if let Some(strength) = code_writer_policy_strength {
let _ = writeln!(
response,
"code_writer_policy={strength} shell_spawners_present={}",
!bypass.is_empty()
);
}
} else {
append_code_writer_policy_warning(&mut response, &bypass);
crate::utils::orchestration::append_initialize_instructions(&mut response);
}
Ok(InitializeOutcome {
text: response,
transition,
recovered_missing_session: recovered_request.is_some(),
context_bytes,
guidelines_bytes,
initial_files_count,
compact_response,
code_writer_policy_strength,
shell_spawners_present: !bypass.is_empty(),
shell_reset_retry_after_seconds,
temporary_artifact_dir: temporary_artifact.directory,
temporary_artifact_ttl_seconds: temporary_artifact.ttl_seconds,
temporary_artifact_max_bytes: temporary_artifact.max_total_bytes,
temporary_artifact_max_session_bytes: temporary_artifact.max_session_bytes,
temporary_artifact_max_file_bytes: temporary_artifact.max_file_bytes,
temporary_artifact_max_files: temporary_artifact.max_session_files,
temporary_artifact_stale_pruned_files: temporary_artifact.stale_pruned_files,
temporary_artifact_stale_pruned_bytes: temporary_artifact.stale_pruned_bytes,
temporary_code_map_max_calls: crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_CALLS,
temporary_code_map_max_unique_files:
crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_UNIQUE_FILES,
session_status: None,
})
}
async fn describe_session_status(
runtime: &dyn ShellRuntime,
bash_state_arc: &Arc<Mutex<Option<BashState>>>,
thread_id: &str,
) -> Result<InitializeOutcome> {
let (workspace_root, cwd, mode, bound_thread_id) = {
let guard = bash_state_arc.lock().await;
let state = guard.as_ref().ok_or(WinxError::BashStateNotInitialized)?;
if !state.initialized {
return Err(WinxError::BashStateNotInitialized);
}
if state.current_thread_id != thread_id {
return Err(WinxError::ThreadIdMismatch(format!(
"thread_id {thread_id} is not the session bound here ({})",
state.current_thread_id
)));
}
(
state.workspace_root.clone(),
state.cwd.clone(),
state.mode,
state.current_thread_id.clone(),
)
};
let status = runtime.describe_session(bash_state_arc).await?;
let temporary_artifact =
crate::utils::agent_temp::session_info(&workspace_root, &bound_thread_id);
let mut response = String::new();
let _ = writeln!(response, "# Session status");
let _ = writeln!(response, "thread_id={bound_thread_id}");
let _ = writeln!(response, "workspace_root={}", workspace_root.display());
let _ = writeln!(response, "cwd={}", cwd.display());
let _ = writeln!(response, "mode={mode}");
let _ = writeln!(response, "runtime={}", status.runtime);
if let Some(pid) = status.guardian_pid {
let _ = writeln!(
response,
"guardian_pid={pid} guardian_epoch={} guardian_build={}",
status.guardian_epoch.as_deref().unwrap_or("?"),
status.guardian_build.as_deref().unwrap_or("?")
);
}
let _ = writeln!(response, "command_running={}", status.command_running);
if !status.background_command_ids.is_empty() {
let _ = writeln!(response, "background_command_ids={:?}", status.background_command_ids);
}
let _ = writeln!(
response,
"next: {}",
if status.command_running {
"poll BashCommand status_check for the running command"
} else {
"keep using this thread_id/workspace_root pair; nothing to re-initialize"
}
);
Ok(InitializeOutcome {
text: response,
transition: InitializeTransition::Status,
recovered_missing_session: false,
context_bytes: 0,
guidelines_bytes: 0,
initial_files_count: 0,
compact_response: true,
code_writer_policy_strength: None,
shell_spawners_present: false,
shell_reset_retry_after_seconds: None,
temporary_artifact_dir: temporary_artifact.directory,
temporary_artifact_ttl_seconds: temporary_artifact.ttl_seconds,
temporary_artifact_max_bytes: temporary_artifact.max_total_bytes,
temporary_artifact_max_session_bytes: temporary_artifact.max_session_bytes,
temporary_artifact_max_file_bytes: temporary_artifact.max_file_bytes,
temporary_artifact_max_files: temporary_artifact.max_session_files,
temporary_artifact_stale_pruned_files: temporary_artifact.stale_pruned_files,
temporary_artifact_stale_pruned_bytes: temporary_artifact.stale_pruned_bytes,
temporary_code_map_max_calls: crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_CALLS,
temporary_code_map_max_unique_files:
crate::utils::agent_temp::MAX_DERIVED_CODE_MAP_UNIQUE_FILES,
session_status: Some(status),
})
}