liminal-cli 0.3.4

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! The harness's own configuration options (model, mode, effort) are read and set from the inbox.

use super::Pump;
use serde_json::{Value, json};

impl Pump {
    /// `{"config":{}}` republishes the advertised options; `{"config":{"option","value"}}` sets one.
    pub(super) fn configure(&mut self, change: &Value) -> anyhow::Result<()> {
        let (Some(option), Some(value)) = (change["option"].as_str(), change.get("value")) else {
            return self.publish_config();
        };
        anyhow::ensure!(
            value.is_string() || value.is_boolean(),
            "config value is neither text nor a switch"
        );
        if self.configuring.is_some() {
            return self
                .stream(json!({"type":"config_failed","reason":"a change is already in flight"}));
        }
        let id = self.next_id;
        self.next_id = id
            .checked_add(1)
            .ok_or_else(|| anyhow::anyhow!("ACP request id exhausted"))?;
        self.configuring = Some(id);
        self.output.request(
            id,
            "session/set_config_option",
            json!({"sessionId":self.session.id,"configId":option,"value":value}),
        )
    }

    pub(super) fn configured(&mut self, result: Result<Value, i64>) -> anyhow::Result<()> {
        self.configuring = None;
        match result {
            Ok(answer) => {
                if let Some(options) = answer
                    .get("configOptions")
                    .filter(|options| options.is_array())
                {
                    self.session.config = options.clone();
                }
                self.publish_config()
            }
            Err(code) => self.stream(json!({"type":"config_failed","code":code})),
        }
    }

    fn publish_config(&self) -> anyhow::Result<()> {
        self.stream(json!({"type":"config_options","options":self.session.config}))
    }

    /// The exchange would not hold this ask as a draft: the harness is told it was declined and
    /// the stream says why, so one refused draft never takes the seat down with it.
    pub fn draft_failed(&mut self, key: &str, error: &anyhow::Error) -> anyhow::Result<()> {
        let Some(pending) = self.pending.remove(key) else {
            return Ok(());
        };
        // A cancelled ask was already answered by the cancel; nothing more reaches the harness.
        let decided = if pending.cancelled {
            "cancelled"
        } else {
            self.output.response(
                pending.id,
                super::decide(&pending.request, super::Decision::Refused),
            )?;
            "deny"
        };
        self.stream(json!({"type":"permission_ask","decided":decided,"request":{"refused":error.to_string()}}))
    }

    fn stream(&self, item: Value) -> anyhow::Result<()> {
        self.publications
            .try_send(super::Publish::Stream(item))
            .map_err(|_| anyhow::anyhow!("stream_queue reached or publisher closed"))
    }
}