| 169 | } |
| 170 | |
| 171 | fn parse_event(&self, hook_type: HookType, input: &[u8]) -> AgentResult<TurnEvent> { |
| 172 | let raw_json = self.parse_json(hook_type, input)?; |
| 173 | let parsed = self.parse_common(hook_type, raw_json.clone())?; |
| 174 | |
| 175 | let mut raw_json = with_xai_provider(raw_json); |
| 176 | if hook_type == HookType::TurnEnd { |
| 177 | raw_json = normalize_stop_raw(raw_json, parsed.reason.as_deref()); |
| 178 | } |
| 179 | if hook_type == HookType::PostToolUse { |
| 180 | raw_json = normalize_tool_raw(raw_json, parsed.tool_result.as_ref()); |
| 181 | } |
| 182 | |
| 183 | // Promote model/provider into raw_json when present (orchestrator reads them). |
| 184 | if let Some(model) = parsed.model.as_ref() { |
| 185 | insert_if_missing(&mut raw_json, "model", Value::String(model.clone())); |
| 186 | } |
| 187 | if let Some(provider) = parsed.provider.as_ref() { |
| 188 | insert_if_missing(&mut raw_json, "provider", Value::String(provider.clone())); |
| 189 | } |
| 190 | |
| 191 | let mut event = TurnEvent::new(Self::extract_session_id(parsed.session_id), hook_type) |
| 192 | .with_raw_json(raw_json); |
| 193 | |
| 194 | if let Some(path) = parsed.transcript_path.filter(|p| !p.is_empty()) { |
| 195 | event = event.with_transcript_path(path); |
| 196 | } |
| 197 | |
| 198 | match hook_type { |
| 199 | HookType::TurnStart => { |
| 200 | if let Some(prompt) = parsed.prompt.filter(|p| !p.is_empty()) { |
| 201 | // Grok's UserPromptSubmit often wraps the human text in |
| 202 | // `<user_query>…</user_query>` (harness envelope). That |
| 203 | // raw string would otherwise become the Atomic change |
| 204 | // message (`atomic log` shows the tags). Strip it so |
| 205 | // provenance and the commit message carry the real text. |
| 206 | let prompt = normalize_user_prompt(&prompt); |
| 207 | if !prompt.is_empty() { |
| 208 | event = event.with_prompt(prompt); |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | HookType::PreToolUse | HookType::PostToolUse => { |
| 213 | if let Some(name) = parsed.tool_name.filter(|n| !n.is_empty()) { |
| 214 | event = event.with_tool_name(name); |
| 215 | } |
| 216 | if let Some(id) = parsed.tool_use_id.filter(|id| !id.is_empty()) { |
| 217 | event = event.with_tool_use_id(id); |
| 218 | } |
| 219 | } |
| 220 | HookType::SessionStart | HookType::SessionEnd | HookType::TurnEnd => {} |
| 221 | } |
| 222 | |
| 223 | Ok(event) |
| 224 | } |
| 225 | |
| 226 | fn install(&self, _repo_root: &Path) -> AgentResult<usize> { |
| 227 | // Installation is handled by the atomic-grok package via the |