4runr-os 2.9.134 → 2.9.136
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.
- package/apps/gateway/package-lock.json +318 -318
- package/dist/tui-handlers.js +197 -27
- package/dist/tui-handlers.js.map +1 -1
- package/mk3-tui/src/app.rs +23 -8
- package/mk3-tui/src/main.rs +14 -5
- package/mk3-tui/src/ui/connection_portal.rs +74 -13
- package/package.json +2 -2
package/mk3-tui/src/app.rs
CHANGED
|
@@ -113,6 +113,8 @@ pub struct ConnectionPortalState {
|
|
|
113
113
|
pub username: String,
|
|
114
114
|
pub focused_field: PortalField,
|
|
115
115
|
pub connecting: bool,
|
|
116
|
+
/// When `connecting` became true (for elapsed timer in the portal UI).
|
|
117
|
+
pub connecting_started: Option<Instant>,
|
|
116
118
|
pub connection_success: bool,
|
|
117
119
|
pub error: Option<String>,
|
|
118
120
|
pub last_successful_url: Option<String>,
|
|
@@ -127,6 +129,7 @@ impl Default for ConnectionPortalState {
|
|
|
127
129
|
username: String::new(),
|
|
128
130
|
focused_field: PortalField::GatewayUrl,
|
|
129
131
|
connecting: false,
|
|
132
|
+
connecting_started: None,
|
|
130
133
|
connection_success: false,
|
|
131
134
|
error: None,
|
|
132
135
|
last_successful_url: None,
|
|
@@ -141,10 +144,21 @@ impl ConnectionPortalState {
|
|
|
141
144
|
self.username = String::new();
|
|
142
145
|
self.focused_field = PortalField::GatewayUrl;
|
|
143
146
|
self.connecting = false;
|
|
147
|
+
self.connecting_started = None;
|
|
144
148
|
self.connection_success = false;
|
|
145
149
|
self.error = None;
|
|
146
150
|
self.activity_log.clear();
|
|
147
151
|
}
|
|
152
|
+
|
|
153
|
+
pub fn begin_connecting(&mut self) {
|
|
154
|
+
self.connecting = true;
|
|
155
|
+
self.connecting_started = Some(Instant::now());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
pub fn finish_connecting(&mut self) {
|
|
159
|
+
self.connecting = false;
|
|
160
|
+
self.connecting_started = None;
|
|
161
|
+
}
|
|
148
162
|
|
|
149
163
|
pub fn toggle_field(&mut self) {
|
|
150
164
|
self.focused_field = match self.focused_field {
|
|
@@ -160,8 +174,9 @@ impl ConnectionPortalState {
|
|
|
160
174
|
message,
|
|
161
175
|
});
|
|
162
176
|
|
|
163
|
-
// Keep
|
|
164
|
-
|
|
177
|
+
// Keep last N lines so the portal can show a full connect trace on small terminals
|
|
178
|
+
const MAX_ACTIVITY: usize = 80;
|
|
179
|
+
if self.activity_log.len() > MAX_ACTIVITY {
|
|
165
180
|
self.activity_log.remove(0);
|
|
166
181
|
}
|
|
167
182
|
}
|
|
@@ -1924,7 +1939,7 @@ impl App {
|
|
|
1924
1939
|
KeyCode::Esc => {
|
|
1925
1940
|
// Drop stale connect attempt + UI state so the next open is not "pre-broken".
|
|
1926
1941
|
self.state.pending_gateway_connect_id = None;
|
|
1927
|
-
self.state.connection_portal.
|
|
1942
|
+
self.state.connection_portal.finish_connecting();
|
|
1928
1943
|
self.state.connection_portal.error = None;
|
|
1929
1944
|
self.state.connection_portal.activity_log.clear();
|
|
1930
1945
|
self.state.navigation.navigate_to_base(Screen::Main);
|
|
@@ -1953,7 +1968,7 @@ impl App {
|
|
|
1953
1968
|
self.state.gateway_url = None;
|
|
1954
1969
|
self.state.connected = false;
|
|
1955
1970
|
self.state.gateway_healthy = false;
|
|
1956
|
-
self.state.connection_portal.
|
|
1971
|
+
self.state.connection_portal.finish_connecting();
|
|
1957
1972
|
self.state.connection_portal.connection_success = false;
|
|
1958
1973
|
self.state.connection_portal.error = None;
|
|
1959
1974
|
self.state.pending_gateway_connect_id = None;
|
|
@@ -2009,8 +2024,8 @@ impl App {
|
|
|
2009
2024
|
}
|
|
2010
2025
|
|
|
2011
2026
|
// Connect to Gateway
|
|
2012
|
-
self.state.connection_portal.connecting = true;
|
|
2013
2027
|
self.state.connection_portal.error = None;
|
|
2028
|
+
self.state.connection_portal.begin_connecting();
|
|
2014
2029
|
|
|
2015
2030
|
if let Some(ws) = ws_client {
|
|
2016
2031
|
let connect_data = serde_json::json!({
|
|
@@ -2025,13 +2040,13 @@ impl App {
|
|
|
2025
2040
|
self.state.pending_gateway_connect_id = Some(id);
|
|
2026
2041
|
}
|
|
2027
2042
|
Err(e) => {
|
|
2028
|
-
self.state.connection_portal.
|
|
2043
|
+
self.state.connection_portal.finish_connecting();
|
|
2029
2044
|
self.state.connection_portal.error = Some(format!("Failed to connect: {}", e));
|
|
2030
2045
|
self.add_log(format!("[ERROR] Gateway connection failed: {}", e));
|
|
2031
2046
|
}
|
|
2032
2047
|
}
|
|
2033
2048
|
} else {
|
|
2034
|
-
self.state.connection_portal.
|
|
2049
|
+
self.state.connection_portal.finish_connecting();
|
|
2035
2050
|
self.state.connection_portal.error = Some("WebSocket not connected".to_string());
|
|
2036
2051
|
self.add_log("[ERROR] WebSocket not connected".to_string());
|
|
2037
2052
|
}
|
|
@@ -2154,7 +2169,7 @@ impl App {
|
|
|
2154
2169
|
self.state.connection_portal.username.clear();
|
|
2155
2170
|
self.state.connection_portal.focused_field = PortalField::GatewayUrl;
|
|
2156
2171
|
self.state.connection_portal.connection_success = false;
|
|
2157
|
-
self.state.connection_portal.
|
|
2172
|
+
self.state.connection_portal.finish_connecting();
|
|
2158
2173
|
|
|
2159
2174
|
if let Some(ref det) = self.state.setup_portal.detection_result {
|
|
2160
2175
|
let now = wall_clock_hms();
|
package/mk3-tui/src/main.rs
CHANGED
|
@@ -82,6 +82,7 @@ fn main() -> Result<()> {
|
|
|
82
82
|
// Track real time for uptime (not from tick counter)
|
|
83
83
|
let start_time = Instant::now();
|
|
84
84
|
let mut last_tick = Instant::now();
|
|
85
|
+
let mut portal_connect_ui_tick = Instant::now();
|
|
85
86
|
|
|
86
87
|
// Track previous screen to detect portal navigation
|
|
87
88
|
let mut previous_screen: Option<crate::screens::Screen> = None;
|
|
@@ -101,7 +102,7 @@ fn main() -> Result<()> {
|
|
|
101
102
|
// Check if portal is active - if so, skip base screen state updates
|
|
102
103
|
// This prevents base screen from affecting portal performance
|
|
103
104
|
use crate::screens::Screen;
|
|
104
|
-
let current_screen = app.state.navigation.current_screen();
|
|
105
|
+
let current_screen = app.state.navigation.current_screen().clone();
|
|
105
106
|
let is_portal_active = matches!(
|
|
106
107
|
current_screen,
|
|
107
108
|
Screen::ConnectionPortal | Screen::SetupPortal | Screen::PortalMonitoring
|
|
@@ -135,6 +136,14 @@ fn main() -> Result<()> {
|
|
|
135
136
|
app.request_render("animation_tick"); // Request render for animation update
|
|
136
137
|
}
|
|
137
138
|
}
|
|
139
|
+
|
|
140
|
+
// Connection Portal: redraw while waiting on gateway.connect so the elapsed timer advances
|
|
141
|
+
if matches!(current_screen, Screen::ConnectionPortal) && app.state.connection_portal.connecting {
|
|
142
|
+
if portal_connect_ui_tick.elapsed() >= Duration::from_millis(500) {
|
|
143
|
+
portal_connect_ui_tick = Instant::now();
|
|
144
|
+
app.request_immediate_render("portal_connecting_tick");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
138
147
|
|
|
139
148
|
// ┌─────────────────────────────────────────────────────────┐
|
|
140
149
|
// │ STEP 2: CHECK WEBSOCKET MESSAGES │
|
|
@@ -465,7 +474,7 @@ fn main() -> Result<()> {
|
|
|
465
474
|
|
|
466
475
|
app.state.gateway_url = Some(url.to_string());
|
|
467
476
|
app.state.connection_portal.last_successful_url = Some(url.to_string());
|
|
468
|
-
app.state.connection_portal.
|
|
477
|
+
app.state.connection_portal.finish_connecting();
|
|
469
478
|
app.state.connection_portal.connection_success = true; // Phase 1.3: Set success flag
|
|
470
479
|
// Dashboard "Demo Mode / NET" used `connected`, which was never set — align with Gateway link
|
|
471
480
|
app.state.connected = true;
|
|
@@ -596,7 +605,7 @@ fn main() -> Result<()> {
|
|
|
596
605
|
|
|
597
606
|
if is_healthy {
|
|
598
607
|
app.state.gateway_healthy = true;
|
|
599
|
-
app.state.connection_portal.
|
|
608
|
+
app.state.connection_portal.finish_connecting();
|
|
600
609
|
let suffix = match deps_ready {
|
|
601
610
|
Some(true) => " — dependencies ready",
|
|
602
611
|
Some(false) => " — Gateway up; /ready reports deps degraded",
|
|
@@ -608,7 +617,7 @@ fn main() -> Result<()> {
|
|
|
608
617
|
));
|
|
609
618
|
} else {
|
|
610
619
|
app.state.gateway_healthy = false;
|
|
611
|
-
app.state.connection_portal.
|
|
620
|
+
app.state.connection_portal.finish_connecting();
|
|
612
621
|
let error = obj.get("error").and_then(|v| v.as_str()).unwrap_or("Unknown error");
|
|
613
622
|
app.add_log(format!("⚠ [{}] Gateway health check failed: {} ({}ms)", short_id, error, latency));
|
|
614
623
|
}
|
|
@@ -660,7 +669,7 @@ fn main() -> Result<()> {
|
|
|
660
669
|
// Check if this is a response to gateway.connect command (Step 7.5)
|
|
661
670
|
else if Some(&resp.id) == app.state.pending_gateway_connect_id.as_ref() {
|
|
662
671
|
app.state.pending_gateway_connect_id = None;
|
|
663
|
-
app.state.connection_portal.
|
|
672
|
+
app.state.connection_portal.finish_connecting();
|
|
664
673
|
app.state.connection_portal.error = Some(error_msg.clone());
|
|
665
674
|
|
|
666
675
|
// Parse activity log from error response if present
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/// Portal Connection Screen (formerly Gateway Connection)
|
|
2
2
|
/// Clean, professional interface for connecting to Gateway servers
|
|
3
3
|
|
|
4
|
+
use std::time::Instant;
|
|
5
|
+
|
|
4
6
|
use ratatui::prelude::*;
|
|
5
7
|
use ratatui::widgets::{Block, Borders, Paragraph, Wrap, Clear};
|
|
6
8
|
use ratatui::layout::Alignment;
|
|
@@ -371,12 +373,16 @@ fn render_content_area(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
371
373
|
if portal.error.is_some() {
|
|
372
374
|
let err_msg = portal.error.as_ref().unwrap();
|
|
373
375
|
if !portal.activity_log.is_empty() {
|
|
374
|
-
//
|
|
376
|
+
// Compact status/error strip; most vertical space for the activity log (CLI trace).
|
|
375
377
|
use ratatui::layout::{Constraint, Direction, Layout};
|
|
376
|
-
|
|
378
|
+
let total_h = area.height;
|
|
379
|
+
let log_min: u16 = 8;
|
|
380
|
+
let err_h = (total_h / 3)
|
|
381
|
+
.max(10)
|
|
382
|
+
.min(total_h.saturating_sub(log_min));
|
|
377
383
|
let chunks = Layout::default()
|
|
378
384
|
.direction(Direction::Vertical)
|
|
379
|
-
.constraints([Constraint::
|
|
385
|
+
.constraints([Constraint::Length(err_h), Constraint::Min(log_min)])
|
|
380
386
|
.split(area);
|
|
381
387
|
render_error(f, chunks[0], err_msg);
|
|
382
388
|
render_activity_log(f, chunks[1], state);
|
|
@@ -386,7 +392,18 @@ fn render_content_area(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
386
392
|
} else if session_linked(state) {
|
|
387
393
|
render_success(f, area, state);
|
|
388
394
|
} else if portal.connecting {
|
|
389
|
-
|
|
395
|
+
if portal.activity_log.is_empty() {
|
|
396
|
+
render_connecting(f, area, state);
|
|
397
|
+
} else {
|
|
398
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
399
|
+
let strip = area.height.min(9).max(6);
|
|
400
|
+
let chunks = Layout::default()
|
|
401
|
+
.direction(Direction::Vertical)
|
|
402
|
+
.constraints([Constraint::Length(strip), Constraint::Min(4)])
|
|
403
|
+
.split(area);
|
|
404
|
+
render_connecting(f, chunks[0], state);
|
|
405
|
+
render_activity_log(f, chunks[1], state);
|
|
406
|
+
}
|
|
390
407
|
} else if !portal.activity_log.is_empty() {
|
|
391
408
|
// Show activity log with instructions when there's no error
|
|
392
409
|
render_activity_log(f, area, state);
|
|
@@ -404,24 +421,68 @@ fn render_connecting(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
404
421
|
} else {
|
|
405
422
|
url_one.as_str()
|
|
406
423
|
};
|
|
424
|
+
let elapsed_secs = portal
|
|
425
|
+
.connecting_started
|
|
426
|
+
.map(|t: Instant| t.elapsed().as_secs())
|
|
427
|
+
.unwrap_or(0);
|
|
407
428
|
let block = Block::default()
|
|
408
|
-
.title(" ⏳ CONNECTING ")
|
|
429
|
+
.title(format!(" ⏳ CONNECTING — {}s ", elapsed_secs))
|
|
409
430
|
.borders(Borders::ALL)
|
|
410
431
|
.border_style(Style::default().fg(AMBER_WARN).bold())
|
|
411
432
|
.style(Style::default().bg(BG_PANEL));
|
|
412
433
|
let inner = block.inner(area);
|
|
413
434
|
f.render_widget(block, area);
|
|
414
|
-
|
|
435
|
+
|
|
436
|
+
// Simple “pulse” bar: fill grows and wraps so the UI keeps moving while we wait on Node.
|
|
437
|
+
let bar_w = (inner.width.saturating_sub(4) as usize).clamp(8, 48);
|
|
438
|
+
let fill = (elapsed_secs as usize) % (bar_w + 1);
|
|
439
|
+
let bar: String = (0..bar_w)
|
|
440
|
+
.map(|i| if i < fill { '█' } else { '░' })
|
|
441
|
+
.collect();
|
|
442
|
+
|
|
443
|
+
let mut lines: Vec<Line> = vec![
|
|
415
444
|
Line::from(""),
|
|
416
|
-
Line::from(Span::styled(
|
|
445
|
+
Line::from(vec![Span::styled(
|
|
417
446
|
format!("Connecting to {} …", url),
|
|
418
|
-
Style::default().fg(AMBER_WARN).bold()
|
|
419
|
-
))
|
|
420
|
-
|
|
421
|
-
Line::from(Span::styled(
|
|
422
|
-
|
|
447
|
+
Style::default().fg(AMBER_WARN).bold(),
|
|
448
|
+
)])
|
|
449
|
+
.alignment(Alignment::Center),
|
|
450
|
+
Line::from(vec![Span::styled(
|
|
451
|
+
format!(
|
|
452
|
+
"Elapsed {}s — waiting on CLI (gateway autostart can take a few minutes)",
|
|
453
|
+
elapsed_secs
|
|
454
|
+
),
|
|
455
|
+
Style::default().fg(CYBER_CYAN),
|
|
456
|
+
)])
|
|
457
|
+
.alignment(Alignment::Center),
|
|
423
458
|
];
|
|
424
|
-
|
|
459
|
+
if inner.height > 5 {
|
|
460
|
+
lines.push(Line::from(vec![Span::styled(
|
|
461
|
+
bar,
|
|
462
|
+
Style::default().fg(TEXT_DIM),
|
|
463
|
+
)])
|
|
464
|
+
.alignment(Alignment::Center));
|
|
465
|
+
lines.push(Line::from(""));
|
|
466
|
+
lines.push(
|
|
467
|
+
Line::from(Span::styled(
|
|
468
|
+
"See Connection Activity Log for live steps from the Gateway starter",
|
|
469
|
+
Style::default().fg(TEXT_DIM),
|
|
470
|
+
))
|
|
471
|
+
.alignment(Alignment::Center),
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
let target_h = inner.height as usize;
|
|
476
|
+
while lines.len() < target_h {
|
|
477
|
+
lines.push(Line::from(Span::styled("", Style::default().bg(BG_PANEL))));
|
|
478
|
+
}
|
|
479
|
+
if lines.len() > target_h {
|
|
480
|
+
lines.truncate(target_h);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let paragraph = Paragraph::new(lines)
|
|
484
|
+
.alignment(Alignment::Center)
|
|
485
|
+
.style(Style::default().bg(BG_PANEL));
|
|
425
486
|
f.render_widget(paragraph, inner);
|
|
426
487
|
}
|
|
427
488
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4runr-os",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.136",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.
|
|
5
|
+
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.136: Gateway autostart diagnostics — port conflict check, bundle verification, spawn/stderr capture, HTTP detail on /health fails. Prior: 2.9.135 longer wait + timer. ⚠️ Pre-MVP / Development Phase",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|