4runr-os 2.9.123 → 2.9.124

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.
@@ -212,6 +212,13 @@ function cleanup(): void {
212
212
  // Start cleanup interval
213
213
  setInterval(cleanup, CONFIG.CLEANUP_INTERVAL_MS);
214
214
 
215
+ /** Lightweight probes used by CLI/TUI, load balancers, and Prometheus — must not trip per-second limits. */
216
+ function isProbeExemptPath(method: string, rawUrl: string): boolean {
217
+ if (method !== 'GET') return false;
218
+ const path = (rawUrl.split('?')[0] || '').toLowerCase();
219
+ return path === '/health' || path === '/ready' || path === '/metrics';
220
+ }
221
+
215
222
  /**
216
223
  * DDoS Protection Middleware
217
224
  */
@@ -219,6 +226,10 @@ export async function ddosProtection(
219
226
  request: FastifyRequest,
220
227
  reply: FastifyReply
221
228
  ): Promise<void> {
229
+ if (isProbeExemptPath(request.method, request.url)) {
230
+ return;
231
+ }
232
+
222
233
  const ip = getClientIP(request);
223
234
  const userAgent = request.headers['user-agent'] || 'unknown';
224
235
 
@@ -373,9 +373,10 @@ fn render_content_area(f: &mut Frame, area: Rect, state: &AppState) {
373
373
  if !portal.activity_log.is_empty() {
374
374
  // Show error message first so user sees "port in use, try 3002", then activity log below
375
375
  use ratatui::layout::{Constraint, Direction, Layout};
376
+ // Give the error panel enough height so wrapped text does not visually collide with the log.
376
377
  let chunks = Layout::default()
377
378
  .direction(Direction::Vertical)
378
- .constraints([Constraint::Min(4), Constraint::Min(6)])
379
+ .constraints([Constraint::Min(12), Constraint::Min(6)])
379
380
  .split(area);
380
381
  render_error(f, chunks[0], err_msg);
381
382
  render_activity_log(f, chunks[1], state);
@@ -496,6 +497,11 @@ fn render_error(f: &mut Frame, area: Rect, error: &str) {
496
497
  ("Port in use", error_msg.clone())
497
498
  } else if error_msg.contains("404") {
498
499
  ("Not the Gateway (404)", "Something is running at this URL but it did not respond as the 4Runr Gateway.\n\nThe process on this port may not be the Gateway. Stop it and start the Gateway (e.g. cd apps/gateway && npm start), or use a different URL.".to_string())
500
+ } else if error_msg.contains("429") || error_msg.to_lowercase().contains("too many requests") {
501
+ (
502
+ "Rate limited (429)",
503
+ "Something on this port returned HTTP 429 (too many requests).\n\nIf this is the 4Runr Gateway, wait a few seconds and press Enter to retry. Otherwise use another port (e.g. http://localhost:3002).".to_string(),
504
+ )
499
505
  } else if error_msg.contains("ECONNREFUSED") || error_msg.contains("Connection refused") {
500
506
  ("Connection Refused", "The Gateway server is not running or not accessible.\n\nStart the server and try again.".to_string())
501
507
  } else if error_msg.contains("ETIMEDOUT") || error_msg.contains("timeout") {
@@ -516,7 +522,7 @@ fn render_error(f: &mut Frame, area: Rect, error: &str) {
516
522
  let inner = block.inner(area);
517
523
  f.render_widget(block, area);
518
524
 
519
- let wrap_w = inner.width.saturating_sub(4) as usize;
525
+ let wrap_w = inner.width.saturating_sub(2) as usize;
520
526
  let mut message_lines: Vec<Line> = Vec::new();
521
527
  for raw in display_msg.split('\n') {
522
528
  let t = raw.trim();
@@ -530,22 +536,30 @@ fn render_error(f: &mut Frame, area: Rect, error: &str) {
530
536
  )));
531
537
  }
532
538
  }
533
-
534
- let mut all_lines = vec![
535
- Line::from(""),
536
- ];
539
+
540
+ // Never draw more lines than fit inside the block (avoids garbled overlap on small panes).
541
+ let footer_h = 2u16;
542
+ let max_body = (inner.height).saturating_sub(footer_h).max(1) as usize;
543
+ if message_lines.len() > max_body {
544
+ message_lines.truncate(max_body.saturating_sub(1));
545
+ message_lines.push(Line::from(Span::styled(
546
+ "… (message truncated — resize taller or read Activity Log below)",
547
+ Style::default().fg(TEXT_DIM).bg(BG_PANEL),
548
+ )));
549
+ }
550
+
551
+ let mut all_lines = vec![Line::from("")];
537
552
  all_lines.extend(message_lines);
538
- all_lines.extend(vec![
539
- Line::from(""),
540
- Line::from(Span::styled(
541
- "Press Enter to retry or ESC to cancel",
542
- Style::default().fg(TEXT_DIM)
543
- )).alignment(Alignment::Center),
544
- ]);
545
-
553
+ all_lines.push(Line::from(""));
554
+ all_lines.push(Line::from(Span::styled(
555
+ "Press Enter to retry or ESC to cancel",
556
+ Style::default().fg(TEXT_DIM).bg(BG_PANEL),
557
+ )));
558
+
546
559
  let paragraph = Paragraph::new(all_lines)
547
560
  .style(Style::default().bg(BG_PANEL))
548
- .alignment(Alignment::Center);
561
+ .alignment(Alignment::Left)
562
+ .wrap(Wrap { trim: true });
549
563
 
550
564
  f.render_widget(paragraph, inner);
551
565
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.9.123",
3
+ "version": "2.9.124",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.123: Setup Portal preserves scan + linked banner; Connection Portal session-linked UI; Portal Monitoring /health+/ready+metrics. ⚠️ Pre-MVP / Development Phase",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.124: Gateway /health /ready /metrics exempt from DDoS rate limit; Connection Portal error UI + 429 hint. Prior: Setup soft-open, session-linked portal, monitoring health lines. ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",