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, TaskId};
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`.
33pub const PROJECT_NAME_ALIAS_KEY: &str = "project_name_alias";
34
35/// `SpawnerLayer` that drops the received alias into `ctx` just before spawn.
36pub struct ProjectNameAliasMiddleware {
37 alias: String,
38}
39
40impl ProjectNameAliasMiddleware {
41 /// Wraps a project alias string to inject on every spawn.
42 pub fn new(alias: impl Into<String>) -> Self {
43 Self {
44 alias: alias.into(),
45 }
46 }
47}
48
49impl SpawnerLayer for ProjectNameAliasMiddleware {
50 fn wrap(&self, inner: Arc<dyn SpawnerAdapter>) -> Arc<dyn SpawnerAdapter> {
51 Arc::new(ProjectNameAliasWrapped {
52 inner,
53 alias: self.alias.clone(),
54 })
55 }
56}
57
58struct ProjectNameAliasWrapped {
59 inner: Arc<dyn SpawnerAdapter>,
60 alias: String,
61}
62
63#[async_trait]
64impl SpawnerAdapter for ProjectNameAliasWrapped {
65 async fn spawn(
66 &self,
67 engine: &Engine,
68 ctx: &Ctx,
69 task_id: TaskId,
70 attempt: u32,
71 token: CapToken,
72 ) -> Result<Box<dyn Worker>, SpawnError> {
73 let mut new_ctx = ctx.clone();
74 new_ctx.meta.runtime.insert(
75 PROJECT_NAME_ALIAS_KEY.to_string(),
76 Value::String(self.alias.clone()),
77 );
78 self.inner
79 .spawn(engine, &new_ctx, task_id, attempt, token)
80 .await
81 }
82}