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(
|
|
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(
|
|
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
|
-
|
|
535
|
-
|
|
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.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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::
|
|
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.
|
|
3
|
+
"version": "2.9.124",
|
|
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.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",
|