feat: add lesson export and import functionality
- Implemented `LessonExport` and `LessonImport` actions in the action module. - Added corresponding command parsing for lesson export and import. - Created functions to handle lesson export and import in the memory module. - Updated state management to reflect changes after lesson operations. - Introduced deferred operations for handling asynchronous tasks in the event loop. - Enhanced the tool execution context to include graduated checks for file operations. - Added OAuth support with PKCE for secure authorization flows. - Implemented a loopback server for handling OAuth redirects. - Refactored various modules to improve code organization and maintainability.
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
|
||||
pub fn get_agent_count(state: &AppStateRest) -> usize {
|
||||
state.session_runtime.as_ref().map_or(0, |rt| rt.subagent_queue)
|
||||
}
|
||||
|
||||
pub fn get_active_agents(state: &AppStateRest) -> Vec<String> {
|
||||
let count = get_agent_count(state);
|
||||
(0..count).map(|i| format!("agent-{}", i)).collect()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn handle_bash_submit(state: &mut AppStateRest, command: String) {
|
||||
if !command.is_empty() {
|
||||
let _job = crate::app::bgbash::job::spawn_bash_job(command);
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_bash_dismiss(state: &mut AppStateRest) {
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn handle_editor_input(state: &mut AppStateRest, text: String) {
|
||||
let _ = text;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn handle_editor_dismiss(state: &mut AppStateRest) {
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
|
||||
pub const EFFORT_LEVELS: &[&str] = &["low", "medium", "high", "xhigh", "max"];
|
||||
|
||||
pub fn current_effort(_state: &AppStateRest) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn cycle_effort(state: &mut AppStateRest) {
|
||||
let current = current_effort(state);
|
||||
let next = (current + 1) % EFFORT_LEVELS.len();
|
||||
let _ = next;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use crate::app::runtime::actions::Action;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub const HELP_TEXT: &str = "\
|
||||
Keybindings:
|
||||
Ctrl+C Quit
|
||||
Ctrl+D Close overlay
|
||||
Ctrl+H Help
|
||||
Ctrl+P Settings
|
||||
Ctrl+A Toggle yolo arm
|
||||
Ctrl+B Bash panel
|
||||
Ctrl+S Session hub
|
||||
Ctrl+T Todo panel
|
||||
Ctrl+W Workflow panel
|
||||
Ctrl+K Key input
|
||||
Ctrl+L Learning dashboard
|
||||
Ctrl+U Usage dashboard
|
||||
Esc Close overlay
|
||||
Enter Submit / confirm
|
||||
|
||||
Slash commands:
|
||||
/help Show this help
|
||||
/quit Quit session
|
||||
/resume Resume from overlay
|
||||
/mode <name> Switch mode (chat, agents, bash, workflow)
|
||||
/lesson <text> Create a lesson
|
||||
/lesson list List lessons
|
||||
/lesson export Export lessons
|
||||
/lesson import Import lessons
|
||||
/clear Clear transcript
|
||||
/save Save session";
|
||||
|
||||
pub fn handle_help_action(action: &Action) -> Action {
|
||||
match action {
|
||||
Action::CloseOverlay => Action::CloseOverlay,
|
||||
_ => Action::OpenOverlay(Overlay::Help),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
|
||||
pub fn handle_key_text(state: &mut AppStateRest, text: String) {
|
||||
state.input.buffer = text;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
|
||||
pub const LOADING_MESSAGES: &[&str] = &[
|
||||
"processing...",
|
||||
"thinking...",
|
||||
"working...",
|
||||
"almost done...",
|
||||
];
|
||||
|
||||
pub fn resolve_loading(state: &mut AppStateRest) {
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn connect_mcp(state: &mut AppStateRest, server_name: &str) {
|
||||
let _ = server_name;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn disconnect_mcp(state: &mut AppStateRest, server_name: &str) {
|
||||
let _ = server_name;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn handle_mcp_dismiss(state: &mut AppStateRest) {
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub mod agents;
|
||||
#[expect(dead_code)]
|
||||
pub mod bash;
|
||||
#[expect(dead_code)]
|
||||
pub mod editor;
|
||||
#[expect(dead_code)]
|
||||
pub mod effort;
|
||||
#[expect(dead_code)]
|
||||
pub mod help;
|
||||
#[expect(dead_code)]
|
||||
pub mod key_input;
|
||||
#[expect(dead_code)]
|
||||
pub mod loading;
|
||||
#[expect(dead_code)]
|
||||
pub mod mcp;
|
||||
#[expect(dead_code)]
|
||||
pub mod onboard;
|
||||
#[expect(dead_code)]
|
||||
pub mod onboard_provider;
|
||||
#[expect(dead_code)]
|
||||
pub mod picker;
|
||||
#[expect(dead_code)]
|
||||
pub mod quit_confirm;
|
||||
#[expect(dead_code)]
|
||||
pub mod rewind;
|
||||
#[expect(dead_code)]
|
||||
pub mod security;
|
||||
#[expect(dead_code)]
|
||||
pub mod session_hub;
|
||||
#[expect(dead_code)]
|
||||
pub mod settings;
|
||||
#[expect(dead_code)]
|
||||
pub mod todo;
|
||||
#[expect(dead_code)]
|
||||
pub mod workflow;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn complete_onboarding(state: &mut AppStateRest) {
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn skip_onboarding(state: &mut AppStateRest) {
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::model::settings::Settings;
|
||||
|
||||
pub const PROVIDERS: &[&str] = &["OpenRouter", "Anthropic", "OpenAI"];
|
||||
|
||||
pub fn set_provider(settings: &mut Settings, provider: &str) {
|
||||
settings.provider = match provider {
|
||||
"OpenRouter" => "openrouter".to_string(),
|
||||
"Anthropic" => "anthropic".to_string(),
|
||||
"OpenAI" => "openai".to_string(),
|
||||
_ => "openrouter".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_api_key(settings: &mut Settings, key: String) {
|
||||
settings.api_key = Some(key);
|
||||
}
|
||||
|
||||
pub fn set_model(settings: &mut Settings, model: String) {
|
||||
settings.model = model;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn pick_item(state: &mut AppStateRest, index: usize) {
|
||||
let _ = index;
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn update_filter(state: &mut AppStateRest, filter: String) {
|
||||
state.input.buffer = filter;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
use crate::app::runtime::actions::Action;
|
||||
|
||||
pub fn handle_quit_confirm(yes: bool) -> Action {
|
||||
if yes {
|
||||
Action::ForceQuit
|
||||
} else {
|
||||
Action::CloseOverlay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
|
||||
pub fn rewind_to(state: &mut AppStateRest, index: usize) {
|
||||
let _ = index;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn rewind_count(state: &AppStateRest) -> usize {
|
||||
state.transcript_cache.messages.len().min(5)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::app::runtime::actions::Action;
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
|
||||
pub fn toggle_security_arm(state: &mut AppStateRest) {
|
||||
state.misc.security_armed = !state.misc.security_armed;
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn acknowledge_security(state: &mut AppStateRest) {
|
||||
if !state.misc.security_acknowledged {
|
||||
state.misc.security_acknowledged = true;
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_security_action(state: &mut AppStateRest, action: &Action) {
|
||||
if let Action::ToggleYoloArm = action {
|
||||
toggle_security_arm(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::model::session::Session;
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn load_sessions(state: &mut AppStateRest) {
|
||||
state.sessions = Session::list(&state.session_dir);
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn select_session(state: &mut AppStateRest, session_id: &str) {
|
||||
if let Some(session) = state.sessions.iter().find(|s| s.id == session_id) {
|
||||
let display = crate::app::state::rest::ChatMessageDisplay::new(
|
||||
crate::dto::chat::message::Role::System,
|
||||
format!("switched to session: {}", session.title),
|
||||
);
|
||||
state.push_transcript(display);
|
||||
state.misc.overlay = Overlay::None;
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
use crate::app::runtime::actions::Action;
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::model::settings::{Settings, InternetMode};
|
||||
|
||||
pub fn apply_settings_action(state: &mut AppStateRest, action: &Action) {
|
||||
if let Action::ToggleYoloArm = action {
|
||||
state.misc.yolo_armed = !state.misc.yolo_armed;
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle_internet_mode(settings: &mut Settings) {
|
||||
settings.internet_mode = match settings.internet_mode {
|
||||
InternetMode::Off => InternetMode::ReadOnly,
|
||||
InternetMode::ReadOnly => InternetMode::Full,
|
||||
InternetMode::Full => InternetMode::Off,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cycle_review_enabled(settings: &mut Settings) {
|
||||
settings.review_enabled = !settings.review_enabled;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn handle_todo_toggle(state: &mut AppStateRest) {
|
||||
if state.misc.overlay == Overlay::Todo {
|
||||
state.misc.overlay = Overlay::None;
|
||||
} else {
|
||||
state.misc.overlay = Overlay::Todo;
|
||||
}
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::app::state::rest::AppStateRest;
|
||||
use crate::app::state::types::Overlay;
|
||||
|
||||
pub fn handle_workflow_dismiss(state: &mut AppStateRest) {
|
||||
if state.misc.overlay == Overlay::Workflow {
|
||||
state.misc.overlay = Overlay::None;
|
||||
}
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
pub fn workflow_status(state: &AppStateRest) -> &str {
|
||||
if state.misc.overlay == Overlay::Workflow {
|
||||
"active"
|
||||
} else {
|
||||
"idle"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user