feat: release v1.0.1 CADWorld 网站与 LeKiwi 智能抓放
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled

集成同源 BYOK 会话隔离、精简模型设置、官方订阅入口和 HTTPS 发布运维;保留本地训练/调参与控制能力。同步 npm 版本及 CHANGELOG,记录公网真实 API 验收仍待用户凭据。
This commit is contained in:
2026-09-24 09:57:41 +08:00
parent 3ad29356c9
commit f3a8a38acd
194 changed files with 32918 additions and 236 deletions
@@ -0,0 +1,93 @@
"""Dependency-free task registry and bounded PPO settings."""
import json
import math
import re
from pathlib import Path
CONTRACTS = Path(__file__).resolve().parents[2] / "contracts"
TASK = json.loads((CONTRACTS / "mobile-manipulator-v2.json").read_text())
ROBOTS = {r["id"]: r for r in json.loads((CONTRACTS / "mobile-robots-v1.json").read_text())}
MOBILE_TASKS = {
"MobileManipulator-LeKiwi-v1": "lekiwi-v1",
"MobileManipulator-LeKiwi-Bundle": "lekiwi-bundle",
}
def validate_mobile_params(value):
if not isinstance(value, dict) or value.keys() - {
"rolloutSteps",
"objectPosition",
"goalPosition",
"stage",
"sourceJobId",
"positionJitter",
"evaluationEpisodes",
"navigationBootstrapSteps",
}:
raise ValueError("mobileParams 包含未知参数")
steps = value.get("rolloutSteps", 128)
if isinstance(steps, bool) or not isinstance(steps, int) or not 8 <= steps <= 4096:
raise ValueError("rolloutSteps 必须为 8–4096 的整数")
stage = value.get("stage", "navigate")
if stage not in ("navigate", "reach", "pick-place"):
raise ValueError("stage 必须为 navigate / reach / pick-place")
source = value.get("sourceJobId")
if source is not None and (
not isinstance(source, str) or not re.fullmatch(r"[0-9a-f]{32}", source)
):
raise ValueError("sourceJobId 必须是服务内的训练作业 ID,不接受 checkpoint 路径")
jitter = value.get("positionJitter", 0.1)
if isinstance(jitter, bool) or not isinstance(jitter, (int, float)) or not 0 <= jitter <= 0.3:
raise ValueError("positionJitter 必须在 0–0.3 m 内")
episodes = value.get("evaluationEpisodes", 10)
if isinstance(episodes, bool) or not isinstance(episodes, int) or not 2 <= episodes <= 64:
raise ValueError("evaluationEpisodes 必须为 2–64 的整数")
bootstrap = value.get("navigationBootstrapSteps", 4096)
if isinstance(bootstrap, bool) or not isinstance(bootstrap, int) or not 0 <= bootstrap <= 10000:
raise ValueError("navigationBootstrapSteps 必须为 0–10000 的整数")
result = {
"navigationBootstrapSteps": bootstrap,
"rolloutSteps": steps,
"stage": stage,
"positionJitter": jitter,
"evaluationEpisodes": episodes,
}
if source is not None:
result["sourceJobId"] = source
for key, default in (
("objectPosition", TASK["objectStart"]),
("goalPosition", TASK["goalStart"]),
):
position = value.get(key, default)
if (
not isinstance(position, list)
or len(position) != 3
or any(
isinstance(x, bool)
or not isinstance(x, (float, int))
or not -TASK["positionScale"] <= x <= TASK["positionScale"]
or not math.isfinite(x)
for x in position
)
or position[2] < TASK["objectStart"][2]
):
raise ValueError(f"{key} 必须是任务范围内的三维坐标,z 不低于物体半高")
result[key] = position.copy()
return result
def mobile_metadata(task_id):
return {
"id": task_id,
"name": f"移动操作 · {ROBOTS[MOBILE_TASKS[task_id]]['label']}",
"family": "mobile-manipulator",
"robotId": MOBILE_TASKS[task_id],
"browserCompatible": True,
"terrainPresets": [],
"terrainParameters": {},
"sensorTypes": [],
"sensorParameters": {},
"mapSyncScope": "当前移动操作场景快照",
"controlDt": TASK["controlDt"],
}