mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-11-06 00:15:06 +00:00
refactor: carry a session's source on AgentEvent and tidy the review fixes
The Codex router reads source off the parsed event instead of parsing the hook body again. The command scan reads each real directory once rather than capping depth, labels capitalise through one helper (alias_label no longer slices bytes), the live input rows are set where Covering is decided, and the transcript is opened once per poll.
This commit is contained in:
@@ -153,8 +153,7 @@ fn build_hook_body(agent: &str, event: &str, stdin_json: &str) -> String {
|
||||
("cwd", "cwd"),
|
||||
// Goose spells the working directory its own way.
|
||||
("cwd", "working_dir"),
|
||||
// What started a session (startup, resume, clear…): routes a Codex
|
||||
// report — see `daemon::pane::codex_report_target`.
|
||||
// What started a session — see `cli_agent::AgentEvent::source`.
|
||||
("source", "source"),
|
||||
] {
|
||||
if let Some(v) = payload
|
||||
@@ -345,14 +344,10 @@ fn turn_reply_model(path: &Path, reply: &str, prompt_id: Option<&str>) -> Option
|
||||
let deadline = std::time::Instant::now() + TRANSCRIPT_CATCH_UP;
|
||||
let mut read = None;
|
||||
loop {
|
||||
let len = std::fs::metadata(path).ok().map(|m| m.len());
|
||||
if len != read {
|
||||
read = len;
|
||||
if let Some(model) =
|
||||
transcript_tail(path).and_then(|t| reply_model_in(&t, reply, prompt_id))
|
||||
{
|
||||
return Some(model);
|
||||
}
|
||||
if let Some(model) =
|
||||
transcript_tail(path, &mut read).and_then(|t| reply_model_in(&t, reply, prompt_id))
|
||||
{
|
||||
return Some(model);
|
||||
}
|
||||
if std::time::Instant::now() >= deadline {
|
||||
return None;
|
||||
@@ -361,10 +356,15 @@ fn turn_reply_model(path: &Path, reply: &str, prompt_id: Option<&str>) -> Option
|
||||
}
|
||||
}
|
||||
|
||||
fn transcript_tail(path: &Path) -> Option<String> {
|
||||
/// The transcript's last [`TRANSCRIPT_TAIL`] bytes, unless it is still the
|
||||
/// length `read` last saw — then nothing new is in it.
|
||||
fn transcript_tail(path: &Path, read: &mut Option<u64>) -> Option<String> {
|
||||
use std::io::{Seek as _, SeekFrom};
|
||||
let mut file = std::fs::File::open(path).ok()?;
|
||||
let len = file.metadata().ok()?.len();
|
||||
if read.replace(len) == Some(len) {
|
||||
return None;
|
||||
}
|
||||
file.seek(SeekFrom::Start(len.saturating_sub(TRANSCRIPT_TAIL)))
|
||||
.ok()?;
|
||||
let mut tail = Vec::new();
|
||||
|
||||
@@ -1201,6 +1201,9 @@ pub struct AgentEvent {
|
||||
pub session_id: Option<String>,
|
||||
pub message: Option<String>,
|
||||
pub cwd: Option<std::path::PathBuf>,
|
||||
/// What started the session, on a session start that says: `startup`,
|
||||
/// `resume`, `clear` (Codex's `/new`), `compact`.
|
||||
pub source: Option<String>,
|
||||
pub readout: AgentReadout,
|
||||
}
|
||||
|
||||
@@ -1227,6 +1230,8 @@ pub fn parse_agent_event_body(json: &[u8]) -> Option<AgentEvent> {
|
||||
message: Option<String>,
|
||||
#[serde(default)]
|
||||
cwd: Option<String>,
|
||||
#[serde(default)]
|
||||
source: Option<String>,
|
||||
#[serde(flatten)]
|
||||
readout: AgentReadout,
|
||||
}
|
||||
@@ -1240,6 +1245,7 @@ pub fn parse_agent_event_body(json: &[u8]) -> Option<AgentEvent> {
|
||||
session_id: nonempty(w.session_id),
|
||||
message: nonempty(w.message),
|
||||
cwd: nonempty(w.cwd).map(std::path::PathBuf::from),
|
||||
source: nonempty(w.source),
|
||||
readout: w.readout,
|
||||
})
|
||||
}
|
||||
@@ -1258,6 +1264,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout,
|
||||
};
|
||||
state.apply_event(&event(AgentReadout {
|
||||
@@ -1769,6 +1776,7 @@ mod tests {
|
||||
session_id: id.map(String::from),
|
||||
message: msg.map(String::from),
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
};
|
||||
|
||||
@@ -1832,6 +1840,7 @@ mod tests {
|
||||
session_id: Some("sid".into()),
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
});
|
||||
assert_eq!(s.status, AgentStatus::Idle);
|
||||
@@ -1848,6 +1857,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
};
|
||||
|
||||
@@ -1882,6 +1892,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
};
|
||||
|
||||
@@ -1934,6 +1945,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
};
|
||||
|
||||
@@ -1971,6 +1983,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
};
|
||||
|
||||
@@ -2003,6 +2016,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: cwd.map(PathBuf::from),
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
};
|
||||
|
||||
|
||||
@@ -6042,6 +6042,7 @@ mod tests {
|
||||
session_id: None,
|
||||
message: None,
|
||||
cwd: None,
|
||||
source: None,
|
||||
readout: Default::default(),
|
||||
}],
|
||||
None,
|
||||
|
||||
@@ -146,10 +146,7 @@ fn codex_report_pane(
|
||||
named,
|
||||
event.session_id.as_deref(),
|
||||
event.cwd.as_deref(),
|
||||
// Codex's SessionStart says what started the session; `clear` is /new.
|
||||
serde_json::from_str::<serde_json::Value>(body)
|
||||
.ok()
|
||||
.is_some_and(|body| body["source"] == "clear"),
|
||||
event.source.as_deref() == Some("clear"),
|
||||
)?;
|
||||
let pane = panes.into_iter().find(|p| p.id == target)?;
|
||||
Some((pane, event))
|
||||
|
||||
+32
-27
@@ -1855,21 +1855,23 @@ fn custom_commands(cwd: Option<&Path>, home: Option<&Path>) -> Vec<(String, Stri
|
||||
),
|
||||
];
|
||||
// Directories are followed through symlinks, as Claude Code follows
|
||||
// them; the bound keeps a link back up the tree from walking forever.
|
||||
const MAX_DEPTH: usize = 8;
|
||||
// them; each is read once, so a link back up the tree ends the walk
|
||||
// instead of looping it.
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for (dir, scope) in roots {
|
||||
let Some(dir) = dir else { continue };
|
||||
let mut stack = vec![(dir, 0)];
|
||||
while let Some((dir, depth)) = stack.pop() {
|
||||
let mut stack = vec![dir];
|
||||
while let Some(dir) = stack.pop() {
|
||||
if !std::fs::canonicalize(&dir).is_ok_and(|real| seen.insert(real)) {
|
||||
continue;
|
||||
}
|
||||
let Ok(entries) = std::fs::read_dir(&dir) else {
|
||||
continue;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if depth < MAX_DEPTH {
|
||||
stack.push((path, depth + 1));
|
||||
}
|
||||
stack.push(path);
|
||||
} else if path.extension().is_some_and(|e| e == "md")
|
||||
&& let Some(stem) = path.file_stem().and_then(|s| s.to_str())
|
||||
{
|
||||
@@ -2170,17 +2172,20 @@ fn model_alias(model: &str) -> Option<&'static str> {
|
||||
fn effort_label(level: &str) -> String {
|
||||
match level {
|
||||
"xhigh" => "Extra high".into(),
|
||||
// A level is whatever the environment or the settings file said, so
|
||||
// its first character need not be one byte.
|
||||
_ => {
|
||||
let mut chars = level.chars();
|
||||
chars.next().map_or_else(String::new, |first| {
|
||||
first.to_uppercase().chain(chars).collect()
|
||||
})
|
||||
}
|
||||
_ => capitalized(level),
|
||||
}
|
||||
}
|
||||
|
||||
/// `text` with its first character upper-cased. A level is whatever the
|
||||
/// environment or the settings file said, so that character need not be one
|
||||
/// byte.
|
||||
fn capitalized(text: &str) -> String {
|
||||
let mut chars = text.chars();
|
||||
chars.next().map_or_else(String::new, |first| {
|
||||
first.to_uppercase().chain(chars).collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// How the model list spells an alias.
|
||||
fn alias_label(alias: &str) -> String {
|
||||
if alias == "default" {
|
||||
@@ -2190,7 +2195,7 @@ fn alias_label(alias: &str) -> String {
|
||||
Some(family) => (family, true),
|
||||
None => (alias, false),
|
||||
};
|
||||
let mut name = family[..1].to_uppercase() + &family[1..];
|
||||
let mut name = capitalized(family);
|
||||
if long {
|
||||
name.push_str(" · 1M");
|
||||
}
|
||||
@@ -2275,15 +2280,16 @@ impl TerminalView {
|
||||
/// and resizing their pane each time the box comes and goes would have
|
||||
/// them redraw for a row of padding.
|
||||
pub(super) fn composer_reserved_rows(&self) -> usize {
|
||||
if !matches!(self.presence(), Presence::Covering(_)) {
|
||||
let Some(c) = self
|
||||
.composer
|
||||
.as_ref()
|
||||
.filter(|c| matches!(c.presence, Presence::Covering(_)))
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
if !self.agent().is_some_and(makes_room) {
|
||||
return 0;
|
||||
}
|
||||
let Some(c) = self.composer.as_ref() else {
|
||||
return 0;
|
||||
};
|
||||
let Some(frame) = c.frame_height.get() else {
|
||||
return 0;
|
||||
};
|
||||
@@ -2619,13 +2625,13 @@ impl TerminalView {
|
||||
Some(a) if !covers(a) => Presence::Docked,
|
||||
Some(_) if asking => Presence::SteppedAside,
|
||||
Some(_) => match &c.area {
|
||||
Some(area) => Presence::Covering(screen_lines.saturating_sub(area.top + offset)),
|
||||
Some(area) => {
|
||||
c.area_rows = screen_lines.saturating_sub(area.top);
|
||||
Presence::Covering(c.area_rows.saturating_sub(offset))
|
||||
}
|
||||
None => Presence::SteppedAside,
|
||||
},
|
||||
};
|
||||
if let Some(area) = &c.area {
|
||||
c.area_rows = screen_lines.saturating_sub(area.top);
|
||||
}
|
||||
|
||||
if let Some(agent) = agent
|
||||
&& c.named != Some(agent)
|
||||
@@ -2646,8 +2652,7 @@ impl TerminalView {
|
||||
c.refocus = c.presence == Presence::SteppedAside;
|
||||
self.composer_focused = false;
|
||||
window.focus(&self.focus_handle, cx);
|
||||
} else if shown && ((c.refocus || c.grab) && !self.composer_focused || !c.typed.is_empty())
|
||||
{
|
||||
} else if shown {
|
||||
let take_focus = (c.refocus || c.grab) && !self.composer_focused;
|
||||
if take_focus {
|
||||
c.refocus = false;
|
||||
|
||||
Reference in New Issue
Block a user