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}