4runr-os 2.10.43 → 2.10.45

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.
@@ -311,7 +311,7 @@ impl Default for PortalMonitoringState {
311
311
  viewport_lines: 18,
312
312
  last_refresh: None,
313
313
  auto_refresh_interval: Duration::from_secs(5),
314
- auto_refresh_enabled: true,
314
+ auto_refresh_enabled: false,
315
315
  last_http_for_delta: None,
316
316
  last_instant_for_delta: None,
317
317
  section_overrides: HashMap::new(),
@@ -936,6 +936,17 @@ impl App {
936
936
  self.request_immediate_render("portal_obs_scroll");
937
937
  }
938
938
 
939
+ fn cancel_portal_monitoring_requests(&mut self) {
940
+ self.state.pending_gateway_observability_id = None;
941
+ self.state.pending_monitoring_refresh_id = None;
942
+ self.state.pending_monitoring_logs_id = None;
943
+ self.state.pending_monitoring_drill_id = None;
944
+ self.state.portal_monitoring.loading = false;
945
+ self.state.portal_monitoring.section_refresh_loading = None;
946
+ self.state.portal_monitoring.logs_fetch_loading = false;
947
+ self.state.portal_monitoring.metrics_drill_loading = false;
948
+ }
949
+
939
950
  /// CLI ↔ TUI WebSocket lost: drop Gateway link UI and leave Portal Monitoring / Connection Portal so the session does not appear still "connected".
940
951
  pub fn on_cli_backend_disconnect(&mut self) {
941
952
  use crate::screens::Screen;
@@ -2535,7 +2546,9 @@ impl App {
2535
2546
 
2536
2547
  // M — Portal Monitoring (after successful connect)
2537
2548
  KeyCode::Char('m') | KeyCode::Char('M')
2538
- if self.state.connection_portal.connection_success =>
2549
+ if self.state.connection_portal.connection_success
2550
+ || (matches!(self.state.operation_mode, OperationMode::Connected)
2551
+ && self.state.gateway_url.is_some()) =>
2539
2552
  {
2540
2553
  self.state
2541
2554
  .navigation
@@ -2750,37 +2763,13 @@ impl App {
2750
2763
  self.request_immediate_render("portal_help_open");
2751
2764
  return Ok(false);
2752
2765
  }
2753
- // R is context-sensitive: Overview full `gateway.observability` snapshot; any other
2754
- // section → `monitoring.refresh` for that section only (cheaper; matches expanded detail).
2766
+ // R is deliberately global: refresh the full snapshot without changing the current view.
2755
2767
  if key.code == KeyCode::Char('r') || key.code == KeyCode::Char('R') {
2756
2768
  if let Some(ws) = ws_client {
2757
- let sections = MonitoringSection::all();
2758
- let idx = self
2759
- .state
2760
- .advanced_monitoring
2761
- .monitoring_state
2762
- .selected_index;
2763
- let current = sections.get(idx).copied();
2764
2769
  self.state.portal_observability_last_poll = None;
2765
- self.state.pending_gateway_observability_id = None;
2766
- self.state.pending_monitoring_refresh_id = None;
2767
- self.state.pending_monitoring_logs_id = None;
2768
- self.state.pending_monitoring_drill_id = None;
2769
- self.state.portal_monitoring.loading = false;
2770
- self.state.portal_monitoring.section_refresh_loading = None;
2771
- self.state.portal_monitoring.logs_fetch_loading = false;
2772
- self.state.portal_monitoring.metrics_drill = None;
2773
- self.state.portal_monitoring.metrics_drill_lines.clear();
2774
- self.state.portal_monitoring.metrics_drill_loading = false;
2770
+ self.cancel_portal_monitoring_requests();
2775
2771
  self.state.portal_monitoring.last_refresh = Some(std::time::Instant::now());
2776
- match current {
2777
- Some(MonitoringSection::Overview) | None => {
2778
- self.begin_portal_observability_request(ws);
2779
- }
2780
- Some(sec) => {
2781
- self.begin_monitoring_refresh_request(ws, sec);
2782
- }
2783
- }
2772
+ self.begin_portal_observability_request(ws);
2784
2773
  self.request_immediate_render("portal_obs_refresh");
2785
2774
  } else {
2786
2775
  self.state.portal_monitoring.error =
@@ -2790,6 +2779,29 @@ impl App {
2790
2779
  return Ok(false);
2791
2780
  }
2792
2781
  if key.code == KeyCode::Char('l') || key.code == KeyCode::Char('L') {
2782
+ let logs_open = self
2783
+ .state
2784
+ .advanced_monitoring
2785
+ .monitoring_state
2786
+ .selected_section
2787
+ == Some(MonitoringSection::Logs)
2788
+ && self
2789
+ .state
2790
+ .advanced_monitoring
2791
+ .monitoring_state
2792
+ .sections
2793
+ .get(&MonitoringSection::Logs)
2794
+ .map(|section| section.expanded)
2795
+ .unwrap_or(false);
2796
+ if logs_open && !self.state.portal_monitoring.logs_fetch_loading {
2797
+ self.state
2798
+ .advanced_monitoring
2799
+ .monitoring_state
2800
+ .collapse_section(MonitoringSection::Logs);
2801
+ self.state.portal_monitoring.scroll_offset = 0;
2802
+ self.request_immediate_render("portal_monitoring_logs_close");
2803
+ return Ok(false);
2804
+ }
2793
2805
  self.state
2794
2806
  .advanced_monitoring
2795
2807
  .monitoring_state
@@ -2799,6 +2811,7 @@ impl App {
2799
2811
  .monitoring_state
2800
2812
  .expand_section(MonitoringSection::Logs);
2801
2813
  if let Some(ws) = ws_client {
2814
+ self.cancel_portal_monitoring_requests();
2802
2815
  self.begin_monitoring_logs_request(ws);
2803
2816
  let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
2804
2817
  let sw = self.state.portal_monitoring.summary_clip_width.max(24);
@@ -2878,6 +2891,7 @@ impl App {
2878
2891
  MonitoringSection::Dependencies,
2879
2892
  );
2880
2893
  if let Some(ws) = ws_client {
2894
+ self.cancel_portal_monitoring_requests();
2881
2895
  self.begin_dependencies_detail_drill(ws);
2882
2896
  self.request_immediate_render("portal_deps_drill");
2883
2897
  } else {
@@ -2907,6 +2921,7 @@ impl App {
2907
2921
  MonitoringSection::System,
2908
2922
  );
2909
2923
  if let Some(ws) = ws_client {
2924
+ self.cancel_portal_monitoring_requests();
2910
2925
  self.begin_system_diagnostics_request(ws);
2911
2926
  self.request_immediate_render("portal_system_diagnostics");
2912
2927
  } else {
@@ -2935,6 +2950,7 @@ impl App {
2935
2950
  MonitoringSection::Metrics,
2936
2951
  );
2937
2952
  if let Some(ws) = ws_client {
2953
+ self.cancel_portal_monitoring_requests();
2938
2954
  let panel = self
2939
2955
  .state
2940
2956
  .portal_monitoring
@@ -3004,7 +3020,6 @@ impl App {
3004
3020
  .monitoring_state
3005
3021
  .selected_section
3006
3022
  == Some(MonitoringSection::Logs)
3007
- && self.state.portal_monitoring.scroll_offset > 0
3008
3023
  {
3009
3024
  self.state.portal_monitoring.scroll_offset =
3010
3025
  self.state.portal_monitoring.scroll_offset.saturating_sub(1);
@@ -3025,7 +3040,6 @@ impl App {
3025
3040
  .monitoring_state
3026
3041
  .selected_section
3027
3042
  == Some(MonitoringSection::Logs)
3028
- && self.state.portal_monitoring.scroll_offset < max_scroll
3029
3043
  {
3030
3044
  self.state.portal_monitoring.scroll_offset =
3031
3045
  (self.state.portal_monitoring.scroll_offset + 1).min(max_scroll);
@@ -890,19 +890,46 @@ fn main() -> Result<()> {
890
890
  .to_string(),
891
891
  );
892
892
  }
893
+ let preserve_scroll =
894
+ app.state.portal_monitoring.scroll_offset;
895
+ let preserve_metrics_drill =
896
+ app.state.portal_monitoring.metrics_drill;
897
+ let preserve_metrics_drill_lines = app
898
+ .state
899
+ .portal_monitoring
900
+ .metrics_drill_lines
901
+ .clone();
902
+ let preserve_logs = app
903
+ .state
904
+ .portal_monitoring
905
+ .section_overrides
906
+ .get(&crate::monitoring::MonitoringSection::Logs)
907
+ .cloned();
893
908
  app.state.portal_monitoring.last_updated =
894
909
  Some(snapshot);
895
910
  app.state.portal_monitoring.content_lines = lines;
896
911
  app.state.portal_monitoring.section_overrides.clear();
912
+ if let Some(logs) = preserve_logs {
913
+ app.state
914
+ .portal_monitoring
915
+ .section_overrides
916
+ .insert(
917
+ crate::monitoring::MonitoringSection::Logs,
918
+ logs,
919
+ );
920
+ }
897
921
  app.state.portal_monitoring.section_refresh_loading =
898
922
  None;
899
923
  app.state.portal_monitoring.logs_fetch_loading = false;
900
- app.state.portal_monitoring.metrics_drill = None;
901
- app.state.portal_monitoring.metrics_drill_lines.clear();
924
+ app.state.portal_monitoring.metrics_drill =
925
+ preserve_metrics_drill;
926
+ app.state.portal_monitoring.metrics_drill_lines =
927
+ preserve_metrics_drill_lines;
902
928
  app.state.portal_monitoring.metrics_drill_loading =
903
929
  false;
904
930
  app.state.portal_monitoring.error = None;
905
- app.state.portal_monitoring.scroll_offset = 0;
931
+ app.state.portal_monitoring.scroll_offset =
932
+ preserve_scroll;
906
933
  } else {
907
934
  app.state.portal_monitoring.error = Some(
908
935
  "Unexpected observability response.".to_string(),
@@ -878,7 +878,7 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
878
878
  ]);
879
879
 
880
880
  let keys_hint = Line::from(vec![Span::styled(
881
- "R sect/full · H trends · E export · Metrics + →/← drill · Dep ▼ + T detail · Sys ▼ + S diagnostics · L logs · A auto",
881
+ "R full refresh · L logs · wheel/Pg scroll · H metrics trends · → metrics drill · T dependencies · S system · A live",
882
882
  Style::default().fg(TEXT_DIM),
883
883
  )]);
884
884
 
@@ -983,7 +983,7 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
983
983
  Span::styled("expand ", Style::default().fg(TEXT_DIM)),
984
984
  Span::styled("│ ", Style::default().fg(SEPARATOR_COLOR)),
985
985
  Span::styled("R ", Style::default().fg(CYBER_CYAN).bold()),
986
- Span::styled("full/sect ", Style::default().fg(TEXT_DIM)),
986
+ Span::styled("refresh ", Style::default().fg(TEXT_DIM)),
987
987
  Span::styled("│ ", Style::default().fg(SEPARATOR_COLOR)),
988
988
  Span::styled("L ", Style::default().fg(CYBER_CYAN).bold()),
989
989
  Span::styled("logs ", Style::default().fg(TEXT_DIM)),
@@ -1032,16 +1032,19 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
1032
1032
  )]),
1033
1033
  Line::from(""),
1034
1034
  Line::from("Navigation"),
1035
- Line::from(" ↑/↓ select section Enter/Space expand/collapse PgUp/PgDn scroll"),
1035
+ Line::from(
1036
+ " ↑/↓ select section Enter/Space expand/collapse PgUp/PgDn/mouse scroll",
1037
+ ),
1038
+ Line::from(" When Logs is selected, ↑/↓ scroll log content first."),
1036
1039
  Line::from(" ESC close help / return to Main"),
1037
1040
  Line::from(""),
1038
1041
  Line::from("Actions"),
1039
- Line::from(" R refresh current row (Overview = full snapshot)"),
1040
- Line::from(
1041
- " L fetch Gateway logs H toggle trends E export JSON to current directory",
1042
- ),
1043
- Line::from(" Metrics expanded: cycle HTTP/Runs/Queue/SSE drill, leave drill"),
1044
- Line::from(" Dependencies expanded: T detail System expanded: S diagnostics"),
1042
+ Line::from(" R full snapshot refresh without closing your current section"),
1043
+ Line::from(" L jump to Logs + fetch Gateway logs"),
1044
+ Line::from(" H jump to Metrics + toggle trends Metrics drill ← leave drill"),
1045
+ Line::from(" T jump to Dependencies detail S jump to System diagnostics"),
1046
+ Line::from(" A toggle live auto-refresh (off by default for stable reading)"),
1047
+ Line::from(" E export diagnostics JSON to the current working directory"),
1045
1048
  Line::from(""),
1046
1049
  Line::from("Recovery"),
1047
1050
  Line::from(
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.10.43",
3
+ "version": "2.10.45",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.43: Makes Portal Monitoring footer shortcuts jump to their sections and fixes log scrolling. v2.10.42: Prevents stale global MK3 binaries from masking packaged TUI fixes.",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.10.45: Fixes Portal Monitoring log toggle, log-bottom scroll, and monitor re-entry from linked Gateway sessions. v2.10.44: Stabilizes Portal Monitoring reading mode, global refresh, logs, and shortcut behavior.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",