fix: address neo skill review findings

This commit is contained in:
zenfun
2026-02-11 19:35:01 +08:00
parent d4dcc6430f
commit afe292de35
4 changed files with 41 additions and 13 deletions

View File

@@ -40,15 +40,19 @@ async def _sync_skills_to_sandbox(booter: ComputerBooter) -> None:
upload_result = await booter.upload_file(zip_path, str(remote_zip))
if not upload_result.get("success", False):
raise RuntimeError("Failed to upload skills bundle to sandbox.")
# Always overwrite with local source of truth to avoid stale skills in long-lived sandboxes.
# Extract into a temp folder first, then replace skills atomically.
tmp_extract_root = f"{SANDBOX_SKILLS_ROOT}_tmp_extract"
await booter.shell.exec(
f"mkdir -p {SANDBOX_SKILLS_ROOT} && "
f"rm -rf {SANDBOX_SKILLS_ROOT}/* && "
f"(unzip -o {remote_zip} -d {SANDBOX_SKILLS_ROOT} || "
f"mkdir -p {SANDBOX_SKILLS_ROOT} {tmp_extract_root} && "
f"rm -rf {tmp_extract_root}/* && "
f"(unzip -o {remote_zip} -d {tmp_extract_root} || "
f"python3 -c \"import zipfile; z=zipfile.ZipFile('{remote_zip}'); "
f"z.extractall('{SANDBOX_SKILLS_ROOT}')\" || "
f"z.extractall('{tmp_extract_root}')\" || "
f"python -c \"import zipfile; z=zipfile.ZipFile('{remote_zip}'); "
f"z.extractall('{SANDBOX_SKILLS_ROOT}')\"); "
f"z.extractall('{tmp_extract_root}')\") && "
f"find {SANDBOX_SKILLS_ROOT} -mindepth 1 -delete && "
f"cp -a {tmp_extract_root}/. {SANDBOX_SKILLS_ROOT}/ && "
f"rm -rf {tmp_extract_root} && "
f"rm -f {remote_zip}"
)
finally:

View File

@@ -250,9 +250,10 @@ class SkillsRoute(Route):
endpoint, access_token = self._get_neo_client_config()
data = await request.get_json()
candidate_id = data.get("candidate_id")
passed = data.get("passed")
if not candidate_id or passed is None:
passed_value = data.get("passed")
if not candidate_id or passed_value is None:
return Response().error("Missing candidate_id or passed").__dict__
passed = _to_bool(passed_value, False)
from shipyard_neo import BayClient
@@ -262,7 +263,7 @@ class SkillsRoute(Route):
) as client:
result = await client.skills.evaluate_candidate(
candidate_id,
passed=bool(passed),
passed=passed,
score=data.get("score"),
benchmark_id=data.get("benchmark_id"),
report=data.get("report"),

View File

@@ -311,6 +311,13 @@ export default {
return payload.skills || [];
};
const normalizeNeoItemsPayload = (res) => {
const payload = res?.data?.data || [];
if (Array.isArray(payload)) return payload;
if (Array.isArray(payload.items)) return payload.items;
return [];
};
const fetchSkills = async () => {
loading.value = true;
try {
@@ -406,8 +413,7 @@ export default {
status: neoFilters.status || undefined,
};
const res = await axios.get("/api/skills/neo/candidates", { params });
const payload = res?.data?.data || {};
neoCandidates.value = payload.items || [];
neoCandidates.value = normalizeNeoItemsPayload(res);
};
const fetchNeoReleases = async () => {
@@ -416,8 +422,15 @@ export default {
stage: neoFilters.stage || undefined,
};
const res = await axios.get("/api/skills/neo/releases", { params });
const payload = res?.data?.data || {};
neoReleases.value = payload.items || [];
neoReleases.value = normalizeNeoItemsPayload(res).map((item) => {
if (!item || typeof item !== "object") {
return item;
}
return {
...item,
is_active: item.is_active ?? item.active ?? false,
};
});
};
const fetchNeoData = async () => {

View File

@@ -385,6 +385,16 @@ async def test_neo_skills_routes(
assert data["data"]["candidate_id"] == "cand-1"
assert data["data"]["passed"] is True
response = await test_client.post(
"/api/skills/neo/evaluate",
json={"candidate_id": "cand-1", "passed": "false", "score": 0.0},
headers=authenticated_header,
)
assert response.status_code == 200
data = await response.get_json()
assert data["status"] == "ok"
assert data["data"]["passed"] is False
response = await test_client.post(
"/api/skills/neo/promote",
json={"candidate_id": "cand-1", "stage": "stable"},