1use std::path::{Path, PathBuf};
8
9use super::{Caps, DroneWake, Harness, Probe, Transcript, Wiring};
10
11pub const EVENTS: [(&str, &str); 6] = [
13 ("SimPrompt", "prompt"),
14 ("SimPreTool", "pre_tool"),
15 ("SimPostTool", "post_tool"),
16 ("SimPostToolFailure", "post_tool_failure"),
17 ("SimStop", "stop"),
18 ("SimEnd", "end"),
19];
20
21pub const CAPS_ENV: &str = "BORG_SIM_CAPS";
22
23const DEFAULT_CAPS: &str = "prompt,tool,deny";
25
26pub struct Sim;
27
28pub fn caps_of(list: &str) -> Caps {
30 let on = |f: &str| list.split(',').any(|x| x.trim() == f);
31 Caps {
32 prompt_context: on("prompt"),
33 tool_context: on("tool"),
34 early_delivery: on("early"),
35 question_dialog: on("dialog"),
36 transcript: if on("transcript") { Transcript::Claude } else { Transcript::None },
37 deny_capture: on("deny"),
38 sends_pid: on("pid"),
39 stop_budget: None,
40 session_env: None,
41 pid_env: None,
42 drone_wake: DroneWake::Model,
43 }
44}
45
46impl Harness for Sim {
47 fn name(&self) -> &'static str {
48 "sim"
49 }
50
51 fn caps(&self) -> Caps {
52 caps_of(&std::env::var(CAPS_ENV).unwrap_or_else(|_| DEFAULT_CAPS.into()))
53 }
54
55 fn wiring(&self) -> Wiring {
56 Wiring::CommandHooks { events: &EVENTS, timeouts: &[("stop", 90)], retired: &[], through_links: false }
57 }
58
59 fn target(&self, home: &Path, _var: &dyn Fn(&str) -> Option<PathBuf>) -> PathBuf {
60 home.join(".sim/hooks.json")
61 }
62
63 fn probe(&self) -> Probe {
64 Probe { cli: "sim", tested: "" }
65 }
66
67 fn hidden(&self) -> bool {
68 true
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn caps_come_from_the_list() {
78 let c = caps_of("early,transcript");
79 assert!(c.early_delivery && !c.question_dialog && !c.prompt_context);
80 assert_eq!(c.transcript, Transcript::Claude);
81 let d = caps_of(DEFAULT_CAPS);
82 assert!(d.prompt_context && d.tool_context && d.deny_capture && !d.early_delivery && !d.sends_pid);
83 }
84}