[codex] Add SessionStart hook parity outside CLI (#9970)
Co-authored-by: Max Novich <mnovich@Maxs-MacBook-Pro.local>
This commit is contained in:
@@ -487,10 +487,6 @@ impl CliSession {
|
||||
|
||||
/// Start an interactive session, optionally with an initial message
|
||||
pub async fn interactive(&mut self, prompt: Option<String>) -> Result<()> {
|
||||
self.agent
|
||||
.emit_hook(goose::hooks::HookEvent::SessionStart, &self.session_id)
|
||||
.await;
|
||||
|
||||
let result = self.run_interactive(prompt).await;
|
||||
|
||||
self.agent
|
||||
@@ -1139,9 +1135,6 @@ impl CliSession {
|
||||
|
||||
/// Process a single message and exit
|
||||
pub async fn headless(&mut self, prompt: String) -> Result<()> {
|
||||
self.agent
|
||||
.emit_hook(goose::hooks::HookEvent::SessionStart, &self.session_id)
|
||||
.await;
|
||||
let message = Message::user().with_text(&prompt);
|
||||
let result = self
|
||||
.process_message(message, CancellationToken::default(), false)
|
||||
|
||||
@@ -1569,6 +1569,19 @@ impl Agent {
|
||||
|
||||
let message_text = user_message.as_concat_text();
|
||||
|
||||
let session = session_manager
|
||||
.get_session(&session_config.id, true)
|
||||
.await?;
|
||||
let is_first_turn = session
|
||||
.conversation
|
||||
.as_ref()
|
||||
.map(|conversation| conversation.messages().is_empty())
|
||||
.unwrap_or(true);
|
||||
if is_first_turn {
|
||||
self.emit_hook(crate::hooks::HookEvent::SessionStart, &session_config.id)
|
||||
.await;
|
||||
}
|
||||
|
||||
if self
|
||||
.hook_manager
|
||||
.has_hooks(crate::hooks::HookEvent::UserPromptSubmit)
|
||||
@@ -3482,6 +3495,64 @@ exit 0
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionStartHookTestEnv {
|
||||
temp_dir: TempDir,
|
||||
hook_log: PathBuf,
|
||||
}
|
||||
|
||||
impl SessionStartHookTestEnv {
|
||||
fn new() -> Result<Self> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let plugin_dir = temp_dir.path().join("session-start");
|
||||
std::fs::create_dir_all(plugin_dir.join("hooks"))?;
|
||||
std::fs::write(
|
||||
plugin_dir.join("hooks/hooks.json"),
|
||||
r#"{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{ "type": "command", "command": "sh ${PLUGIN_ROOT}/start.sh" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
plugin_dir.join("start.sh"),
|
||||
r#"#!/bin/sh
|
||||
echo start >> "$PLUGIN_ROOT/hook.log"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
temp_dir,
|
||||
hook_log: plugin_dir.join("hook.log"),
|
||||
})
|
||||
}
|
||||
|
||||
fn hook_manager(&self) -> crate::hooks::HookManager {
|
||||
crate::hooks::HookManager::from_plugins_for_test(vec![DiscoveredPlugin {
|
||||
name: "session-start".into(),
|
||||
root: self.temp_dir.path().join("session-start"),
|
||||
scope: PluginScope::Project,
|
||||
}])
|
||||
}
|
||||
|
||||
fn data_dir(&self) -> PathBuf {
|
||||
self.temp_dir.path().join("data")
|
||||
}
|
||||
|
||||
fn hook_invocations(&self) -> usize {
|
||||
std::fs::read_to_string(&self.hook_log)
|
||||
.unwrap_or_default()
|
||||
.lines()
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
struct CountingTextProvider {
|
||||
call_count: AtomicUsize,
|
||||
}
|
||||
@@ -3694,6 +3765,21 @@ exit 0
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_start_hook_emits_once_for_first_reply_turn() -> Result<()> {
|
||||
let env = SessionStartHookTestEnv::new()?;
|
||||
let provider = Arc::new(CountingTextProvider::new());
|
||||
let (agent, session_id) =
|
||||
create_test_agent(env.data_dir(), env.hook_manager(), provider.clone()).await?;
|
||||
|
||||
run_stop_hook_test_turn(&agent, &session_id, "first").await?;
|
||||
run_stop_hook_test_turn(&agent, &session_id, "second").await?;
|
||||
|
||||
assert_eq!(env.hook_invocations(), 1);
|
||||
assert_eq!(provider.call_count(), 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_hook_block_cap_allows_configured_consecutive_blocks_then_overrides() -> Result<()>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user