1use std::path::{Path, PathBuf};
8
9use super::{CONTEXT_GUARD, Caps, DroneWake, Harness, Probe, STOP_TIMEOUT_S, 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 context_chars: CONTEXT_GUARD,
42 session_env: None,
43 pid_env: None,
44 drone_wake: DroneWake::Model,
45 }
46}
47
48impl Harness for Sim {
49 fn name(&self) -> &'static str {
50 "sim"
51 }
52
53 fn caps(&self) -> Caps {
54 caps_of(&std::env::var(CAPS_ENV).unwrap_or_else(|_| DEFAULT_CAPS.into()))
55 }
56
57 fn wiring(&self) -> Wiring {
58 Wiring::CommandHooks { events: &EVENTS, timeouts: &[("stop", STOP_TIMEOUT_S)], retired: &[], through_links: false }
59 }
60
61 fn target(&self, home: &Path, _var: &dyn Fn(&str) -> Option<PathBuf>) -> PathBuf {
62 home.join(".sim/hooks.json")
63 }
64
65 fn probe(&self) -> Probe {
66 Probe { cli: "sim", tested: "" }
67 }
68
69 fn hidden(&self) -> bool {
70 true
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn caps_come_from_the_list() {
80 let c = caps_of("early,transcript");
81 assert!(c.early_delivery && !c.question_dialog && !c.prompt_context);
82 assert_eq!(c.transcript, Transcript::Claude);
83 let d = caps_of(DEFAULT_CAPS);
84 assert!(d.prompt_context && d.tool_context && d.deny_capture && !d.early_delivery && !d.sends_pid);
85 }
86}