mobius 0.15.0

A small, modular Rust framework for building coding agents
Documentation
use serde::Deserialize;
use serde_json::Value;

use super::{
    Basis, MAX_NOTE_BYTES, Scope, ScratchpadStore, SwarmScope, WriteOutcome, publish_widgets, text,
};
use crate::backend::model::ToolDefinition;
use crate::middleware::FrontendEventSink;
use crate::middleware::tools::{ApprovalRequirement, Tool, ToolContext};
use crate::{BoxFuture, Result};

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WriteArgs {
    scope: Scope,
    note: String,
}

pub(super) struct WriteScratchpad {
    pub(super) store: ScratchpadStore,
    pub(super) swarm: SwarmScope,
    pub(super) frontend: FrontendEventSink,
}

impl Tool for WriteScratchpad {
    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "write_scratchpad".into(),
            description: text::TOOL_WRITE_SCRATCHPAD_DESCRIPTION.into(),
            parameters: serde_json::json!({
                "type": "object",
                "properties": {
                    "scope": {
                        "type": "string",
                        "enum": ["swarm", "global"],
                        "description": text::TOOL_WRITE_SCRATCHPAD_PARAMETER_SCOPE_DESCRIPTION
                    },
                    "note": {
                        "type": "string",
                        "description": text::TOOL_WRITE_SCRATCHPAD_PARAMETER_NOTE_DESCRIPTION,
                        "maxLength": MAX_NOTE_BYTES
                    }
                },
                "required": ["scope", "note"],
                "additionalProperties": false
            }),
        }
    }

    fn approval(&self) -> ApprovalRequirement {
        ApprovalRequirement::Always
    }

    fn call<'a>(
        &'a self,
        _context: ToolContext,
        arguments: Value,
    ) -> BoxFuture<'a, Result<String>> {
        Box::pin(async move {
            let arguments: WriteArgs = serde_json::from_value(arguments)?;
            let access = self.store.lock_access().await;
            let swarm_id = self.swarm.resolve().await?;
            let outcome = self
                .store
                .write_locked(
                    swarm_id.as_deref(),
                    arguments.scope,
                    &arguments.note,
                    Basis::AgentObservation,
                    &access,
                )
                .await?;
            if outcome != WriteOutcome::Existing {
                let snapshot = self
                    .store
                    .snapshot_locked(swarm_id.as_deref(), &access)
                    .await?;
                publish_widgets(&self.frontend, &snapshot)?;
            }
            Ok(match outcome {
                WriteOutcome::Added => text::MESSAGE_ADDED,
                WriteOutcome::Updated => text::MESSAGE_UPDATED,
                WriteOutcome::Existing => text::MESSAGE_EXISTING,
            }
            .into())
        })
    }
}