use super::*;
use meerkat_core::SessionId;
use meerkat_core::agent::{BindOutcome, DispatcherCapabilities, OpsLifecycleBindError};
use meerkat_core::ops::AsyncOpRef;
use meerkat_core::ops_lifecycle::OpsLifecycleRegistry;
use meerkat_core::service::MobToolAuthorityContext;
use meerkat_core::types::{ToolProvenance, ToolSourceKind};
use std::collections::HashSet;
use meerkat_core::DynamicToolComposite;
pub(crate) struct NameFilteredDispatcher {
inner: Arc<dyn AgentToolDispatcher>,
excluded: HashSet<String>,
}
impl NameFilteredDispatcher {
pub(crate) fn new(inner: Arc<dyn AgentToolDispatcher>, excluded: HashSet<String>) -> Self {
Self { inner, excluded }
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AgentToolDispatcher for NameFilteredDispatcher {
fn tools(&self) -> Arc<[Arc<ToolDef>]> {
self.inner
.tools()
.iter()
.filter(|t| !self.excluded.contains(&t.name))
.cloned()
.collect::<Vec<_>>()
.into()
}
async fn dispatch(
&self,
call: ToolCallView<'_>,
) -> Result<meerkat_core::ToolDispatchOutcome, ToolError> {
if self.excluded.contains(call.name) {
return Err(ToolError::not_found(call.name));
}
self.inner.dispatch(call).await
}
async fn poll_external_updates(&self) -> meerkat_core::ExternalToolUpdate {
self.inner.poll_external_updates().await
}
fn capabilities(&self) -> DispatcherCapabilities {
self.inner.capabilities()
}
fn bind_ops_lifecycle(
self: Arc<Self>,
registry: Arc<dyn OpsLifecycleRegistry>,
owner_session_id: SessionId,
) -> Result<BindOutcome, OpsLifecycleBindError> {
let owned = Arc::try_unwrap(self).map_err(|_| OpsLifecycleBindError::SharedOwnership)?;
let outcome = owned.inner.bind_ops_lifecycle(registry, owner_session_id)?;
let bound = outcome.was_bound();
let inner = outcome.into_dispatcher();
let wrapper = Arc::new(NameFilteredDispatcher {
inner,
excluded: owned.excluded,
});
Ok(if bound {
BindOutcome::Bound(wrapper)
} else {
BindOutcome::Skipped(wrapper)
})
}
}
pub(super) fn compose_external_tools_for_profile(
profile: &crate::profile::Profile,
tool_bundles: &BTreeMap<String, Arc<dyn AgentToolDispatcher>>,
mob_handle: MobHandle,
default_external_tools: Option<Arc<dyn AgentToolDispatcher>>,
mob_tool_access_context: crate::build::MobToolAccessContext,
) -> Result<Option<Arc<dyn AgentToolDispatcher>>, MobError> {
let mut dispatchers: Vec<Arc<dyn AgentToolDispatcher>> = Vec::new();
if let crate::build::MobToolAccessContext::InjectedAuthority(authority_context) =
mob_tool_access_context
&& (profile.tools.mob || profile.tools.mob_tasks)
{
dispatchers.push(Arc::new(MobOperatorToolDispatcher::new(
mob_handle,
profile.tools.mob,
profile.tools.mob_tasks,
authority_context,
)));
}
for name in &profile.tools.rust_bundles {
let dispatcher = tool_bundles.get(name).cloned().ok_or_else(|| {
MobError::Internal(format!(
"tool bundle '{name}' is not registered on this mob builder"
))
})?;
dispatchers.push(dispatcher);
}
if let Some(ext) = default_external_tools {
let profile_names: HashSet<String> = dispatchers
.iter()
.flat_map(|d| d.tools().iter().map(|t| t.name.clone()).collect::<Vec<_>>())
.collect();
let collisions: HashSet<String> = ext
.tools()
.iter()
.filter(|t| profile_names.contains(&t.name))
.map(|t| t.name.clone())
.collect();
if collisions.is_empty() {
dispatchers.push(ext);
} else {
dispatchers.push(Arc::new(NameFilteredDispatcher::new(ext, collisions)));
}
}
if dispatchers.is_empty() {
return Ok(None);
}
if dispatchers.len() == 1 {
return Ok(dispatchers.pop());
}
Ok(Some(Arc::new(DynamicToolComposite::new(dispatchers))))
}
struct MobOperatorToolDispatcher {
handle: MobHandle,
authority_context: MobToolAuthorityContext,
tools: Arc<[Arc<ToolDef>]>,
owner_session_id: Option<SessionId>,
ops_registry: Option<Arc<dyn OpsLifecycleRegistry>>,
}
impl MobOperatorToolDispatcher {
fn new(
handle: MobHandle,
enable_mob: bool,
enable_mob_tasks: bool,
authority_context: MobToolAuthorityContext,
) -> Self {
let mut defs: Vec<Arc<ToolDef>> = Vec::new();
if enable_mob {
defs.push(tool_def(
TOOL_SPAWN_MEERKAT,
"Spawn a meerkat from a profile. Supports fresh, resume, or fork launch modes.",
json!({
"type": "object",
"properties": {
"profile": {"type": "string"},
"meerkat_id": {"type": "string"},
"initial_message": content_input_schema(),
"resume_session_id": {"type": "string", "description": "Deprecated: use launch_mode.resume instead"},
"backend": {"type": "string", "enum": ["session", "external"]},
"runtime_mode": {"type": "string", "enum": ["autonomous_host", "turn_driven"]},
"launch_mode": {
"type": "object",
"description": "Launch mode: fresh (default), resume {session_id}, or fork {source_member_id, fork_context}",
},
"tool_access_policy": {
"type": "object",
"description": "Tool access policy: inherit (default), allow_list, or deny_list"
},
"budget_split_policy": {
"type": "object",
"description": "Budget split policy: equal, proportional, remaining, or fixed"
},
"auto_wire_parent": {"type": "boolean", "description": "Auto-wire to spawner after spawn"}
},
"required": ["profile", "meerkat_id"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_SPAWN_MANY_MEERKATS,
"Spawn multiple meerkats in one call. Returns per-item results in input order.",
json!({
"type": "object",
"properties": {
"specs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"profile": {"type": "string"},
"meerkat_id": {"type": "string"},
"initial_message": content_input_schema(),
"resume_session_id": {"type": "string"},
"backend": {"type": "string", "enum": ["session", "external"]},
"runtime_mode": {"type": "string", "enum": ["autonomous_host", "turn_driven"]}
},
"required": ["profile", "meerkat_id"]
}
}
},
"required": ["specs"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_RETIRE_MEERKAT,
"Retire a meerkat and archive its session",
json!({
"type": "object",
"properties": {"meerkat_id": {"type": "string"}},
"required": ["meerkat_id"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_WIRE_PEERS,
"Wire two meerkats with bidirectional trust",
json!({
"type": "object",
"properties": {"a": {"type": "string"}, "b": {"type": "string"}},
"required": ["a", "b"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_UNWIRE_PEERS,
"Unwire two meerkats and revoke bidirectional trust",
json!({
"type": "object",
"properties": {"a": {"type": "string"}, "b": {"type": "string"}},
"required": ["a", "b"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_LIST_MEERKATS,
"List all active meerkats. Response includes meerkat_id, profile, member_ref, peer_id, session_id, wired_to, external_peer_specs.",
json!({
"type": "object",
"properties": {}
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_MOB_LIST_FLOWS,
"List all configured flow IDs for this mob.",
json!({
"type": "object",
"properties": {}
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_MOB_RUN_FLOW,
"Run a configured flow by ID with optional activation params. Returns run_id.",
json!({
"type": "object",
"properties": {
"flow_id": {"type": "string"},
"params": {"type": "object"}
},
"required": ["flow_id"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_MOB_FLOW_STATUS,
"Get persisted status and ledgers for a flow run.",
json!({
"type": "object",
"properties": {
"run_id": {"type": "string"}
},
"required": ["run_id"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_MOB_CANCEL_FLOW,
"Cancel an in-flight flow run by run_id.",
json!({
"type": "object",
"properties": {
"run_id": {"type": "string"}
},
"required": ["run_id"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_FORCE_CANCEL_MEERKAT,
"Force-cancel a meerkat's in-flight turn. Does not retire the member.",
json!({
"type": "object",
"properties": {
"meerkat_id": {"type": "string"}
},
"required": ["meerkat_id"]
}),
ToolSourceKind::Mob,
));
defs.push(tool_def(
TOOL_MEERKAT_STATUS,
"Get a meerkat's execution status snapshot including output preview and token usage.",
json!({
"type": "object",
"properties": {
"meerkat_id": {"type": "string"}
},
"required": ["meerkat_id"]
}),
ToolSourceKind::Mob,
));
}
if enable_mob_tasks {
defs.push(tool_def(
TOOL_MOB_TASK_CREATE,
"Create a shared mob task. Response includes generated task_id.",
json!({
"type": "object",
"properties": {
"subject": {"type": "string"},
"description": {"type": "string"},
"blocked_by": {"type": "array", "items": {"type": "string"}}
},
"required": ["subject", "description"]
}),
ToolSourceKind::MobTasks,
));
defs.push(tool_def(
TOOL_MOB_TASK_LIST,
"List shared mob tasks",
json!({
"type": "object",
"properties": {}
}),
ToolSourceKind::MobTasks,
));
defs.push(tool_def(
TOOL_MOB_TASK_UPDATE,
"Update task status and owner. If owner is set, status must be in_progress and blocked_by dependencies must be completed.",
json!({
"type": "object",
"properties": {
"task_id": {"type": "string"},
"status": {
"type": "string",
"enum": ["open", "in_progress", "completed", "cancelled"]
},
"owner": {"type": "string", "description": "Optional. Only valid when status is in_progress."}
},
"required": ["task_id", "status"]
}),
ToolSourceKind::MobTasks,
));
defs.push(tool_def(
TOOL_MOB_TASK_GET,
"Get a shared task by ID",
json!({
"type": "object",
"properties": {"task_id": {"type": "string"}},
"required": ["task_id"]
}),
ToolSourceKind::MobTasks,
));
}
Self {
handle,
authority_context,
tools: defs.into(),
owner_session_id: None,
ops_registry: None,
}
}
fn ensure_current_mob_scope(&self, tool_name: &str) -> Result<(), ToolError> {
let mob_id = self.handle.definition().id.as_str();
if self.authority_context.can_manage_mob(mob_id) {
return Ok(());
}
Err(ToolError::access_denied(tool_name))
}
fn map_mob_error(call: ToolCallView<'_>, error: MobError) -> ToolError {
ToolError::execution_failed(format!("tool '{}' failed: {error}", call.name))
}
fn encode_result(
call: ToolCallView<'_>,
value: serde_json::Value,
) -> Result<meerkat_core::ToolDispatchOutcome, ToolError> {
Self::encode_result_with_async_ops(call, value, Vec::new())
}
fn encode_result_with_async_ops(
call: ToolCallView<'_>,
value: serde_json::Value,
async_ops: Vec<AsyncOpRef>,
) -> Result<meerkat_core::ToolDispatchOutcome, ToolError> {
let content = serde_json::to_string(&value)
.map_err(|error| ToolError::execution_failed(format!("encode tool result: {error}")))?;
Ok(meerkat_core::ToolDispatchOutcome {
result: ToolResult::new(call.id.to_string(), content, false),
async_ops,
session_effects: vec![],
})
}
async fn record_successful_operator_action(&self, tool_name: &str) {
if let Err(error) = self
.handle
.record_operator_action_provenance(tool_name, &self.authority_context)
.await
{
tracing::warn!(
tool_name,
mob_id = %self.handle.definition().id,
error = %error,
"operator provenance projection append failed"
);
}
}
}
fn tool_def(
name: &str,
description: &str,
input_schema: serde_json::Value,
kind: ToolSourceKind,
) -> Arc<ToolDef> {
Arc::new(ToolDef {
name: name.to_string(),
description: description.to_string(),
input_schema,
provenance: Some(ToolProvenance {
kind,
source_id: "mob".into(),
}),
})
}
fn content_input_schema() -> serde_json::Value {
json!({
"oneOf": [
{ "type": "string" },
{
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"type": { "const": "text" },
"text": { "type": "string" }
},
"required": ["type", "text"]
},
{
"type": "object",
"properties": {
"type": { "const": "image" },
"media_type": { "type": "string" },
"data": { "type": "string" }
},
"required": ["type", "media_type", "data"]
}
]
}
}
]
})
}
#[derive(Deserialize)]
struct SpawnMeerkatArgs {
profile: String,
meerkat_id: String,
#[serde(default)]
initial_message: Option<ContentInput>,
#[serde(default)]
resume_session_id: Option<meerkat_core::types::SessionId>,
#[serde(default)]
backend: Option<MobBackendKind>,
#[serde(default)]
runtime_mode: Option<crate::MobRuntimeMode>,
#[serde(default)]
launch_mode: Option<crate::launch::MemberLaunchMode>,
#[serde(default)]
tool_access_policy: Option<meerkat_core::ops::ToolAccessPolicy>,
#[serde(default)]
budget_split_policy: Option<crate::launch::BudgetSplitPolicy>,
#[serde(default)]
auto_wire_parent: Option<bool>,
}
#[derive(Deserialize)]
struct ForceCancelArgs {
meerkat_id: String,
}
#[derive(Deserialize)]
struct MeerkatStatusArgs {
meerkat_id: String,
}
#[derive(Deserialize)]
struct SpawnManyMeerkatsArgs {
specs: Vec<SpawnMeerkatArgs>,
}
#[derive(Deserialize)]
struct RetireMeerkatArgs {
meerkat_id: String,
}
#[derive(Deserialize)]
struct WirePeersArgs {
a: String,
b: String,
}
#[derive(Deserialize)]
struct TaskCreateArgs {
subject: String,
description: String,
#[serde(default)]
blocked_by: Vec<TaskId>,
}
#[derive(Deserialize)]
struct TaskUpdateArgs {
task_id: TaskId,
status: TaskStatus,
owner: Option<String>,
}
#[derive(Deserialize)]
struct TaskGetArgs {
task_id: TaskId,
}
#[derive(Deserialize)]
struct RunFlowArgs {
flow_id: String,
#[serde(default)]
params: serde_json::Value,
}
#[derive(Deserialize)]
struct FlowStatusArgs {
run_id: String,
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl AgentToolDispatcher for MobOperatorToolDispatcher {
fn tools(&self) -> Arc<[Arc<ToolDef>]> {
Arc::clone(&self.tools)
}
async fn dispatch(
&self,
call: ToolCallView<'_>,
) -> Result<meerkat_core::ToolDispatchOutcome, ToolError> {
if self.tools.iter().any(|tool| tool.name == call.name) {
self.ensure_current_mob_scope(call.name)?;
}
match call.name {
TOOL_SPAWN_MEERKAT => {
let args: SpawnMeerkatArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let mut spec = SpawnMemberSpec::from_wire(
args.profile,
args.meerkat_id,
args.initial_message,
args.runtime_mode,
args.backend,
);
if let Some(launch_mode) = args.launch_mode {
spec = spec.with_launch_mode(launch_mode);
} else if let Some(session_id) = args.resume_session_id {
spec = spec.with_resume_session_id(session_id);
}
if let Some(policy) = args.tool_access_policy {
spec = spec.with_tool_access_policy(policy);
}
if let Some(policy) = args.budget_split_policy {
spec = spec.with_budget_split_policy(policy);
}
if let Some(auto_wire) = args.auto_wire_parent {
spec = spec.with_auto_wire_parent(auto_wire);
}
let (result, async_ops) = match (&self.owner_session_id, &self.ops_registry) {
(Some(owner_session_id), Some(ops_registry)) => {
let receipt = self
.handle
.spawn_spec_receipt_with_owner_context(
spec,
super::handle::CanonicalOpsOwnerContext {
owner_session_id: owner_session_id.clone(),
ops_registry: Arc::clone(ops_registry),
},
)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
(
json!({
"member_ref": receipt.member_ref,
"session_id": receipt.member_ref.session_id(),
}),
vec![AsyncOpRef::detached(receipt.operation_id)],
)
}
_ => {
let member_ref = self
.handle
.spawn_spec(spec)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
(
json!({
"member_ref": member_ref,
"session_id": member_ref.session_id(),
}),
Vec::new(),
)
}
};
self.record_successful_operator_action(call.name).await;
Self::encode_result_with_async_ops(call, result, async_ops)
}
TOOL_SPAWN_MANY_MEERKATS => {
let args: SpawnManyMeerkatsArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let specs = args
.specs
.into_iter()
.map(|spec| {
let mut spawn_spec = SpawnMemberSpec::from_wire(
spec.profile,
spec.meerkat_id,
spec.initial_message,
spec.runtime_mode,
spec.backend,
);
if let Some(launch_mode) = spec.launch_mode {
spawn_spec = spawn_spec.with_launch_mode(launch_mode);
} else if let Some(session_id) = spec.resume_session_id {
spawn_spec = spawn_spec.with_resume_session_id(session_id);
}
if let Some(policy) = spec.tool_access_policy {
spawn_spec = spawn_spec.with_tool_access_policy(policy);
}
if let Some(policy) = spec.budget_split_policy {
spawn_spec = spawn_spec.with_budget_split_policy(policy);
}
if let Some(auto_wire) = spec.auto_wire_parent {
spawn_spec = spawn_spec.with_auto_wire_parent(auto_wire);
}
spawn_spec
})
.collect::<Vec<_>>();
let (results, async_ops) = match (&self.owner_session_id, &self.ops_registry) {
(Some(owner_session_id), Some(ops_registry)) => {
let receipts = self
.handle
.spawn_many_receipts_with_owner_context(
specs,
super::handle::CanonicalOpsOwnerContext {
owner_session_id: owner_session_id.clone(),
ops_registry: Arc::clone(ops_registry),
},
)
.await;
let async_ops = receipts
.iter()
.filter_map(|result| result.as_ref().ok())
.map(|receipt| AsyncOpRef::detached(receipt.operation_id.clone()))
.collect::<Vec<_>>();
let results = receipts
.into_iter()
.map(|result| match result {
Ok(receipt) => json!({
"ok": true,
"member_ref": receipt.member_ref,
"session_id": receipt.member_ref.session_id(),
}),
Err(error) => json!({
"ok": false,
"error": error.to_string(),
}),
})
.collect::<Vec<_>>();
(results, async_ops)
}
_ => {
let results = self
.handle
.spawn_many(specs)
.await
.into_iter()
.map(|result| match result {
Ok(member_ref) => json!({
"ok": true,
"member_ref": member_ref,
"session_id": member_ref.session_id(),
}),
Err(error) => json!({
"ok": false,
"error": error.to_string(),
}),
})
.collect::<Vec<_>>();
(results, Vec::new())
}
};
self.record_successful_operator_action(call.name).await;
Self::encode_result_with_async_ops(call, json!({ "results": results }), async_ops)
}
TOOL_RETIRE_MEERKAT => {
let args: RetireMeerkatArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
self.handle
.retire(MeerkatId::from(args.meerkat_id))
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true}))
}
TOOL_WIRE_PEERS => {
let args: WirePeersArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
self.handle
.wire(MeerkatId::from(args.a), MeerkatId::from(args.b))
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true}))
}
TOOL_UNWIRE_PEERS => {
let args: WirePeersArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
self.handle
.unwire(MeerkatId::from(args.a), MeerkatId::from(args.b))
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true}))
}
TOOL_LIST_MEERKATS => {
let meerkats = self.handle.list_members().await;
let meerkats = meerkats
.into_iter()
.map(|entry| {
json!({
"meerkat_id": entry.meerkat_id,
"profile": entry.profile,
"runtime_mode": entry.runtime_mode,
"member_ref": entry.member_ref,
"peer_id": entry.peer_id,
"session_id": entry.session_id(),
"wired_to": entry.wired_to,
"external_peer_specs": entry.external_peer_specs,
"status": entry.status,
"error": entry.error,
"is_final": entry.is_final,
"current_session_id": entry.current_session_id,
})
})
.collect::<Vec<_>>();
Self::encode_result(call, json!({ "meerkats": meerkats }))
}
TOOL_MOB_LIST_FLOWS => {
let flows = self.handle.list_flows();
Self::encode_result(call, json!({ "flows": flows }))
}
TOOL_MOB_RUN_FLOW => {
let args: RunFlowArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let run_id = self
.handle
.run_flow(FlowId::from(args.flow_id), args.params)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({ "run_id": run_id }))
}
TOOL_MOB_FLOW_STATUS => {
let args: FlowStatusArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let run_id = args.run_id.parse::<RunId>().map_err(|error| {
ToolError::invalid_arguments(call.name, format!("invalid run_id: {error}"))
})?;
let run = self
.handle
.flow_status(run_id)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
Self::encode_result(call, json!({ "run": run }))
}
TOOL_MOB_CANCEL_FLOW => {
let args: FlowStatusArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let run_id = args.run_id.parse::<RunId>().map_err(|error| {
ToolError::invalid_arguments(call.name, format!("invalid run_id: {error}"))
})?;
self.handle
.cancel_flow(run_id)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true}))
}
TOOL_MOB_TASK_CREATE => {
let args: TaskCreateArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let task_id = self
.handle
.task_create(args.subject, args.description, args.blocked_by)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true, "task_id": task_id}))
}
TOOL_MOB_TASK_LIST => {
let tasks = self
.handle
.task_list()
.await
.map_err(|error| Self::map_mob_error(call, error))?;
Self::encode_result(call, json!({ "tasks": tasks }))
}
TOOL_MOB_TASK_UPDATE => {
let args: TaskUpdateArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
self.handle
.task_update(args.task_id, args.status, args.owner.map(MeerkatId::from))
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true}))
}
TOOL_MOB_TASK_GET => {
let args: TaskGetArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let task = self
.handle
.task_get(&args.task_id)
.await
.map_err(|error| Self::map_mob_error(call, error))?;
Self::encode_result(call, json!({ "task": task }))
}
TOOL_FORCE_CANCEL_MEERKAT => {
let args: ForceCancelArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
self.handle
.force_cancel_member(MeerkatId::from(args.meerkat_id))
.await
.map_err(|error| Self::map_mob_error(call, error))?;
self.record_successful_operator_action(call.name).await;
Self::encode_result(call, json!({"ok": true}))
}
TOOL_MEERKAT_STATUS => {
let args: MeerkatStatusArgs = call
.parse_args()
.map_err(|error| ToolError::invalid_arguments(call.name, error.to_string()))?;
let snapshot = self
.handle
.member_status(&MeerkatId::from(args.meerkat_id))
.await
.map_err(|error| Self::map_mob_error(call, error))?;
Self::encode_result(call, json!(snapshot))
}
_ => Err(ToolError::not_found(call.name)),
}
}
fn capabilities(&self) -> DispatcherCapabilities {
DispatcherCapabilities {
ops_lifecycle: true,
}
}
fn bind_ops_lifecycle(
self: Arc<Self>,
registry: Arc<dyn OpsLifecycleRegistry>,
owner_session_id: SessionId,
) -> Result<BindOutcome, OpsLifecycleBindError> {
if Arc::strong_count(&self) != 1 {
return Err(OpsLifecycleBindError::SharedOwnership);
}
let this = Arc::try_unwrap(self).map_err(|_| OpsLifecycleBindError::SharedOwnership)?;
Ok(BindOutcome::Bound(Arc::new(Self {
handle: this.handle,
authority_context: this.authority_context,
tools: this.tools,
owner_session_id: Some(owner_session_id),
ops_registry: Some(registry),
})))
}
}
const TOOL_SPAWN_MEERKAT: &str = "spawn_meerkat";
const TOOL_SPAWN_MANY_MEERKATS: &str = "spawn_many_meerkats";
const TOOL_RETIRE_MEERKAT: &str = "retire_meerkat";
const TOOL_FORCE_CANCEL_MEERKAT: &str = "force_cancel_meerkat";
const TOOL_MEERKAT_STATUS: &str = "meerkat_status";
const TOOL_WIRE_PEERS: &str = "wire_peers";
const TOOL_UNWIRE_PEERS: &str = "unwire_peers";
const TOOL_LIST_MEERKATS: &str = "list_meerkats";
const TOOL_MOB_LIST_FLOWS: &str = "mob_list_flows";
const TOOL_MOB_RUN_FLOW: &str = "mob_run_flow";
const TOOL_MOB_FLOW_STATUS: &str = "mob_flow_status";
const TOOL_MOB_CANCEL_FLOW: &str = "mob_cancel_flow";
const TOOL_MOB_TASK_CREATE: &str = "mob_task_create";
const TOOL_MOB_TASK_LIST: &str = "mob_task_list";
const TOOL_MOB_TASK_UPDATE: &str = "mob_task_update";
const TOOL_MOB_TASK_GET: &str = "mob_task_get";