Skip to main content

opencode_ctl/
startup.rs

1//! Everything `serve` does before it builds the server, callable from tests without stdio.
2//! (`resolve_identity` / `apply_identity` live in `pairing.rs` and run before this; `startup`
3//! takes the already-applied environment.)
4
5use std::path::Path;
6
7use crate::engine::EnvSnapshot;
8use crate::error::ToolError;
9use crate::host::Host;
10use crate::liveness::{ProcessProbe, RebindOutcome};
11use crate::logging::{init_file_logging, LogHandle, LogScope};
12use crate::sweep::{startup_sweep, SweepReport};
13
14pub struct StartupReport {
15    pub log: Option<LogHandle>,
16    pub log_warning: Option<String>,
17    pub identity_stderr: Vec<String>,
18    pub sweep: SweepReport,
19    pub rebind: Option<RebindOutcome>,
20}
21
22/// One warning, one line: `CODE: message (hint)`.
23pub fn render_warning(e: &ToolError) -> String {
24    match &e.hint {
25        Some(h) => format!("{e} ({h})"),
26        None => e.to_string(),
27    }
28}
29
30#[allow(clippy::too_many_arguments)]
31pub fn startup(
32    home: &Path,
33    _host: Host,
34    env: &EnvSnapshot,
35    probe: &dyn ProcessProbe,
36    now_ms: i64,
37    debug: bool,
38    scope: LogScope,
39    identity_warnings: &[ToolError],
40) -> StartupReport {
41    let log_path = home
42        .join("logs")
43        .join(format!("serve-{}.log", std::process::id()));
44    let (log, log_warning) = match init_file_logging(&log_path, debug, scope) {
45        Ok(h) => (Some(h), None),
46        Err(e) => (
47            None,
48            Some(format!(
49                "opencode-ctl: cannot open the log {}: {e}; continuing without a file log",
50                log_path.display()
51            )),
52        ),
53    };
54
55    let mut identity_stderr = Vec::new();
56    for w in identity_warnings {
57        let line = render_warning(w);
58        if log.is_some() {
59            tracing::warn!("{line}");
60        } else {
61            identity_stderr.push(line);
62        }
63    }
64
65    let sweep = startup_sweep(home, now_ms, probe);
66    for e in &sweep.errors {
67        tracing::warn!("startup sweep: {e}");
68    }
69    if !sweep.removed.is_empty() {
70        tracing::info!("startup sweep removed {} entries", sweep.removed.len());
71    }
72
73    let rebind = env.pair.as_deref().and_then(|pair| {
74        let Some(parent) = probe.parent_pid(std::process::id()) else {
75            tracing::warn!("pair {pair}: parent process unknown, no re-bind");
76            return None;
77        };
78        match crate::liveness::rebind_on_spawn(home, pair, parent, probe) {
79            Ok(outcome) => Some(outcome),
80            Err(e) => {
81                tracing::warn!("pair {pair}: re-bind failed: {e}");
82                None
83            }
84        }
85    });
86
87    StartupReport {
88        log,
89        log_warning,
90        identity_stderr,
91        sweep,
92        rebind,
93    }
94}