Skip to content

Commit 0217270

Browse files
authored
Merge pull request #21 from 23092003e/fix/antigravity-windows-hook
fix(windows): Antigravity hook sessions now display correctly
2 parents aeabf8e + ab1c98a commit 0217270

1 file changed

Lines changed: 133 additions & 2 deletions

File tree

windows/src-tauri/src/cli.rs

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,22 @@ pub fn run_hook(args: &[String]) {
3535
let _ = std::io::stdin().read_to_string(&mut buf);
3636
let v: Value = serde_json::from_str(&buf).unwrap_or(Value::Null);
3737

38-
let event = first_str(&v, &["hook_event_name", "agent_action_name", "hookEventName", "eventName"]);
38+
// Antigravity sends no event-name field; infer state from discriminator fields
39+
// (mirrors AntigravityHookPayload.makeEvent in the macOS core).
40+
let event = if agent == "antigravity" {
41+
if v.get("terminationReason").is_some() || v.get("fullyIdle").is_some() {
42+
Some("done".to_string())
43+
} else if v.get("toolCall").is_some()
44+
|| v.get("invocationNum").is_some()
45+
|| v.get("stepIdx").is_some()
46+
{
47+
Some("working".to_string())
48+
} else {
49+
None
50+
}
51+
} else {
52+
first_str(&v, &["hook_event_name", "agent_action_name", "hookEventName", "eventName"])
53+
};
3954
let session = first_str(&v, &["session_id", "conversation_id", "trajectory_id", "sessionId", "conversationId"]);
4055
let project = first_str(&v, &["cwd", "projectRoot"])
4156
.or_else(|| {
@@ -45,6 +60,14 @@ pub fn run_hook(args: &[String]) {
4560
.and_then(|x| x.as_str())
4661
.map(String::from)
4762
})
63+
.or_else(|| {
64+
// Antigravity uses camelCase workspacePaths (array), not workspace_roots
65+
v.get("workspacePaths")
66+
.and_then(|a| a.as_array())
67+
.and_then(|a| a.iter().find(|x| x.as_str().map(|s| !s.is_empty()).unwrap_or(false)))
68+
.and_then(|x| x.as_str())
69+
.map(String::from)
70+
})
4871
.unwrap_or_default();
4972

5073
if session.as_deref().unwrap_or("").is_empty() && event.as_deref().unwrap_or("").is_empty() {
@@ -204,7 +227,7 @@ fn flag(args: &[String], name: &str) -> Option<String> {
204227
None
205228
}
206229

207-
fn first_str(v: &Value, keys: &[&str]) -> Option<String> {
230+
pub(super) fn first_str(v: &Value, keys: &[&str]) -> Option<String> {
208231
for k in keys {
209232
if let Some(s) = v.get(*k).and_then(|x| x.as_str()) {
210233
if !s.is_empty() {
@@ -215,6 +238,114 @@ fn first_str(v: &Value, keys: &[&str]) -> Option<String> {
215238
None
216239
}
217240

241+
#[cfg(test)]
242+
mod tests {
243+
use serde_json::json;
244+
245+
fn infer_antigravity_event(v: &serde_json::Value) -> Option<String> {
246+
if v.get("terminationReason").is_some() || v.get("fullyIdle").is_some() {
247+
Some("done".to_string())
248+
} else if v.get("toolCall").is_some()
249+
|| v.get("invocationNum").is_some()
250+
|| v.get("stepIdx").is_some()
251+
{
252+
Some("working".to_string())
253+
} else {
254+
None
255+
}
256+
}
257+
258+
fn extract_project(v: &serde_json::Value) -> String {
259+
super::first_str(v, &["cwd", "projectRoot"])
260+
.or_else(|| {
261+
v.get("workspace_roots")
262+
.and_then(|a| a.as_array())
263+
.and_then(|a| a.first())
264+
.and_then(|x| x.as_str())
265+
.map(String::from)
266+
})
267+
.or_else(|| {
268+
v.get("workspacePaths")
269+
.and_then(|a| a.as_array())
270+
.and_then(|a| a.iter().find(|x| x.as_str().map(|s| !s.is_empty()).unwrap_or(false)))
271+
.and_then(|x| x.as_str())
272+
.map(String::from)
273+
})
274+
.unwrap_or_default()
275+
}
276+
277+
// --- Antigravity event inference ---
278+
279+
#[test]
280+
fn antigravity_step_idx_is_working() {
281+
let v = json!({"conversationId":"c1","workspacePaths":["/Users/me/proj"],"stepIdx":0,"toolCall":{"name":"run_command"}});
282+
assert_eq!(infer_antigravity_event(&v), Some("working".to_string()));
283+
}
284+
285+
#[test]
286+
fn antigravity_invocation_num_is_working() {
287+
let v = json!({"conversationId":"c3","invocationNum":2,"initialNumSteps":5});
288+
assert_eq!(infer_antigravity_event(&v), Some("working".to_string()));
289+
}
290+
291+
#[test]
292+
fn antigravity_termination_reason_is_done() {
293+
let v = json!({"conversationId":"c2","executionNum":1,"terminationReason":"model_stop","fullyIdle":true});
294+
assert_eq!(infer_antigravity_event(&v), Some("done".to_string()));
295+
}
296+
297+
#[test]
298+
fn antigravity_fully_idle_alone_is_done() {
299+
let v = json!({"conversationId":"c4","fullyIdle":true});
300+
assert_eq!(infer_antigravity_event(&v), Some("done".to_string()));
301+
}
302+
303+
#[test]
304+
fn antigravity_done_takes_priority_over_working_fields() {
305+
// terminationReason present alongside stepIdx — done wins (stop event).
306+
let v = json!({"conversationId":"c5","terminationReason":"model_stop","stepIdx":3});
307+
assert_eq!(infer_antigravity_event(&v), Some("done".to_string()));
308+
}
309+
310+
#[test]
311+
fn antigravity_no_discriminator_returns_none() {
312+
let v = json!({"conversationId":"c6","workspacePaths":["/proj"]});
313+
assert_eq!(infer_antigravity_event(&v), None);
314+
}
315+
316+
// --- project extraction ---
317+
318+
#[test]
319+
fn project_from_cwd() {
320+
let v = json!({"cwd":"/home/user/proj","eventName":"PreToolUse"});
321+
assert_eq!(extract_project(&v), "/home/user/proj");
322+
}
323+
324+
#[test]
325+
fn project_from_workspace_roots() {
326+
let v = json!({"workspace_roots":["/home/user/proj"]});
327+
assert_eq!(extract_project(&v), "/home/user/proj");
328+
}
329+
330+
#[test]
331+
fn project_from_workspace_paths_camel_case() {
332+
let v = json!({"conversationId":"c1","workspacePaths":["/Users/me/proj"],"stepIdx":0});
333+
assert_eq!(extract_project(&v), "/Users/me/proj");
334+
}
335+
336+
#[test]
337+
fn project_skips_empty_workspace_paths_entries() {
338+
let v = json!({"workspacePaths":["","/Users/me/proj"]});
339+
assert_eq!(extract_project(&v), "/Users/me/proj");
340+
}
341+
342+
#[test]
343+
fn project_empty_when_no_field() {
344+
let v = json!({"conversationId":"c1","stepIdx":0});
345+
assert_eq!(extract_project(&v), "");
346+
}
347+
}
348+
218349
/// Minimal HTTP POST to the local listener, bounded by short timeouts so a hook
219350
/// never hangs the agent that invoked it.
220351
fn post(body: &str) -> std::io::Result<()> {

0 commit comments

Comments
 (0)