aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/server/pane_graphics_stream.rs21
-rw-r--r--src/app/api.rs1
-rw-r--r--src/app/api/layouts.rs4
-rw-r--r--src/app/api/worktrees.rs7
-rw-r--r--src/app/mod.rs22
-rw-r--r--src/client/shell/preferences.rs2
-rw-r--r--src/client/shell/tests/agents_worktrees_notifications.rs4
-rw-r--r--src/detect/manifest_update.rs27
-rw-r--r--src/integration/assets/herdr-agent-state.test.ts6
-rw-r--r--src/integration/tests.rs136
-rw-r--r--src/pane/terminal.rs10
-rw-r--r--src/platform/mod.rs8
-rw-r--r--src/platform/windows.rs33
-rw-r--r--src/server/headless/tests/mod.rs6
-rw-r--r--src/terminal/metadata.rs9
15 files changed, 89 insertions, 207 deletions
diff --git a/src/api/server/pane_graphics_stream.rs b/src/api/server/pane_graphics_stream.rs
index e4d3315d..0df857fc 100644
--- a/src/api/server/pane_graphics_stream.rs
+++ b/src/api/server/pane_graphics_stream.rs
@@ -393,7 +393,7 @@ fn read_line(
total_deadline,
"timed out reading stream frame header",
)?;
- match stream.read(&mut byte) {
+ match wait.read(stream, &mut byte) {
Ok(0) => return Ok(None),
Ok(_) => {
wait.on_progress();
@@ -455,7 +455,7 @@ fn read_exact(
)?;
let remaining = len - data.len();
let read_len = remaining.min(chunk.len());
- match stream.read(&mut chunk[..read_len]) {
+ match wait.read(stream, &mut chunk[..read_len]) {
Ok(0) if data.is_empty() => return Ok(None),
Ok(0) => {
return Err(io::Error::new(
@@ -494,6 +494,17 @@ enum ReadWait {
}
impl ReadWait {
+ fn read(&self, stream: &mut LocalStream, buffer: &mut [u8]) -> io::Result<usize> {
+ if matches!(self, Self::SocketTimeout) {
+ return stream.read(buffer);
+ }
+ match crate::ipc::poll_local_stream_read_count(stream, buffer)? {
+ crate::ipc::LocalStreamReadCount::Data(count) => Ok(count),
+ crate::ipc::LocalStreamReadCount::Pending => Err(io::ErrorKind::WouldBlock.into()),
+ crate::ipc::LocalStreamReadCount::Closed => Ok(0),
+ }
+ }
+
fn after_retry(&mut self, idle_deadline: Option<Instant>, total_deadline: Option<Instant>) {
if let Self::Poll(backoff) = self {
sleep_until_poll(idle_deadline, total_deadline, backoff.interval);
@@ -545,9 +556,11 @@ fn with_timed_reads<T>(
finish_timed_read(result, || stream.set_recv_timeout(None))
}
Err(err) if err.kind() == io::ErrorKind::Unsupported => {
- stream.set_nonblocking(true)?;
+ crate::ipc::set_local_stream_polling(stream, true)?;
let result = read(stream, ReadWait::Poll(PollBackoff::new()));
- finish_timed_read(result, || stream.set_nonblocking(false))
+ finish_timed_read(result, || {
+ crate::ipc::set_local_stream_polling(stream, false)
+ })
}
// A peer can disconnect after the caller's running check but before
// setsockopt. macOS reports that closed-socket race as EINVAL.
diff --git a/src/app/api.rs b/src/app/api.rs
index 598356b0..c504b4c0 100644
--- a/src/app/api.rs
+++ b/src/app/api.rs
@@ -2207,6 +2207,7 @@ mod tests {
crate::api::EventHub::default(),
);
let workspace = crate::workspace::Workspace::test_new("restored");
+ app.state.default_shell = test_support::exiting_test_command().into();
let pane_id = workspace.tabs[0].root_pane;
let terminal_id = workspace.terminal_id(pane_id).cloned().unwrap();
app.state.workspaces = vec![workspace];
diff --git a/src/app/api/layouts.rs b/src/app/api/layouts.rs
index 943ed835..74190434 100644
--- a/src/app/api/layouts.rs
+++ b/src/app/api/layouts.rs
@@ -747,7 +747,7 @@ mod tests {
second: Box::new(LayoutNode::Pane {
pane: LayoutPane {
label: Some("tests".into()),
- command: Some(vec!["sh".into(), "-c".into(), "true".into()]),
+ command: Some(vec![exiting_test_command().into()]),
env: std::collections::HashMap::from([(
"HERDR_ROLE".into(),
"tests".into(),
@@ -789,7 +789,7 @@ mod tests {
assert_eq!(second_pane.label.as_deref(), Some("tests"));
assert_eq!(
second_pane.command,
- Some(vec!["sh".into(), "-c".into(), "true".into()])
+ Some(vec![exiting_test_command().into()])
);
assert!(matches!(
&app.event_hub.events_after(0).last().expect("layout event").1.data,
diff --git a/src/app/api/worktrees.rs b/src/app/api/worktrees.rs
index fd7bb0fb..3c8407b9 100644
--- a/src/app/api/worktrees.rs
+++ b/src/app/api/worktrees.rs
@@ -794,13 +794,15 @@ mod tests {
fn test_app_with_event_hub(event_hub: crate::api::EventHub) -> App {
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
- App::new(
+ let mut app = App::new(
&Config::default(),
crate::app::AppPolicy::TEST,
None,
api_rx,
event_hub,
- )
+ );
+ app.state.default_shell = test_shell().into();
+ app
}
#[cfg(windows)]
@@ -815,7 +817,6 @@ mod tests {
fn app_with_parent(repo: &Path) -> App {
let mut app = test_app();
- app.state.default_shell = test_shell().into();
let mut parent = Workspace::test_new("main");
parent.identity_cwd = repo.to_path_buf();
app.state.workspaces = vec![parent];
diff --git a/src/app/mod.rs b/src/app/mod.rs
index ed2c5fd9..0f78cf8b 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -9,6 +9,8 @@ pub(crate) mod agent_view;
mod agents;
pub(crate) use agents::{AGENT_START_SETTLE_DELAY, MAX_AGENT_START_TIMEOUT};
mod api;
+#[cfg(test)]
+pub(crate) use api::test_support::exiting_test_command;
mod api_helpers;
pub(crate) use api_helpers::limit_snapshot_lines;
mod creation;
@@ -975,13 +977,15 @@ mod tests {
fn test_app() -> App {
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
- App::new(
+ let mut app = App::new(
&Config::default(),
crate::app::AppPolicy::TEST,
None,
api_rx,
crate::api::EventHub::default(),
- )
+ );
+ app.state.default_shell = exiting_test_command().into();
+ app
}
fn unique_temp_path(name: &str) -> std::path::PathBuf {
@@ -992,16 +996,6 @@ mod tests {
std::env::temp_dir().join(format!("herdr-{name}-{}-{stamp}", std::process::id()))
}
- #[cfg(windows)]
- fn exiting_test_command() -> &'static str {
- "C:\\Windows\\System32\\whoami.exe"
- }
-
- #[cfg(not(windows))]
- fn exiting_test_command() -> &'static str {
- "/usr/bin/true"
- }
-
fn config_env_lock() -> &'static Mutex<()> {
crate::config::test_config_env_lock()
}
@@ -2657,7 +2651,7 @@ mod tests {
async fn pane_split_request_applies_ratio() {
let _guard = config_env_lock().lock().unwrap();
let original_shell = std::env::var_os("SHELL");
- std::env::set_var("SHELL", "/usr/bin/true");
+ std::env::set_var("SHELL", exiting_test_command());
let mut app = test_app();
let workspace = Workspace::test_new("api-pane-split-ratio");
@@ -2713,7 +2707,7 @@ mod tests {
async fn pane_split_request_uses_active_focused_pane_when_target_is_omitted() {
let _guard = config_env_lock().lock().unwrap();
let original_shell = std::env::var_os("SHELL");
- std::env::set_var("SHELL", "/usr/bin/true");
+ std::env::set_var("SHELL", exiting_test_command());
let mut app = test_app();
let workspace = Workspace::test_new("api-pane-split-current");
diff --git a/src/client/shell/preferences.rs b/src/client/shell/preferences.rs
index dc6cc1ac..f657dfbf 100644
--- a/src/client/shell/preferences.rs
+++ b/src/client/shell/preferences.rs
@@ -54,7 +54,7 @@ pub(super) fn store(path: &Path, preferences: ClientChromePreferences) -> Result
let temp_path = parent.join(temp_name);
std::fs::write(&temp_path, content)
.map_err(|error| format!("failed to write client shell state: {error}"))?;
- crate::platform::replace_file(&temp_path, path).map_err(|error| {
+ std::fs::rename(&temp_path, path).map_err(|error| {
let _ = std::fs::remove_file(&temp_path);
format!("failed to replace client shell state: {error}")
})
diff --git a/src/client/shell/tests/agents_worktrees_notifications.rs b/src/client/shell/tests/agents_worktrees_notifications.rs
index 055b594d..2ccfe77b 100644
--- a/src/client/shell/tests/agents_worktrees_notifications.rs
+++ b/src/client/shell/tests/agents_worktrees_notifications.rs
@@ -966,7 +966,9 @@ fn worktree_create_previews_the_endpoint_owned_checkout_path() {
&state.overlay,
Some(ClientShellOverlay::WorktreeCreate(create))
if create.checkout_path
- == "/tmp/herdr-worktrees/repo/feature-client-shell"
+ == std::path::Path::new("/tmp/herdr-worktrees")
+ .join("repo")
+ .join("feature-client-shell").to_string_lossy()
));
let submit = state.handle_input_bytes(b"\r");
let [ClientShellAction::Endpoint { request, .. }] = &submit.actions[..] else {
diff --git a/src/detect/manifest_update.rs b/src/detect/manifest_update.rs
index 4e165fe9..33493059 100644
--- a/src/detect/manifest_update.rs
+++ b/src/detect/manifest_update.rs
@@ -699,7 +699,14 @@ path = "codex.toml"
.unwrap();
std::env::set_var(
CATALOG_URL_ENV,
- format!("file://{}", web_dir.join("index.toml").display()),
+ format!(
+ "file:///{}",
+ web_dir
+ .join("index.toml")
+ .to_string_lossy()
+ .replace('\\', "/")
+ .trim_start_matches('/')
+ ),
);
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
@@ -762,7 +769,14 @@ path = "codex.toml"
fs::write(remote_manifest_path(Agent::Codex), current).unwrap();
std::env::set_var(
CATALOG_URL_ENV,
- format!("file://{}", web_dir.join("index.toml").display()),
+ format!(
+ "file:///{}",
+ web_dir
+ .join("index.toml")
+ .to_string_lossy()
+ .replace('\\', "/")
+ .trim_start_matches('/')
+ ),
);
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
@@ -833,7 +847,14 @@ path = "missing-cursor.toml"
.unwrap();
std::env::set_var(
CATALOG_URL_ENV,
- format!("file://{}", web_dir.join("index.toml").display()),
+ format!(
+ "file:///{}",
+ web_dir
+ .join("index.toml")
+ .to_string_lossy()
+ .replace('\\', "/")
+ .trim_start_matches('/')
+ ),
);
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
diff --git a/src/integration/assets/herdr-agent-state.test.ts b/src/integration/assets/herdr-agent-state.test.ts
index 2a86f0ca..7720e088 100644
--- a/src/integration/assets/herdr-agent-state.test.ts
+++ b/src/integration/assets/herdr-agent-state.test.ts
@@ -121,7 +121,7 @@ async function startRecordingServer(name: string): Promise<unknown[]> {
server = recordingServer;
await new Promise<void>((resolve, reject) => {
recordingServer.once("error", reject);
- recordingServer.listen(recordingSocketPath, resolve);
+ recordingServer.listen(originalPlatform === "win32" ? `\\\\.\\pipe\\${recordingSocketPath}` : recordingSocketPath, resolve);
});
configureIntegrationEnvironment(recordingSocketPath);
return requests;
@@ -378,7 +378,7 @@ test("Pi waits for a replacement session report before publishing state", async
server = recordingServer;
await new Promise<void>((resolve, reject) => {
recordingServer.once("error", reject);
- recordingServer.listen(recordingSocketPath, resolve);
+ recordingServer.listen(originalPlatform === "win32" ? `\\\\.\\pipe\\${recordingSocketPath}` : recordingSocketPath, resolve);
});
configureIntegrationEnvironment(recordingSocketPath);
@@ -457,7 +457,7 @@ async function startDroppedFirstResponseServer(name: string) {
server = recordingServer;
await new Promise<void>((resolve, reject) => {
recordingServer.once("error", reject);
- recordingServer.listen(recordingSocketPath, resolve);
+ recordingServer.listen(originalPlatform === "win32" ? `\\\\.\\pipe\\${recordingSocketPath}` : recordingSocketPath, resolve);
});
configureIntegrationEnvironment(recordingSocketPath);
diff --git a/src/integration/tests.rs b/src/integration/tests.rs
index a02a549e..b2210074 100644
--- a/src/integration/tests.rs
+++ b/src/integration/tests.rs
@@ -2082,8 +2082,8 @@ fn install_droid_writes_hook_to_settings_and_cleans_legacy_hooks_json() {
fs::write(
droid_dir.join("hooks.json"),
format!(
- r#"{{"hooks":{{"SessionStart":[{{"hooks":[{{"type":"command","command":"{}","timeout":10}}]}}],"PreToolUse":[{{"matcher":"Read","hooks":[{{"type":"command","command":"echo keep","timeout":10}}]}}]}}}}"#,
- legacy_command,
+ r#"{{"hooks":{{"SessionStart":[{{"hooks":[{{"type":"command","command":{},"timeout":10}}]}}],"PreToolUse":[{{"matcher":"Read","hooks":[{{"type":"command","command":"echo keep","timeout":10}}]}}]}}}}"#,
+ serde_json::to_string(&legacy_command).unwrap(),
),
)
.unwrap();
@@ -2207,16 +2207,16 @@ fn uninstall_droid_removes_herdr_hooks_and_preserves_others() {
fs::write(
droid_dir.join("hooks.json"),
format!(
- r#"{{"hooks":{{"SessionStart":[{{"hooks":[{{"type":"command","command":"{}","timeout":10}},{{"type":"command","command":"echo keep","timeout":10}}]}}],"PreToolUse":[{{"matcher":"Read","hooks":[{{"type":"command","command":"echo read","timeout":10}}]}}]}}}}"#,
- command,
+ r#"{{"hooks":{{"SessionStart":[{{"hooks":[{{"type":"command","command":{},"timeout":10}},{{"type":"command","command":"echo keep","timeout":10}}]}}],"PreToolUse":[{{"matcher":"Read","hooks":[{{"type":"command","command":"echo read","timeout":10}}]}}]}}}}"#,
+ serde_json::to_string(&command).unwrap(),
),
)
.unwrap();
fs::write(
droid_dir.join("settings.json"),
format!(
- r#"{{"hooks":{{"SessionStart":[{{"hooks":[{{"type":"command","command":"{}","timeout":10}}]}}],"PostToolUse":[{{"matcher":"Edit","hooks":[{{"type":"command","command":"echo post","timeout":10}}]}}]}}}}"#,
- command,
+ r#"{{"hooks":{{"SessionStart":[{{"hooks":[{{"type":"command","command":{},"timeout":10}}]}}],"PostToolUse":[{{"matcher":"Edit","hooks":[{{"type":"command","command":"echo post","timeout":10}}]}}]}}}}"#,
+ serde_json::to_string(&command).unwrap(),
),
)
.unwrap();
@@ -2812,128 +2812,6 @@ fn bundled_integration_asset_versions_match_expected_versions() {
}
#[test]
-fn bundled_integration_assets_report_session_refs() {
- assert!(PI_EXTENSION_ASSET.contains("agent_session_path"));
- assert!(PI_EXTENSION_ASSET.contains("agent_session_id"));
- assert!(PI_EXTENSION_ASSET.contains("ctx?.mode !== \"tui\""));
- assert!(PI_EXTENSION_ASSET.contains("pane.report_agent_session"));
- assert!(PI_EXTENSION_ASSET.contains("pane.report_agent\""));
- assert!(PI_EXTENSION_ASSET.contains("pi.on(\"agent_start\""));
- assert!(PI_EXTENSION_ASSET.contains("pi.on(\"agent_settled\""));
- assert!(!PI_EXTENSION_ASSET.contains("pi.on(\"session_shutdown\""));
- assert!(OMP_EXTENSION_ASSET.contains("agent_session_path"));
- assert!(OMP_EXTENSION_ASSET.contains("agent_session_id"));
- assert!(OMP_EXTENSION_ASSET.contains("ctx?.hasUI !== true"));
- assert!(OMP_EXTENSION_ASSET.contains("pane.report_agent_session"));
- assert!(OMP_EXTENSION_ASSET.contains("pane.report_agent\""));
- assert!(OMP_EXTENSION_ASSET.contains("pi.on(\"agent_start\""));
- assert!(OMP_EXTENSION_ASSET.contains("pi.on(\"agent_end\""));
- assert!(OMP_EXTENSION_ASSET.contains("pi.on(\"session_shutdown\""));
- assert!(
- CLAUDE_HOOK_ASSET.contains("agent_session_id")
- || CLAUDE_HOOK_ASSET.contains("--agent-session-id")
- );
- assert!(
- CLAUDE_HOOK_ASSET.contains("agent_session_path")
- || CLAUDE_HOOK_ASSET.contains("--agent-session-path")
- );
- assert!(CLAUDE_HOOK_ASSET.contains("agent_id"));
- assert!(
- CLAUDE_HOOK_ASSET.contains("session_start_source")
- || CLAUDE_HOOK_ASSET.contains("--session-start-source")
- );
- assert!(
- CLAUDE_HOOK_ASSET.contains("pane.report_agent_session")
- || CLAUDE_HOOK_ASSET.contains("report-agent-session")
- );
- assert!(!CLAUDE_HOOK_ASSET.contains("\"state\": action"));
- assert!(!CLAUDE_HOOK_ASSET.contains("pane.release_agent"));
- assert!(
- CODEX_HOOK_ASSET.contains("HERDR_HOOK_INPUT_FILE")
- || CODEX_HOOK_ASSET.contains("In.ReadToEnd")
- );
- assert!(
- CODEX_HOOK_ASSET.contains("agent_session_id")
- || CODEX_HOOK_ASSET.contains("--agent-session-id")
- );
- assert!(
- CODEX_HOOK_ASSET.contains("session_start_source")
- || CODEX_HOOK_ASSET.contains("--session-start-source")
- );
- assert!(CODEX_HOOK_ASSET.contains("CODEX_THREAD_ID"));
- assert!(
- CODEX_HOOK_ASSET.contains("pane.report_agent_session")
- || CODEX_HOOK_ASSET.contains("report-agent-session")
- );
- assert!(!CODEX_HOOK_ASSET.contains("\"state\": action"));
- assert!(!CODEX_HOOK_ASSET.contains("pane.release_agent"));
- assert!(KIMI_HOOK_ASSET.contains("source\": \"herdr:kimi"));
- assert!(KIMI_HOOK_ASSET.contains("agent_session_id"));
- assert!(KIMI_HOOK_ASSET.contains("method = \"pane.report_agent_session\""));
- assert!(KIMI_HOOK_ASSET.contains("params[\"session_start_source\"] = \"startup\""));
- assert!(KIMI_HOOK_ASSET.contains("method = \"pane.report_agent\""));
- assert!(KIMI_HOOK_ASSET.contains("params[\"state\"] = action"));
- assert!(!KIMI_HOOK_ASSET.contains("pane.release_agent"));
- assert!(COPILOT_HOOK_ASSET.contains("agent_session_id"));
- assert!(COPILOT_HOOK_ASSET.contains("pane.report_agent_session"));
- assert!(!COPILOT_HOOK_ASSET.contains("\"state\":"));
- assert!(!COPILOT_HOOK_ASSET.contains("pane.release_agent"));
- assert!(DEVIN_HOOK_ASSET.contains("HERDR_DEVIN_LIST_JSON"));
- assert!(DEVIN_HOOK_ASSET.contains("\"method\": \"pane.report_agent_session\""));
- assert!(!DEVIN_HOOK_ASSET.contains("\"method\": \"pane.report_agent\""));
- assert!(!DEVIN_HOOK_ASSET.contains("\"state\":"));
- assert!(!DEVIN_HOOK_ASSET.contains("pane.release_agent"));
- assert!(DEVIN_HOOK_ASSET.contains("agent_session_id"));
- assert!(DROID_HOOK_ASSET.contains("agent_session_id"));
- assert!(DROID_HOOK_ASSET.contains("pane.report_agent_session"));
- assert!(!DROID_HOOK_ASSET.contains("\"state\": action"));
- assert!(!DROID_HOOK_ASSET.contains("pane.release_agent"));
- assert!(OPENCODE_PLUGIN_ASSET.contains("properties?.sessionID"));
- assert!(OPENCODE_PLUGIN_ASSET.contains("params.agent_session_id = sessionID"));
- assert!(OPENCODE_PLUGIN_ASSET.contains("pane.report_agent_session"));
- assert!(OPENCODE_PLUGIN_ASSET.contains("reportState"));
- assert!(!OPENCODE_PLUGIN_ASSET.contains("pane.release_agent"));
- assert!(KILO_PLUGIN_ASSET.contains("SOURCE = \"herdr:kilo\""));
- assert!(KILO_PLUGIN_ASSET.contains("AGENT = \"kilo\""));
- assert!(KILO_PLUGIN_ASSET.contains("pane.report_agent_session"));
- assert!(KILO_PLUGIN_ASSET.contains("session_start_source: \"startup\""));
- assert!(KILO_PLUGIN_ASSET.contains("reportState"));
- assert!(!KILO_PLUGIN_ASSET.contains("pane.release_agent"));
- assert!(QODERCLI_HOOK_ASSET.contains("HERDR_PANE_ID"));
- assert!(QODERCLI_HOOK_ASSET.contains("session_id"));
- assert!(QODERCLI_HOOK_ASSET.contains("report-agent-session"));
- assert!(QODERCLI_HOOK_ASSET.contains("--agent-session-id"));
- assert!(!QODERCLI_HOOK_ASSET.contains("report-agent\""));
- assert!(!QODERCLI_HOOK_ASSET.contains("release-agent"));
- assert!(CURSOR_HOOK_ASSET.contains("HERDR_INTEGRATION_ID=cursor"));
- assert!(CURSOR_HOOK_ASSET.contains("conversation_id"));
- assert!(CURSOR_HOOK_ASSET.contains("conversationId"));
- assert!(CURSOR_HOOK_ASSET.contains("sessionId"));
- assert!(CURSOR_HOOK_ASSET.contains("agent_session_id"));
- assert!(CURSOR_HOOK_ASSET.contains("pane.report_agent_session"));
- assert!(CURSOR_HOOK_ASSET.contains("hook_event_name"));
- assert!(CURSOR_HOOK_ASSET.contains("sessionStart"));
- assert!(!CURSOR_HOOK_ASSET.contains("\"state\":"));
- assert!(!CURSOR_HOOK_ASSET.contains("pane.release_agent"));
- assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_ID=mastracode"));
- assert!(MASTRACODE_HOOK_ASSET.contains("HERDR_INTEGRATION_VERSION=2"));
- assert!(MASTRACODE_HOOK_ASSET.contains("session_id"));
- assert!(!MASTRACODE_HOOK_ASSET.contains("run_id"));
- assert!(MASTRACODE_HOOK_ASSET.contains("agent_session_id"));
- assert!(MASTRACODE_HOOK_ASSET.contains("pane.report_agent_session"));
- assert!(MASTRACODE_HOOK_ASSET.contains("session_start_source"));
- assert!(MASTRACODE_HOOK_ASSET.contains("pane.report_agent"));
- assert!(GROK_HOOK_ASSET.contains("HERDR_INTEGRATION_ID=grok"));
- assert!(GROK_HOOK_ASSET.contains("GROK_SESSION_ID"));
- assert!(GROK_HOOK_ASSET.contains("sessionId"));
- assert!(GROK_HOOK_ASSET.contains("agent_session_id"));
- assert!(GROK_HOOK_ASSET.contains("pane.report_agent_session"));
- assert!(GROK_HOOK_ASSET.contains("herdr:grok"));
- assert!(!GROK_HOOK_ASSET.contains("\"state\":"));
- assert!(!GROK_HOOK_ASSET.contains("pane.release_agent"));
-}
-
-#[test]
fn process_owned_integration_assets_do_not_report_release() {
for (name, asset) in [
("pi", PI_EXTENSION_ASSET),
@@ -4061,7 +3939,7 @@ fn install_antigravity_cli_rewrites_stale_herdr_block() {
assert!(entries[0]
.get("command")
.and_then(Value::as_str)
- .is_some_and(|command| command.contains("herdr-agent-state")));
+ .is_some_and(|command| command != "stale" && command != "stale idle"));
std::env::remove_var(ANTIGRAVITY_CLI_CONFIG_DIR_ENV_VAR);
let _ = fs::remove_dir_all(base);
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs
index ecc12ed5..a804a7fb 100644
--- a/src/pane/terminal.rs
+++ b/src/pane/terminal.rs
@@ -4681,10 +4681,18 @@ mod tests {
unicode: u16::from(b'/'),
control_key_state: 0x0010,
});
+ #[cfg(windows)]
+ let legacy_expected = b"\x1b[55;8;47;1;16;3_".as_slice();
+ #[cfg(not(windows))]
+ let legacy_expected = b"///".as_slice();
assert_eq!(
pane.encode_terminal_key(shifted.clone(), crate::input::KeyboardProtocol::Legacy,),
- b"///"
+ legacy_expected
);
+ let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap();
+ terminal.write(b"\x1b[>15u");
+ let (tx, _rx) = mpsc::channel(4);
+ let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap();
assert_eq!(
pane.encode_terminal_key(shifted, crate::input::KeyboardProtocol::Kitty { flags: 15 },),
b"\x1b[47;2:1u\x1b[47;2:2u\x1b[47;2:2u"
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index f15c71ef..402b695d 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -227,14 +227,6 @@ mod unix_common;
#[cfg(unix)]
pub(crate) use unix_common::{begin_cli_output, end_cli_output};
-#[cfg(not(windows))]
-pub(crate) fn replace_file(
- source: &std::path::Path,
- destination: &std::path::Path,
-) -> std::io::Result<()> {
- std::fs::rename(source, destination)
-}
-
#[cfg(not(unix))]
pub(crate) fn begin_cli_output() {}
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index 8d370964..10fbb6f0 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -19,39 +19,6 @@ pub(super) fn read_terminal_grid_size() -> std::io::Result<(u16, u16)> {
crossterm::terminal::size()
}
-pub(crate) fn replace_file(
- source: &std::path::Path,
- destination: &std::path::Path,
-) -> std::io::Result<()> {
- use std::os::windows::ffi::OsStrExt;
- use windows_sys::Win32::Storage::FileSystem::{
- MoveFileExW, MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH,
- };
-
- let source = source
- .as_os_str()
- .encode_wide()
- .chain(std::iter::once(0))
- .collect::<Vec<_>>();
- let destination = destination
- .as_os_str()
- .encode_wide()
- .chain(std::iter::once(0))
- .collect::<Vec<_>>();
- let moved = unsafe {
- MoveFileExW(
- source.as_ptr(),
- destination.as_ptr(),
- MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
- )
- };
- if moved == 0 {
- Err(std::io::Error::last_os_error())
- } else {
- Ok(())
- }
-}
-
pub(crate) fn set_default_plugin_pane_pwd(
_env: &mut Vec<(String, String)>,
_cwd: &std::path::Path,
diff --git a/src/server/headless/tests/mod.rs b/src/server/headless/tests/mod.rs
index 1e4249b5..e7e73e8b 100644
--- a/src/server/headless/tests/mod.rs
+++ b/src/server/headless/tests/mod.rs
@@ -18,7 +18,7 @@ fn test_headless_server() -> HeadlessServer {
fn test_headless_server_with_event_hub(event_hub: api::EventHub) -> HeadlessServer {
let config = crate::config::Config::default();
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
- let app = crate::app::App::new(
+ let mut app = crate::app::App::new(
&config,
crate::app::AppPolicy::TEST,
None,
@@ -26,6 +26,7 @@ fn test_headless_server_with_event_hub(event_hub: api::EventHub) -> HeadlessServ
event_hub,
);
+ app.state.default_shell = crate::app::exiting_test_command().into();
let dir = std::env::temp_dir().join(format!(
"hh-{}-{}",
std::process::id(),
@@ -619,6 +620,9 @@ async fn client_shell_attach_seeds_workspace() {
#[tokio::test]
async fn client_shell_endpoint_request_uses_the_selected_connection() {
let mut server = test_headless_server();
+ server.app.state.workspaces = vec![crate::workspace::Workspace::test_new("endpoint")];
+ server.app.state.ensure_test_terminals();
+ server.app.state.active = Some(0);
let (writer, control_rx, _render_rx) = test_client_writer();
let client_id = 41;
assert!(
diff --git a/src/terminal/metadata.rs b/src/terminal/metadata.rs
index 2a912b27..91b1d582 100644
--- a/src/terminal/metadata.rs
+++ b/src/terminal/metadata.rs
@@ -881,11 +881,12 @@ mod tests {
});
assert_eq!(terminal.next_agent_metadata_expiry(), Some(old_deadline));
- assert_eq!(
- terminal.effective_presentation().title.as_deref(),
- Some("Prompt title")
+ let presentation = terminal.effective_presentation_for_state_at(
+ terminal.state,
+ old_deadline - Duration::from_millis(1),
);
- assert_eq!(terminal.effective_presentation().display_agent, None);
+ assert_eq!(presentation.title.as_deref(), Some("Prompt title"));
+ assert_eq!(presentation.display_agent, None);
let mutation = terminal
.expire_agent_metadata_at(old_deadline, old_deadline)