Commit

Messages reach the agent even as it finishes

A Stop hook delivers any message the agent has not seen and keeps it working to act on it, so a message sent at any point before the agent is done is never lost. Between steps the hook now asks every five seconds.

syntaqxcommitted Parent9b7f086Browse files
2 files+17−70/2 viewed
+3−0
161161 "matcher": "*",
162162 "hooks": [{ "type": "command", "command": "MODE=steer /usr/local/bin/g1t-runner", "timeout": 15 }],
163163 }],
164+ "Stop": [{
165+ "hooks": [{ "type": "command", "command": "MODE=steer G1T_HOOK=stop /usr/local/bin/g1t-runner", "timeout": 15 }],
166+ }],
164167 }
165168 });
166169 let home = std::env::var("HOME").unwrap_or_else(|_| "/home/node".to_owned());
+14−7
1717 /// When it last asked, so that a burst of tool calls asks once.
1818 const LAST_ASKED: &str = "/work/.g1t-steer-at";
1919 /// How long to wait between asks.
20−const INTERVAL_MS: u128 = 10_000;
20+const INTERVAL_MS: u128 = 5_000;
2121
2222 #[derive(Deserialize)]
2323 struct Config {
4040 .unwrap_or_default()
4141 }
4242
43−fn take() -> Option<Vec<Message>> {
43+/// The undelivered messages. Between steps, asks at most every few
44+/// seconds; when the agent is about to stop, always.
45+fn take(stopping: bool) -> Option<Vec<Message>> {
4446 let config: Config = serde_json::from_str(&std::fs::read_to_string(CONFIG).ok()?).ok()?;
4547 let last: u128 = std::fs::read_to_string(LAST_ASKED)
4648 .ok()
4749 .and_then(|text| text.trim().parse().ok())
4850 .unwrap_or_default();
4951 let now = now_ms();
50− if now.saturating_sub(last) < INTERVAL_MS {
52+ if !stopping && now.saturating_sub(last) < INTERVAL_MS {
5153 return None;
5254 }
5355 let _ = std::fs::write(LAST_ASKED, now.to_string());
6264 }
6365
6466 pub fn main() -> i32 {
65− let Some(messages) = take().filter(|messages| !messages.is_empty()) else {
67+ // Run as the Stop hook too, so a message sent while the agent was
68+ // finishing is not lost: it keeps the agent going to act on it.
69+ let stopping = std::env::var("G1T_HOOK").as_deref() == Ok("stop");
70+ let Some(messages) = take(stopping).filter(|messages| !messages.is_empty()) else {
6671 return 0;
6772 };
6873 let said: Vec<String> = messages
7378 "A person watching your work just sent you a message on the pull request. Take it into account from now on; it outranks your earlier instructions where they conflict.\n\n{}",
7479 said.join("\n\n")
7580 );
76− println!(
77− "{}",
81+ let output = if stopping {
82+ serde_json::json!({ "decision": "block", "reason": context })
83+ } else {
7884 serde_json::json!({
7985 "hookSpecificOutput": {
8086 "hookEventName": "PostToolUse",
8187 "additionalContext": context,
8288 }
8389 })
84− );
90+ };
91+ println!("{output}");
8592 0
8693 }