Expose the session cwd in the notify payload and update docs so scripts and extensions receive the real project path; users get accurate project-aware notifications in CLI and VS Code. Fixes #5387
88 lines
2.9 KiB
Rust
88 lines
2.9 KiB
Rust
use serde::Serialize;
|
||
use tracing::error;
|
||
use tracing::warn;
|
||
|
||
#[derive(Debug, Default)]
|
||
pub(crate) struct UserNotifier {
|
||
notify_command: Option<Vec<String>>,
|
||
}
|
||
|
||
impl UserNotifier {
|
||
pub(crate) fn notify(&self, notification: &UserNotification) {
|
||
if let Some(notify_command) = &self.notify_command
|
||
&& !notify_command.is_empty()
|
||
{
|
||
self.invoke_notify(notify_command, notification)
|
||
}
|
||
}
|
||
|
||
fn invoke_notify(&self, notify_command: &[String], notification: &UserNotification) {
|
||
let Ok(json) = serde_json::to_string(¬ification) else {
|
||
error!("failed to serialise notification payload");
|
||
return;
|
||
};
|
||
|
||
let mut command = std::process::Command::new(¬ify_command[0]);
|
||
if notify_command.len() > 1 {
|
||
command.args(¬ify_command[1..]);
|
||
}
|
||
command.arg(json);
|
||
|
||
// Fire-and-forget – we do not wait for completion.
|
||
if let Err(e) = command.spawn() {
|
||
warn!("failed to spawn notifier '{}': {e}", notify_command[0]);
|
||
}
|
||
}
|
||
|
||
pub(crate) fn new(notify: Option<Vec<String>>) -> Self {
|
||
Self {
|
||
notify_command: notify,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// User can configure a program that will receive notifications. Each
|
||
/// notification is serialized as JSON and passed as an argument to the
|
||
/// program.
|
||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||
#[serde(tag = "type", rename_all = "kebab-case")]
|
||
pub(crate) enum UserNotification {
|
||
#[serde(rename_all = "kebab-case")]
|
||
AgentTurnComplete {
|
||
thread_id: String,
|
||
turn_id: String,
|
||
cwd: String,
|
||
|
||
/// Messages that the user sent to the agent to initiate the turn.
|
||
input_messages: Vec<String>,
|
||
|
||
/// The last message sent by the assistant in the turn.
|
||
last_assistant_message: Option<String>,
|
||
},
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use anyhow::Result;
|
||
|
||
#[test]
|
||
fn test_user_notification() -> Result<()> {
|
||
let notification = UserNotification::AgentTurnComplete {
|
||
thread_id: "b5f6c1c2-1111-2222-3333-444455556666".to_string(),
|
||
turn_id: "12345".to_string(),
|
||
cwd: "/Users/example/project".to_string(),
|
||
input_messages: vec!["Rename `foo` to `bar` and update the callsites.".to_string()],
|
||
last_assistant_message: Some(
|
||
"Rename complete and verified `cargo build` succeeds.".to_string(),
|
||
),
|
||
};
|
||
let serialized = serde_json::to_string(¬ification)?;
|
||
assert_eq!(
|
||
serialized,
|
||
r#"{"type":"agent-turn-complete","thread-id":"b5f6c1c2-1111-2222-3333-444455556666","turn-id":"12345","cwd":"/Users/example/project","input-messages":["Rename `foo` to `bar` and update the callsites."],"last-assistant-message":"Rename complete and verified `cargo build` succeeds."}"#
|
||
);
|
||
Ok(())
|
||
}
|
||
}
|