parent
05e44d0039
commit
f7b26a9117
|
|
@ -10,6 +10,7 @@
|
|||
- Added native Kiro CLI detection with idle and working state heuristics. (#185)
|
||||
|
||||
### Fixed
|
||||
- The Claude Code integration no longer lets subagent completion hooks report durable `working`, preventing delayed recap or subagent completion events from reviving an idle pane. (#198)
|
||||
- Remote clients now bridge local clipboard images into the remote pane by staging them as temporary image files and pasting the remote path, so Claude Code image paste works over `herdr --remote`. (#205)
|
||||
|
||||
### Breaking Changes
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# installed by herdr
|
||||
# safe to edit. this hook only activates inside herdr-managed panes.
|
||||
# HERDR_INTEGRATION_ID=claude
|
||||
# HERDR_INTEGRATION_VERSION=2
|
||||
# HERDR_INTEGRATION_VERSION=3
|
||||
|
||||
set -eu
|
||||
|
||||
|
|
@ -47,9 +47,16 @@ if hook_input_file:
|
|||
except Exception:
|
||||
hook_input = {}
|
||||
|
||||
hook_event_name = str(hook_input.get("hook_event_name") or "")
|
||||
is_subagent = bool(hook_input.get("agent_id"))
|
||||
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)
|
||||
if is_subagent and action in ("idle", "release"):
|
||||
action = "working"
|
||||
# Subagent completion must not make the parent pane look done early.
|
||||
raise SystemExit(0)
|
||||
|
||||
request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}"
|
||||
report_seq = time.time_ns()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const PI_INTEGRATION_VERSION: u32 = 1;
|
|||
const PI_CODING_AGENT_DIR_ENV_VAR: &str = "PI_CODING_AGENT_DIR";
|
||||
const CLAUDE_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh";
|
||||
const CLAUDE_HOOK_ASSET: &str = include_str!("assets/claude/herdr-agent-state.sh");
|
||||
const CLAUDE_INTEGRATION_VERSION: u32 = 2;
|
||||
const CLAUDE_INTEGRATION_VERSION: u32 = 3;
|
||||
const CLAUDE_CONFIG_DIR_ENV_VAR: &str = "CLAUDE_CONFIG_DIR";
|
||||
const CODEX_HOOK_INSTALL_NAME: &str = "herdr-agent-state.sh";
|
||||
const CODEX_HOOK_ASSET: &str = include_str!("assets/codex/herdr-agent-state.sh");
|
||||
|
|
@ -591,6 +591,11 @@ pub(crate) fn install_claude() -> io::Result<ClaudeInstallPaths> {
|
|||
"PostToolUseFailure",
|
||||
&format!("bash {quoted_hook_path} working"),
|
||||
)?;
|
||||
remove_command_hook(
|
||||
hooks,
|
||||
"SubagentStop",
|
||||
&format!("bash {quoted_hook_path} working"),
|
||||
)?;
|
||||
ensure_command_hook(
|
||||
hooks,
|
||||
"UserPromptSubmit",
|
||||
|
|
@ -612,13 +617,6 @@ pub(crate) fn install_claude() -> io::Result<ClaudeInstallPaths> {
|
|||
10,
|
||||
Some("*"),
|
||||
)?;
|
||||
ensure_command_hook(
|
||||
hooks,
|
||||
"SubagentStop",
|
||||
format!("bash {quoted_hook_path} working"),
|
||||
10,
|
||||
Some("*"),
|
||||
)?;
|
||||
ensure_command_hook(
|
||||
hooks,
|
||||
"Stop",
|
||||
|
|
@ -1680,10 +1678,7 @@ mod tests {
|
|||
);
|
||||
assert!(settings["hooks"].get("PostToolUse").is_none());
|
||||
assert!(settings["hooks"].get("PostToolUseFailure").is_none());
|
||||
assert!(settings["hooks"]["SubagentStop"][0]["hooks"][0]["command"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains(" working"));
|
||||
assert!(settings["hooks"].get("SubagentStop").is_none());
|
||||
assert!(settings["hooks"]["Stop"][0]["hooks"][0]["command"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
|
|
@ -1749,10 +1744,7 @@ mod tests {
|
|||
);
|
||||
assert!(settings["hooks"].get("PostToolUse").is_none());
|
||||
assert!(settings["hooks"].get("PostToolUseFailure").is_none());
|
||||
assert_eq!(
|
||||
settings["hooks"]["SubagentStop"].as_array().unwrap().len(),
|
||||
1
|
||||
);
|
||||
assert!(settings["hooks"].get("SubagentStop").is_none());
|
||||
assert_eq!(settings["hooks"]["Stop"].as_array().unwrap().len(), 1);
|
||||
assert_eq!(settings["hooks"]["SessionEnd"].as_array().unwrap().len(), 1);
|
||||
|
||||
|
|
@ -1761,7 +1753,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn install_claude_removes_deprecated_post_tool_hooks_and_preserves_user_hooks() {
|
||||
fn install_claude_removes_deprecated_completion_hooks_and_preserves_user_hooks() {
|
||||
let _lock = integration_env_lock();
|
||||
let base = unique_base();
|
||||
let home = base.join("home");
|
||||
|
|
@ -1772,7 +1764,8 @@ mod tests {
|
|||
fs::write(
|
||||
claude_dir.join("settings.json"),
|
||||
format!(
|
||||
r#"{{"hooks":{{"PostToolUse":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-post","timeout":10}}]}}],"PostToolUseFailure":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-failure","timeout":10}}]}}]}}}}"#,
|
||||
r#"{{"hooks":{{"PostToolUse":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-post","timeout":10}}]}}],"PostToolUseFailure":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-failure","timeout":10}}]}}],"SubagentStop":[{{"matcher":"*","hooks":[{{"type":"command","command":"bash '{}' working","timeout":10}},{{"type":"command","command":"echo keep-subagent","timeout":10}}]}}]}}}}"#,
|
||||
hook_path.display(),
|
||||
hook_path.display(),
|
||||
hook_path.display(),
|
||||
),
|
||||
|
|
@ -1793,6 +1786,10 @@ mod tests {
|
|||
settings["hooks"]["PostToolUseFailure"][0]["hooks"][0]["command"],
|
||||
"echo keep-failure"
|
||||
);
|
||||
assert_eq!(
|
||||
settings["hooks"]["SubagentStop"][0]["hooks"][0]["command"],
|
||||
"echo keep-subagent"
|
||||
);
|
||||
assert_eq!(
|
||||
settings["hooks"]["UserPromptSubmit"]
|
||||
.as_array()
|
||||
|
|
@ -1830,7 +1827,37 @@ mod tests {
|
|||
|
||||
assert_eq!(claude.path, hook_path);
|
||||
assert_eq!(claude.installed_version, Some(1));
|
||||
assert_eq!(claude.expected_version, 2);
|
||||
assert_eq!(claude.expected_version, 3);
|
||||
assert_eq!(claude.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_v2_integration_status_is_outdated() {
|
||||
let _lock = integration_env_lock();
|
||||
let base = unique_base();
|
||||
let home = base.join("home");
|
||||
let claude_hooks_dir = home.join(".claude").join("hooks");
|
||||
fs::create_dir_all(&claude_hooks_dir).unwrap();
|
||||
let hook_path = claude_hooks_dir.join(CLAUDE_HOOK_INSTALL_NAME);
|
||||
fs::write(
|
||||
&hook_path,
|
||||
"#!/bin/sh\n# HERDR_INTEGRATION_ID=claude\n# HERDR_INTEGRATION_VERSION=2\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::env::set_var("HOME", &home);
|
||||
|
||||
let statuses = installed_integration_statuses();
|
||||
let claude = statuses
|
||||
.iter()
|
||||
.find(|status| status.target == crate::api::schema::IntegrationTarget::Claude)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(claude.path, hook_path);
|
||||
assert_eq!(claude.installed_version, Some(2));
|
||||
assert_eq!(claude.expected_version, 3);
|
||||
assert_eq!(claude.state, IntegrationStatusKind::Outdated);
|
||||
|
||||
std::env::remove_var("HOME");
|
||||
|
|
|
|||
|
|
@ -461,19 +461,13 @@ fn claude_hook_reports_subagent_working_and_blocked() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn claude_hook_converts_subagent_idle_and_release_to_working() {
|
||||
fn claude_hook_ignores_subagent_completion_reports() {
|
||||
let subagent_input =
|
||||
r#"{"hook_event_name":"SubagentStop","agent_id":"agent-abc123","agent_type":"Explore"}"#;
|
||||
|
||||
let idle = run_claude_hook("idle", subagent_input)
|
||||
.expect("subagent idle should keep parent pane working");
|
||||
assert_eq!(idle["method"], "pane.report_agent");
|
||||
assert_eq!(idle["params"]["state"], "working");
|
||||
|
||||
let release = run_claude_hook("release", subagent_input)
|
||||
.expect("subagent release should keep parent pane working");
|
||||
assert_eq!(release["method"], "pane.report_agent");
|
||||
assert_eq!(release["params"]["state"], "working");
|
||||
assert!(run_claude_hook("working", subagent_input).is_none());
|
||||
assert!(run_claude_hook("idle", subagent_input).is_none());
|
||||
assert!(run_claude_hook("release", subagent_input).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue