From 7078365d546da93db2930067761950a34f974358 Mon Sep 17 00:00:00 2001 From: Xiaolv <2116262909@qq.com> Date: Thu, 10 Sep 2026 14:50:34 +0800 Subject: [PATCH 1/3] feat(skills): add the RoboDojo G05 skill Add one robodojo Skill with a single formal g05 profile: manifest, Skill document, Dora dataflow, and the Gateway / bench-endpoint / benchmark profile configuration for the put_bottles_into_dustbin demo. The profile resolves every node from the installed Node bundles through FORGE_RUNTIME_BIN and reaches profile assets through PAOS_SKILL_ROOT, so the Skill tree carries no benchmark or policy implementation. --- .../skills/forge-skill/robodojo/SKILL.md | 144 ++++++++++++++ .../robodojo/profiles/g05/bench_endpoint.yaml | 13 ++ .../robodojo/profiles/g05/config.yaml | 29 +++ .../robodojo/profiles/g05/dataflow.yaml | 82 ++++++++ .../robodojo/profiles/g05/env.template.sh | 54 +++++ .../robodojo/profiles/g05/gateway.yaml | 188 ++++++++++++++++++ .../skills/forge-skill/robodojo/skill.yaml | 61 ++++++ 7 files changed, 571 insertions(+) create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/SKILL.md create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/bench_endpoint.yaml create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/config.yaml create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/dataflow.yaml create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/env.template.sh create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/gateway.yaml create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/skill.yaml diff --git a/PhyAgentOS/skills/forge-skill/robodojo/SKILL.md b/PhyAgentOS/skills/forge-skill/robodojo/SKILL.md new file mode 100644 index 000000000..c9a164b08 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/SKILL.md @@ -0,0 +1,144 @@ +--- +name: robodojo +description: Run the RoboDojo G05 benchmark and report its aggregate score. +metadata: {"PhyAgentOS":{"always":false,"requires":{"runtime":["robodojo"]}}} +--- + +# RoboDojo Benchmark + +Run a RoboDojo benchmark batch in Isaac Sim and report the score the benchmark +produced. Use only these stable Tool IDs: + +- `bench.describe`: Query; return the live task inventory, defaults, and episode + budget limits. Does not touch the simulator state. +- `bench.run`: Action; run one batch of episodes and return its aggregate result. + Long running — minutes per episode. + +Use the PAOS bridge tools `forge_tool_context`, `forge_tool_query`, +`forge_tool_start_action`, `forge_tool_action_status`, `forge_tool_action_result`, +and `forge_tool_cancel_action`. Always pass the stable Forge Tool ID explicitly. +Do not use shell commands and do not construct Gateway HTTP requests directly. + +## What this Skill must not do + +Scoring belongs entirely to the benchmark node. This Skill selects and sequences +Tools, and reports what came back. It must not: + +- compute, recompute, adjust, average, or extrapolate `success_rate` or any other + metric; +- decide whether an individual episode succeeded, failed, or timed out; +- aggregate results across separate `bench.run` invocations into one number; +- convert a partial or failed batch into a score, or present one as if the batch + had completed; +- estimate a score when the result is unavailable. + +If a number is needed and no terminal result exists, say so. Do not produce one. + +## 1. Always describe before running + +Call `bench.describe` first, every time. Never fill `bench.run` arguments from +memory or from an earlier conversation — the installed suite, task count, and +limits are properties of the running profile, not of this document. + +`bench.describe` returns: + +- `benchmark`, `suite`, `policy_id`, `env_cfg_type`, `action_type` — fixed by the + profile. They are read-only context, not parameters you can change. +- `tasks[]` — each with `task_id`, `name`, `language`, `num_init_states`. The + init-state count differs per task; do not assume one task's count applies to + another. +- `defaults` — `num_runs`, `seed`, `max_steps` to use when the user gave no value. +- `limits` — `max_total_episodes`, `max_max_steps`. Hard caps; see §2. + +If `bench.describe` fails with `BENCH_NOT_READY`, the simulator is still starting. +Wait and retry; do not proceed to `bench.run`. + +## 2. Build the bench.run arguments + +All five fields are required. There are no defaults on the wire. + +- `task_ids`: an array of ids taken from `bench.describe`, or `null` for every + task. `null` and `[]` are different — `[]` is always an error, never "all". +- `init_state_ids`: an array, or `null` for every init state shared by all + selected tasks. Validated per task. +- `num_runs`: repeats per (task, init_state) pair. Use `defaults.num_runs` unless + the user asked for more. +- `seed`: base seed. The same seed and the same ids reproduce the same episodes. + Change it only when the user asks for a different sample. +- `max_steps`: a **hard cap on control steps**. The episode is terminated as + `timed_out` if this many steps are reached before the environment ends. The + task native `step_lim` defines its normal end (success / partial / failed); set `max_steps` to at least the task horizon (from `bench.describe` defaults) to allow completion, or lower to force an explicit step-limit stop. + +Episode count is `len(task_ids) x len(init_state_ids) x num_runs`. Keep it at or +below `limits.max_total_episodes`; exceeding it is rejected, not clamped. When the +user asks for something larger, report the cap and the requested count and ask +before splitting the work into several batches. + +Nothing else is selectable. Policy weights, suite, camera set, and GPU are fixed by +the profile. If the user asks to change one of those, say it requires a different +profile — do not try to express it through these arguments. + +## 3. Account for the Action lifecycle + +`bench.run` is an Action. Accepted is not completed. + +1. `forge_tool_start_action` returns an acknowledgement with an `invocation_id` and + `total_episodes`. The batch has not run yet. +2. `forge_tool_action_status` reports `accepted`, `running`, or a terminal phase. + Status is **advisory** — never report a score from it. Poll at a human pace; + a single episode takes minutes. +3. `forge_tool_action_result` is the only authoritative source of the outcome. + +Treat the four terminal outcomes as four different things: + +- `succeeded` — the batch ran to completion. Report per §4. +- `failed` — the batch ended abnormally. Report the error code and the episode + counts; do not present `success_rate` as the headline. +- `cancelled` — stopped on request. Partial counts only. +- `unknown` — **the outcome cannot be recovered**. Side effects have already + happened: GPU time was consumed and a `result.json` may exist on disk. Do not + blindly retry. Report that the outcome is unknown, give the `invocation_id`, and + ask how to proceed. + +Use `forge_tool_cancel_action` only when the user asks to stop. Cancel is accepted, +not immediate — keep polling until a terminal result appears. + +## 4. Reporting rules + +Report these together, always, in this order: + +1. `status` of the batch. +2. `success_rate`, immediately followed by `successes` / `total_episodes`. +3. `completed_episodes` / `total_episodes`. + +`success_rate` is computed over `total_episodes`, not over +`completed_episodes`. When a batch ends early these differ, and `success_rate` +alone reads as "the policy scored low" when the real story is "the batch did not +finish". If `completed_episodes < total_episodes`, say that first, before the +score. + +Also report: + +- `result_path` — the authoritative record on disk. Always include it. +- `timed_out_episodes` when non-zero. +- `obs_republished` when non-zero: observations were re-sent during the run. The + score is still valid, but mention it. +- `episodes[]` per-episode outcomes when the user asks for detail. When + `episodes_truncated` is true the list was too large to inline — point at + `result_path` instead of summarising from an empty array. + +Never present a single number without its denominator. + +## 5. Errors and replanning + +| code | retryable | what to do | +|---|---|---| +| `BENCH_NOT_READY` | yes | Simulator still starting. Wait, retry `bench.describe`. | +| `FORGE_BUSY` | yes | Another batch is running; one simulator per machine. Report which `invocation_id` holds it. Do not queue. | +| `BENCH_INVALID_ARGUMENT` | no | Arguments are wrong. Re-read `bench.describe` and rebuild them. `details` carries the valid range and the requested vs allowed episode count. Never retry unchanged. | +| `BENCH_NODE_UNAVAILABLE` | yes | The benchmark node stopped reporting. The outcome is unknown — see §3. Do not restart the batch without asking. | +| `BENCH_UNKNOWN_INVOCATION` | no | The id is not known here. Do not invent one; start a new run if the user wants one. | +| `FORGE_DEADLINE_EXCEEDED` | no | The gateway stopped tracking; the batch may still be running. Do not start another — that would collide. Report and ask. | + +When two errors are plausible, prefer the more conservative reading: an unknown +outcome is not a failure, and a rejected batch is not a zero score. diff --git a/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/bench_endpoint.yaml b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/bench_endpoint.yaml new file mode 100644 index 000000000..1e094f55f --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/bench_endpoint.yaml @@ -0,0 +1,13 @@ +# bench_endpoint 节点配置(g05 profile,与 build_tower 相同)。 +# 连续多久收不到 bench_report 就把在跑的 invocation 判成 unknown。 +# +# ⚠️ 必须大于 benchmark 节点一次最长的阻塞调用(episode 启动时 reset_episode 要 +# 加载 Isaac Sim 场景,整个循环阻塞几十秒),不是心跳周期的几倍。太小会把正常的 +# 场景加载误判成掉线 → invocation 被写成终态 unknown,然后真结果到达时被丢弃, +# 仿真器还在烧 GPU。 +heartbeat_timeout_s: 180.0 + +# 保留多少条历史 invocation 供对账。 +retention: 8 + +log_level: INFO diff --git a/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/config.yaml b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/config.yaml new file mode 100644 index 000000000..f5b581a53 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/config.yaml @@ -0,0 +1,29 @@ +# benchmark 节点的服务模式配置(robodojo g05 profile) - 可移植版本。 +# result_dir / record_dir / device_id 允许被环境变量覆盖(见 main.py)。 +benchmark: robodojo +suite: put_bottles_into_dustbin + +env_cfg_type: arx_x5 +device_id: 0 # 可被 ROBODOJO_DEVICE_ID 覆盖 + +serve: true +init_states_per_task: 1 +num_runs: 1 +seed: 0 +max_steps: 700 + +max_total_episodes: 20 +max_max_steps: 2000 + +action_type: joint +policy_id: G05 +additional_info: forge + +camera_map: + image/head: cam_head + image/left_wrist: cam_left_wrist + image/right_wrist: cam_right_wrist + +# 默认相对路径(解析到 bundle 内源码目录); 正式运行请用环境变量覆盖。 +record_dir: results/robodojo/videos +result_dir: results/robodojo diff --git a/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/dataflow.yaml b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/dataflow.yaml new file mode 100644 index 000000000..a5497d5c3 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/dataflow.yaml @@ -0,0 +1,82 @@ +# 完整链路: gateway -> bench_endpoint -> robodojo_benchmark -> policy(G05) -> Isaac Sim +# 职责边界: benchmark/policy 的实现由 forge_runtime 的 Node 包维护, 本 Skill 只带 +# 描述、清单、g05 profile、dataflow、安装脚本与文档。四个 node 一律走已安装 Node 的 +# 入口 (${FORGE_RUNTIME_BIN}/), Skill 内不再存放节点源码或 launcher。 +# 运行环境经 required_environment 由宿主提供, 并在 dora start 时以 ${VAR} 展开写入 +# 各 node env。 +nodes: + - id: gateway + path: ${FORGE_RUNTIME_BIN}/gateway + args: --config ${PAOS_SKILL_ROOT}/profiles/g05/gateway.yaml + env: + FORGE_GATEWAY_PYTHON: ${FORGE_GATEWAY_PYTHON} + inputs: + tick: dora/timer/millis/50 + bench_tool_in: bench_endpoint/tool_out + outputs: + - tool_response + - bench_tool_out + - id: bench_endpoint + path: ${FORGE_RUNTIME_BIN}/bench_endpoint + args: --config ${PAOS_SKILL_ROOT}/profiles/g05/bench_endpoint.yaml + env: + BENCH_ENDPOINT_PYTHON: ${BENCH_ENDPOINT_PYTHON} + inputs: + tick: dora/timer/millis/50 + tool_in: gateway/bench_tool_out + bench_report: robodojo_benchmark/bench_report + outputs: + - tool_out + - bench_cmd + - id: robodojo_benchmark + path: ${FORGE_RUNTIME_BIN}/robodojo_benchmark + env: + BENCHMARK_CONFIG: ${PAOS_SKILL_ROOT}/profiles/g05/config.yaml + PATH: ${PATH} + ROBODOJO_ENV: ${ROBODOJO_ENV} + ROBODOJO_ROOT: ${ROBODOJO_ROOT} + ROBODOJO_CACHE_ROOT: ${ROBODOJO_CACHE_ROOT} + ROBODOJO_DEVICE_ID: ${ROBODOJO_DEVICE_ID} + ROBODOJO_RESULT_DIR: ${ROBODOJO_RESULT_DIR} + CUDA_HOME: ${CUDA_HOME} + CUDA_VISIBLE_DEVICES: ${CUDA_VISIBLE_DEVICES} + inputs: + action: policy/action + tick: dora/timer/millis/50 + bench_cmd: + source: bench_endpoint/bench_cmd + queue_size: 16 + outputs: + - proprio_state + - image/head + - image/left_wrist + - image/right_wrist + - policy_command + - benchmark_status + - benchmark_result + - bench_report + - id: policy + path: ${FORGE_RUNTIME_BIN}/robodojo_policy + env: + ROBODOJO_ENV: ${ROBODOJO_ENV} + XPOLICYLAB_ROOT: ${XPOLICYLAB_ROOT} + POLICY_NAME: ${POLICY_NAME} + ENV_CFG_TYPE: ${ENV_CFG_TYPE} + ACTION_TYPE: ${ACTION_TYPE} + TASK_NAME: ${TASK_NAME} + G05_CKPT_PATH: ${G05_CKPT_PATH} + G05_PROCESSOR_PATH: ${G05_PROCESSOR_PATH} + CUDA_HOME: ${CUDA_HOME} + CUDA_VISIBLE_DEVICES: ${CUDA_VISIBLE_DEVICES} + ROBODOJO_CACHE_ROOT: ${ROBODOJO_CACHE_ROOT} + inputs: + tick: dora/timer/millis/20 + policy_command: + source: robodojo_benchmark/policy_command + queue_size: 32 + proprio_state: robodojo_benchmark/proprio_state + image/head: robodojo_benchmark/image/head + image/left_wrist: robodojo_benchmark/image/left_wrist + image/right_wrist: robodojo_benchmark/image/right_wrist + outputs: + - action diff --git a/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/env.template.sh b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/env.template.sh new file mode 100644 index 000000000..7572d7eb9 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/env.template.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# robodojo / g05 部署环境模板。 +# +# 用法: +# cp env.template.sh env..sh +# 把下面的 /path/to/... 换成目标机真实值(不要提交带个人路径的副本) +# source env..sh +# python scripts/precheck_robodojo_env.py # GPU-free 预检 +# +# 注意:只包含本 Skill 需要的必要变量,不要用 `env | sort` 导出全量环境。 +# 值里不要带引号包住路径(避免把路径当命令)。 + +# ---------- 工具协议(forge_gateway / forge_tool 源码经 PYTHONPATH 使用) ---------- +# forge_bench 里 install 了版本化依赖 forge-msgs==1.0.1、dora-rs==0.4.1。 +export FORGE_GATEWAY_PYTHON=/path/to/forge_bench/python +# NOTE(node bundles): forge_gateway / forge_tool are shipped inside the installed +# forge_runtime + robodojo_endpoint Node bundles; FORGE_PKGS_ROOT is NOT needed. +export BENCH_ENDPOINT_PYTHON=/path/to/forge_bench/python +# NOTE(node bundles): bench_endpoint is shipped inside the robodojo_endpoint Node bundle; +# BENCH_ENDPOINT_SRC is NOT needed. + +# ---------- RoboDojo / XPolicyLab ---------- +# ROBODOJO_ENV 需 python 3.11(3.11.x)、torch 2.7.0+cu128、isaaclab 0.54.3、 +# isaacsim 5.1.0.0、opencv-python(cv2)。benchmark 录像用 cv2,不需要 ffmpeg。 +export ROBODOJO_ENV=/path/to/robodojo/envs/RoboDojo +export ROBODOJO_ROOT=/path/to/robodojo/repo # 需含 env/ task/ utils/ src/ +export XPOLICYLAB_ROOT=/path/to/robodojo/repo/XPolicyLab # G05 adapter 在 policy/G05 +export ROBODOJO_CACHE_ROOT=/path/to/robodojo/cache # 派生 HF_HOME/MODELSCOPE_CACHE/...;不设则跳过 + +# ---------- G05 模型 / processor ---------- +export G05_CKPT_PATH=/path/to/.../checkpoints/checkpoint # 约 33.9GB,可读 +export G05_PROCESSOR_PATH=/path/to/G05/hf_processor # 需含 config.json/tokenizer.json/tokenizer_config.json 等 + +# ---------- policy 选择(profile 固定) ---------- +export POLICY_NAME=G05 +export ENV_CFG_TYPE=arx_x5 +export ACTION_TYPE=joint +export TASK_NAME=put_bottles_into_dustbin + +# ---------- CUDA / GPU ---------- +# CUDA_HOME 需含 bin/、lib64/。CUDA_VISIBLE_DEVICES 决定策略进程可见 GPU; +# benchmark 的 ROBODOJO_DEVICE_ID 相对“当前可见集合”编号,必须 < 可见 GPU 个数。 +export CUDA_HOME=/path/to/cuda +export CUDA_VISIBLE_DEVICES=0,1 +export ROBODOJO_DEVICE_ID=0 +export ROBODOJO_RESULT_DIR=/path/to/result_dir # 结果落盘目录,需可写 + +# 可选:录像目录;不设则用 profile 默认相对路径。 +# export ROBODOJO_RECORD_DIR=/path/to/record_dir +# dora CLI lives in the bin dir above; paos skill start needs dora on PATH. +export PATH="/path/to/forge_bench/bin:$PATH" +export PAOS_SRC_ROOT=/path/to/paos_src/phyagentos +# Which python runs the PAOS CLI (paos env); used by scripts/accept_robodojo_skill.sh. +export PAOS_PY=/path/to/paos/bin/python diff --git a/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/gateway.yaml b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/gateway.yaml new file mode 100644 index 000000000..da0c2313a --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/profiles/g05/gateway.yaml @@ -0,0 +1,188 @@ +# Forge Gateway 配置:把 bench_endpoint 暴露成两个 Tool。 +# +# 与 build_tower 的区别:port 改 19011,避免端口冲突。specs / providers 的 +# 结构性约定完全相同(endpoint_id、input_id/output_id 与 dataflow 通道一一对应, +# 对不上会静默失败,tool 永远 ready:false)。 + +host: 127.0.0.1 +port: 19011 + +# benchmark 场景没有关节流也没有图像流经过网关,两个都留空。 +joint_order: [] +image_input_ids: [] + +readiness: + require_proprio_state: false + require_images: false + +agent: + enabled: false + +tools: + enabled: true + lease_ttl_ms: 3000 + # ⚠️ 每次调用的 deadline,Query 与 Action 共用一个。按「一批最长跑多久」来定, + # 上限由 config.yaml 的 max_total_episodes 兜着。 + invoke_timeout_ms: 3600000 + request_input_id: tool_request + response_output_id: tool_response + + specs: + - tool_id: bench.describe + implementation_id: robodojo.benchmark + endpoint_id: bench.runner + operation: describe + semantics: query + description: >- + Return the live benchmark inventory: available tasks, per-task init state + counts, default parameters, and episode budget limits. Call this before + bench.run; never assume task ids or counts. + input_schema: + type: object + additionalProperties: false + properties: {} + output_schema: + type: object + additionalProperties: false + required: + - benchmark + - suite + - policy_id + - num_tasks + - tasks + - defaults + - limits + properties: + benchmark: {type: string} + suite: {type: string} + policy_id: {type: string} + env_cfg_type: {type: string} + action_type: {type: string} + num_tasks: {type: integer, minimum: 1} + tasks: + type: array + items: + type: object + additionalProperties: false + required: [task_id, num_init_states] + properties: + task_id: {type: integer, minimum: 0} + name: {type: string} + language: {type: string} + num_init_states: {type: integer, minimum: 1} + joint_names: + type: array + items: {type: string} + camera_ids: + type: array + items: {type: string} + defaults: + type: object + additionalProperties: false + required: [num_runs, seed, max_steps] + properties: + num_runs: {type: integer, minimum: 1} + seed: {type: integer} + max_steps: {type: integer, minimum: 1} + limits: + type: object + additionalProperties: false + properties: + max_total_episodes: {type: integer, minimum: 0} + max_max_steps: {type: integer, minimum: 0} + readiness: [] + + - tool_id: bench.run + implementation_id: robodojo.benchmark + endpoint_id: bench.runner + operation: run + semantics: action + description: >- + Run one benchmark batch (task_ids x init_state_ids x num_runs) and return + the aggregate score. Long running: accepted immediately, poll status and + result. Policy weights, suite, and device (G05 / put_bottles_into_dustbin) + are fixed by the profile and are not selectable here. + input_schema: + type: object + additionalProperties: false + required: [task_ids, init_state_ids, num_runs, seed, max_steps] + properties: + task_ids: + description: Task ids from bench.describe; null means every task. + oneOf: + - type: "null" + - type: array + minItems: 1 + items: {type: integer, minimum: 0} + init_state_ids: + description: Init state ids; null means every init state shared by all selected tasks. + oneOf: + - type: "null" + - type: array + minItems: 1 + items: {type: integer, minimum: 0} + num_runs: {type: integer, minimum: 1} + seed: {type: integer} + max_steps: + description: >- + Hard cap on control steps. The episode is terminated as timed_out if + this many steps are reached before the environment terminates. For + RoboDojo the task's native step_lim defines its normal end (success / + partial / failed); set this to at least the task horizon (from + bench.describe defaults) to allow completion, or lower to force an + explicit step-limit stop. Both max_steps and num_steps count control + steps (policy actions). + type: integer + minimum: 1 + output_schema: + type: object + additionalProperties: false + required: + - status + - success_rate + - successes + - total_episodes + - completed_episodes + - result_path + properties: + status: {enum: [succeeded, failed, cancelled]} + primary_metric: + type: object + properties: + name: {type: string} + value: {type: [number, "null"]} + success_rate: {type: [number, "null"], minimum: 0, maximum: 1} + successes: {type: integer, minimum: 0} + total_episodes: {type: integer, minimum: 0} + completed_episodes: {type: integer, minimum: 0} + valid_episodes: {type: integer, minimum: 0} + timed_out_episodes: {type: integer, minimum: 0} + num_steps: {type: integer, minimum: 0} + mean_policy_latency_ms: {type: [number, "null"]} + elapsed_s: {type: [number, "null"]} + obs_republished: + description: Non-zero means observations were re-sent; the score is still valid. + type: [integer, "null"] + result_path: {type: [string, "null"]} + episodes: + type: array + items: + type: object + additionalProperties: false + properties: + task_id: {type: integer} + init_state_id: {type: integer} + run_index: {type: integer} + seed: {type: [integer, "null"]} + success: {type: boolean} + termination: {type: string} + error_code: {type: [string, "null"]} + episodes_truncated: + description: True when the batch was too large to inline; read result_path instead. + type: boolean + readiness: [] + + providers: + - endpoint_id: bench.runner + input_id: bench_tool_in + output_id: bench_tool_out diff --git a/PhyAgentOS/skills/forge-skill/robodojo/skill.yaml b/PhyAgentOS/skills/forge-skill/robodojo/skill.yaml new file mode 100644 index 000000000..fe0815601 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/skill.yaml @@ -0,0 +1,61 @@ +manifest_version: 2 +name: robodojo +version: 0.1.0 +description: Run the RoboDojo G05 benchmark and return its aggregate score. +skill_document: SKILL.md +gateway_url: http://127.0.0.1:19011 +required_tools: +- bench.describe +- bench.run +profiles: + g05: + dataflow: profiles/g05/dataflow.yaml + required_binaries: + - gateway + - bench_endpoint + - robodojo_benchmark + - robodojo_policy + required_assets: + - profiles/g05/gateway.yaml + - profiles/g05/bench_endpoint.yaml + - profiles/g05/config.yaml + required_environment: + - FORGE_GATEWAY_PYTHON + - BENCH_ENDPOINT_PYTHON + - ROBODOJO_ENV + - ROBODOJO_ROOT + - XPOLICYLAB_ROOT + - G05_CKPT_PATH + - G05_PROCESSOR_PATH + - CUDA_HOME + - CUDA_VISIBLE_DEVICES + - ROBODOJO_CACHE_ROOT + - ROBODOJO_DEVICE_ID + - ROBODOJO_RESULT_DIR + environment: + POLICY_NAME: G05 + ENV_CFG_TYPE: arx_x5 + ACTION_TYPE: joint + PYTHONNOUSERSITE: '1' + OMNI_KIT_ACCEPT_EULA: 'YES' +artifacts: + resolver: registry + nodes: + forge_runtime: + artifact_id: forge-runtime-0.1.0 + version: 0.1.0 + platform: linux + arch: x86_64 + digest: cee927eb587f3f958c1803086e59692f6f7ae373477b3cfd15118d01d76c408e + robodojo_endpoint: + artifact_id: robodojo-endpoint-0.1.0 + version: 0.1.0 + platform: linux + arch: x86_64 + digest: b9a66fb07b04ee1f76bb8221f68f96b35d1f1ce136d3d6cf967cb7a34da8e159 + robodojo_benchmark: + artifact_id: robodojo-benchmark-0.1.0 + version: 0.1.0 + platform: linux + arch: x86_64 + digest: e48cf4e838ba73512ac1eb0c144ea17ea177bb56ad41e524c8332f9a38c19ff2 From bbf7d21a658188c5db74b36232531e24914222c9 Mon Sep 17 00:00:00 2001 From: Xiaolv <2116262909@qq.com> Date: Thu, 10 Sep 2026 14:50:34 +0800 Subject: [PATCH 2/3] feat(robodojo): add install and inventory helpers Add the per-run install helper and the unsigned schema-v3 index template. The helper presigns each bucket_key on every run, verifies expected_sha256 for each downloaded object, writes the generated index to a private temp dir, prepares the isolated instance, and resolves paos/tosutil through --paos/--tosutil, then PAOS_BIN/TOSUTIL, then PATH. check_bundle_inventory.py keeps the template inventory in sync with the Skill files. --- .../docs/paos-forge-packages.template.yaml | 574 ++++++++++++++++++ .../scripts/check_bundle_inventory.py | 167 +++++ .../robodojo/scripts/install_robodojo.py | 264 ++++++++ 3 files changed, 1005 insertions(+) create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/docs/paos-forge-packages.template.yaml create mode 100644 PhyAgentOS/skills/forge-skill/robodojo/scripts/check_bundle_inventory.py create mode 100755 PhyAgentOS/skills/forge-skill/robodojo/scripts/install_robodojo.py diff --git a/PhyAgentOS/skills/forge-skill/robodojo/docs/paos-forge-packages.template.yaml b/PhyAgentOS/skills/forge-skill/robodojo/docs/paos-forge-packages.template.yaml new file mode 100644 index 000000000..dfef4f243 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/docs/paos-forge-packages.template.yaml @@ -0,0 +1,574 @@ +# Unsigned schema-v3 static package index template for the robodojo Skill. +# +# Template-only fields (stripped by scripts/install_robodojo.py): +# bucket_key object key inside TOS bucket phyagentos-resource-inner +# expected_sha256 archive sha256 of the published object; the helper re-downloads the +# object, hashes it, and aborts on mismatch. `null` means the object is +# not published yet (candidate). +# +# `paos skill install --index` requires `direct_download_url`, `sha256` and `size`; the +# helper presigns `bucket_key`, measures size and sha256 itself and writes those three +# fields into a generated index. Never feed this template to `--index` directly. +# +# Scope: benchmark and G05 policy node implementations are NOT part of the Skill bundle. +# They are Forge Nodes built from framework/forge_runtime +# (packages/nodes/benchmarks/robodojo, scripts/build_node_artifacts.py) and locked here by +# node_digest. The published 0.1.0 objects are frozen pre-split snapshots and stay +# untouched; the Skill entry below points at the rebuilt post-split candidate, which still +# needs a new object key or version. +schema_version: 3 +packages: +- kind: skill_bundle + package_key: robodojo + artifact_id: robodojo + version: 0.1.0 + platform: linux + arch: x86_64 + description: Run the RoboDojo G05 benchmark and return its aggregate score. + dependencies: + - forge-runtime-0.1.0 + - robodojo-endpoint-0.1.0 + - robodojo-benchmark-0.1.0 + bucket_key: skill-bundles/robodojo/0.1.0/robodojo-0.1.0.tar.gz + expected_sha256: null + inventory: + - path: README.md + sha256: 56381c08a3c46ce8e82bced526a29a7e331adb6a5e61a634b3826549d98087c9 + size: 7350 + category: file + - path: SKILL.md + sha256: 82aac86d637f634453ca94bbe61de112bff7dd4b63d5659db75ee29e0f1f3ec3 + size: 7310 + category: file + - path: docs/DELIVERY_0.1.0.md + sha256: d430abc97a74f974875b7bb59aba39eb92adc3a96b694c736d6aeccb31c93392 + size: 10969 + category: file + - path: profiles/g05/bench_endpoint.yaml + sha256: 9765a4828104c30b16ebe73f09ea0fafa57ba02c21dda75e7d48d9ef593459e4 + size: 618 + category: file + - path: profiles/g05/config.yaml + sha256: 3ccb45faf483fb08f1e7fbae534bf6e1cb2714223ab98b8ac4c8e6fd2e28372c + size: 740 + category: file + - path: profiles/g05/dataflow.yaml + sha256: 050cc1df9a8e9db86cfa0053e0f0b53767daacf3224cc45c2f62b87285a4ae6a + size: 2957 + category: file + - path: profiles/g05/env.template.sh + sha256: 7ae4e5bec354fe05eeae4f67e20ae91ac4d0b8901392c51f9777c3105d438e22 + size: 2857 + category: file + - path: profiles/g05/gateway.yaml + sha256: c5b49b11ee39d4df38c88514c9dc20f4e490efabec43d837bf2bbe8596ac4eb2 + size: 6788 + category: file + - path: scripts/check_bundle_inventory.py + sha256: 81218a85c909c0be95cd46f9d964a331f57570e7fa2db8fd3a3a26ce9c3d8bb3 + size: 6330 + category: file + - path: scripts/install_robodojo.py + sha256: 266eead1af8162a9c57f09f58da8a4a1efb1402571334b6d612ffc79caebf135 + size: 10422 + category: executable + - path: skill.yaml + sha256: 0c5bbdde16b91c10808ce49090486afe5caeb1ff63631ade484bfb03e43e95b8 + size: 1635 + category: file + backend_url: null + direct_download_url: null + archive: + format: tar.gz + todos: >- + Post-split candidate. The published robodojo-0.1.0.tar.gz (sha256 + 5c7806843e5cff16a8a407254ffe7736c9b1d5d13982bd1b82195c7e4f5bc907) still contains the + vendored nodes/ tree and profiles/g05/bin/; publish the rebuilt archive under a new + object key or version before pointing expected_sha256 at it. The inventory omits + docs/paos-forge-packages.template.yaml, which cannot hash itself. +- kind: node_bundle + package_key: forge-runtime-0.1.0 + artifact_id: forge-runtime-0.1.0 + version: 0.1.0 + platform: linux + arch: x86_64 + install_root: ~/.PhyAgentOS/forge_runtime/nodes/forge_runtime + entrypoints: + gateway: bin/gateway + bucket_key: node-bundles/forge_runtime/0.1.0/forge-runtime-0.1.0-linux-x86_64.tar.gz + expected_sha256: c524ae7038775e48f2b0a6036e621913b35b327ad2c710f8a8e0c5cad9e7613b + node_digest: cee927eb587f3f958c1803086e59692f6f7ae373477b3cfd15118d01d76c408e + inventory: + - path: bin/gateway + sha256: cb134ebbc0f3ed0f804606dd07eda73fa74dad0a4f75f21fe135394fcd687339 + size: 369 + category: executable + - path: forge_gateway/__init__.py + sha256: 51035d2c0bb249b54edb472735e3bb20692df33dc41e99b8d0395e015bc74fe6 + size: 58 + category: file + - path: forge_gateway/__main__.py + sha256: 406b7188a391e7691b262bd978dbeeed3ac654264d533005b48cbad968543e37 + size: 61 + category: file + - path: forge_gateway/adapters/__init__.py + sha256: 779761d9503c1887b2b5d2afcf9642b9785248ceed65339ca422f86240caeeef + size: 41 + category: file + - path: forge_gateway/adapters/dora_adapter.py + sha256: 237fc682eb211dcb20c0c0004e53e71844430eaf3e54c92b8ef72aa295c373dc + size: 11226 + category: file + - path: forge_gateway/adapters/dora_runtime.py + sha256: 92c8afccfd8b4bbb3177f450f8b351bfbc17883dfaca5c5e2400bfcedfe2f8e1 + size: 8145 + category: file + - path: forge_gateway/adapters/http_app.py + sha256: f2aef6d34b20131e558415e5a778f19a067d54779cba88b5c47f3111190a5ce9 + size: 1975 + category: file + - path: forge_gateway/adapters/manifest_loader.py + sha256: 4897749c2db9455bef76282550d7d84037aac2c34401cf0a48eddb6242469d40 + size: 3385 + category: file + - path: forge_gateway/adapters/state_files.py + sha256: d4e97037f3a7183f700716c425c89daaf77ea5de284d5b679d9a9843ef3f557e + size: 2275 + category: file + - path: forge_gateway/adapters/tool_dora.py + sha256: 8b4baa800f5d952e19a38120d34c403f66737e8272c074cb9f732bb0d3be9643 + size: 4923 + category: file + - path: forge_gateway/adapters/websocket.py + sha256: 1c0485bc225f34720177538bc53a6b8bc793dc6026bc89f6acba8d9f7e786b9b + size: 755 + category: file + - path: forge_gateway/application.py + sha256: 55440507745ef30385699e73672f11d30a3448a6bd77dfbf35120ba4f819b8c2 + size: 22323 + category: file + - path: forge_gateway/cli.py + sha256: cb75a68e022c150b9c27c45c1697f0f79b3f4b98a7cc79f29f10da71bc23320e + size: 5049 + category: file + - path: forge_gateway/config.py + sha256: a9e9e7885771b58e743b6661a9aaeb22e040d4c1d7f2deb9f1e99708a0bf719e + size: 28171 + category: file + - path: forge_gateway/controllers/__init__.py + sha256: d55994c06ed732fec0708abd5cc0ddd75507ff4bbfb6203fd10d68690da3113b + size: 38 + category: file + - path: forge_gateway/controllers/agent_controller.py + sha256: 1504f61d37ba87a89e68f513caa564e973a1659c8dd0f821c806c212f0bb72f6 + size: 1947 + category: file + - path: forge_gateway/controllers/playback_controller.py + sha256: 0fbc9368cfac7f30da67c77f7907806ac77c69e46098efb55e4615259cf1eee8 + size: 1543 + category: file + - path: forge_gateway/controllers/policy_controller.py + sha256: fd313e13690c7efe254795f36055ef3fdea45f6a8aab4590350753f67df00bbf + size: 1224 + category: file + - path: forge_gateway/controllers/record_controller.py + sha256: c001662bbcbfc90eda2c751a808b443f66f3b418fa858cd52ad534847029ea6e + size: 2372 + category: file + - path: forge_gateway/controllers/runtime_controller.py + sha256: a0ae571e36ca8ea6605aab32ce963ae1ce3c3e8170c72f03f3e77ab4ee9ca933 + size: 3797 + category: file + - path: forge_gateway/controllers/tool_controller.py + sha256: a95c9f0fe1f97777f99a6ecad904af68affc98fdc9d8ab63a497c6047ae5ebec + size: 16693 + category: file + - path: forge_gateway/controllers/utils.py + sha256: ee72b155211db2f246d3849a7d1c80f1c3069db8826058008cef5ff9f9ee6687 + size: 1015 + category: file + - path: forge_gateway/controllers/websocket_controller.py + sha256: 7af3820b05e5edf997e6a8e5ff5f18a77076bf01b2c0466a8a17d6627092bdc8 + size: 4641 + category: file + - path: forge_gateway/domain/__init__.py + sha256: 729c6c3a6499d01f8b62640144680c26b7e80dec91b18847329a780f2fe26c81 + size: 37 + category: file + - path: forge_gateway/domain/action_invocations.py + sha256: 44d3626ef598184855c5df9d0625ffe617d285e09d20881cf7aebf767aeb3c6f + size: 15424 + category: file + - path: forge_gateway/domain/action_manifest.py + sha256: c8a6849a35dd1fae860744c30fe9911066c07973f0b97128d533ef3942570b95 + size: 4410 + category: file + - path: forge_gateway/domain/commands.py + sha256: a76c576172697aa761f6760be1a7dce2ec1bd9817f4e5a95766b89f8d0ab6aa3 + size: 1815 + category: file + - path: forge_gateway/domain/node_status.py + sha256: 736cdb41ff11f867c7064bf6cc4654184cbfcefa58a56b770eb0c546d8054a80 + size: 545 + category: file + - path: forge_gateway/domain/sessions.py + sha256: 68a66d5b6d4114ba876a3942c00abf9ac7fa8061686055c73281961c2dadba21 + size: 1251 + category: file + - path: forge_gateway/domain/tool_catalog.py + sha256: 3af1304e13ef6b7663da6eeea6120e5677957ae5b68505bcd6d6d6de4369a70f + size: 7082 + category: file + - path: forge_gateway/domain/tool_directory.py + sha256: f698e9e0456df2877ac57d80d53fe628b577a474412d6992ba45b2a7ae31ca07 + size: 17271 + category: file + - path: forge_gateway/resources/actions/piper/sam3.md + sha256: 07509d31d23d68944188eeb1674047492b07ff2569354e298bd8bfc4d64f6452 + size: 1860 + category: file + - path: forge_gateway/resources/actions/piper/sam3_policy_core.md + sha256: b186b3009ccd0eb2dccd89f6710f6513922d6ae6bdbf7005653427f9890055d6 + size: 4605 + category: file + - path: forge_gateway/resources/static/app.js + sha256: 7fe0f8a181f90a14da22c8f5272c21531f1e9d58edda9ddcf2e78d09b48b162d + size: 41895 + category: file + - path: forge_gateway/resources/static/index.html + sha256: d84488345acd381fe31e28e1c4263341fad0ab66c831d0bc8c14fdf14cddb9d5 + size: 10519 + category: file + - path: forge_gateway/resources/static/styles.css + sha256: a2d45832b254a98cc8726a167b76001ffdd78b588503e7ff38b28028a4987432 + size: 21987 + category: file + - path: forge_gateway/services/__init__.py + sha256: 954a57e6961f9cf4eaab070cdd969c46c3c48ebbdd71a29647814cdcb6ddd1ba + size: 24 + category: file + - path: forge_gateway/services/action_registry.py + sha256: a1c6ad1fae35adf0fb91938f332a3b6f7d22e567200988b4c76b3e6bb9c6f70b + size: 1298 + category: file + - path: forge_gateway/services/agent_service.py + sha256: 41f52c0625dd1938e9023d83068afcc6e89c8db4877efab1fe141a1fb5a709b8 + size: 14885 + category: file + - path: forge_gateway/services/capability_service.py + sha256: befd539fcc4ae96bc98109809e5a387340d9aaab98576483767cf4cb47fa183a + size: 1712 + category: file + - path: forge_gateway/services/command_service.py + sha256: ff520c966f716e44e915864a3276ae3c5dc38cc9dab046f37d566f71945b75a4 + size: 4192 + category: file + - path: forge_gateway/services/image_service.py + sha256: c8a9a031f7cdaebfd4141f8ab17b8c88d5a5e9b10aec258928722bdbb02713df + size: 6706 + category: file + - path: forge_gateway/services/runtime_service.py + sha256: 08c8676c470af9c4bd01c722999ce5f8c49f73fff5d82f61f394821245d9bb43 + size: 25318 + category: file + - path: forge_gateway/services/tool_gateway_service.py + sha256: bb4a64ad0a6d23e79c1e8bf6dae99a7c4adb5ce3ffad36ad9ec95a7cb8d8982a + size: 90822 + category: file + - path: forge_tool/__init__.py + sha256: 06c9c6b830c3771046a14b8d2f4b0dfa7d0b287e3c41cc3ae6ba4f4054dbac91 + size: 5580 + category: file + - path: forge_tool/_tool_message.py + sha256: afd99c1061cc9cfaa35386c2c954aafa00ccb22c5eac42222e5ffb1dcbf70e50 + size: 14854 + category: file + - path: forge_tool/dora.py + sha256: 3612f8bc27288898881f228405ac797058d11e20e48f93c16c94274d02bbb2b1 + size: 16642 + category: file + - path: forge_tool/endpoint/__init__.py + sha256: 2a46324051783e729e508536d43066414dbe866865e2fc4c37d44c8aa2b46f46 + size: 1603 + category: file + - path: forge_tool/endpoint/exceptions.py + sha256: 2dd2d6fe793a5308979d623a4e2e9f90de7861cef365091249526f16646e1408 + size: 724 + category: file + - path: forge_tool/endpoint/models.py + sha256: 5d6b0db2559b0cc513dfb3e592e995377b64ebbb90959a29ee5bb84492c610bc + size: 20673 + category: file + - path: forge_tool/endpoint/protocol.py + sha256: 5243819dc9eadd561c719fea357c97dba1581eacf29de1bd99613923fbe93329 + size: 2981 + category: file + - path: forge_tool/handler.py + sha256: 59024c7edfb8be659d773293269a0826c8010ee1acc7f4bf39c0b453369f1d97 + size: 34074 + category: file + - path: forge_tool/py.typed + sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + size: 0 + category: file + - path: forge_tool/wire/__init__.py + sha256: 033d32a508ea857433bb9b4f9f75e4aa5d5ea57b0092b1f182c9c0500bdd0501 + size: 3960 + category: file + - path: forge_tool/wire/codec.py + sha256: 4e80832a524355478ade051ac6dd1931ff374fea4514c29075ba49fafd53bd7d + size: 7820 + category: file + - path: forge_tool/wire/descriptor.py + sha256: f65b1dd4f0b7689d5b833bc5894d4cf2096c254ade99b49afb281ee4e1b1f506 + size: 6317 + category: file + - path: forge_tool/wire/envelope.py + sha256: 18c9fb9b905238d0a915431f9accc14ff0107262cc3cfc31648251f4d3a8e0e6 + size: 9584 + category: file + - path: forge_tool/wire/errors.py + sha256: 96c938ec1f1084d7128bdeebf3f782c68899af6f4298cb685df6549873a2fd67 + size: 507 + category: file + - path: forge_tool/wire/factories.py + sha256: 24da5817bab5c97316f040b8456170d8eedee25875447cb4831e277d43898f8e + size: 14031 + category: file + - path: forge_tool/wire/messages.py + sha256: 831862cb31a2f3d30562c407891bf71a1e5266ff22b06574662ddaea69356b41 + size: 19549 + category: file + backend_url: null + direct_download_url: null + archive: + format: tar.gz + todos: null + node_id: forge_runtime +- kind: node_bundle + package_key: robodojo-endpoint-0.1.0 + artifact_id: robodojo-endpoint-0.1.0 + version: 0.1.0 + platform: linux + arch: x86_64 + install_root: ~/.PhyAgentOS/forge_runtime/nodes/robodojo_endpoint + entrypoints: + bench_endpoint: bin/bench_endpoint + bucket_key: node-bundles/robodojo_endpoint/0.1.0/robodojo-endpoint-0.1.0-linux-x86_64.tar.gz + expected_sha256: 80b7076b04b8abf21712f6f3b11bd9523fde91d5e49bbe3ed202bf17f238e46c + node_digest: b9a66fb07b04ee1f76bb8221f68f96b35d1f1ce136d3d6cf967cb7a34da8e159 + inventory: + - path: bench_endpoint/__init__.py + sha256: 25a0bc14f808edadbfb636629ab28412009addd8aae96ec7abbf9031139524d2 + size: 366 + category: file + - path: bench_endpoint/__main__.py + sha256: 6aa1969dbad95243ba9dac5d9792e7f3ec4359f0ce27eed96458406dfb6f774f + size: 564 + category: file + - path: bench_endpoint/config.py + sha256: 8f43dac26ac9401d5d7f1bd47ac36f037506b40126e9b2ec007dd4e2a46432e4 + size: 1457 + category: file + - path: bench_endpoint/descriptor.py + sha256: 4b0e1671bfdd9439a84da1acede00cdc9df36a804660b583ccd206d38f069469 + size: 2802 + category: file + - path: bench_endpoint/endpoint_lease.py + sha256: 0c4dcaf11b30e9ec17a5ef87ad958177e121455c65e18f94f4f1f4f24945e2d4 + size: 2600 + category: file + - path: bench_endpoint/endpoints.py + sha256: 88f103821a6f5bb65e2412bf3f4908b50029607f2ff5b40527073deeb25f8c94 + size: 4851 + category: file + - path: bench_endpoint/invocations.py + sha256: c086bfb19ad4e37ce638ca7fa790d9b2ac383b2ab7a16cc6f9354973b70dd04f + size: 7004 + category: file + - path: bench_endpoint/peer.py + sha256: 46579c0806e9577594231ec95e3885e6d24f7852119c70b90dea910de1cba723 + size: 9187 + category: file + - path: bench_endpoint/runner.py + sha256: bb45ae890790e578d3e0b2483325383bc3a4452ea75e151ce8cbd56177c74c29 + size: 5664 + category: file + - path: bench_endpoint/wire.py + sha256: b7f1df988ca1373bbbf4d07aee79b230db7e86950a7b1efc8f5bf5a1d1c1a5fd + size: 3812 + category: file + - path: bin/bench_endpoint + sha256: 37cabb6b35214691fcbb66c2354bdf965be3ca599a9004af6750853340459ba0 + size: 373 + category: executable + - path: forge_tool/__init__.py + sha256: 06c9c6b830c3771046a14b8d2f4b0dfa7d0b287e3c41cc3ae6ba4f4054dbac91 + size: 5580 + category: file + - path: forge_tool/_tool_message.py + sha256: afd99c1061cc9cfaa35386c2c954aafa00ccb22c5eac42222e5ffb1dcbf70e50 + size: 14854 + category: file + - path: forge_tool/dora.py + sha256: 3612f8bc27288898881f228405ac797058d11e20e48f93c16c94274d02bbb2b1 + size: 16642 + category: file + - path: forge_tool/endpoint/__init__.py + sha256: 2a46324051783e729e508536d43066414dbe866865e2fc4c37d44c8aa2b46f46 + size: 1603 + category: file + - path: forge_tool/endpoint/exceptions.py + sha256: 2dd2d6fe793a5308979d623a4e2e9f90de7861cef365091249526f16646e1408 + size: 724 + category: file + - path: forge_tool/endpoint/models.py + sha256: 5d6b0db2559b0cc513dfb3e592e995377b64ebbb90959a29ee5bb84492c610bc + size: 20673 + category: file + - path: forge_tool/endpoint/protocol.py + sha256: 5243819dc9eadd561c719fea357c97dba1581eacf29de1bd99613923fbe93329 + size: 2981 + category: file + - path: forge_tool/handler.py + sha256: 59024c7edfb8be659d773293269a0826c8010ee1acc7f4bf39c0b453369f1d97 + size: 34074 + category: file + - path: forge_tool/py.typed + sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + size: 0 + category: file + - path: forge_tool/wire/__init__.py + sha256: 033d32a508ea857433bb9b4f9f75e4aa5d5ea57b0092b1f182c9c0500bdd0501 + size: 3960 + category: file + - path: forge_tool/wire/codec.py + sha256: 4e80832a524355478ade051ac6dd1931ff374fea4514c29075ba49fafd53bd7d + size: 7820 + category: file + - path: forge_tool/wire/descriptor.py + sha256: f65b1dd4f0b7689d5b833bc5894d4cf2096c254ade99b49afb281ee4e1b1f506 + size: 6317 + category: file + - path: forge_tool/wire/envelope.py + sha256: 18c9fb9b905238d0a915431f9accc14ff0107262cc3cfc31648251f4d3a8e0e6 + size: 9584 + category: file + - path: forge_tool/wire/errors.py + sha256: 96c938ec1f1084d7128bdeebf3f782c68899af6f4298cb685df6549873a2fd67 + size: 507 + category: file + - path: forge_tool/wire/factories.py + sha256: 24da5817bab5c97316f040b8456170d8eedee25875447cb4831e277d43898f8e + size: 14031 + category: file + - path: forge_tool/wire/messages.py + sha256: 831862cb31a2f3d30562c407891bf71a1e5266ff22b06574662ddaea69356b41 + size: 19549 + category: file + backend_url: null + direct_download_url: null + archive: + format: tar.gz + todos: null + node_id: robodojo_endpoint +- kind: node_bundle + package_key: robodojo-benchmark-0.1.0 + artifact_id: robodojo-benchmark-0.1.0 + version: 0.1.0 + platform: linux + arch: x86_64 + install_root: ~/.PhyAgentOS/forge_runtime/nodes/robodojo_benchmark + entrypoints: + robodojo_benchmark: bin/robodojo_benchmark + robodojo_policy: bin/robodojo_policy + bucket_key: node-bundles/robodojo_benchmark/0.1.0/robodojo-benchmark-0.1.0-linux-x86_64.tar.gz + expected_sha256: null + node_digest: e48cf4e838ba73512ac1eb0c144ea17ea177bb56ad41e524c8332f9a38c19ff2 + inventory: + - path: bin/robodojo_benchmark + sha256: 6a4e075963c837a98d05f755f6d52a1837e79450b8a673e3430a98a24aa969fb + size: 1092 + category: executable + - path: bin/robodojo_policy + sha256: 403261c5274b0c545fc97cd85ad5df698161f9d83fd233b68be3e157c61c77bb + size: 944 + category: executable + - path: robodojo/.gitignore + sha256: 78902f790bbf2718606af332dfb97847652c5b38446c13607c1b27020926a29c + size: 124 + category: file + - path: robodojo/README.md + sha256: 25c80e046c6aa6e749943370e6045a5861e1cc74250aa4c07aac6effa248a3fc + size: 5302 + category: file + - path: robodojo/_core/__init__.py + sha256: ef1a5aa477c1cb5b1eb663e799fd08f09329c952b7f9dd41643bfe60e479db25 + size: 53 + category: file + - path: robodojo/_core/adapter.py + sha256: 93240ca820d7d356ae720ae41d5d31216dcc2e6bd7b3fa1230b42b51b8563237 + size: 4433 + category: file + - path: robodojo/_core/config.py + sha256: 9bb75d07bd64e7c26283af5da73fa6c7630079292ff7d6036b84afe514d5864c + size: 5687 + category: file + - path: robodojo/_core/node.py + sha256: 031142bce97f56876c0e0ab1921991b171b8852a4a0d0b64cedd93edd8b65fb1 + size: 12342 + category: file + - path: robodojo/_core/result.py + sha256: 88ee7589b46c43a8365c4d63eec0ad5295e5fa7b168d0746b2ae2bd6327d42b9 + size: 5173 + category: file + - path: robodojo/_core/runner.py + sha256: f2a6482e50b0535df17914abd83b2958443032156bcfb71189fb5d2af4438bea + size: 18288 + category: file + - path: robodojo/_core/session.py + sha256: 21c43500383464572873435db1d67403c8209e92b6d429960d34f20db7796cdc + size: 10534 + category: file + - path: robodojo/_core/video.py + sha256: 0343107afc90108761ba775c1a815bff0cccb24482d5c563a9774d9ea495ba2f + size: 3261 + category: file + - path: robodojo/adapters/__init__.py + sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + size: 0 + category: file + - path: robodojo/adapters/robodojo_adapter.py + sha256: 6d4f119784d6897a041c52c511d3765e57e022e9961ae2efa48869eeb34abd48 + size: 28254 + category: file + - path: robodojo/config.example.yaml + sha256: 41f37e10d76eb59bdb03c7a76e7dd0e51d036d9689720017f3f36860949023d2 + size: 1313 + category: file + - path: robodojo/config.py + sha256: 5a745ceff770876a87c727a069e901de983c2c71fb745740c92744b072f67be7 + size: 3069 + category: file + - path: robodojo/dataflow.example.yaml + sha256: 90ee1299028caef52d0dd2a275a26c8154adbde8ffb23923c0ba57c3f5d6cf2d + size: 2504 + category: file + - path: robodojo/main.py + sha256: 918ba94b61c7fdba77e20ff80229d528d3f35edf07e359c1a6826ddaaef8175b + size: 4280 + category: file + - path: robodojo/policy_node.py + sha256: 310569e1f4315119db126bbe69a2c10b1ea66d58e7435d94391d7311bb9857f0 + size: 14638 + category: file + - path: robodojo/pyproject.toml + sha256: e53220f19b584c83a38fa9f205df0f782ac1d7c6663d6f74545faf677ad9078f + size: 607 + category: file + backend_url: null + direct_download_url: null + archive: + format: tar.gz + todos: "Candidate: built from framework/forge_runtime packages/nodes/benchmarks/robodojo; not uploaded yet." + node_id: robodojo_benchmark diff --git a/PhyAgentOS/skills/forge-skill/robodojo/scripts/check_bundle_inventory.py b/PhyAgentOS/skills/forge-skill/robodojo/scripts/check_bundle_inventory.py new file mode 100644 index 000000000..62192dedd --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/scripts/check_bundle_inventory.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Verify the published index template against the files in this Skill tree. + +``docs/paos-forge-packages.template.yaml`` records a ``sha256`` and ``size`` for +every Skill file so a maintainer can inspect or register the bundle without +unpacking it. Nothing reads those entries at install time, so editing any Skill +file makes them stale silently. Run this before publishing: + + python3 scripts/check_bundle_inventory.py --check # exit 1 on drift + python3 scripts/check_bundle_inventory.py --write # rewrite the inventory + +``--write`` regenerates only the Skill inventory block; the header comments and +the node entries keep their formatting. Categories are re-derived from the file +mode, so run ``--write`` on the pack host: a Windows checkout has no executable +bit, and ``--write`` there keeps the categories already recorded instead. +""" +from __future__ import annotations + +import argparse +import hashlib +import os +import sys +from pathlib import Path + +SKILL_ROOT = Path(__file__).resolve().parent.parent +TEMPLATE = SKILL_ROOT / "docs" / "paos-forge-packages.template.yaml" +# The template cannot hash itself, and the packager generates archive-manifest.json. +SKIPPED = {"docs/paos-forge-packages.template.yaml", "archive-manifest.json"} +# Windows checkouts do not carry the executable bit. +CHECK_CATEGORY = os.name != "nt" + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def filesystem_category(path: Path) -> str: + return "executable" if path.stat().st_mode & 0o111 else "file" + + +def actual_files(root: Path) -> list[dict[str, object]]: + found = [ + { + "path": path.relative_to(root).as_posix(), + "sha256": sha256_file(path), + "size": path.stat().st_size, + "category": filesystem_category(path), + } + for path in root.rglob("*") + if path.is_file() and path.relative_to(root).as_posix() not in SKIPPED + ] + return sorted(found, key=lambda item: str(item["path"])) + + +def skill_bounds(lines: list[str]) -> tuple[int, int]: + start = next( + (index for index, line in enumerate(lines) if line.strip() == "- kind: skill_bundle"), + None, + ) + if start is None: + raise SystemExit(f"{TEMPLATE} has no skill_bundle entry") + end = next( + (index for index in range(start + 1, len(lines)) if lines[index].startswith("- kind:")), + len(lines), + ) + return start, end + + +def recorded_entries(lines: list[str], start: int, end: int) -> dict[str, dict[str, str]]: + entries: dict[str, dict[str, str]] = {} + current: str | None = None + for line in lines[start:end]: + stripped = line.strip() + if stripped.startswith("- path: "): + current = stripped[len("- path: ") :].strip() + entries[current] = {} + elif current and ":" in stripped: + key, _, value = stripped.partition(":") + if key in {"sha256", "size", "category"}: + entries[current][key] = value.strip() + return entries + + +def render_block(expected: list[dict[str, object]], recorded: dict[str, dict[str, str]]) -> list[str]: + lines = [" inventory:"] + for item in expected: + path = str(item["path"]) + lines.append(f" - path: {path}") + lines.append(f" sha256: {item['sha256']}") + lines.append(f" size: {item['size']}") + # Keep a recorded category on hosts that cannot report the mode; otherwise + # derive it from the file so a Linux pack host stays authoritative. + category = item["category"] if CHECK_CATEGORY else recorded.get(path, {}).get( + "category", item["category"] + ) + lines.append(f" category: {category}") + return lines + + +def rewrite(lines: list[str], start: int, end: int, block: list[str]) -> list[str]: + output = list(lines[:start]) + index = start + while index < end: + line = lines[index] + if line.strip() == "inventory:": + output.extend(block) + index += 1 + while index < end: + current = lines[index] + if current.startswith(" - path:"): + index += 1 + while index < end and lines[index].startswith(" "): + index += 1 + continue + break + continue + output.append(line) + index += 1 + return output + list(lines[end:]) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true", help="report drift (default)") + parser.add_argument("--write", action="store_true", help="rewrite the Skill inventory") + args = parser.parse_args() + + lines = TEMPLATE.read_text(encoding="utf-8").splitlines() + start, end = skill_bounds(lines) + recorded = recorded_entries(lines, start, end) + expected = actual_files(SKILL_ROOT) + by_path = {str(item["path"]): item for item in expected} + + problems: list[str] = [] + for item in expected: + path = str(item["path"]) + entry = recorded.get(path) + if entry is None: + problems.append(f"missing from inventory: {path}") + continue + if entry.get("sha256") != item["sha256"] or entry.get("size") != str(item["size"]): + problems.append(f"stale hash/size: {path}") + elif CHECK_CATEGORY and entry.get("category") != item["category"]: + problems.append(f"stale category: {path}") + for path in sorted(set(recorded) - set(by_path)): + problems.append(f"inventory lists a missing file: {path}") + + if not problems: + print(f"ok: {len(expected)} Skill files match {TEMPLATE.name}") + return 0 + for problem in problems: + print(problem) + if not args.write: + print("run with --write to regenerate the Skill inventory") + return 1 + block = render_block(expected, recorded) + TEMPLATE.write_text("\n".join(rewrite(lines, start, end, block)) + "\n", encoding="utf-8", newline="") + print(f"rewrote the Skill inventory in {TEMPLATE.name}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/PhyAgentOS/skills/forge-skill/robodojo/scripts/install_robodojo.py b/PhyAgentOS/skills/forge-skill/robodojo/scripts/install_robodojo.py new file mode 100755 index 000000000..84d8b0037 --- /dev/null +++ b/PhyAgentOS/skills/forge-skill/robodojo/scripts/install_robodojo.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +"""One-shot robodojo install helper (self-contained). + +Reads the unsigned schema-v3 index template shipped next to this script, presigns a fresh +TOS download URL for every ``bucket_key``, downloads each object once to measure ``size`` +and confirm ``expected_sha256``, writes a generated schema-v3 index to a private temp dir, +then calls:: + + paos skill install robodojo --version 0.1.0 --index + +``paos skill install --index`` rejects an index entry that has no ``sha256``/``size``, which +is why the template carries ``bucket_key`` + ``expected_sha256`` instead of a link and a +hand-written size: the helper derives the real values from the object it just downloaded and +aborts on a hash mismatch. + +Requires TOS read permission for the inner bucket plus ``tosutil``; this is a +credential-enabled route, not a credential-less public one. Requires PyYAML (already a PAOS +runtime dependency). + +Self-contained preparation: + * locates the template next to this script (downloaded together from TOS) OR in the repo + layout (