feat(chat): propage l'intention chat → require_structured dans le pipeline de lancement
- Ajoute le champ cell_kind dans LaunchAgentRequestDto pour distinguer les demandes chat (custom CLI) des lancements PTY historiques - Convertit cellKind:Chat en require_structured:true dans commands.rs et web-server/src/lib.rs - Implémente la logique de routing structuré dans LaunchAgent::execute : - valide que require_structured implique un profil avec structured_adapter - remplace une session PTY existante quand require_structured est vrai - route vers structured seulement quand wants_structured est vrai - Met à jour tous les appels historiques avec require_structured:false pour préserver le contrat PTY - Ajoute les tests unitaires human_launcher_with_chat_intent_routes_structured_profile_to_chat_session et chat_intent_replaces_existing_pty_instead_of_returning_pty_session - Wire les ports structured dans BackendCore pour le launcher humain Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -44,7 +44,7 @@ use crate::dto::{
|
|||||||
parse_project_id, parse_session_id, parse_skill_id, parse_task_id, parse_template_id,
|
parse_project_id, parse_session_id, parse_skill_id, parse_task_id, parse_template_id,
|
||||||
parse_ticket_id, save_model_server_input, AgentDriftListDto, AgentDto, AgentListDto,
|
parse_ticket_id, save_model_server_input, AgentDriftListDto, AgentDto, AgentListDto,
|
||||||
AppExitWorkGuardStateDto, AssignSkillRequestDto, AttachBackgroundTaskResultDto,
|
AppExitWorkGuardStateDto, AssignSkillRequestDto, AttachBackgroundTaskResultDto,
|
||||||
AttachLiveAgentRequestDto, AttachLiveAgentResponseDto, BackgroundTaskDto,
|
AttachLiveAgentRequestDto, AttachLiveAgentResponseDto, BackgroundTaskDto, CellKind,
|
||||||
ChangeAgentProfileDto, ChangeAgentProfileRequestDto, CloneOpenCodeProfileFromSeedRequestDto,
|
ChangeAgentProfileDto, ChangeAgentProfileRequestDto, CloneOpenCodeProfileFromSeedRequestDto,
|
||||||
CloneProfileFromSeedRequestDto, ConfigureProfilesRequestDto, ConversationDetailsDto,
|
CloneProfileFromSeedRequestDto, ConfigureProfilesRequestDto, ConversationDetailsDto,
|
||||||
CreateAgentFromTemplateRequestDto, CreateAgentRequestDto, CreateLayoutRequestDto,
|
CreateAgentFromTemplateRequestDto, CreateAgentRequestDto, CreateLayoutRequestDto,
|
||||||
@ -1972,6 +1972,7 @@ pub async fn launch_agent(
|
|||||||
conversation_id: request.conversation_id.clone(),
|
conversation_id: request.conversation_id.clone(),
|
||||||
mcp_runtime,
|
mcp_runtime,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: request.cell_kind == Some(CellKind::Chat),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(ErrorDto::from)?;
|
.map_err(ErrorDto::from)?;
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
//! and `From<LaunchAgentOutput>` for [`TerminalSessionDto`].
|
//! and `From<LaunchAgentOutput>` for [`TerminalSessionDto`].
|
||||||
|
|
||||||
use app_tauri_lib::dto::{
|
use app_tauri_lib::dto::{
|
||||||
parse_agent_id, AgentDto, AgentListDto, ConversationDetailsDto, CreateAgentRequestDto,
|
parse_agent_id, AgentDto, AgentListDto, CellKind, ConversationDetailsDto,
|
||||||
InspectConversationRequestDto, LaunchAgentRequestDto, LiveAgentListDto, TerminalSessionDto,
|
CreateAgentRequestDto, InspectConversationRequestDto, LaunchAgentRequestDto, LiveAgentListDto,
|
||||||
UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
TerminalSessionDto, UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||||
};
|
};
|
||||||
use application::AppError;
|
use application::AppError;
|
||||||
use application::{
|
use application::{
|
||||||
@ -192,6 +192,8 @@ fn launch_agent_request_deserialises_camelcase() {
|
|||||||
assert_eq!(dto.conversation_id, None);
|
assert_eq!(dto.conversation_id, None);
|
||||||
// Omitting the node id defaults to None (a fresh node is minted backend-side).
|
// Omitting the node id defaults to None (a fresh node is minted backend-side).
|
||||||
assert_eq!(dto.node_id, None);
|
assert_eq!(dto.node_id, None);
|
||||||
|
// Omitting the requested cell kind preserves the historical PTY launch path.
|
||||||
|
assert_eq!(dto.cell_kind, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -211,6 +213,19 @@ fn launch_agent_request_carries_node_id() {
|
|||||||
// No snake_case leak on the wire.
|
// No snake_case leak on the wire.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn launch_agent_request_can_require_chat_cell_kind() {
|
||||||
|
let raw = json!({
|
||||||
|
"projectId": Uuid::from_u128(1).to_string(),
|
||||||
|
"agentId": Uuid::from_u128(2).to_string(),
|
||||||
|
"rows": 24,
|
||||||
|
"cols": 80,
|
||||||
|
"cellKind": "chat"
|
||||||
|
});
|
||||||
|
let dto: LaunchAgentRequestDto = serde_json::from_value(raw).unwrap();
|
||||||
|
assert_eq!(dto.cell_kind, Some(CellKind::Chat));
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// AGENT_ALREADY_RUNNING error code + LiveAgentListDto (T2/T3)
|
// AGENT_ALREADY_RUNNING error code + LiveAgentListDto (T2/T3)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -959,6 +959,7 @@ impl ChangeAgentProfile {
|
|||||||
// endpoint is re-driven through the app-tauri launch path.
|
// endpoint is re-driven through the app-tauri launch path.
|
||||||
mcp_runtime: None,
|
mcp_runtime: None,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: false,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Some(output.session))
|
Ok(Some(output.session))
|
||||||
@ -1112,6 +1113,12 @@ pub struct LaunchAgentInput {
|
|||||||
///
|
///
|
||||||
/// Ordinary UI launches keep this `false` and retain the singleton/rebind guard.
|
/// Ordinary UI launches keep this `false` and retain the singleton/rebind guard.
|
||||||
pub allow_structured_alongside_pty: bool,
|
pub allow_structured_alongside_pty: bool,
|
||||||
|
/// UI routing intent for the custom chat CLI: when `true`, a profile carrying a
|
||||||
|
/// `structured_adapter` must be launched as an [`AgentSession`] even on the
|
||||||
|
/// human-facing launcher, instead of taking the native TUI/PTY fallback.
|
||||||
|
///
|
||||||
|
/// Historical callers leave this `false`, preserving the PTY launch contract.
|
||||||
|
pub require_structured: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OS/runtime facts injected by the composition root (`app-tauri`) to materialise
|
/// OS/runtime facts injected by the composition root (`app-tauri`) to materialise
|
||||||
@ -1813,6 +1820,25 @@ impl LaunchAgent {
|
|||||||
let agent = entry
|
let agent = entry
|
||||||
.to_agent()
|
.to_agent()
|
||||||
.map_err(|e| AppError::Invalid(e.to_string()))?;
|
.map_err(|e| AppError::Invalid(e.to_string()))?;
|
||||||
|
let mut profile = self
|
||||||
|
.profiles
|
||||||
|
.list()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.find(|p| p.id == agent.profile_id)
|
||||||
|
.ok_or_else(|| AppError::NotFound(format!("profile {} for agent", agent.profile_id)))?;
|
||||||
|
if input.require_structured && profile.structured_adapter.is_none() {
|
||||||
|
return Err(AppError::Invalid(format!(
|
||||||
|
"agent {}: le lancement structuré a été demandé, mais le profil {} \
|
||||||
|
ne déclare pas d'adaptateur structured/headless",
|
||||||
|
input.agent_id, profile.id
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let wants_structured = input.require_structured
|
||||||
|
|| matches!(
|
||||||
|
self.structured_routing_mode,
|
||||||
|
StructuredRoutingMode::RequireStructured
|
||||||
|
);
|
||||||
|
|
||||||
// 1b. Enforce the "one live session per agent" invariant (decision: an
|
// 1b. Enforce the "one live session per agent" invariant (decision: an
|
||||||
// agent is a singleton that runs in a single cell at a time). This
|
// agent is a singleton that runs in a single cell at a time). This
|
||||||
@ -1847,12 +1873,16 @@ impl LaunchAgent {
|
|||||||
let host_node = self
|
let host_node = self
|
||||||
.sessions
|
.sessions
|
||||||
.node_for_agent_in_project(input.project.id, &input.agent_id);
|
.node_for_agent_in_project(input.project.id, &input.agent_id);
|
||||||
if input.allow_structured_alongside_pty && input.node_id.is_none() {
|
if input.allow_structured_alongside_pty && input.node_id.is_none() && wants_structured {
|
||||||
crate::diag!(
|
crate::diag!(
|
||||||
"[launch] existing PTY kept while structured launch proceeds: agent={} \
|
"[launch] existing PTY kept while structured launch proceeds: agent={} \
|
||||||
pty_session={existing_id}",
|
pty_session={existing_id}",
|
||||||
input.agent_id
|
input.agent_id
|
||||||
);
|
);
|
||||||
|
} else if input.require_structured && profile.structured_adapter.is_some() {
|
||||||
|
if let Some(handle) = self.sessions.remove(&existing_id) {
|
||||||
|
self.pty.kill(&handle).await?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
match reattach_decision(input.node_id, host_node, input.conversation_id.as_deref())
|
match reattach_decision(input.node_id, host_node, input.conversation_id.as_deref())
|
||||||
{
|
{
|
||||||
@ -1947,13 +1977,6 @@ impl LaunchAgent {
|
|||||||
.contexts
|
.contexts
|
||||||
.read_context(&input.project, &agent.id)
|
.read_context(&input.project, &agent.id)
|
||||||
.await?;
|
.await?;
|
||||||
let mut profile = self
|
|
||||||
.profiles
|
|
||||||
.list()
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.find(|p| p.id == agent.profile_id)
|
|
||||||
.ok_or_else(|| AppError::NotFound(format!("profile {} for agent", agent.profile_id)))?;
|
|
||||||
if input.allow_structured_alongside_pty && profile.structured_adapter.is_none() {
|
if input.allow_structured_alongside_pty && profile.structured_adapter.is_none() {
|
||||||
return Err(AppError::Invalid(format!(
|
return Err(AppError::Invalid(format!(
|
||||||
"agent {}: le lancement headless structuré a été demandé, mais le profil {} \
|
"agent {}: le lancement headless structuré a été demandé, mais le profil {} \
|
||||||
@ -2109,10 +2132,10 @@ impl LaunchAgent {
|
|||||||
profile.structured_adapter.is_some(),
|
profile.structured_adapter.is_some(),
|
||||||
self.session_factory.as_ref(),
|
self.session_factory.as_ref(),
|
||||||
self.structured.as_ref(),
|
self.structured.as_ref(),
|
||||||
self.structured_routing_mode,
|
wants_structured,
|
||||||
) {
|
) {
|
||||||
(false, _, _, _) => {}
|
(false, _, _, _) => {}
|
||||||
(true, Some(factory), Some(structured), _) => {
|
(true, Some(factory), Some(structured), true) => {
|
||||||
// ── Clé **logique** de la cellule = id de paire IdeA (ARCHITECTURE §19.7,
|
// ── Clé **logique** de la cellule = id de paire IdeA (ARCHITECTURE §19.7,
|
||||||
// lot P8a) ──
|
// lot P8a) ──
|
||||||
// - cellule porteuse d'un `conversation_id` (resume, ou lancement délégué
|
// - cellule porteuse d'un `conversation_id` (resume, ou lancement délégué
|
||||||
@ -2152,10 +2175,10 @@ impl LaunchAgent {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
(true, _, _, StructuredRoutingMode::HumanPtyFallback) => {
|
(true, _, _, false) => {
|
||||||
// Fallback humain intentionnel : cellule interactive native en PTY.
|
// Fallback humain intentionnel : cellule interactive native en PTY.
|
||||||
}
|
}
|
||||||
(true, _, _, StructuredRoutingMode::RequireStructured) => {
|
(true, _, _, true) => {
|
||||||
return Err(AppError::Process(
|
return Err(AppError::Process(
|
||||||
"structured profile requires structured session factory".to_owned(),
|
"structured profile requires structured session factory".to_owned(),
|
||||||
));
|
));
|
||||||
|
|||||||
@ -1733,6 +1733,7 @@ impl OrchestratorService {
|
|||||||
// agent is (re)launched through the app-tauri composition root.
|
// agent is (re)launched through the app-tauri composition root.
|
||||||
mcp_runtime: None,
|
mcp_runtime: None,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: false,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -2568,6 +2569,7 @@ impl OrchestratorService {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|p| p.runtime_for(project, agent_id)),
|
.and_then(|p| p.runtime_for(project, agent_id)),
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: false,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -2648,6 +2650,7 @@ impl OrchestratorService {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|p| p.runtime_for(project, agent_id)),
|
.and_then(|p| p.runtime_for(project, agent_id)),
|
||||||
allow_structured_alongside_pty: true,
|
allow_structured_alongside_pty: true,
|
||||||
|
require_structured: false,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@ -516,6 +516,7 @@ struct FakePty {
|
|||||||
next_id: SessionId,
|
next_id: SessionId,
|
||||||
spawns: Arc<Mutex<Vec<SpawnSpec>>>,
|
spawns: Arc<Mutex<Vec<SpawnSpec>>>,
|
||||||
writes: WriteLog<SessionId>,
|
writes: WriteLog<SessionId>,
|
||||||
|
kills: Arc<Mutex<Vec<SessionId>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FakePty {
|
impl FakePty {
|
||||||
@ -525,6 +526,7 @@ impl FakePty {
|
|||||||
next_id,
|
next_id,
|
||||||
spawns: Arc::new(Mutex::new(Vec::new())),
|
spawns: Arc::new(Mutex::new(Vec::new())),
|
||||||
writes: Arc::new(Mutex::new(Vec::new())),
|
writes: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
kills: Arc::new(Mutex::new(Vec::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn spawns(&self) -> Vec<SpawnSpec> {
|
fn spawns(&self) -> Vec<SpawnSpec> {
|
||||||
@ -533,6 +535,9 @@ impl FakePty {
|
|||||||
fn writes(&self) -> Vec<(SessionId, Vec<u8>)> {
|
fn writes(&self) -> Vec<(SessionId, Vec<u8>)> {
|
||||||
self.writes.lock().unwrap().clone()
|
self.writes.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
fn kills(&self) -> Vec<SessionId> {
|
||||||
|
self.kills.lock().unwrap().clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -566,7 +571,8 @@ impl PtyPort for FakePty {
|
|||||||
fn try_wait(&self, _handle: &PtyHandle) -> Result<Option<ExitStatus>, PtyError> {
|
fn try_wait(&self, _handle: &PtyHandle) -> Result<Option<ExitStatus>, PtyError> {
|
||||||
Ok(Some(ExitStatus { code: Some(0) }))
|
Ok(Some(ExitStatus { code: Some(0) }))
|
||||||
}
|
}
|
||||||
async fn kill(&self, _handle: &PtyHandle) -> Result<ExitStatus, PtyError> {
|
async fn kill(&self, handle: &PtyHandle) -> Result<ExitStatus, PtyError> {
|
||||||
|
self.kills.lock().unwrap().push(handle.session_id);
|
||||||
Ok(ExitStatus { code: Some(0) })
|
Ok(ExitStatus { code: Some(0) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1130,6 +1136,7 @@ fn launch_input(agent_id: AgentId) -> LaunchAgentInput {
|
|||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
mcp_runtime: None,
|
mcp_runtime: None,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1257,6 +1264,86 @@ async fn structured_profile_with_factory_routes_to_structured_session_without_pt
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn human_launcher_with_chat_intent_routes_structured_profile_to_chat_session() {
|
||||||
|
let profile = profile(
|
||||||
|
pid(9),
|
||||||
|
ContextInjection::convention_file("CLAUDE.md").unwrap(),
|
||||||
|
)
|
||||||
|
.with_structured_adapter(StructuredAdapter::Claude);
|
||||||
|
let (launch, agent, _fs, pty, _bus, _sessions, tr, _session) = launch_fixture_with_profile(
|
||||||
|
profile,
|
||||||
|
Some(ContextInjectionPlan::File {
|
||||||
|
target: "CLAUDE.md".to_owned(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let factory = FakeStructuredFactory::new(Arc::clone(&tr), sid(888));
|
||||||
|
let structured = Arc::new(StructuredSessions::new());
|
||||||
|
let launch = launch
|
||||||
|
.with_structured_routing_mode(StructuredRoutingMode::HumanPtyFallback)
|
||||||
|
.with_structured(Arc::new(factory.clone()), Arc::clone(&structured));
|
||||||
|
|
||||||
|
let mut input = launch_input(agent.id);
|
||||||
|
input.node_id = Some(nid(77));
|
||||||
|
input.require_structured = true;
|
||||||
|
let out = launch.execute(input).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(factory.starts(), vec![pid(9)]);
|
||||||
|
assert!(pty.spawns().is_empty(), "custom chat must not spawn PTY");
|
||||||
|
assert!(
|
||||||
|
out.structured.is_some(),
|
||||||
|
"custom chat receives a descriptor"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
structured.node_for_agent_in_project(project().id, &agent.id),
|
||||||
|
Some(nid(77))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn chat_intent_replaces_existing_pty_instead_of_returning_pty_session() {
|
||||||
|
let profile = profile(
|
||||||
|
pid(9),
|
||||||
|
ContextInjection::convention_file("CLAUDE.md").unwrap(),
|
||||||
|
)
|
||||||
|
.with_structured_adapter(StructuredAdapter::Claude);
|
||||||
|
let (launch, agent, _fs, pty, _bus, sessions, tr, _session) = launch_fixture_with_profile(
|
||||||
|
profile,
|
||||||
|
Some(ContextInjectionPlan::File {
|
||||||
|
target: "CLAUDE.md".to_owned(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
seed_live_agent_session(&sessions, agent.id, nid(7), sid(42));
|
||||||
|
let factory = FakeStructuredFactory::new(Arc::clone(&tr), sid(888));
|
||||||
|
let structured = Arc::new(StructuredSessions::new());
|
||||||
|
let launch = launch
|
||||||
|
.with_structured_routing_mode(StructuredRoutingMode::HumanPtyFallback)
|
||||||
|
.with_structured(Arc::new(factory.clone()), Arc::clone(&structured));
|
||||||
|
|
||||||
|
let mut input = launch_input(agent.id);
|
||||||
|
input.node_id = Some(nid(7));
|
||||||
|
input.require_structured = true;
|
||||||
|
let out = launch.execute(input).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(pty.kills(), vec![sid(42)], "old PTY is closed");
|
||||||
|
assert!(
|
||||||
|
pty.spawns().is_empty(),
|
||||||
|
"replacement uses structured, not PTY"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
sessions
|
||||||
|
.session_for_agent_in_project(project().id, &agent.id)
|
||||||
|
.is_none(),
|
||||||
|
"old PTY registration is removed"
|
||||||
|
);
|
||||||
|
assert_eq!(factory.starts(), vec![pid(9)]);
|
||||||
|
assert_eq!(out.session.id, sid(888));
|
||||||
|
assert!(
|
||||||
|
out.structured.is_some(),
|
||||||
|
"backend returns cellKind=chat via DTO"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn launch_agent_structured_forwards_resolved_effort_ahead_of_profile_default() {
|
async fn launch_agent_structured_forwards_resolved_effort_ahead_of_profile_default() {
|
||||||
let profile = profile(
|
let profile = profile(
|
||||||
|
|||||||
@ -771,6 +771,7 @@ fn launch_input(agent_id: AgentId) -> LaunchAgentInput {
|
|||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
mcp_runtime: None,
|
mcp_runtime: None,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2823,6 +2823,11 @@ pub struct LaunchAgentRequestDto {
|
|||||||
/// refused).
|
/// refused).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub node_id: Option<String>,
|
pub node_id: Option<String>,
|
||||||
|
/// Requested render/runtime kind for this launch. Omitted by historical callers
|
||||||
|
/// and native TUI cells, which keeps the backend on the PTY path. The custom chat
|
||||||
|
/// view sends `"chat"` to require a structured/headless session.
|
||||||
|
#[serde(default)]
|
||||||
|
pub cell_kind: Option<CellKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LaunchAgentOutput> for TerminalSessionDto {
|
impl From<LaunchAgentOutput> for TerminalSessionDto {
|
||||||
|
|||||||
@ -725,6 +725,7 @@ impl WakeSessionProvider for AppWakeSessionProvider {
|
|||||||
conversation_id: None,
|
conversation_id: None,
|
||||||
mcp_runtime: None,
|
mcp_runtime: None,
|
||||||
allow_structured_alongside_pty: true,
|
allow_structured_alongside_pty: true,
|
||||||
|
require_structured: false,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|err| WakeError::Session(err.to_string()))?;
|
.map_err(|err| WakeError::Session(err.to_string()))?;
|
||||||
@ -844,6 +845,7 @@ impl AgentResumer for AppAgentResumer {
|
|||||||
conversation_id,
|
conversation_id,
|
||||||
mcp_runtime,
|
mcp_runtime,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: false,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -2028,9 +2030,9 @@ impl BackendCore {
|
|||||||
// LaunchAgent shares the SAME pty_port and terminal_sessions as the terminal
|
// LaunchAgent shares the SAME pty_port and terminal_sessions as the terminal
|
||||||
// use cases — indispensable for transport bridges to work correctly.
|
// use cases — indispensable for transport bridges to work correctly.
|
||||||
//
|
//
|
||||||
// The human-facing launcher intentionally stays PTY-only: when the user opens an
|
// The human-facing launcher defaults to PTY for native Claude/Codex TUI cells, but
|
||||||
// agent cell, they keep the native Claude/Codex CLI and its commands. Inter-agent
|
// it also carries the structured ports so the custom chat CLI can explicitly
|
||||||
// delegation gets its own launcher below, wired to structured/headless sessions.
|
// request a structured/headless launch through the same Tauri command.
|
||||||
|
|
||||||
// --- Permission projectors (lot LP3-5) ---
|
// --- Permission projectors (lot LP3-5) ---
|
||||||
// UN seul registre, source unique de vérité, injecté à l'identique dans
|
// UN seul registre, source unique de vérité, injecté à l'identique dans
|
||||||
@ -2084,7 +2086,11 @@ impl BackendCore {
|
|||||||
clock: Arc::clone(&clock) as Arc<dyn Clock>,
|
clock: Arc::clone(&clock) as Arc<dyn Clock>,
|
||||||
}) as Arc<dyn LiveStateLeanProvider>)
|
}) as Arc<dyn LiveStateLeanProvider>)
|
||||||
.with_local_model_server(Arc::clone(&ensure_local_model_server))
|
.with_local_model_server(Arc::clone(&ensure_local_model_server))
|
||||||
.with_secret_store(Arc::clone(&secret_store_port)),
|
.with_secret_store(Arc::clone(&secret_store_port))
|
||||||
|
.with_structured(
|
||||||
|
Arc::clone(&session_factory),
|
||||||
|
Arc::clone(&structured_sessions),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Inter-agent launcher: same context, memory, permissions and live-state
|
// Inter-agent launcher: same context, memory, permissions and live-state
|
||||||
|
|||||||
@ -1516,6 +1516,7 @@ async fn execute_launch_agent_for_ws(
|
|||||||
conversation_id: request.conversation_id.clone(),
|
conversation_id: request.conversation_id.clone(),
|
||||||
mcp_runtime,
|
mcp_runtime,
|
||||||
allow_structured_alongside_pty: false,
|
allow_structured_alongside_pty: false,
|
||||||
|
require_structured: request.cell_kind == Some(backend::dto::CellKind::Chat),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(ErrorDto::from)?;
|
.map_err(ErrorDto::from)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user