feat(TASK-036): fire tool, LLM, and job hook events at their seams
Wire the hook engine into the three runtime seams: - tool.*: ToolCall::eval fires tool.started once the name and arguments are resolved, then exactly one of tool.completed/tool.failed, discriminated on both the Err path and tool_call_error-shaped outputs. The arguments JSON is handed to the engine as the payload; eval itself performs no payload-file I/O. - llm.request.*: the three transport free functions (call_chat_completions, call_chat_completions_streaming, and the quiet graph-node variant) fire started/completed/failed for every provider and caller, classifying failures as aborted, api_error (with COYOTE_LLM_STATUS), or other. - job.*: handle_job_tool fires job.started before either spawn and the terminal event inside the detached closures via a pre-resolved fire_resolved snapshot, so job__start's return is never delayed. The test sink now captures the payload and cwd of each dispatch (sink-only change), and seam tests pin event cadence, env contracts, and byte-identical tool results and errors.
This commit is contained in:
4 files changed
+1105
-11
No files matched your search
+15
-3
@@ -248,7 +248,7 @@ pub fn fire_resolved(
|
||||
.collect();
|
||||
|
||||
#[cfg(test)]
|
||||
if test_sink::record(event, &hook, &envs) {
|
||||
if test_sink::record(event, &hook, &envs, payload.as_deref()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -496,6 +496,11 @@ pub(crate) mod test_sink {
|
||||
pub event: HookEvent,
|
||||
pub hook_name: String,
|
||||
pub envs: HashMap<String, String>,
|
||||
/// The tool-arguments JSON as handed to dispatch; `None` for events
|
||||
/// that carry no payload.
|
||||
pub payload: Option<String>,
|
||||
/// The working directory the hook would have spawned in.
|
||||
pub cwd: std::path::PathBuf,
|
||||
}
|
||||
|
||||
/// Suppresses process spawning and records every dispatch synchronously
|
||||
@@ -518,7 +523,12 @@ pub(crate) mod test_sink {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn record(event: HookEvent, hook: &ResolvedHook, envs: &[(String, String)]) -> bool {
|
||||
pub(super) fn record(
|
||||
event: HookEvent,
|
||||
hook: &ResolvedHook,
|
||||
envs: &[(String, String)],
|
||||
payload: Option<&str>,
|
||||
) -> bool {
|
||||
if INSTALLED.load(Ordering::SeqCst) == 0 {
|
||||
return false;
|
||||
}
|
||||
@@ -529,6 +539,8 @@ pub(crate) mod test_sink {
|
||||
event,
|
||||
hook_name: hook.name.clone(),
|
||||
envs: envs.iter().cloned().collect(),
|
||||
payload: payload.map(str::to_string),
|
||||
cwd: hook.cwd.clone(),
|
||||
});
|
||||
true
|
||||
}
|
||||
@@ -901,7 +913,7 @@ mod tests {
|
||||
command: "true".to_string(),
|
||||
cwd: env::temp_dir(),
|
||||
};
|
||||
assert!(test_sink::record(HookEvent::TurnFailed, &hook, &[]));
|
||||
assert!(test_sink::record(HookEvent::TurnFailed, &hook, &[], None));
|
||||
|
||||
let captures = test_sink::drain();
|
||||
assert!(
|
||||
|
||||
Reference in new issue
Block a user