4runr-os 2.9.95 → 2.9.96

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.
@@ -1506,11 +1506,13 @@ impl App {
1506
1506
  } else {
1507
1507
  // Open detail view
1508
1508
  if let Some(run) = self.state.run_manager.selected_run() {
1509
- self.add_log(format!("[RUN] Viewing run: {}", run.name));
1509
+ let run_id = run.id.clone();
1510
+ let run_name = run.name.clone();
1511
+ self.add_log(format!("[RUN] Viewing run: {}", run_name));
1510
1512
  self.state.run_manager.toggle_detail_view();
1511
1513
  if self.state.operation_mode == OperationMode::Connected {
1512
1514
  if let Some(ws) = ws_client {
1513
- let data = serde_json::json!({ "runId": run.id.clone() });
1515
+ let data = serde_json::json!({ "runId": run_id });
1514
1516
  if let Ok(id) = ws.send_command("run.get", Some(data)) {
1515
1517
  self.state.pending_run_get_id = Some(id);
1516
1518
  }
@@ -1716,20 +1718,22 @@ impl App {
1716
1718
  self.add_log("[AGENT] Connect to Gateway first to run on the server (key x).".to_string());
1717
1719
  } else if self.state.agent_list.agents.is_empty() {
1718
1720
  self.add_log("[AGENT] No agents in list.".to_string());
1719
- } else if let Some(agent) = self.get_selected_agent() {
1721
+ } else if let Some((agent_name, agent_model)) =
1722
+ self.get_selected_agent().map(|a| (a.name.clone(), a.model.clone()))
1723
+ {
1720
1724
  if let Some(ws) = ws_client {
1721
- let name = format!("TUI: {}", agent.name);
1725
+ let name = format!("TUI: {}", agent_name);
1722
1726
  let input = serde_json::json!({
1723
1727
  "agent_id": "test",
1724
1728
  "data": {
1725
- "prompt": format!("Quick run for local agent profile '{}' (model {}).", agent.name, agent.model)
1729
+ "prompt": format!("Quick run for local agent profile '{}' (model {}).", agent_name, agent_model)
1726
1730
  }
1727
1731
  });
1728
1732
  let data = serde_json::json!({ "name": name, "input": input });
1729
1733
  match ws.send_command("run.quick", Some(data)) {
1730
1734
  Ok(id) => {
1731
1735
  self.state.pending_run_quick_id = Some(id);
1732
- self.add_log(format!("[AGENT] Queued Gateway run for profile '{}' (x)", agent.name));
1736
+ self.add_log(format!("[AGENT] Queued Gateway run for profile '{}' (x)", agent_name));
1733
1737
  }
1734
1738
  Err(e) => self.add_log(format!("[ERROR] run.quick: {}", e)),
1735
1739
  }
@@ -0,0 +1,124 @@
1
+ //! File-based debug logger for Setup Portal.
2
+ //! When SETUP_PORTAL_DEBUG=1 or SETUP_PORTAL_DEBUG_FILE is set, all inputs and render state
3
+ //! are written to a file so you can capture the full trace when reproducing the navigation bug.
4
+ //!
5
+ //! Usage:
6
+ //! set SETUP_PORTAL_DEBUG_FILE=setup-portal-debug.log
7
+ //! mk3-tui (or 4r)
8
+ //! Then open Setup Portal (windowed), reproduce the bug, exit. Inspect setup-portal-debug.log.
9
+
10
+ use std::io::Write;
11
+ use std::sync::Mutex;
12
+ use std::sync::OnceLock;
13
+ use std::time::{SystemTime, UNIX_EPOCH};
14
+
15
+ fn enabled() -> bool {
16
+ std::env::var("SETUP_PORTAL_DEBUG").as_deref() == Ok("1")
17
+ || std::env::var("SETUP_PORTAL_DEBUG_FILE").is_ok()
18
+ }
19
+
20
+ fn path() -> String {
21
+ std::env::var("SETUP_PORTAL_DEBUG_FILE")
22
+ .unwrap_or_else(|_| "setup-portal-debug.log".to_string())
23
+ }
24
+
25
+ static FILE: OnceLock<Mutex<Option<std::io::BufWriter<std::fs::File>>>> = OnceLock::new();
26
+
27
+ fn ensure_open() -> bool {
28
+ if !enabled() {
29
+ return false;
30
+ }
31
+ let mutex = FILE.get_or_init(|| Mutex::new(None));
32
+ let mut guard = mutex.lock().unwrap();
33
+ if guard.is_none() {
34
+ let path = path();
35
+ match std::fs::OpenOptions::new()
36
+ .create(true)
37
+ .append(true)
38
+ .open(&path)
39
+ {
40
+ Ok(file_handle) => {
41
+ // Resolve full path so user always knows where the log is
42
+ let full_path: String = std::path::Path::new(&path)
43
+ .canonicalize()
44
+ .map(|p| p.display().to_string())
45
+ .or_else(|_| std::env::current_dir().map(|cwd| cwd.join(&path).display().to_string()))
46
+ .unwrap_or_else(|_| path.clone());
47
+ let _ = writeln!(
48
+ std::io::stderr(),
49
+ "[SETUP_PORTAL_DEBUG] Log file: {}",
50
+ full_path
51
+ );
52
+ let mut w = std::io::BufWriter::new(file_handle);
53
+ let banner = format!("{} === Setup Portal debug log opened: {} ===\n", timestamp(), full_path);
54
+ let _ = w.write_all(banner.as_bytes());
55
+ let _ = w.flush();
56
+ *guard = Some(w);
57
+ return true;
58
+ }
59
+ Err(e) => {
60
+ let _ = writeln!(
61
+ std::io::stderr(),
62
+ "[SETUP_PORTAL_DEBUG] Failed to open \"{}\": {}",
63
+ path, e
64
+ );
65
+ return false;
66
+ }
67
+ }
68
+ }
69
+ true
70
+ }
71
+
72
+ fn log_raw(msg: &str) {
73
+ if !enabled() {
74
+ return;
75
+ }
76
+ if !ensure_open() {
77
+ return;
78
+ }
79
+ let mutex = FILE.get().unwrap();
80
+ let mut guard = mutex.lock().unwrap();
81
+ if let Some(ref mut w) = *guard {
82
+ let _ = w.write_all(msg.as_bytes());
83
+ let _ = w.write_all(b"\n");
84
+ let _ = w.flush();
85
+ }
86
+ }
87
+
88
+ fn timestamp() -> String {
89
+ SystemTime::now()
90
+ .duration_since(UNIX_EPOCH)
91
+ .map(|d| format!("{}.{:03}", d.as_secs(), d.subsec_millis()))
92
+ .unwrap_or_else(|_| "?.???".to_string())
93
+ }
94
+
95
+ /// Log a line with timestamp. Call from main or setup_portal when debug file is enabled.
96
+ pub fn log(msg: &str) {
97
+ log_raw(&format!("{} {}", timestamp(), msg));
98
+ }
99
+
100
+ /// Log screen switch (enter/leave Setup Portal).
101
+ pub fn log_screen(from: &str, to: &str) {
102
+ log(&format!("SCREEN {} -> {}", from, to));
103
+ }
104
+
105
+ /// Log an input event (key, mouse, resize).
106
+ pub fn log_input(kind: &str, detail: &str) {
107
+ log(&format!("INPUT {} {}", kind, detail));
108
+ }
109
+
110
+ /// Log render state (dims, rects, selected option). Throttled by caller.
111
+ pub fn log_render(msg: &str) {
112
+ log(&format!("RENDER {}", msg));
113
+ }
114
+
115
+ /// Log flicker-debug: when we clear and when we finish draw (to correlate with visible flicker).
116
+ /// Writes to debug file when SETUP_PORTAL_DEBUG or SETUP_PORTAL_DEBUG_FILE is set.
117
+ pub fn log_flicker_debug(event: &str, detail: &str) {
118
+ let msg = format!("FLICKER {} {}", event, detail);
119
+ log(&msg);
120
+ // Also to stderr when file debug enabled, so "4r 2> setup-debug.log" captures it
121
+ if enabled() {
122
+ let _ = writeln!(std::io::stderr(), "[SETUP_PORTAL_DEBUG] {}", msg);
123
+ }
124
+ }
@@ -126,16 +126,17 @@ fn render_header(f: &mut Frame, area: Rect, state: &AppState) {
126
126
 
127
127
  let portal = &state.connection_portal;
128
128
 
129
- let (title, border_color) = if portal.connection_success {
129
+ let (title, border_color): (String, Color) = if portal.connection_success {
130
130
  let u = portal.last_successful_url.as_deref().unwrap_or(portal.gateway_url.as_str());
131
- let short = u.replace("http://", "").replace("https://", "").split('/').next().unwrap_or(u).to_string();
132
- (format!(" Portal Connection - Connected to {} ", short), NEON_GREEN)
131
+ let stripped = u.replace("http://", "").replace("https://", "");
132
+ let host = stripped.split('/').next().unwrap_or(u).to_string();
133
+ (format!(" Portal Connection - Connected to {} ", host), NEON_GREEN)
133
134
  } else if portal.error.is_some() {
134
- (" Portal Connection - Error ", ERROR_RED)
135
+ (" Portal Connection - Error ".to_string(), ERROR_RED)
135
136
  } else if portal.connecting {
136
- (" Portal Connection - Connecting... ", AMBER_WARN)
137
+ (" Portal Connection - Connecting... ".to_string(), AMBER_WARN)
137
138
  } else {
138
- (" Portal Connection ", BRAND_PURPLE)
139
+ (" Portal Connection ".to_string(), BRAND_PURPLE)
139
140
  };
140
141
 
141
142
  let block = Block::default()
@@ -182,7 +183,8 @@ fn render_input_fields(f: &mut Frame, area: Rect, state: &AppState) {
182
183
  let portal = &state.connection_portal;
183
184
  if portal.connection_success {
184
185
  let url = state.gateway_url.as_deref().or(portal.last_successful_url.as_deref()).unwrap_or(portal.gateway_url.as_str());
185
- let short = url.replace("http://", "").replace("https://", "").split('/').next().unwrap_or(url);
186
+ let stripped = url.replace("http://", "").replace("https://", "");
187
+ let short = stripped.split('/').next().unwrap_or(url);
186
188
  let block = Block::default().title(" URL ").borders(Borders::ALL).border_style(Style::default().fg(NEON_GREEN)).style(Style::default().bg(BG_PANEL));
187
189
  let inner = block.inner(area);
188
190
  f.render_widget(block, area);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.9.95",
3
+ "version": "2.9.96",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.95: Monorepo cleanup (archive legacy root trees), gateway deps/security (Fastify 5.x), fixed root scripts; prepublish syncs vendored gateway/mk3. ⚠️ Pre-MVP / Development Phase",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.96: npm bundle — copy-mk3 includes debug_log.rs (fixes global Rust build); mk3-tui borrow/type fixes (connection portal, run detail, run.quick). ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",