Skip to main content

mlua_swarm/middleware/
project_name_alias.rs

1//! `ProjectNameAliasMiddleware` — a `SpawnerLayer` that propagates a
2//! task-level project alias.
3//!
4//! When `Blueprint.metadata.project_name_alias` is `Some(_)`, `service::
5//! linker::link` places this layer on the stack. Just before spawn it
6//! inserts the literal value into `Ctx.meta.runtime` under the
7//! `project_name_alias` key.
8//!
9//! Downstream Operator / spawner code (for example `mlua-swarm-server`'s
10//! `Operator::execute`) reads it via
11//! `ctx.meta.runtime.get("project_name_alias")` and splices it into the
12//! Spawn directive prompt body, handing MainAI the discipline "run
13//! `mcp__lds__session_create(root=..., alias=<this>)` and inject
14//! `LDS Session Alias: <this>` into every SubAgent dispatch prompt". The
15//! authoritative discipline lives in `docs/project-name-alias.md`, the
16//! public doc.
17//!
18//! Same shape as `AgentResolver` / `ResolverMiddleware`: a thin layer that
19//! does not touch engine state.
20
21use crate::core::ctx::Ctx;
22use crate::core::engine::Engine;
23use crate::middleware::SpawnerLayer;
24use crate::types::{CapToken, StepId};
25use crate::worker::adapter::{SpawnError, SpawnerAdapter};
26use crate::worker::Worker;
27use async_trait::async_trait;
28use serde_json::Value;
29use std::sync::Arc;
30
31/// Key under `ctx.meta.runtime` that downstream Operator code reads with
32/// `get`.
33///
34/// GH #20: canonical definition moved to
35/// [`crate::core::agent_context::PROJECT_NAME_ALIAS_KEY`]; re-exported
36/// here so existing import paths
37/// (`crate::middleware::project_name_alias::PROJECT_NAME_ALIAS_KEY`, and
38/// the crate-root `mlua_swarm::PROJECT_NAME_ALIAS_KEY` re-export) stay
39/// valid.
40pub use crate::core::agent_context::PROJECT_NAME_ALIAS_KEY;
41
42/// `SpawnerLayer` that drops the received alias into `ctx` just before spawn.
43pub struct ProjectNameAliasMiddleware {
44    alias: String,
45}
46
47impl ProjectNameAliasMiddleware {
48    /// Wraps a project alias string to inject on every spawn.
49    pub fn new(alias: impl Into<String>) -> Self {
50        Self {
51            alias: alias.into(),
52        }
53    }
54}
55
56impl SpawnerLayer for ProjectNameAliasMiddleware {
57    fn wrap(&self, inner: Arc<dyn SpawnerAdapter>) -> Arc<dyn SpawnerAdapter> {
58        Arc::new(ProjectNameAliasWrapped {
59            inner,
60            alias: self.alias.clone(),
61        })
62    }
63}
64
65struct ProjectNameAliasWrapped {
66    inner: Arc<dyn SpawnerAdapter>,
67    alias: String,
68}
69
70#[async_trait]
71impl SpawnerAdapter for ProjectNameAliasWrapped {
72    async fn spawn(
73        &self,
74        engine: &Engine,
75        ctx: &Ctx,
76        task_id: StepId,
77        attempt: u32,
78        token: CapToken,
79    ) -> Result<Box<dyn Worker>, SpawnError> {
80        let mut new_ctx = ctx.clone();
81        new_ctx.meta.runtime.insert(
82            PROJECT_NAME_ALIAS_KEY.to_string(),
83            Value::String(self.alias.clone()),
84        );
85        self.inner
86            .spawn(engine, &new_ctx, task_id, attempt, token)
87            .await
88    }
89}