aboutsummaryrefslogtreecommitdiff
path: root/src/terminal/state.rs
diff options
context:
space:
mode:
authorLudovico Magnocavallo <ludo@qix.it>2026-08-01 18:32:47 +0200
committerGitHub <noreply@github.com>2026-08-01 19:32:47 +0300
commit679584fdd49f9dee5ad95755af65eedc7d0f455c (patch)
treeb21359553af294c2a2d57726e99029d86534cda0 /src/terminal/state.rs
parent219e0be351436b5bf1ccf5b7c13dbf6c2cdb0986 (diff)
feat: add antigravity-cli integration (#2087)
* feat: add antigravity_cli integration harness Register the `antigravity_cli` target, resolve its configuration directory to `~/.gemini/antigravity-cli/`, wire target actions, and add lifecycle hook scripts for Unix and Windows. * feat: support session resume for antigravity-cli * fix: correct antigravity-cli hook config path, shape, and agent label Antigravity CLI reads global customizations from ~/.gemini/config, keys hooks.json by hook name, and only accepts the matcher/hooks wrapper for the tool events. The previous install wrote event arrays at the top level of ~/.gemini/antigravity-cli/hooks.json, which agy never reads and would reject anyway, so no Herdr hook ever ran. Session reports were dropped for a third reason: the hooks reported the agent as antigravity-cli, but the server normalizes that to the canonical label agy before matching, so is_official_agent_source never matched and agy --conversation resume was unreachable. - Resolve the config dir to ~/.gemini/config - Nest Herdr entries under a Herdr-owned "herdr" block that install rewrites and uninstall removes, leaving other named hooks untouched - Emit grouped entries for PreToolUse/PostToolUse and flat handler lists for PreInvocation/PostInvocation/Stop - Report the canonical agy label from both hook assets and match it in agent_resume - Compile-gate the hook asset constants and stop shadowing $args in the PowerShell hook - Document the real directory, that it must already exist, and the named-block behavior refs #1011 refs #1571 * fix: make antigravity-cli integration session-only antigravity cli cannot express lifecycle safely: there is no blocked event, postinvocation is skipped on interruption, and stop fires at the end of a turn rather than on process exit, which left stale state and released authority at the wrong time. report only the conversation on preinvocation and let screen detection own agent state. resume via `agy --conversation <id>` is unchanged. * fix: update antigravity session after conversation switch --------- Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/terminal/state.rs')
-rw-r--r--src/terminal/state.rs213
1 files changed, 119 insertions, 94 deletions
diff --git a/src/terminal/state.rs b/src/terminal/state.rs
index e806967e..5ed5adc4 100644
--- a/src/terminal/state.rs
+++ b/src/terminal/state.rs
@@ -1234,7 +1234,7 @@ impl TerminalState {
&& current_kind == crate::agent_resume::AgentSessionRefKind::Id
&& session_ref.kind == crate::agent_resume::AgentSessionRefKind::Id
&& current_value != session_ref.value
- && !Self::session_start_source_allows_session_replacement(
+ && !Self::session_report_allows_session_replacement(
source,
agent_label,
session_start_source,
@@ -1267,7 +1267,7 @@ impl TerminalState {
})
}
- fn session_start_source_allows_session_replacement(
+ fn session_report_allows_session_replacement(
source: &str,
agent_label: &str,
session_start_source: Option<&str>,
@@ -1291,6 +1291,7 @@ impl TerminalState {
"omp",
Some("startup" | "new" | "resume" | "fork")
)
+ | ("herdr:antigravity_cli", "agy", None)
)
}
@@ -1413,12 +1414,12 @@ impl TerminalState {
if self.known_agent_label_conflicts_with_detected_agent(&agent_label) {
return None;
}
- let session_replacement_allowed = Self::session_start_source_allows_session_replacement(
+ let session_replacement_allowed = Self::session_report_allows_session_replacement(
&source,
&agent_label,
session_start_source.as_deref(),
);
- let replacing_hermes_session =
+ let replacing_identity_only_session =
crate::detect::session_identity_only_integration(&source, &agent_label)
&& session_replacement_allowed
&& self.current_session_identity_for_persistence().is_some_and(
@@ -1430,7 +1431,7 @@ impl TerminalState {
&& current_value != session_ref.value
},
);
- if replacing_hermes_session && !process_present {
+ if replacing_identity_only_session && !process_present {
return None;
}
let owner_conflicts = self.current_session_owner_conflicts(&source, &agent_label);
@@ -2309,101 +2310,125 @@ mod tests {
}
#[test]
- fn hermes_session_claim_leaves_state_to_detection() {
- let mut terminal = test_terminal();
- terminal.set_detected_state(Some(Agent::Hermes), AgentState::Idle);
- let session_ref = crate::agent_resume::AgentSessionRef::id("hermes-root").unwrap();
-
- let session = terminal.set_agent_session_ref_for_session_start(
- "herdr:hermes".into(),
- "hermes".into(),
- Some(session_ref.clone()),
- Some(10),
- Some("startup".into()),
- );
-
- assert!(session.is_some());
- assert!(terminal.hook_authority.is_none());
- assert_eq!(
- terminal
- .persisted_agent_session
- .as_ref()
- .map(|session| &session.session_ref),
- Some(&session_ref)
- );
-
- terminal.set_detected_state(Some(Agent::Hermes), AgentState::Working);
+ fn session_identity_claims_leave_state_to_detection() {
+ for (source, label, agent, start_source, replacement_source) in [
+ (
+ "herdr:hermes",
+ "hermes",
+ Agent::Hermes,
+ Some("startup"),
+ Some("resume"),
+ ),
+ (
+ "herdr:antigravity_cli",
+ "agy",
+ Agent::Antigravity,
+ None,
+ None,
+ ),
+ ] {
+ let mut terminal = test_terminal();
+ terminal.set_detected_state(Some(agent), AgentState::Idle);
+ let first_ref =
+ crate::agent_resume::AgentSessionRef::id(format!("{label}-root")).unwrap();
+ let first = terminal.set_agent_session_ref_for_session_start(
+ source.into(),
+ label.into(),
+ Some(first_ref.clone()),
+ Some(10),
+ start_source.map(str::to_string),
+ );
- assert_eq!(terminal.state, AgentState::Working);
- assert!(terminal.hook_authority.is_none());
+ assert!(first.is_some(), "{label} should accept its session");
+ assert!(terminal.hook_authority.is_none());
+ assert_eq!(terminal.state, AgentState::Idle);
+ assert_eq!(
+ terminal
+ .persisted_agent_session
+ .as_ref()
+ .map(|session| &session.session_ref),
+ Some(&first_ref)
+ );
- let replacement_ref =
- crate::agent_resume::AgentSessionRef::id("hermes-replacement").unwrap();
- let replacement = terminal.set_agent_session_ref_for_session_start(
- "herdr:hermes".into(),
- "hermes".into(),
- Some(replacement_ref.clone()),
- Some(11),
- Some("startup".into()),
- );
+ terminal.set_detected_state(Some(agent), AgentState::Working);
+ let replacement_ref =
+ crate::agent_resume::AgentSessionRef::id(format!("{label}-replacement")).unwrap();
+ let replacement = terminal.set_agent_session_ref_for_session_start(
+ source.into(),
+ label.into(),
+ Some(replacement_ref.clone()),
+ Some(11),
+ start_source.map(str::to_string),
+ );
- assert!(replacement.is_some());
- assert_eq!(terminal.state, AgentState::Working);
- assert!(terminal.hook_authority.is_none());
- assert_eq!(
- terminal
- .persisted_agent_session
- .as_ref()
- .map(|session| &session.session_ref),
- Some(&replacement_ref)
- );
+ assert!(
+ replacement.is_some_and(|mutation| mutation.session_ref_changed),
+ "{label} should replace its detected session"
+ );
+ assert!(terminal.hook_authority.is_none());
+ assert_eq!(terminal.state, AgentState::Working);
+ assert_eq!(
+ terminal
+ .persisted_agent_session
+ .as_ref()
+ .map(|session| &session.session_ref),
+ Some(&replacement_ref)
+ );
- let legacy_state = terminal.set_hook_authority_with_session_ref(
- "herdr:hermes".into(),
- "hermes".into(),
- AgentState::Blocked,
- None,
- Some(replacement_ref.clone()),
- Some(12),
- );
- assert!(legacy_state.is_none());
- assert_eq!(terminal.state, AgentState::Working);
- assert!(terminal.hook_authority.is_none());
+ let legacy_state = terminal.set_hook_authority_with_session_ref(
+ source.into(),
+ label.into(),
+ AgentState::Blocked,
+ None,
+ Some(replacement_ref.clone()),
+ Some(12),
+ );
+ assert!(legacy_state.is_none());
+ assert!(terminal.hook_authority.is_none());
+ assert_eq!(terminal.state, AgentState::Working);
- terminal.set_detected_state(None, AgentState::Unknown);
- let background_replacement = terminal.set_agent_session_ref_for_session_start(
- "herdr:hermes".into(),
- "hermes".into(),
- crate::agent_resume::AgentSessionRef::id("hermes-background"),
- Some(13),
- Some("resume".into()),
- );
- assert!(background_replacement.is_none());
- assert_eq!(
- terminal
- .persisted_agent_session
- .as_ref()
- .map(|session| &session.session_ref),
- Some(&replacement_ref)
- );
+ terminal.set_detected_state(None, AgentState::Unknown);
+ let background_ref =
+ crate::agent_resume::AgentSessionRef::id(format!("{label}-background")).unwrap();
+ let background_replacement = terminal.set_agent_session_ref_for_session_start(
+ source.into(),
+ label.into(),
+ Some(background_ref.clone()),
+ Some(13),
+ replacement_source.map(str::to_string),
+ );
+ assert!(
+ background_replacement.is_none(),
+ "{label} should reject a background replacement"
+ );
+ assert_eq!(
+ terminal
+ .persisted_agent_session
+ .as_ref()
+ .map(|session| &session.session_ref),
+ Some(&replacement_ref)
+ );
- terminal.set_detected_state(Some(Agent::Hermes), AgentState::Idle);
- let retried_ref = crate::agent_resume::AgentSessionRef::id("hermes-background").unwrap();
- let retried_replacement = terminal.set_agent_session_ref_for_session_start(
- "herdr:hermes".into(),
- "hermes".into(),
- Some(retried_ref.clone()),
- Some(14),
- Some("resume".into()),
- );
- assert!(retried_replacement.is_some());
- assert_eq!(
- terminal
- .persisted_agent_session
- .as_ref()
- .map(|session| &session.session_ref),
- Some(&retried_ref)
- );
+ terminal.set_detected_state(Some(agent), AgentState::Idle);
+ let retried_replacement = terminal.set_agent_session_ref_for_session_start(
+ source.into(),
+ label.into(),
+ Some(background_ref.clone()),
+ Some(14),
+ replacement_source.map(str::to_string),
+ );
+ assert!(
+ retried_replacement.is_some_and(|mutation| mutation.session_ref_changed),
+ "{label} should replace the session once detected"
+ );
+ assert_eq!(
+ terminal
+ .persisted_agent_session
+ .as_ref()
+ .map(|session| &session.session_ref),
+ Some(&background_ref)
+ );
+ }
}
#[test]