use std::collections::{BinaryHeap, 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, SessionOptions, SystemPromptSlots, ToolCallResult,
};
use a3s_tui::cmd::{self, Cmd};
use a3s_tui::components::textarea::TextareaMsg;
use a3s_tui::components::viewport::ViewportMsg;
use a3s_tui::components::{
Alert, AlertKind, ChoicePrompt, ChoicePromptItem, ChoicePromptMsg, 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"]
mod asset_clone;
#[path = "assets/lifecycle.rs"]
mod asset_lifecycle;
#[path = "assets/naming.rs"]
mod asset_naming;
#[path = "code_cli.rs"]
mod code_cli;
pub(crate) use code_cli::{is_code_cli_command, run_code_cli};
#[path = "deep_research/artifacts.rs"]
mod deep_research_artifacts;
#[path = "deep_research/convergence.rs"]
mod deep_research_convergence;
#[cfg(test)]
#[path = "deep_research/engineered_loop_tests.rs"]
mod deep_research_engineered_loop_tests;
#[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/prompts.rs"]
mod deep_research_prompts;
#[path = "deep_research/report_audit.rs"]
mod deep_research_report_audit;
#[path = "deep_research/report_phase.rs"]
mod deep_research_report_phase;
#[path = "deep_research/state_journal.rs"]
mod deep_research_state_journal;
#[path = "deep_research/workflow_store.rs"]
mod deep_research_workflow_store;
#[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_artifacts_from_output_for_current_run,
deep_research_report_artifacts_from_output_for_query, deep_research_report_slug,
deep_research_workflow_needs_recovery_report,
materialize_deep_research_completed_report_from_answer_text,
materialize_deep_research_completed_report_from_markdown,
materialize_deep_research_completed_report_from_workflow_evidence,
materialize_deep_research_recovery_report, parse_embedded_structured_evidence_json,
research_report_artifacts_from_output, research_report_artifacts_from_output_for_current_run,
snapshot_deep_research_report_artifacts, DeepResearchReportArtifactBaseline,
ResearchReportArtifacts,
};
use deep_research_artifacts::{normalize_research_source_anchor, workflow_evidence_summary};
use deep_research_convergence::{evaluate_convergence, ConvergenceDecision, ConvergenceInput};
use deep_research_evidence_ledger::{
accepted_evidence_ledger,
synthesis_payload_with_context as accepted_evidence_synthesis_payload, 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::*;
use deep_research_host_workflow::*;
use deep_research_report_phase::{
suppress_tool_output as suppress_deep_research_report_phase_tool_output, ReportPhaseToolBuffer,
};
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_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, ResearchOutcome, ResearchRunProjection, ResearchSpec,
};
pub(crate) use deep_research_workflow_store::{
ensure_deep_research_workflow_run_id, recover_deep_research_workflow_run_from_store,
};
#[path = "system/skills.rs"]
pub(crate) mod skills;
#[path = "system/update.rs"]
mod update;
#[path = "workspace/gitutil.rs"]
mod gitutil;
#[path = "knowledge/kbutil.rs"]
pub(crate) mod kbutil;
#[path = "context/memutil.rs"]
mod memutil;
#[path = "os/progressive.rs"]
mod os_progressive;
#[path = "os/remote_ui.rs"]
mod remote_ui;
#[path = "os/runtime_policy.rs"]
mod runtime_policy;
mod runtime_projection;
mod transcript;
#[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/launch.rs"]
mod app_launch;
#[path = "app/permissions.rs"]
mod app_permissions;
#[path = "app/projections.rs"]
mod app_projections;
#[path = "app/research.rs"]
mod app_research;
#[path = "app/runtime.rs"]
mod app_runtime;
#[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/chrome.rs"]
mod chrome;
#[path = "ui/design_markdown.rs"]
mod design_markdown;
#[path = "ui/editor_state.rs"]
mod editor_state;
#[path = "ui/image.rs"]
mod image;
#[path = "ui/program_preview.rs"]
mod program_preview;
#[path = "ui/render.rs"]
mod render;
#[path = "ui/syntax.rs"]
mod syntax;
#[path = "ui/util.rs"]
mod util;
mod panels;
use crate::budget::{
budget_plan_for_effort_index, context_limit_for_model, effort_uses_automatic_delegation,
resolve_ctx_limit, BudgetPlan, BudgetWorkload, AUTO_COMPACT_THRESHOLD,
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::run;
use app_permissions::*;
use app_projections::*;
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,
run_deep_research_smoke_artifact_step,
};
use app_types::*;
use app_update::*;
use app_workflow_capture::*;
use asset_naming::*;
use chrome::*;
use design_markdown::StreamingMarkdown;
use editor_state::*;
use gitutil::*;
use image::*;
use memutil::*;
pub(crate) use panels::loop_engineering;
use panels::transcript::{SemanticTranscriptViewport, TranscriptViewportAction};
use render::*;
use runtime_policy::RuntimePolicy;
use runtime_projection::{
CompletedSubagent, CompletedTool, RuntimeProjection, SubagentOutcome, ToolCallState,
};
use skills::*;
use syntax::*;
use transcript::{Transcript, TranscriptAnchor, TranscriptEntry, TranscriptEntryId};
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_SCRIPT_TIMEOUT_MS: u64 = 300 * 1000;
const DEEP_RESEARCH_WORKFLOW_HOST_GRACE_MS: u64 = 30_000;
const DEEP_RESEARCH_RUN_HARD_TIMEOUT_MS: u64 = 6 * 60 * 1000;
const DEEP_RESEARCH_SMOKE_FINALIZATION_RESERVE_MS: u64 = 10_000;
const DEEP_RESEARCH_SYNTHESIS_TIMEOUT_MS: u64 = 90 * 1000;
const DEEP_RESEARCH_REPAIR_TIMEOUT_MS: u64 = 90 * 1000;
const DEEP_RESEARCH_ABORT_GRACE_MS: u64 = 2_000;
const GRACEFUL_QUIT_STREAM_GRACE_MS: u64 = 2_000;
const GRACEFUL_QUIT_ABORT_SETTLE_MS: u64 = 250;
const DEEP_RESEARCH_TOOL_COMPLETION_GRACE_MS: u64 = 15_000;
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,
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,
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,
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>,
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_repair_used: bool,
deep_research_workflow: DeepResearchWorkflowSnapshot,
deep_research_outcome: DeepResearchRunOutcome,
pending_deep_research_report_repair_prompt: Option<String>,
deep_research_stream_timeout_token: u64,
stream_start_token: u64,
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,
selection: Option<Selection>,
last_workflow: Option<String>,
pending_images: Vec<a3s_code_core::llm::Attachment>,
goal: Option<String>,
goal_since: Option<Instant>,
goal_run: Option<panels::goal_engineering::GoalRunState>,
goal_generation: u64,
pending_goal_failure: Option<String>,
deep_research_goal_restore: Option<(Option<String>, Option<Instant>)>,
loop_remaining: usize,
runtime: RuntimeProjection,
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,
deep_research_report_tools: ReportPhaseToolBuffer,
got_delta: bool,
compacting: Option<Instant>,
updating: Option<Instant>,
last_paint: Option<Instant>,
thinking: String,
state: State,
messages: Transcript,
rx: Option<SharedRx>,
stream_join: Option<StreamJoin>,
stream_join_settling: bool,
host_tool_abort: Option<HostToolAbort>,
host_progress_inflight: bool,
host_tool_call_id: Option<String>,
interrupting: bool,
pending_tools: VecDeque<(String, String)>,
approval_sel: usize,
history: Vec<String>,
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: BinaryHeap<Queued>,
seq: u64,
running_task: Option<String>,
plan: PlanProjection,
ide: Option<Ide>,
memory: Option<MemPanel>,
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.state == State::Awaiting
|| self.transcript_view.is_some()
|| self.model_menu.is_some()
|| self.effort_panel.is_some()
|| self.theme_panel.is_some()
|| self.plugins_panel.is_some()
|| self.review_open
|| self.memory.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;
}
self.quitting = true;
self.interrupting = true;
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();
self.rx = None;
Some(cmd::cmd(move || async move {
if let Some(abort) = host_tool_abort {
abort.abort();
}
match stream_join {
Some(stream_join) => {
let close = session.close();
let settle = settle_stream_join_for_quit(
stream_join,
Duration::from_millis(GRACEFUL_QUIT_STREAM_GRACE_MS),
);
let _ = tokio::join!(close, settle);
}
None => session.close().await,
}
Msg::QuitReady
}))
}
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)
}
}
fn approval_menu_lines(label: &str, selected: usize, width: usize) -> Vec<String> {
approval_prompt(label, selected).lines(width as u16, APPROVAL_PANEL_HEIGHT)
}
const APPROVAL_PANEL_HEIGHT: usize = 5;
const FULLSCREEN_APPROVAL_ROWS_BELOW: usize = 1;
fn approval_rows_below_for(transcript_open: bool, composer_rows_below: usize) -> usize {
if transcript_open {
FULLSCREEN_APPROVAL_ROWS_BELOW
} else {
composer_rows_below
}
}
fn approval_prompt(label: &str, selected: usize) -> ChoicePrompt {
ChoicePrompt::new(
format!("⏵ Run {label}?"),
vec![
ChoicePromptItem::new("Allow once").shortcut('y'),
ChoicePromptItem::new("Allow all tools this session").shortcut('a'),
ChoicePromptItem::new("Deny").shortcut('n').danger(),
],
)
.selected(selected)
.indent(2)
.marker("❯")
.title_color(TN_YELLOW)
.text_color(TN_FG)
.muted_color(TN_GRAY)
.danger_color(TN_RED)
.selected_colors(TN_FG, SURFACE_SELECTED)
.hint("Enter select · ↑/↓ · 1–3 · Esc")
}
fn approval_overlay_y_offset(screen_height: usize, row_count: usize, rows_below: usize) -> u16 {
screen_height
.saturating_sub(rows_below)
.saturating_sub(row_count)
.min(u16::MAX as usize) as u16
}
#[cfg(test)]
mod tests;