feat(sync): 添加 Vault 所有的用户 Skill 记录
This commit is contained in:
@@ -368,5 +368,149 @@ async fn real_core_notes_roundtrip_only_through_bound_host_and_confirm_commits()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(stored["hash"], first["revision"]);
|
||||
assert_eq!(workspace.lock().unwrap().pending_count().unwrap(), 9);
|
||||
|
||||
// User-created Skills are Vault records, use Host CAS/idempotency, and are
|
||||
// resolved by the actual Agent route without copying package paths or grants.
|
||||
let create_skill_operation = uuid::Uuid::new_v4();
|
||||
let skill_body = json!({
|
||||
"revision":"", "name":"Vault reviewer", "description":"portable",
|
||||
"prompt":"Answer with the exact phrase user-skill-active.", "tools":[],
|
||||
"permissions":[], "retrieval":{"top_k":10,"rerank":true,"citation":true},
|
||||
"required_capabilities":["chat"]
|
||||
});
|
||||
let (status, user_skill) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/user-skills",
|
||||
&vault,
|
||||
&create_skill_operation.to_string(),
|
||||
Some(skill_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 201, "{user_skill}");
|
||||
let skill_id = user_skill["skill_id"].as_str().unwrap();
|
||||
assert_eq!(
|
||||
skill_id,
|
||||
format!("user_skill_{}", create_skill_operation.simple())
|
||||
);
|
||||
assert_eq!(user_skill["status"], "ready");
|
||||
for _ in 0..20 {
|
||||
let (status, replay) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/user-skills",
|
||||
&vault,
|
||||
&create_skill_operation.to_string(),
|
||||
Some(skill_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 201, "{replay}");
|
||||
assert_eq!(replay, user_skill);
|
||||
}
|
||||
let mut changed = skill_body.clone();
|
||||
changed["name"] = json!("Changed replay");
|
||||
let (status, conflict) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/user-skills",
|
||||
&vault,
|
||||
&create_skill_operation.to_string(),
|
||||
Some(changed),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 409, "{conflict}");
|
||||
assert_eq!(conflict["error"]["code"], "USER_SKILL_OPERATION_CONFLICT");
|
||||
let (status, listed) = request(
|
||||
&mut core,
|
||||
"GET",
|
||||
"/api/user-skills?limit=100&offset=0",
|
||||
&vault,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{listed}");
|
||||
assert_eq!(listed["items"][0]["skill_id"], skill_id);
|
||||
let (status, denied) = request(
|
||||
&mut core,
|
||||
"GET",
|
||||
"/api/user-skills?limit=100&offset=0",
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 409, "{denied}");
|
||||
assert_eq!(denied["error"]["code"], "VAULT_PERMISSION_CHANGED");
|
||||
let mut update_body = skill_body.clone();
|
||||
update_body["revision"] = user_skill["revision"].clone();
|
||||
update_body["name"] = json!("Updated reviewer");
|
||||
let update_operation = uuid::Uuid::new_v4().to_string();
|
||||
let (status, updated_skill) = request(
|
||||
&mut core,
|
||||
"PUT",
|
||||
&format!("/api/user-skills/{skill_id}"),
|
||||
&vault,
|
||||
&update_operation,
|
||||
Some(update_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{updated_skill}");
|
||||
assert_eq!(updated_skill["data"]["version"], 2);
|
||||
let (status, stale) = request(
|
||||
&mut core,
|
||||
"PUT",
|
||||
&format!("/api/user-skills/{skill_id}"),
|
||||
&vault,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
Some(update_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 409, "{stale}");
|
||||
assert_eq!(stale["error"]["code"], "USER_SKILL_REVISION_CONFLICT");
|
||||
for _ in 0..2 {
|
||||
let (status, replay) = request(
|
||||
&mut core,
|
||||
"PUT",
|
||||
&format!("/api/user-skills/{skill_id}"),
|
||||
&vault,
|
||||
&update_operation,
|
||||
Some(update_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{replay}");
|
||||
assert_eq!(replay, updated_skill);
|
||||
}
|
||||
let (status, run) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/agent/runs",
|
||||
&vault,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
Some(json!({"input":"Confirm the Skill configuration","provider_id":"mock","model":"mock-1","skill_id":skill_id})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 202, "{run}");
|
||||
assert_eq!(run["skill_id"], skill_id);
|
||||
let delete_operation = uuid::Uuid::new_v4().to_string();
|
||||
let delete_path = format!(
|
||||
"/api/user-skills/{skill_id}?revision={}",
|
||||
updated_skill["revision"].as_str().unwrap()
|
||||
);
|
||||
for _ in 0..2 {
|
||||
let (status, deleted) = request(
|
||||
&mut core,
|
||||
"DELETE",
|
||||
&delete_path,
|
||||
&vault,
|
||||
&delete_operation,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{deleted}");
|
||||
}
|
||||
assert!(!root
|
||||
.join(format!("opennexus-records/v1/user-skills/{skill_id}.json"))
|
||||
.exists());
|
||||
assert_eq!(workspace.lock().unwrap().pending_count().unwrap(), 12);
|
||||
}
|
||||
|
||||
@@ -566,6 +566,12 @@ async fn actual_service_accepts_ordered_push_and_repeat_commit_without_duplicate
|
||||
json!({"primaryExpanded":true,"workspaceWidth":272,"chatWidth":320}),
|
||||
"workspaceWidth",
|
||||
),
|
||||
(
|
||||
"user_skill",
|
||||
"user_skill_00000000000000000000000000000001",
|
||||
json!({"version":1,"name":"initial","description":"portable","prompt":"Review carefully","tools":["notes.read"],"permissions":["notes.read"],"retrieval":{"top_k":10,"rerank":true,"citation":true},"required_capabilities":["chat"],"created_at_ms":1,"updated_at_ms":1}),
|
||||
"name",
|
||||
),
|
||||
] {
|
||||
let path = notesagent_host::records::path_for(kind, id).unwrap();
|
||||
let record = json!({"schema":1,"kind":kind,"id":id,"data":data});
|
||||
@@ -582,7 +588,7 @@ async fn actual_service_accepts_ordered_push_and_repeat_commit_without_duplicate
|
||||
let mut ws = target.lock().unwrap();
|
||||
let current = ws.record_get_kind(kind, id).unwrap().unwrap();
|
||||
let mut next = current["record"].clone();
|
||||
next["data"][field] = if kind == "persona" {
|
||||
next["data"][field] = if kind != "layout" {
|
||||
json!(format!("side-{side}-round-{round}"))
|
||||
} else {
|
||||
json!(300 + round * 2 + side)
|
||||
@@ -665,7 +671,11 @@ async fn actual_service_accepts_ordered_push_and_repeat_commit_without_duplicate
|
||||
0
|
||||
);
|
||||
assert!(!client_b.push_one(&workspace_b, &binding_b).await.unwrap());
|
||||
for (kind, id) in [("persona", "default"), ("layout", "sidebars")] {
|
||||
for (kind, id) in [
|
||||
("persona", "default"),
|
||||
("layout", "sidebars"),
|
||||
("user_skill", "user_skill_00000000000000000000000000000001"),
|
||||
] {
|
||||
assert_eq!(
|
||||
workspace.lock().unwrap().record_get_kind(kind, id).unwrap(),
|
||||
workspace_b
|
||||
@@ -691,6 +701,18 @@ async fn actual_service_accepts_ordered_push_and_repeat_commit_without_duplicate
|
||||
.unwrap()
|
||||
> 0
|
||||
{}
|
||||
assert_eq!(
|
||||
excluded_ws
|
||||
.lock()
|
||||
.unwrap()
|
||||
.record_get_kind("user_skill", "user_skill_00000000000000000000000000000001")
|
||||
.unwrap(),
|
||||
workspace
|
||||
.lock()
|
||||
.unwrap()
|
||||
.record_get_kind("user_skill", "user_skill_00000000000000000000000000000001")
|
||||
.unwrap()
|
||||
);
|
||||
for (kind, id) in [("persona", "default"), ("layout", "sidebars")] {
|
||||
let original = workspace
|
||||
.lock()
|
||||
|
||||
Reference in New Issue
Block a user