mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 00:01:06 +00:00
@@ -6,6 +6,7 @@
|
||||
- Custom themes can now define separate light and dark color overrides when automatic theme switching is enabled. (#837, thanks @aneym)
|
||||
|
||||
### Fixed
|
||||
- Claude Code integration hooks now ignore Cursor CLI's Claude-compatible session events, preventing Cursor sessions from being stored as resumable Claude sessions. (#2832)
|
||||
- Running named servers now activate remote agent-detection manifests downloaded by another server, preventing stale agent states and `agent explain` output until restart. (#2711)
|
||||
- New lifecycle event subscriptions now stream only events emitted after subscription begins instead of replaying retained history. (#1270)
|
||||
- Windows users whose endpoint security blocks the fileless PowerShell install command can now use a local `install.cmd` bootstrap; installer downloads use `curl.exe` while preserving package checksum verification. (#2751)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=claude
|
||||
# HERDR_INTEGRATION_VERSION=8
|
||||
# HERDR_INTEGRATION_VERSION=9
|
||||
|
||||
param([string]$Action = "")
|
||||
|
||||
@@ -17,8 +17,10 @@ try {
|
||||
exit 0
|
||||
}
|
||||
|
||||
$propertyNames = @($payload.PSObject.Properties.Name)
|
||||
if ((Test-Path Env:CURSOR_VERSION) -or $propertyNames -ccontains "cursor_version") { exit 0 }
|
||||
if (-not ($propertyNames -ccontains "hook_event_name") -or $payload.hook_event_name -isnot [string] -or $payload.hook_event_name -cne "SessionStart") { exit 0 }
|
||||
if (-not [string]::IsNullOrWhiteSpace($payload.agent_id)) { exit 0 }
|
||||
if ($payload.hook_event_name -eq "SubagentStop") { exit 0 }
|
||||
|
||||
$sessionId = $payload.session_id
|
||||
if ([string]::IsNullOrWhiteSpace($sessionId)) { exit 0 }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# managed by herdr; reinstalling or updating the integration overwrites this file.
|
||||
# add custom hooks beside this file instead of editing it.
|
||||
# HERDR_INTEGRATION_ID=claude
|
||||
# HERDR_INTEGRATION_VERSION=8
|
||||
# HERDR_INTEGRATION_VERSION=9
|
||||
|
||||
set -eu
|
||||
|
||||
@@ -48,15 +48,14 @@ if hook_input_file:
|
||||
except Exception:
|
||||
hook_input = {}
|
||||
|
||||
if "CURSOR_VERSION" in os.environ or "cursor_version" in hook_input:
|
||||
raise SystemExit(0)
|
||||
hook_event_name = str(hook_input.get("hook_event_name") or "")
|
||||
if hook_event_name != "SessionStart":
|
||||
raise SystemExit(0)
|
||||
is_subagent = bool(hook_input.get("agent_id"))
|
||||
if is_subagent:
|
||||
raise SystemExit(0)
|
||||
if hook_event_name == "SubagentStop":
|
||||
# SubagentStop is a completion event. Older Herdr integrations mapped it
|
||||
# to durable working, but Claude recap/away-summary can emit it after the
|
||||
# main turn has already stopped. Never let it revive an idle pane.
|
||||
raise SystemExit(0)
|
||||
request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}"
|
||||
report_seq = time.time_ns()
|
||||
session_id = hook_input.get("session_id")
|
||||
|
||||
@@ -38,7 +38,7 @@ const CLAUDE_HOOK_ASSET: &str = if cfg!(windows) {
|
||||
} else {
|
||||
include_str!("assets/claude/herdr-agent-state.sh")
|
||||
};
|
||||
const CLAUDE_INTEGRATION_VERSION: u32 = 8;
|
||||
const CLAUDE_INTEGRATION_VERSION: u32 = 9;
|
||||
const CODEX_HOOK_INSTALL_NAME: &str = if cfg!(windows) {
|
||||
"herdr-agent-state.ps1"
|
||||
} else {
|
||||
|
||||
@@ -1060,7 +1060,7 @@ fn claude_v1_integration_status_is_outdated() {
|
||||
|
||||
assert_eq!(claude.path, hook_path);
|
||||
assert_eq!(claude.installed_version, Some(1));
|
||||
assert_eq!(claude.expected_version, 8);
|
||||
assert_eq!(claude.expected_version, 9);
|
||||
assert_eq!(claude.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
@@ -1090,7 +1090,7 @@ fn claude_v2_integration_status_is_outdated() {
|
||||
|
||||
assert_eq!(claude.path, hook_path);
|
||||
assert_eq!(claude.installed_version, Some(2));
|
||||
assert_eq!(claude.expected_version, 8);
|
||||
assert_eq!(claude.expected_version, 9);
|
||||
assert_eq!(claude.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
|
||||
@@ -84,6 +84,7 @@ fn run_shell_hook_with_env(
|
||||
.env("HERDR_SOCKET_PATH", &socket_path)
|
||||
.env("HERDR_PANE_ID", "p_test")
|
||||
.env_remove("CODEX_THREAD_ID")
|
||||
.env_remove("CURSOR_VERSION")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
@@ -150,6 +151,31 @@ fn claude_hook_reports_session_id_from_stdin() {
|
||||
assert!(request["params"].get("state").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_hook_ignores_cursor_compatibility_payloads() {
|
||||
assert!(run_claude_hook(
|
||||
"session",
|
||||
r#"{"hook_event_name":"sessionStart","session_id":"cursor-session"}"#,
|
||||
)
|
||||
.is_none());
|
||||
|
||||
assert!(run_claude_hook(
|
||||
"session",
|
||||
r#"{"hook_event_name":"SessionStart","session_id":"cursor-session","cursor_version":"2026.08.11-e8db854"}"#,
|
||||
)
|
||||
.is_none());
|
||||
|
||||
for cursor_version in ["2026.08.11-e8db854", ""] {
|
||||
assert!(run_shell_hook_with_env(
|
||||
"src/integration/assets/claude/herdr-agent-state.sh",
|
||||
&["session"],
|
||||
r#"{"hook_event_name":"SessionStart","session_id":"cursor-session"}"#,
|
||||
&[("CURSOR_VERSION", cursor_version)],
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_hook_reports_persisted_root_session_and_ignores_ephemeral_or_nested_sessions() {
|
||||
let request = run_codex_hook(
|
||||
|
||||
Reference in New Issue
Block a user