Skip to main content

onlyne_client/backend/
port.rs

1use super::*;
2
3pub trait SessionBackend: Send + Sync {
4    fn name(&self) -> &'static str;
5    fn capabilities(&self) -> Capabilities;
6    fn available(&self) -> Result<bool>;
7    fn spawn(&self, spec: SpawnSpec) -> Result<SessionRef>;
8    fn attach(&self, session: &SessionRef) -> Result<SessionRef>;
9    fn probe(&self, session: &SessionRef) -> Result<ResourceProbe>;
10    fn close(&self, session: &SessionRef, reason: CloseReason, force: bool) -> Result<()>;
11    fn rename(&self, _session: &SessionRef, _title: &str) -> Result<()> {
12        Err(unsupported(
13            self.name(),
14            "rename",
15            "backend does not expose rename",
16        ))
17    }
18    fn focus(&self, _session: &SessionRef) -> Result<()> {
19        Err(unsupported(
20            self.name(),
21            "focus",
22            "backend does not expose focus",
23        ))
24    }
25
26    /// Whether this backend carries task payloads itself. A backend that
27    /// answers true delivers through [`SessionBackend::deliver`]; one that
28    /// answers false keeps the adapter-socket path where a mounted plugin
29    /// receives `assign` or `config_get{key:"stdin:<text>"}`.
30    fn self_driven(&self) -> bool {
31        false
32    }
33
34    /// Hand one delivery's rendered text to a session this backend owns.
35    ///
36    /// The text is the delivery and nothing else: a backend that needs words of
37    /// its own sends them through [`SessionBackend::nudge`].
38    fn deliver(&self, _session: &SessionRef, _task_id: &str, _prompt: &str) -> Result<()> {
39        Err(unsupported(
40            self.name(),
41            "deliver",
42            "backend delivers through an adapter socket",
43        ))
44    }
45
46    /// Tell one session its turn ended without a completion, in this client's
47    /// own words (`docs/v2-CONTRACT.md` §3c).
48    ///
49    /// A backend that can hand the text over answers `Ok`; one that cannot —
50    /// because the drive has no injection channel, or its process is gone —
51    /// answers an error, and the turn-end rule settles the delivery at that
52    /// ending instead: a drive that cannot be nudged must not be told it was.
53    /// The text is this client's, verbatim; a backend composes none of it and
54    /// keeps no copy.
55    fn nudge(&self, _session: &SessionRef, _task_id: &str, _text: &str) -> Result<()> {
56        Err(unsupported(
57            self.name(),
58            "nudge",
59            "backend has no channel to hand the sentence to",
60        ))
61    }
62
63    /// Install the client-owned destination for records journalled by this
64    /// backend. Backends without a journal keep the default no-op.
65    fn set_content_sink(&self, _sink: Arc<dyn crate::content::ContentSink>) {}
66
67    /// Terminal facts this backend observed without an adapter report.
68    /// Absent for backends whose sessions end through the adapter socket.
69    fn outcomes(&self) -> Option<OutcomeFeed> {
70        None
71    }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct CommandOutput {
76    pub status: i32,
77    pub stdout: Vec<u8>,
78    pub stderr: Vec<u8>,
79}
80
81pub trait Runner: Send + Sync {
82    fn run(
83        &self,
84        program: &str,
85        args: &[String],
86        cwd: Option<&Path>,
87        env: &BTreeMap<String, String>,
88    ) -> Result<CommandOutput>;
89}
90
91#[derive(Debug, Default, Clone, Copy)]
92pub struct ProcessRunner;
93
94impl Runner for ProcessRunner {
95    fn run(
96        &self,
97        program: &str,
98        args: &[String],
99        cwd: Option<&Path>,
100        env: &BTreeMap<String, String>,
101    ) -> Result<CommandOutput> {
102        let mut command = std::process::Command::new(program);
103        command.args(args);
104        if let Some(cwd) = cwd {
105            command.current_dir(cwd);
106        }
107        command.envs(env);
108        let output = command.output()?;
109        Ok(CommandOutput {
110            status: output.status.code().unwrap_or(-1),
111            stdout: output.stdout,
112            stderr: output.stderr,
113        })
114    }
115}