4runr-os 2.9.72 → 2.9.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -341,6 +341,8 @@ impl GatewayOption {
341
341
  pub struct SetupPortalState {
342
342
  pub selected_option: GatewayOption,
343
343
  pub detecting: bool,
344
+ /// When detection started (for timeout so portal never stays frozen)
345
+ pub detecting_since: Option<std::time::Instant>,
344
346
  pub detection_result: Option<DetectionResult>,
345
347
  pub error: Option<String>,
346
348
  pub list_state: ratatui::widgets::ListState,
@@ -372,6 +374,7 @@ impl Default for SetupPortalState {
372
374
  Self {
373
375
  selected_option: GatewayOption::LocalBundle,
374
376
  detecting: false,
377
+ detecting_since: None,
375
378
  detection_result: None,
376
379
  error: None,
377
380
  list_state,
@@ -405,6 +408,7 @@ impl SetupPortalState {
405
408
 
406
409
  pub fn reset(&mut self) {
407
410
  self.detecting = false;
411
+ self.detecting_since = None;
408
412
  self.detection_result = None;
409
413
  self.error = None;
410
414
  }
@@ -647,11 +651,13 @@ impl App {
647
651
  self.state.setup_portal.reset();
648
652
  if let Some(ws) = ws_client {
649
653
  self.state.setup_portal.detecting = true;
654
+ self.state.setup_portal.detecting_since = Some(std::time::Instant::now());
650
655
  if let Ok(id) = ws.send_command("setup.detect", None) {
651
656
  self.state.pending_setup_detect_id = Some(id);
652
657
  self.add_log("[SETUP] Detecting Gateway options...".to_string());
653
658
  } else {
654
659
  self.state.setup_portal.detecting = false;
660
+ self.state.setup_portal.detecting_since = None;
655
661
  self.state.setup_portal.error = Some("Failed to start detection".to_string());
656
662
  }
657
663
  }
@@ -796,11 +802,13 @@ impl App {
796
802
  // Auto-detect Gateway options when portal opens
797
803
  if let Some(ws) = ws_client {
798
804
  self.state.setup_portal.detecting = true;
805
+ self.state.setup_portal.detecting_since = Some(std::time::Instant::now());
799
806
  if let Ok(id) = ws.send_command("setup.detect", None) {
800
807
  self.state.pending_setup_detect_id = Some(id);
801
808
  self.add_log("[SETUP] Detecting Gateway options...".to_string());
802
809
  } else {
803
810
  self.state.setup_portal.detecting = false;
811
+ self.state.setup_portal.detecting_since = None;
804
812
  self.state.setup_portal.error = Some("Failed to start detection".to_string());
805
813
  }
806
814
  }
@@ -1832,6 +1840,7 @@ impl App {
1832
1840
  // ESC always closes the portal (even when detecting), so user can always get out
1833
1841
  if key.code == KeyCode::Esc {
1834
1842
  self.state.setup_portal.detecting = false;
1843
+ self.state.setup_portal.detecting_since = None;
1835
1844
  self.state.navigation.navigate_to_base(Screen::Main);
1836
1845
  self.add_log("[NAV] Setup Portal closed".to_string());
1837
1846
  self.request_immediate_render("setup_portal_close");
@@ -1,6 +1,6 @@
1
1
  use anyhow::Result;
2
2
  use crossterm::cursor;
3
- use crossterm::event::{self, Event, KeyEventKind, MouseEventKind};
3
+ use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseEventKind};
4
4
  use crossterm::execute;
5
5
  use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, Clear, ClearType};
6
6
  use ratatui::prelude::*;
@@ -370,6 +370,7 @@ fn main() -> Result<()> {
370
370
  if Some(&resp.id) == app.state.pending_setup_detect_id.as_ref() {
371
371
  app.state.pending_setup_detect_id = None;
372
372
  app.state.setup_portal.detecting = false;
373
+ app.state.setup_portal.detecting_since = None;
373
374
 
374
375
  use crate::app::{DetectionResult, BundleStatus, CloudStatus};
375
376
 
@@ -404,6 +405,7 @@ fn main() -> Result<()> {
404
405
  let running = obj.get("running").and_then(|v| v.as_bool()).unwrap_or(false);
405
406
 
406
407
  app.state.setup_portal.detecting = false;
408
+ app.state.setup_portal.detecting_since = None;
407
409
  // Note: We don't update detection_result here anymore
408
410
  // The new system uses setup.detect for comprehensive detection
409
411
 
@@ -524,6 +526,7 @@ fn main() -> Result<()> {
524
526
  else if Some(&resp.id) == app.state.pending_setup_detect_id.as_ref() {
525
527
  app.state.pending_setup_detect_id = None;
526
528
  app.state.setup_portal.detecting = false;
529
+ app.state.setup_portal.detecting_since = None;
527
530
  app.state.setup_portal.error = Some(error_msg.clone());
528
531
  app.add_log(format!("✗ [{}] Setup detection failed: {}", short_id, error_msg));
529
532
  }
@@ -531,6 +534,7 @@ fn main() -> Result<()> {
531
534
  else if Some(&resp.id) == app.state.pending_bundle_check_id.as_ref() {
532
535
  app.state.pending_bundle_check_id = None;
533
536
  app.state.setup_portal.detecting = false;
537
+ app.state.setup_portal.detecting_since = None;
534
538
  app.state.setup_portal.error = Some(error_msg.clone());
535
539
  app.add_log(format!("✗ [{}] Bundle check failed: {}", short_id, error_msg));
536
540
  }
@@ -639,11 +643,27 @@ fn main() -> Result<()> {
639
643
  previous_screen = Some(current_screen);
640
644
  }
641
645
 
646
+ // Setup Portal: if detection has been running too long, unstick so user can ESC or retry
647
+ if app.state.navigation.current_screen() == &screens::Screen::SetupPortal
648
+ && app.state.setup_portal.detecting
649
+ {
650
+ if let Some(since) = app.state.setup_portal.detecting_since {
651
+ if since.elapsed() >= Duration::from_secs(8) {
652
+ app.state.setup_portal.detecting = false;
653
+ app.state.setup_portal.detecting_since = None;
654
+ app.state.setup_portal.error = Some("Detection timed out. Press ESC to close or try again.".to_string());
655
+ app.add_log("[SETUP] Detection timed out".to_string());
656
+ app.request_render("setup_detect_timeout");
657
+ }
658
+ }
659
+ }
660
+
642
661
  // ┌─────────────────────────────────────────────────────────┐
643
662
  // │ STEP 4: CHECK FOR INPUT (non-blocking with timeout) │
644
663
  // └─────────────────────────────────────────────────────────┘
645
664
  // OPTIMIZATION: Reduced poll timeout to 1ms for faster input detection
646
665
  // This allows loop to run ~1000 times/second when idle, but input is detected immediately
666
+ // Handle ESC on both Press and Release so we never miss it (e.g. when stuck on "Detecting...")
647
667
  if crossterm::event::poll(Duration::from_millis(1))? {
648
668
  let on_setup = app.state.navigation.current_screen() == &screens::Screen::SetupPortal;
649
669
  match event::read()? {
@@ -651,7 +671,9 @@ fn main() -> Result<()> {
651
671
  if on_setup {
652
672
  debug_log::log_input("Key", &format!("{:?}", key));
653
673
  }
654
- if key.kind == KeyEventKind::Press {
674
+ let should_handle = key.kind == KeyEventKind::Press
675
+ || (key.code == KeyCode::Esc && key.kind == KeyEventKind::Release);
676
+ if should_handle {
655
677
  if app.handle_input(key, &mut io_handler, ws_client.as_ref())? {
656
678
  break; // Exit requested
657
679
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.9.72",
3
+ "version": "2.9.73",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.72: TUI version from CLI env, Setup Portal no console spam. ⚠️ Pre-MVP / Development Phase",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.73: Setup Portal no longer freezes on Detecting (timeout + ESC-on-Release). ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",