use std::collections::{HashMap, HashSet, VecDeque};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use a3s_code_core::config::{CodeConfig, OsConfig};
use a3s_code_core::context::RecentWorkspaceFilesContextProvider;
#[cfg(test)]
use a3s_code_core::dynamic_workflow_store_path;
use a3s_code_core::hitl::TimeoutAction;
use a3s_code_core::llm::{ContentBlock, Message};
use a3s_code_core::workspace::{
LocalWorkspaceManifest, LocalWorkspaceManifestSnapshot, ManifestWorkspaceBackend,
WorkspaceServices,
};
use a3s_code_core::{
Agent, AgentEvent, AgentSession, CodeDiagnosticSeverity, CodeError,
CodeIntelligenceCapabilities, CodeIntelligenceState, CodeLocation, CodePosition,
CodeSymbolKind, DocumentSymbol, LocalCodeIntelligence, NavigationKind, SessionOptions,
SymbolInformation, SystemPromptSlots, ToolCallResult, WorkspaceCodeIntelligence,
};
use a3s_lane::{PriorityItem, PriorityQueue};
use a3s_tui::cmd::{self, Cmd};
use a3s_tui::components::textarea::TextareaMsg;
use a3s_tui::components::viewport::ViewportMsg;
use a3s_tui::components::{
Alert, AlertKind, DiffLineKind, DiffSpan, InlineAction, Meter, Scrollbar, SessionStatusChip,
Spinner, Textarea, Toast, ToastKind, Viewport,
};
use a3s_tui::event::{KeyEvent, MouseEvent};
use a3s_tui::keymap::{KeyBinding, Keymap};
use a3s_tui::layout::{Constraint, Layout};
use a3s_tui::style::{Color, Style};
use a3s_tui::{
AgentChrome, Event, KeyCode, KeyModifiers, Model, ProgramBuilder, Theme as TuiTheme,
};
use tokio::sync::{mpsc, Mutex};
#[path = "assets/clone.rs"]
pub(crate) mod asset_clone;
#[path = "assets/lifecycle.rs"]
pub(crate) mod asset_lifecycle;
use crate::commands::code::naming as asset_naming;
#[path = "deep_research/artifacts.rs"]
mod deep_research_artifacts;
#[path = "deep_research/convergence.rs"]
mod deep_research_convergence;
#[path = "deep_research/evidence_ledger.rs"]
mod deep_research_evidence_ledger;
#[path = "deep_research/host_digest.rs"]
mod deep_research_host_digest;
#[path = "deep_research/host_evidence.rs"]
mod deep_research_host_evidence;
#[path = "deep_research/host_metadata.rs"]
mod deep_research_host_metadata;
#[path = "deep_research/host_prompt.rs"]
mod deep_research_host_prompt;
#[path = "deep_research/host_report.rs"]
mod deep_research_host_report;
#[path = "deep_research/host_workflow.rs"]
mod deep_research_host_workflow;
#[path = "deep_research/inquiry_runtime.rs"]
mod deep_research_inquiry_runtime;
#[path = "deep_research/report_audit.rs"]
mod deep_research_report_audit;
#[path = "deep_research/report_generation.rs"]
mod deep_research_report_generation;
#[cfg(test)]
#[path = "deep_research/report_pipeline_tests.rs"]
mod deep_research_report_pipeline_tests;
#[cfg(test)]
#[path = "deep_research/retrieval_contract_tests.rs"]
mod deep_research_retrieval_contract_tests;
#[cfg(test)]
#[path = "deep_research/retrieval_integration_tests.rs"]
mod deep_research_retrieval_integration_tests;
#[path = "deep_research/sectioned_report.rs"]
mod deep_research_sectioned_report;
#[path = "deep_research/state_journal.rs"]
mod deep_research_state_journal;
#[path = "deep_research/workflow_store.rs"]
mod deep_research_workflow_store;
#[cfg(test)]
pub(crate) use deep_research_artifacts::deep_research_workflow_needs_recovery_report;
#[cfg(test)]
use deep_research_artifacts::looks_like_deep_research_fallback_draft;
#[cfg(test)]
pub(crate) use deep_research_artifacts::materialize_deep_research_fallback_draft;
#[cfg(test)]
use deep_research_artifacts::research_report_artifacts_from_output_for_query;
pub(crate) use deep_research_artifacts::{
clean_deep_research_final_text_from_artifacts, deep_research_contains_workflow_store_reference,
deep_research_output_has_internal_leak,
deep_research_report_rejection_diagnostic_from_answer_text,
deep_research_workflow_needs_recovery_report_with_metadata,
materialize_deep_research_completed_report_from_generation,
materialize_deep_research_recovery_report, parse_embedded_structured_evidence_json,
research_report_artifacts_from_output, ResearchReportArtifacts,
};
#[cfg(test)]
use deep_research_artifacts::{
deep_research_completed_report_html_for_test,
deep_research_report_artifacts_from_output_for_query, deep_research_report_slug,
};
use deep_research_artifacts::{normalize_research_source_anchor, workflow_evidence_summary};
use deep_research_convergence::{
evaluate_terminal_inquiry_convergence, inquiry_terminal_outcome, validated_inquiry_projection,
validated_inquiry_publication_outcome, ConvergenceAction, ConvergenceDecision,
InquiryTerminalOutcome, ValidatedInquiryProjection,
};
use deep_research_evidence_ledger::{accepted_evidence_ledger, AcceptedEvidence};
use deep_research_host_digest::*;
use deep_research_host_evidence::*;
use deep_research_host_metadata::*;
use deep_research_host_prompt::*;
use deep_research_host_report::*;
pub(crate) use deep_research_host_workflow::DeepResearchEvidenceScope;
use deep_research_host_workflow::*;
use deep_research_inquiry_runtime::inquiry_projection_from_workflow;
pub(crate) use deep_research_inquiry_runtime::{
spawn_deep_research_inquiry, DEEP_RESEARCH_INQUIRY_FINALIZATION_RESERVE_MS,
DEEP_RESEARCH_INQUIRY_HOST_TIMEOUT_MS, DEEP_RESEARCH_QUESTION_REVIEW_STAGE_TIMEOUT_MS,
DEEP_RESEARCH_RETRIEVAL_STAGE_TIMEOUT_MS,
};
use deep_research_report_generation::*;
use deep_research_sectioned_report::{
generate_sectioned_report, merge_sectioned_inquiry_projection, sectioned_report_available,
SECTIONED_REPORT_BUDGET_MS,
};
#[cfg(test)]
pub(crate) use deep_research_state_journal::load_inquiry_state as deep_research_test_load_inquiry_state;
pub(crate) use deep_research_state_journal::ResearchOutcome;
use deep_research_state_journal::{
fork_current_for_contradiction_review, reconcile_interrupted_latest_run,
record_child_event as record_deep_research_child_event,
record_convergence as record_deep_research_convergence,
record_evidence_ledger as record_deep_research_evidence_ledger,
record_inquiry_state as record_deep_research_inquiry_state,
record_run_terminal as record_deep_research_run_terminal,
record_workflow_completed as record_deep_research_workflow_completed,
record_workflow_started as record_deep_research_workflow_started, research_diagnostic,
research_diff, ResearchDiagnosticKind, ResearchRunProjection, ResearchSpec,
};
#[cfg(test)]
pub(crate) use deep_research_state_journal::{
record_workflow_started as deep_research_test_record_workflow_started,
ResearchSpec as DeepResearchTestResearchSpec,
};
pub(crate) use deep_research_workflow_store::{
ensure_deep_research_workflow_run_id, recover_deep_research_bootstrap_acquisition_from_store,
recover_deep_research_initial_retrieval_from_store,
recover_deep_research_workflow_run_from_store,
};
pub(crate) fn deep_research_cli_workflow_args_for_budget(
query: &str,
budget: BudgetPlan,
evidence_scope: Option<DeepResearchEvidenceScope>,
) -> serde_json::Value {
let evidence_scope =
evidence_scope.unwrap_or_else(|| deep_research_inferred_evidence_scope(query));
deep_research_workflow_args_for_budget(query, evidence_scope, budget)
}
pub(crate) fn deep_research_cli_report_is_qualified(
query: &str,
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
) -> Result<bool, String> {
let canonical_output =
deep_research_canonical_workflow_output(workflow_output, workflow_metadata);
let evidence_scope = deep_research_inferred_evidence_scope(query);
let outcome = deep_research_report_outcome_for_workflow(
query,
evidence_scope,
&canonical_output,
workflow_metadata,
);
if matches!(outcome, DeepResearchRunOutcome::Degraded) {
return Err("evidence collection did not produce a reportable package".to_string());
}
let accepted = accepted_evidence_ledger(&canonical_output, workflow_metadata);
if accepted.is_empty() {
return Err("evidence collection produced no accepted evidence".to_string());
}
Ok(matches!(outcome, DeepResearchRunOutcome::Qualified))
}
#[cfg(test)]
pub(crate) fn deep_research_test_accepted_evidence_ledger(
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
) -> Vec<AcceptedEvidence> {
accepted_evidence_ledger(workflow_output, workflow_metadata)
}
pub(crate) fn deep_research_cli_canonical_workflow_output(
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
) -> String {
deep_research_canonical_workflow_output(workflow_output, workflow_metadata)
}
pub(crate) async fn settle_deep_research_cli_run(
workspace: &std::path::Path,
run_id: &str,
workflow_succeeded: bool,
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
requested_outcome: ResearchOutcome,
artifacts: &ResearchReportArtifacts,
) -> Result<ResearchOutcome, String> {
record_deep_research_workflow_completed(workspace, run_id, workflow_succeeded)
.await
.map_err(|error| format!("record DeepResearch CLI workflow completion: {error:#}"))?;
let canonical = deep_research_canonical_workflow_output(workflow_output, workflow_metadata);
let evidence = accepted_evidence_ledger(&canonical, workflow_metadata);
record_deep_research_evidence_ledger(workspace, run_id, &evidence)
.await
.map_err(|error| format!("record DeepResearch CLI accepted evidence: {error:#}"))?;
let projection =
record_deep_research_run_terminal(workspace, run_id, requested_outcome, Some(artifacts))
.await
.map_err(|error| format!("record DeepResearch CLI terminal report: {error:#}"))?;
if !projection.outcome.is_terminal()
|| !projection.active_steps.is_empty()
|| !projection.active_children.is_empty()
{
return Err(format!(
"DeepResearch CLI journal did not settle: outcome={:?}, active_steps={}, active_children={}",
projection.outcome,
projection.active_steps.len(),
projection.active_children.len()
));
}
Ok(projection.outcome)
}
#[cfg(test)]
pub(crate) async fn deep_research_test_run_status(
workspace: &std::path::Path,
run_id: &str,
) -> Result<String, String> {
research_diagnostic(workspace, Some(run_id), ResearchDiagnosticKind::Status)
.await
.map_err(|error| error.to_string())
}
pub(crate) fn deep_research_cli_sectioned_report_available(
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
) -> bool {
sectioned_report_available(workflow_output, workflow_metadata)
}
pub(crate) async fn complete_deep_research_cli_sectioned_report(
session: &AgentSession,
query: &str,
workflow_output: &mut String,
workflow_metadata: &mut Option<serde_json::Value>,
run_id: &str,
timeout_ms: u64,
) -> Result<ToolCallResult, String> {
if !sectioned_report_available(workflow_output, workflow_metadata.as_ref()) {
return Err(
"DeepResearch CLI report synthesis requires an Inquiry in Outlining".to_string(),
);
}
let report_deadline = Instant::now()
.checked_add(Duration::from_millis(timeout_ms))
.ok_or_else(|| "DeepResearch CLI report deadline overflowed".to_string())?;
let generated = generate_sectioned_report(
session,
query,
workflow_output,
workflow_metadata.as_ref(),
run_id,
report_deadline,
)
.await?;
let mut merged_output = workflow_output.clone();
let mut merged_metadata = workflow_metadata.clone();
merge_sectioned_inquiry_projection(
&mut merged_output,
merged_metadata.as_mut(),
generated.metadata.as_ref(),
)?;
match deep_research_inquiry_publication_outcome(&merged_output, merged_metadata.as_ref())? {
Some(DeepResearchRunOutcome::Completed | DeepResearchRunOutcome::Qualified) => {}
Some(outcome) => {
return Err(format!(
"DeepResearch CLI report pipeline ended with non-publishable outcome {outcome:?}"
));
}
None => {
return Err(
"DeepResearch CLI report pipeline omitted terminal Inquiry publication authority"
.to_string(),
);
}
}
*workflow_output = merged_output;
*workflow_metadata = merged_metadata;
Ok(generated)
}
pub(crate) fn materialize_deep_research_cli_generated_report(
workspace: &Path,
query: &str,
output: &str,
exit_code: i32,
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
) -> Result<(String, PathBuf, PathBuf), String> {
let report = deep_research_report_from_generation(output, exit_code)?;
let artifacts = materialize_deep_research_completed_report_from_generation(
workspace,
query,
&report,
workflow_output,
workflow_metadata,
)?;
let text = clean_deep_research_final_text_from_artifacts(&artifacts, workspace)
.unwrap_or(report.markdown);
Ok((text, artifacts.markdown, artifacts.html))
}
pub(crate) fn materialize_deep_research_cli_recovery_report(
workspace: &Path,
query: &str,
reason: &str,
workflow_output: &str,
workflow_metadata: Option<&serde_json::Value>,
) -> Result<(String, PathBuf, PathBuf), String> {
let artifacts = materialize_deep_research_recovery_report(
workspace,
query,
reason,
workflow_output,
workflow_metadata,
)?;
let text = clean_deep_research_final_text_from_artifacts(&artifacts, workspace)
.unwrap_or_else(|| reason.to_string());
Ok((text, artifacts.markdown, artifacts.html))
}
#[path = "system/skills.rs"]
pub(crate) mod skills;
#[path = "system/update.rs"]
mod update;
#[path = "workspace/git_snapshot.rs"]
mod git_snapshot;
#[path = "workspace/gitutil.rs"]
mod gitutil;
#[path = "knowledge/kbutil.rs"]
pub(crate) mod kbutil;
#[path = "context/memutil.rs"]
pub(crate) mod memutil;
#[path = "os/progressive.rs"]
mod os_progressive;
#[path = "os/remote_ui.rs"]
pub(crate) mod remote_ui;
#[path = "os/runtime_policy.rs"]
mod runtime_policy;
mod runtime_projection;
mod transcript;
#[path = "app/agent_presence.rs"]
mod agent_presence;
#[path = "app/actions.rs"]
mod app_actions;
#[path = "app/async_dispatch.rs"]
mod app_async_dispatch;
#[path = "app/commands.rs"]
mod app_commands;
#[path = "app/events.rs"]
mod app_events;
#[path = "app/fork.rs"]
mod app_fork;
#[path = "app/launch.rs"]
mod app_launch;
#[path = "app/permission_rules.rs"]
mod app_permission_rules;
#[path = "app/permissions.rs"]
mod app_permissions;
#[path = "app/projections.rs"]
mod app_projections;
#[path = "app/research.rs"]
mod app_research;
#[path = "app/research_workflow.rs"]
mod app_research_workflow;
#[path = "app/rewind.rs"]
mod app_rewind;
#[path = "app/runtime.rs"]
mod app_runtime;
#[path = "app/selection.rs"]
mod app_selection;
#[path = "app/session_share.rs"]
mod app_session_share;
#[path = "app/session_state.rs"]
mod app_session_state;
#[path = "app/smoke.rs"]
mod app_smoke;
#[path = "app/submit.rs"]
mod app_submit;
#[path = "app/types.rs"]
mod app_types;
#[path = "app/update.rs"]
mod app_update;
#[path = "app/update_dispatch.rs"]
mod app_update_dispatch;
#[path = "app/view.rs"]
mod app_view;
#[path = "app/workflow_capture.rs"]
mod app_workflow_capture;
#[path = "ui/approval.rs"]
mod approval;
#[path = "ui/attachments.rs"]
mod attachments;
#[path = "ui/batch_view.rs"]
mod batch_view;
#[path = "ui/chrome.rs"]
mod chrome;
#[path = "ui/design_markdown.rs"]
mod design_markdown;
#[path = "ui/editor_state.rs"]
mod editor_state;
#[path = "ui/file_change_view.rs"]
mod file_change_view;
#[path = "ui/image.rs"]
mod image;
#[path = "ui/message_chrome.rs"]
mod message_chrome;
#[path = "ui/plan_review.rs"]
mod plan_review;
#[path = "ui/program_preview.rs"]
mod program_preview;
#[path = "ui/render.rs"]
mod render;
#[path = "ui/syntax.rs"]
mod syntax;
#[path = "ui/tool_style.rs"]
mod tool_style;
#[path = "ui/tool_transcript_view.rs"]
mod tool_transcript_view;
#[path = "ui/util.rs"]
mod util;
use agent_presence::{agent_presence_tick, AgentIslandLaunchOutcome};
pub(crate) mod panels;
#[cfg(test)]
use crate::budget::AUTO_COMPACT_THRESHOLD;
use crate::budget::{
budget_plan_for_effort_index, context_limit_for_model, effort_uses_automatic_delegation,
resolve_ctx_limit, BudgetPlan, BudgetWorkload, DEFAULT_TUI_EFFORT_INDEX, EFFORT_LEVELS,
ULTRACODE_INDEX as ULTRACODE,
};
use crate::config::*;
use app_commands::*;
#[cfg(test)]
use app_launch::resumed_transcript_entries;
pub(crate) use app_launch::{resolve_tui_session_store_dir, run_in};
use app_permission_rules::*;
use app_permissions::*;
use app_projections::*;
pub(crate) use app_session_state::tui_session_state_path;
use app_session_state::*;
use app_smoke::run_smoke;
#[cfg(test)]
use app_smoke::{
deep_research_smoke_execution_deadline, deep_research_smoke_exhausted_phase_message,
deep_research_smoke_finalization_phase_deadline, deep_research_smoke_phase_deadline,
deep_research_smoke_remaining_budget, deep_research_smoke_run_deadline,
finalize_deep_research_smoke_journal, run_deep_research_smoke_artifact_step,
};
use app_types::*;
use app_update::*;
use app_workflow_capture::*;
use approval::{ApprovalPrompt, ApprovalPromptMsg};
use asset_naming::*;
use attachments::*;
use chrome::*;
use design_markdown::StreamingMarkdown;
use editor_state::*;
use git_snapshot::*;
use gitutil::*;
use image::*;
use memutil::*;
use message_chrome::*;
pub(crate) use panels::ctx::{parse_ctx_search, strip_controls};
pub(crate) use panels::loop_engineering;
use panels::transcript::{SemanticTranscriptViewport, TranscriptViewportAction};
use plan_review::*;
use render::*;
use runtime_policy::RuntimePolicy;
use runtime_projection::{
CompletedSubagent, CompletedTool, RuntimeProjection, RuntimeToolCheckpoint, SubagentOutcome,
ToolCallState,
};
use skills::*;
use syntax::*;
use transcript::{
join_transcript_blocks, transcript_block_separator, Transcript, TranscriptAnchor,
TranscriptEntry, TranscriptEntryId, TranscriptPoint, TranscriptSelection,
};
use update::*;
use util::*;
const HITL_CONFIRM_TIMEOUT_MS: u64 = 60 * 60 * 1000;
const BACKGROUND_CONFIRM_TIMEOUT_MS: u64 = 500;
const AUTO_REVIEW_IDLE: Duration = Duration::from_secs(300);
const TOOL_EXEC_TIMEOUT_MS: u64 = 30 * 60 * 1000;
const DEEP_RESEARCH_SMOKE_FINALIZATION_RESERVE_MS: u64 = 10_000;
pub(crate) const DEEP_RESEARCH_SECTIONED_SYNTHESIS_TIMEOUT_MS: u64 = SECTIONED_REPORT_BUDGET_MS;
const DEEP_RESEARCH_ABORT_GRACE_MS: u64 = 2_000;
const DEEP_RESEARCH_RUN_HARD_TIMEOUT_MS: u64 = DEEP_RESEARCH_INQUIRY_HOST_TIMEOUT_MS
+ DEEP_RESEARCH_SECTIONED_SYNTHESIS_TIMEOUT_MS
+ (2 * DEEP_RESEARCH_ABORT_GRACE_MS)
+ DEEP_RESEARCH_SMOKE_FINALIZATION_RESERVE_MS;
const STREAM_START_TIMEOUT_MS: u64 = 10_000;
const STREAM_JOIN_SETTLE_GRACE_MS: u64 = 2_000;
const GRACEFUL_QUIT_STREAM_GRACE_MS: u64 = 2_000;
const GRACEFUL_QUIT_ABORT_SETTLE_MS: u64 = 250;
const GRACEFUL_QUIT_AGENT_PRESENCE_GRACE_MS: u64 = 500;
const GRACEFUL_QUIT_SESSION_CLOSE_GRACE_MS: u64 = 8_000;
const QUEUE_ADMISSION_RETRY_BASE_MS: u64 = 40;
const QUEUE_ADMISSION_RETRY_MAX_MS: u64 = 500;
const TUI_DUPLICATE_TOOL_CALL_THRESHOLD: u32 = 12;
#[allow(dead_code)]
const RESUME_TIMELINE_PAGE_LIMIT: usize = 200;
struct App {
session: Arc<AgentSession>,
active_session: SharedActiveSession,
use_registry: Option<crate::use_registry::UseRegistryHandle>,
agent: Arc<Agent>,
store: Arc<dyn a3s_code_core::store::SessionStore>,
confirmation: a3s_code_core::hitl::ConfirmationPolicy,
deep_research_report_tool_gate: DeepResearchReportToolGate,
session_id: String,
model_source: ModelSelectionSource,
session_rebuild_seq: u64,
session_rebuild_pending: Option<u64>,
models: Vec<String>,
model_ctx: std::collections::HashMap<String, u32>,
context_limit: u32,
last_prompt_tokens: usize,
compact_summary: Option<String>,
ctx_warned_tier: u8,
model_menu: Option<usize>,
model_tab: usize,
relay_panel: Option<panels::relay::RelayPanel>,
relay_scan_seq: u64,
task_panel: Option<panels::tasks::TaskPanel>,
task_panel_seq: u64,
permission_panel: Option<panels::permissions::PermissionPanel>,
codex_account_models: Vec<crate::account_providers::codex::CodexModel>,
codex_models_loading: bool,
codex_models_refreshed_at: Option<Instant>,
account_models: HashMap<crate::account_providers::AccountProvider, Vec<String>>,
account_models_loading: HashSet<crate::account_providers::AccountProvider>,
account_model_errors: HashMap<crate::account_providers::AccountProvider, String>,
llm_override: Option<LlmOverride>,
code_config: Arc<CodeConfig>,
asset_directories: crate::commands::config::CodeAssetDirectories,
config_path: PathBuf,
memory_dir: PathBuf,
auto_compact_threshold: f64,
os_config: Option<OsConfig>,
os_session: Option<crate::a3s_os::StoredOsSession>,
os_refreshing: bool,
os_gateway_models: Option<Vec<String>>,
os_gateway_models_loading: bool,
os_gateway_error: Option<String>,
last_view: Option<remote_ui::ViewSpec>,
pending_deep_research_report_view: Option<remote_ui::ViewSpec>,
deep_research_loop: Option<DeepResearchLoop>,
deep_research_report_resume_used: bool,
deep_research_workflow: DeepResearchWorkflowSnapshot,
deep_research_outcome: DeepResearchRunOutcome,
pending_deep_research_report_resume: bool,
deep_research_stream_timeout_token: u64,
stream_start_token: u64,
interrupted_stream_start_token: Option<u64>,
pending_interrupted_continuation: Option<InterruptedContinuation>,
runtime_expectation: Option<RuntimeExpectation>,
effort: usize,
effort_panel: Option<usize>,
theme_panel: Option<usize>,
quit_armed: Option<Instant>,
quitting: bool,
last_activity: Instant,
auto_review: AutoReviewTracker,
shell_mode: bool,
research_mode: bool,
review_pending: bool,
sleep_pending: bool,
review: Option<panels::review::ReviewState>,
flow: Option<panels::flow::FlowPanel>,
pending_flow_subcommand: Option<panels::flow::FlowSubcommand>,
agent_picker: Option<panels::agent::AgentPanel>,
pending_agent_subcommand: Option<panels::agent::AgentSubcommand>,
agent_dev: Option<panels::agent::AgentDevSession>,
mcp_picker: Option<panels::mcp::McpPanel>,
pending_mcp_subcommand: Option<panels::mcp::McpSubcommand>,
mcp_dev: Option<panels::mcp::McpDevSession>,
skill_picker: Option<panels::skill::SkillPanel>,
pending_skill_subcommand: Option<panels::skill::SkillSubcommand>,
skill_dev: Option<panels::skill::SkillDevSession>,
okf_picker: Option<panels::okf::OkfPackagePanel>,
pending_okf_subcommand: Option<panels::okf::OkfCommand>,
okf_dev: Option<panels::okf::OkfDevSession>,
review_open: bool,
ctx_ready: bool,
ctx_hits: Vec<panels::ctx::CtxHit>,
pending_ctx: Option<String>,
loop_continuation: bool,
turn_text: String,
llm_turn_checkpoint: Option<LlmTurnUiCheckpoint>,
selection: Option<Selection>,
last_workflow: Option<String>,
pending_images: Vec<PendingImage>,
goal: Option<String>,
goal_since: Option<Instant>,
goal_run: Option<panels::goal_engineering::GoalRunState>,
paused_goal: Option<PausedGoalState>,
goal_resume_prompt: Option<usize>,
goal_generation: u64,
pending_goal_failure: Option<String>,
deep_research_goal_restore: Option<(Option<String>, Option<Instant>)>,
loop_remaining: usize,
runtime: RuntimeProjection,
agent_presence: agent_presence::AgentPresenceRuntime,
background_subagent_watches: HashSet<(u64, String)>,
subagent_snapshot_request_id: u64,
deep_research_subagent_settlement_inflight: bool,
deep_research_journal_finalization_inflight: bool,
deep_research_terminal_artifacts: Option<ResearchReportArtifacts>,
deep_research_agent_event_sequence: u64,
deep_research_projection: Option<ResearchRunProjection>,
turn_had_agent_activity: bool,
turn_text_after_activity: bool,
ultracode_synthesis_inflight: bool,
ultracode_synthesis_used: bool,
instructions: Option<String>,
workspace_manifest: Arc<LocalWorkspaceManifest>,
workspace_manifest_rx: SharedManifestRx,
workspace_services: Arc<WorkspaceServices>,
gradient_until: Option<Instant>,
gradient_frame: usize,
ultracode_animation_epoch: u64,
effort_anim: Option<Instant>,
transcript_view: Option<SemanticTranscriptViewport>,
viewport: Viewport,
textarea: Textarea,
spinner: Spinner,
streaming: StreamingMarkdown,
got_delta: bool,
compacting: Option<Instant>,
updating: Option<Instant>,
checkup_inflight: bool,
last_paint: Option<Instant>,
thinking: String,
state: State,
messages: Transcript,
rx: Option<SharedRx>,
stream_join: Option<StreamJoin>,
stream_join_settling: bool,
stream_settle_abort: Option<tokio::task::AbortHandle>,
host_tool_abort: Option<HostToolAbort>,
host_progress_inflight: bool,
host_tool_call_id: Option<String>,
interrupting: bool,
pending_tools: VecDeque<PendingToolApproval>,
permission_grants: TuiPermissionGrants,
execution_policy: TuiExecutionPolicy,
project_permission_rules_path: PathBuf,
permission_rule_write_inflight: Option<String>,
project_permission_revoke_seq: u64,
project_permission_revoke_inflight: Option<(u64, ExactPermissionGrant)>,
approval_feedback: Option<ApprovalFeedback>,
approval_sel: usize,
history: Vec<String>,
history_panel: Option<panels::history::HistoryPanel>,
history_pos: Option<usize>,
history_draft: Option<String>,
model: Option<String>,
output_tokens: usize,
stream_started: Option<Instant>,
blink_tick: u8,
anim: u8,
mode: Mode,
autonomy_restore: Option<Mode>,
queue: PriorityQueue<Queued>,
queued_turn_modes: HashMap<u64, Mode>,
queued_plan_drafts: HashMap<u64, PlanDraftRequest>,
send_now_queued_sequence: Option<u64>,
queue_panel: Option<panels::queue::QueuePanel>,
active_rewind_checkpoint: Option<RewindCheckpointSeed>,
rewind_checkpoints: VecDeque<RewindCheckpoint>,
next_rewind_checkpoint_id: u64,
rewind_finalization_pending: Option<u64>,
active_queued_turn: Option<PriorityItem<Queued>>,
active_queued_turn_token: Option<u64>,
active_turn_mode: Option<Mode>,
active_plan_draft: Option<PlanDraftRequest>,
queue_retry_generation: u64,
queue_retry_attempt: u8,
running_task: Option<String>,
plan: PlanProjection,
pending_plan_review: Option<PlanReviewState>,
plan_review: Option<PlanReviewState>,
ide: Option<Ide>,
memory: Option<MemPanel>,
evolution: Option<panels::evolution::EvolutionPanel>,
asset_list: Option<panels::asset_resources::AssetListPanel>,
runtime_activity: Option<panels::asset_resources::RuntimeActivityPanel>,
kb: Option<panels::kb::KbPanel>,
loop_panel: Option<panels::loop_engineering::LoopPanel>,
help_open: bool,
help_scroll: usize,
completed: usize,
cwd: String,
branch: Option<String>,
slash_sel: usize,
slash_menu_dismissed_for: Option<String>,
files: Vec<String>,
file_sel: usize,
at_expanded: std::collections::HashSet<String>,
skill_count: usize,
skills: Vec<(String, String)>,
disabled_skills: std::collections::HashSet<String>,
plugins_panel: Option<usize>,
update_available: Option<String>,
width: u16,
height: u16,
keymap: Keymap<Action>,
}
impl App {
fn composer_input_is_hidden(&self) -> bool {
self.goal_resume_prompt.is_some()
|| self.state == State::Awaiting
|| (self.plan_review.is_some() && !self.plan_review_input_active())
|| self.transcript_view.is_some()
|| self.queue_panel.is_some()
|| self.history_panel.is_some()
|| self.model_menu.is_some()
|| self.relay_panel.is_some()
|| self.task_panel.is_some()
|| self.permission_panel.is_some()
|| self.checkup_inflight
|| self.effort_panel.is_some()
|| self.theme_panel.is_some()
|| self.plugins_panel.is_some()
|| self.review_open
|| self.memory.is_some()
|| self.evolution.is_some()
|| self.asset_list.is_some()
|| self.runtime_activity.is_some()
|| self.kb.is_some()
|| self.loop_panel.is_some()
|| self.flow.is_some()
|| self.agent_picker.is_some()
|| self.mcp_picker.is_some()
|| self.skill_picker.is_some()
|| self.okf_picker.is_some()
|| self.help_open
}
fn begin_graceful_quit(&mut self) -> Option<Cmd<Msg>> {
if self.quitting {
return None;
}
if let Err(error) = self.persist_tui_session_state() {
tracing::warn!(%error, "failed to checkpoint TUI session settings before exit");
}
self.quitting = true;
self.interrupting = true;
if let Some(ide) = self.ide.as_mut() {
ide.intelligence_cancellation.cancel();
ide.intelligence_jump_cancellation.cancel();
}
self.stream_start_token = self.stream_start_token.wrapping_add(1);
self.deep_research_stream_timeout_token =
self.deep_research_stream_timeout_token.wrapping_add(1);
self.push_line(&Style::new().fg(TN_YELLOW).render(" exiting…"));
let session = Arc::clone(&self.session);
let stream_join = self.stream_join.take();
let host_tool_abort = self.host_tool_abort.take();
let agent_presence = self.agent_presence.publisher.clone();
self.rx = None;
Some(cmd::cmd(move || async move {
if tokio::time::timeout(
Duration::from_millis(GRACEFUL_QUIT_AGENT_PRESENCE_GRACE_MS),
agent_presence.remove(),
)
.await
.is_err()
{
tracing::warn!("timed out removing the local agent-presence heartbeat");
}
if let Some(abort) = host_tool_abort {
abort.abort();
}
let close = settle_session_close_for_quit(
async move {
session.close().await;
},
Duration::from_millis(GRACEFUL_QUIT_SESSION_CLOSE_GRACE_MS),
);
match stream_join {
Some(stream_join) => {
let settle = settle_stream_join_for_quit(
stream_join,
Duration::from_millis(GRACEFUL_QUIT_STREAM_GRACE_MS),
);
let _ = tokio::join!(close, settle);
}
None => {
close.await;
}
}
Msg::QuitReady
}))
}
fn finish_graceful_quit(&mut self) -> Option<Cmd<Msg>> {
self.pause_goal_for_exit();
if let Err(error) = self.persist_tui_session_state() {
tracing::warn!(%error, "failed to finalize TUI session settings before exit");
}
Some(cmd::quit())
}
fn request_subagent_snapshots(&mut self) -> Cmd<Msg> {
self.subagent_snapshot_request_id = self.subagent_snapshot_request_id.wrapping_add(1);
load_subagent_snapshots(
self.session.clone(),
self.session_id.clone(),
self.session_rebuild_seq,
self.subagent_snapshot_request_id,
)
}
fn invalidate_subagent_snapshots(&mut self) {
self.subagent_snapshot_request_id = self.subagent_snapshot_request_id.wrapping_add(1);
}
pub(crate) fn touch_workspace_file(&self, path: &str) {
self.workspace_manifest.touch_file(path);
}
pub(crate) fn viewport_content_width(&self) -> usize {
viewport_content_width_for(self.width)
}
fn transcript_markdown_width(&self) -> usize {
transcript_markdown_width_for(self.width)
}
}
include!("approval_layout.rs");