feat(terminals): reprise de conversation par cellule + fix ordre d'écriture
Permet de recharger la conversation CLI précédente de chaque cellule à la
réouverture du projet, de façon universelle (indépendant du modèle/CLI).
- profil AgentRuntime: bloc déclaratif optionnel `session { assignFlag, resumeFlag }`
- LeafCell: `conversationId` (persistant, distinct du SessionId PTY) + `agentWasRunning`
- runtime: SessionPlan (None/Assign/Resume) + composition pure des args
- LaunchAgent: décide Assign vs Resume, génère l'UUID, remonte l'id assigné
(persistance par l'appelant via setCellConversation — découplage SRP)
- close: SnapshotRunningAgents fige `agentWasRunning` avant le kill-all
(statut clot/en cours universel, sans parsing CLI)
- SessionInspector: port optionnel best-effort + adapter ClaudeTranscriptInspector
- popup de reprise par cellule (statut + sujet/tokens si dispo), intercalée
avant le Resume auto, jamais sur le chemin reattach
fix(terminals): sérialise les écritures PTY (file FIFO par handle) — corrige
les caractères mélangés/accents dus au réordonnancement des invoke Tauri concurrents
fix(layout): l'opération `move` préservait mal les champs du leaf (perdait `agent`)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -5,8 +5,8 @@ mod helpers;
|
||||
|
||||
use domain::{
|
||||
Agent, AgentManifest, AgentOrigin, AgentProfile, AgentTemplate, ContextInjection, DomainError,
|
||||
ManifestEntry, MarkdownDoc, ProfileId, Project, ProjectPath, PtySize, RemoteRef, Skill, SkillId,
|
||||
SkillRef, SkillScope, SshAuth, TemplateId, TemplateVersion,
|
||||
ManifestEntry, MarkdownDoc, ProfileId, Project, ProjectPath, PtySize, RemoteRef, SessionStrategy,
|
||||
Skill, SkillId, SkillRef, SkillScope, SshAuth, TemplateId, TemplateVersion,
|
||||
};
|
||||
use helpers::{AtomicSeqIdGenerator, FixedClock};
|
||||
use uuid::Uuid;
|
||||
@ -180,6 +180,7 @@ fn profile_valid() {
|
||||
ci_stdin(),
|
||||
Some("claude --version".into()),
|
||||
"{projectRoot}",
|
||||
None,
|
||||
);
|
||||
assert!(p.is_ok());
|
||||
}
|
||||
@ -194,6 +195,7 @@ fn profile_rejects_empty_command() {
|
||||
ci_stdin(),
|
||||
None,
|
||||
"{projectRoot}",
|
||||
None,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, DomainError::EmptyField { field } if field == "profile.command"));
|
||||
@ -201,11 +203,36 @@ fn profile_rejects_empty_command() {
|
||||
|
||||
#[test]
|
||||
fn profile_rejects_empty_name() {
|
||||
let err = AgentProfile::new(profile_id(), "", "claude", vec![], ci_stdin(), None, "{r}")
|
||||
let err = AgentProfile::new(profile_id(), "", "claude", vec![], ci_stdin(), None, "{r}", None)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, DomainError::EmptyField { field } if field == "profile.name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_strategy_valid_with_assign_flag() {
|
||||
let s = SessionStrategy::new(Some("--session-id".into()), "--resume");
|
||||
assert!(s.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_strategy_valid_without_assign_flag() {
|
||||
let s = SessionStrategy::new(None, "--continue").unwrap();
|
||||
assert_eq!(s.assign_flag, None);
|
||||
assert_eq!(s.resume_flag, "--continue");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_strategy_rejects_empty_resume_flag() {
|
||||
let err = SessionStrategy::new(Some("--session-id".into()), "").unwrap_err();
|
||||
assert!(matches!(err, DomainError::EmptyField { field } if field == "session.resumeFlag"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_strategy_rejects_empty_assign_flag() {
|
||||
let err = SessionStrategy::new(Some(String::new()), "--resume").unwrap_err();
|
||||
assert!(matches!(err, DomainError::EmptyField { field } if field == "session.assignFlag"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RemoteRef invariants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user