fix: fix some ui displays and features miss implemented

This commit is contained in:
2026-06-06 16:15:19 +02:00
parent 9736c42424
commit 2332b7f815
22 changed files with 1599 additions and 14 deletions

View File

@ -5,8 +5,8 @@ mod helpers;
use domain::{
Agent, AgentManifest, AgentOrigin, AgentProfile, AgentTemplate, ContextInjection, DomainError,
ManifestEntry, MarkdownDoc, ProfileId, Project, ProjectPath, PtySize, RemoteRef, SshAuth,
TemplateId, TemplateVersion,
ManifestEntry, MarkdownDoc, ProfileId, Project, ProjectPath, PtySize, RemoteRef, Skill, SkillId,
SkillRef, SkillScope, SshAuth, TemplateId, TemplateVersion,
};
use helpers::{AtomicSeqIdGenerator, FixedClock};
use uuid::Uuid;
@ -409,3 +409,121 @@ fn manifest_unique_md_paths_ok() {
.unwrap();
assert!(AgentManifest::new(1, vec![e1, e2]).is_ok());
}
// ---------------------------------------------------------------------------
// Skill invariants (L12, ARCHITECTURE §14.2)
// ---------------------------------------------------------------------------
fn skill_id(n: u128) -> SkillId {
SkillId::from_uuid(Uuid::from_u128(n))
}
#[test]
fn skill_valid_construction() {
let s = Skill::new(
skill_id(1),
"code-review",
MarkdownDoc::new("review the diff"),
SkillScope::Global,
);
assert!(s.is_ok());
}
#[test]
fn skill_rejects_empty_name() {
let err = Skill::new(
skill_id(1),
"",
MarkdownDoc::new("body"),
SkillScope::Project,
)
.unwrap_err();
assert_eq!(err, DomainError::EmptyField { field: "skill.name" });
}
#[test]
fn skill_rejects_empty_content() {
let err = Skill::new(skill_id(1), "x", MarkdownDoc::new(""), SkillScope::Global).unwrap_err();
assert_eq!(
err,
DomainError::EmptyField {
field: "skill.content_md"
}
);
}
#[test]
fn skill_with_content_revalidates() {
let s = Skill::new(skill_id(1), "x", MarkdownDoc::new("a"), SkillScope::Global).unwrap();
assert!(s.with_content(MarkdownDoc::new("b")).is_ok());
assert!(s.with_content(MarkdownDoc::new("")).is_err());
}
#[test]
fn agent_assign_skill_is_idempotent() {
let mut a = Agent::new(
agent_id(1),
"dev",
"agents/dev.md",
profile_id(),
AgentOrigin::Scratch,
false,
)
.unwrap();
let r = SkillRef::new(skill_id(7), SkillScope::Global);
assert!(a.assign_skill(r)); // first assignment
assert!(!a.assign_skill(r)); // duplicate ignored
assert_eq!(a.skills, vec![r]);
}
#[test]
fn agent_unassign_skill() {
let mut a = Agent::new(
agent_id(1),
"dev",
"agents/dev.md",
profile_id(),
AgentOrigin::Scratch,
false,
)
.unwrap();
let r = SkillRef::new(skill_id(7), SkillScope::Project);
a.assign_skill(r);
assert!(a.unassign_skill(skill_id(7)));
assert!(!a.unassign_skill(skill_id(7))); // already gone
assert!(a.skills.is_empty());
}
#[test]
fn agent_with_skills_dedups() {
let r = SkillRef::new(skill_id(7), SkillScope::Global);
let a = Agent::new(
agent_id(1),
"dev",
"agents/dev.md",
profile_id(),
AgentOrigin::Scratch,
false,
)
.unwrap()
.with_skills(vec![r, r]);
assert_eq!(a.skills, vec![r]);
}
#[test]
fn manifest_entry_preserves_skills_through_agent_roundtrip() {
let r = SkillRef::new(skill_id(7), SkillScope::Project);
let agent = Agent::new(
agent_id(1),
"dev",
"agents/dev.md",
profile_id(),
AgentOrigin::Scratch,
false,
)
.unwrap()
.with_skills(vec![r]);
let entry = ManifestEntry::from_agent(&agent);
assert_eq!(entry.skills, vec![r]);
assert_eq!(entry.to_agent().unwrap(), agent);
}

View File

@ -6,7 +6,7 @@ mod helpers;
use domain::{
Agent, AgentManifest, AgentOrigin, AgentProfile, AgentTemplate, ContextInjection, Direction,
LayoutNode, LayoutTree, LeafCell, ManifestEntry, MarkdownDoc, Project, ProjectPath, RemoteRef,
SplitContainer, SshAuth, TemplateVersion, WeightedChild,
Skill, SkillId, SkillRef, SkillScope, SplitContainer, SshAuth, TemplateVersion, WeightedChild,
};
use helpers::{node, session};
use uuid::Uuid;
@ -229,6 +229,61 @@ fn manifest_roundtrip_and_camel_case() {
assert!(!json.contains("\"templateId\":null"), "json was {json}");
}
// ---------------------------------------------------------------------------
// Skill (L12) — round-trip, camelCase scope tag, manifest skills back-compat
// ---------------------------------------------------------------------------
fn sid(n: u128) -> SkillId {
SkillId::from_uuid(Uuid::from_u128(n))
}
#[test]
fn skill_roundtrip_and_camel_case_scope() {
let s = Skill::new(sid(1), "code-review", MarkdownDoc::new("body"), SkillScope::Global).unwrap();
assert_eq!(roundtrip(&s), s);
let json = serde_json::to_string(&s).unwrap();
assert!(json.contains("\"scope\":\"global\""), "json was {json}");
assert!(json.contains("\"contentMd\""), "json was {json}");
let p = Skill::new(sid(2), "simplify", MarkdownDoc::new("b"), SkillScope::Project).unwrap();
let pj = serde_json::to_string(&p).unwrap();
assert!(pj.contains("\"scope\":\"project\""), "json was {pj}");
}
#[test]
fn manifest_entry_skills_roundtrip_and_camel_case() {
let entry = ManifestEntry::from_agent(
&Agent::new(
aid(1),
"dev",
"agents/dev.md",
profid(9),
AgentOrigin::Scratch,
false,
)
.unwrap()
.with_skills(vec![SkillRef::new(sid(5), SkillScope::Project)]),
);
assert_eq!(roundtrip(&entry), entry);
let json = serde_json::to_string(&entry).unwrap();
assert!(json.contains("\"skillId\""), "json was {json}");
assert!(json.contains("\"scope\":\"project\""), "json was {json}");
}
#[test]
fn manifest_entry_without_skills_omits_field_and_deserialises() {
// An entry with no skills must not emit "skills" (skip_serializing_if),
// and a pre-L12 manifest JSON (no skills key) must deserialise to empty.
let entry =
ManifestEntry::new(aid(1), "dev", "agents/dev.md", profid(9), None, false, None).unwrap();
let json = serde_json::to_string(&entry).unwrap();
assert!(!json.contains("\"skills\""), "json was {json}");
let legacy = r#"{"agentId":"00000000-0000-0000-0000-000000000001","name":"dev","mdPath":"agents/dev.md","profileId":"00000000-0000-0000-0000-000000000009","synchronized":false}"#;
let parsed: ManifestEntry = serde_json::from_str(legacy).unwrap();
assert!(parsed.skills.is_empty());
}
// ---------------------------------------------------------------------------
// LayoutTree (tagged enum: type/node)
// ---------------------------------------------------------------------------