@masyv/relay 0.2.0 → 0.3.0

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.
@@ -69,33 +69,27 @@ impl Agent for CodexAgent {
69
69
  let tmp = std::env::temp_dir().join("relay_handoff.md");
70
70
  std::fs::write(&tmp, handoff_prompt)?;
71
71
 
72
- // Use `codex exec --full-auto` for non-interactive auto-approval
73
- let output = Command::new(&binary)
72
+ // Launch Codex INTERACTIVELY with the handoff as the initial prompt.
73
+ // This opens the Codex TUI so the user can keep working with it.
74
+ // stdin/stdout/stderr are inherited so the user sees the Codex UI.
75
+ let status = Command::new(&binary)
74
76
  .current_dir(project_dir)
75
- .arg("exec")
76
77
  .arg("--full-auto")
77
78
  .arg("-m")
78
79
  .arg(&self.model)
79
80
  .arg(handoff_prompt)
80
- .output()?;
81
-
82
- let stdout = String::from_utf8_lossy(&output.stdout);
83
- let stderr = String::from_utf8_lossy(&output.stderr);
84
-
85
- if !stdout.is_empty() {
86
- println!("{stdout}");
87
- }
88
- if !stderr.is_empty() {
89
- eprintln!("{stderr}");
90
- }
81
+ .stdin(std::process::Stdio::inherit())
82
+ .stdout(std::process::Stdio::inherit())
83
+ .stderr(std::process::Stdio::inherit())
84
+ .status()?;
91
85
 
92
86
  Ok(HandoffResult {
93
87
  agent: "codex".into(),
94
- success: output.status.success(),
95
- message: if output.status.success() {
96
- format!("Codex ({}) completed handoff task", self.model)
88
+ success: status.success(),
89
+ message: if status.success() {
90
+ format!("Codex ({}) session ended", self.model)
97
91
  } else {
98
- format!("Codex exited with code {:?}", output.status.code())
92
+ format!("Codex exited with code {:?}", status.code())
99
93
  },
100
94
  handoff_file: Some(tmp.to_string_lossy().to_string()),
101
95
  })
@@ -3,6 +3,10 @@
3
3
  //! tool calls with results, errors, and decisions.
4
4
 
5
5
  use std::path::Path;
6
+ use std::sync::atomic::AtomicUsize;
7
+
8
+ /// Controls how many conversation turns to keep. Set before calling read_latest_session.
9
+ pub static MAX_CONVERSATION_TURNS: AtomicUsize = AtomicUsize::new(25);
6
10
 
7
11
  /// Extracted session info from Claude's transcript.
8
12
  pub struct SessionInfo {
@@ -143,7 +147,7 @@ fn parse_session_transcript(path: &std::path::Path) -> SessionInfo {
143
147
 
144
148
  conversation.push(ConversationTurn {
145
149
  role: "tool_result".into(),
146
- content: truncate(&result_text, 600),
150
+ content: truncate(&result_text, 200),
147
151
  });
148
152
  }
149
153
  }
@@ -168,7 +172,7 @@ fn parse_session_transcript(path: &std::path::Path) -> SessionInfo {
168
172
  }
169
173
  conversation.push(ConversationTurn {
170
174
  role: "user".into(),
171
- content: truncate(&user_text, 400),
175
+ content: truncate(&user_text, 300),
172
176
  });
173
177
  }
174
178
  }
@@ -185,7 +189,7 @@ fn parse_session_transcript(path: &std::path::Path) -> SessionInfo {
185
189
  if !text.is_empty() {
186
190
  conversation.push(ConversationTurn {
187
191
  role: "assistant".into(),
188
- content: truncate(text, 500),
192
+ content: truncate(text, 200),
189
193
  });
190
194
  // Extract decisions
191
195
  for line in text.lines() {
@@ -230,8 +234,8 @@ fn parse_session_transcript(path: &std::path::Path) -> SessionInfo {
230
234
  decisions.dedup();
231
235
  decisions.truncate(15);
232
236
 
233
- // Keep last N conversation turns to fit context prioritize recent
234
- let max_turns = 80;
237
+ // Keep last N conversation turns caller can override via max_conversation_turns
238
+ let max_turns = MAX_CONVERSATION_TURNS.load(std::sync::atomic::Ordering::Relaxed);
235
239
  if conversation.len() > max_turns {
236
240
  let skip = conversation.len() - max_turns;
237
241
  conversation = conversation.into_iter().skip(skip).collect();
@@ -135,11 +135,15 @@ pub fn build_handoff(
135
135
 
136
136
  let mut full = sections.join("\n\n");
137
137
 
138
- // Truncate if too long (rough token estimate: chars / 3.5)
139
- let estimated_tokens = full.len() as f64 / 3.5;
140
- if estimated_tokens > max_tokens as f64 {
141
- let max_chars = (max_tokens as f64 * 3.5) as usize;
142
- full.truncate(max_chars);
138
+ // Hard cap at max_tokens (rough estimate: chars / 3.5)
139
+ let max_chars = (max_tokens as f64 * 3.5) as usize;
140
+ if full.len() > max_chars {
141
+ // Find a valid UTF-8 char boundary
142
+ let mut end = max_chars;
143
+ while end > 0 && !full.is_char_boundary(end) {
144
+ end -= 1;
145
+ }
146
+ full.truncate(end);
143
147
  full.push_str("\n\n[...truncated to fit context limit]");
144
148
  }
145
149
 
package/core/src/main.rs CHANGED
@@ -44,6 +44,14 @@ enum Commands {
44
44
  /// Don't execute — just print the handoff package
45
45
  #[arg(long)]
46
46
  dry_run: bool,
47
+
48
+ /// How many conversation turns to include (default: 25)
49
+ #[arg(long, default_value = "25")]
50
+ turns: usize,
51
+
52
+ /// What to include: all, conversation, git, todos (comma-separated)
53
+ #[arg(long, default_value = "all")]
54
+ include: String,
47
55
  },
48
56
 
49
57
  /// Show current session snapshot (what would be handed off)
@@ -84,14 +92,33 @@ fn main() -> Result<()> {
84
92
  });
85
93
 
86
94
  match cli.command {
87
- Commands::Handoff { to, deadline, dry_run } => {
95
+ Commands::Handoff { to, deadline, dry_run, turns, include } => {
88
96
  eprintln!("{}", "⚡ Relay — capturing session state...".yellow().bold());
89
97
 
90
- let snapshot = capture::capture_snapshot(
98
+ // Set conversation turn limit before capture
99
+ relay::capture::session::MAX_CONVERSATION_TURNS
100
+ .store(turns, std::sync::atomic::Ordering::Relaxed);
101
+
102
+ let mut snapshot = capture::capture_snapshot(
91
103
  &project_dir,
92
104
  deadline.as_deref(),
93
105
  )?;
94
106
 
107
+ // Filter sections based on --include flag
108
+ let includes: Vec<&str> = include.split(',').map(|s| s.trim()).collect();
109
+ if !includes.contains(&"all") {
110
+ if !includes.contains(&"conversation") {
111
+ snapshot.conversation.clear();
112
+ }
113
+ if !includes.contains(&"git") {
114
+ snapshot.git_state = None;
115
+ snapshot.recent_files.clear();
116
+ }
117
+ if !includes.contains(&"todos") {
118
+ snapshot.todos.clear();
119
+ }
120
+ }
121
+
95
122
  let target = to.as_deref().unwrap_or("auto");
96
123
  let handoff_text = handoff::build_handoff(
97
124
  &snapshot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masyv/relay",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Relay — When Claude's rate limit hits, another agent picks up exactly where you left off. Captures session state and hands off to Codex, Gemini, Ollama, or GPT-4.",
5
5
  "scripts": {
6
6
  "build": "./scripts/build.sh",