Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,19 @@ Frontend structure and verification details: [docs/web/development.md](web/devel

Builtin runtime templates live under `internal/templates/embed/<name>/` and are embedded directly via `go:embed` (`agent.toml`, `workspace/`, etc.). Each docker embed template carries a `version` field and matching `image.ref`.

**Local (before PR)**: `make build-all` bumps `version`, syncs `image.ref`, rebuilds `csgclaw` (so embed matches image tags), then builds Docker images.
**Local (before PR)**: `make build-all` runs two independent steps:

1. **version/ref (bump)**: increments the last segment and syncs `image.ref` per template only when `cmd/csgclaw-cli/` or that template's `Dockerfile` differs from git `HEAD` and working-tree `version` still matches `HEAD` (at most once vs baseline). `workspace/` changes do not bump. Force: `DOCKER_EMBED_FORCE_BUMP=1`.
2. **Docker images (build)**: `csgclaw-cli` changed → build all embed images; one `Dockerfile` changed → build that template only; repeatable without changing `version`. Skips when no image inputs changed. Force all: `DOCKER_EMBED_FORCE_BUILD=1`. Explicit targets (e.g. `make build-picoclaw-manager-image`) always build the named image.

Then rebuilds `csgclaw` (`workspace` ships via `go:embed`, not the sandbox image).

**GitLab CI (main)**: reads committed `version` / `image.ref`, builds and pushes images **without** modifying `agent.toml`; runs when embed `agent.toml` changed in the pushed range (`CI_COMMIT_BEFORE_SHA..HEAD`) or `version` differs from the compare base.

| Target | Description |
|--------|-------------|
| `make sync-docker-embed-image-refs` | Sync `image.ref` from current `version` (no bump) |
| `make bump-docker-embed-version` | Bump `version` and sync `image.ref` for all docker embed templates |
| `make bump-docker-embed-version` | Bump (or skip per image-input rules) `version` and sync `image.ref` per template (independent of docker build) |
| `make ensure-docker-embed-manifests` | Run `sync-docker-embed-image-refs` when `image.ref` is missing or out of sync with `version` (used by `make build` / `make test`) |

Templates with a `Dockerfile` are discovered by `scripts/list-docker-embed-templates.sh` (currently `openclaw-manager`, `openclaw-worker`, `picoclaw-manager`, and `picoclaw-worker`).
Expand Down
9 changes: 7 additions & 2 deletions docs/build.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,19 @@ make build-all

内置运行时模板位于 `internal/templates/embed/<name>/`,通过 `go:embed` 直接嵌入(`agent.toml`、`workspace/` 等)。每个 docker embed 模板在 `agent.toml` 中有 `version` 字段与对应的 `image.ref`。

**本地(PR 前)**:`make build-all` 会先递增 `version`、同步 `image.ref`,再编译 `csgclaw`(使 embed 与镜像 tag 一致),最后构建 Docker 镜像。
**本地(PR 前)**:`make build-all` 分两步,互不影响:

1. **version/ref(bump)**:仅当 `cmd/csgclaw-cli/` 或某模板 `Dockerfile` 相对 git `HEAD` 有改动,且工作区 `version` 仍等于 `HEAD` 时,对该模板 `version` 末段 +1 并同步 `image.ref`;相对基线最多自增一次。`workspace/` 改动不 bump。强制递增:`DOCKER_EMBED_FORCE_BUMP=1`。
2. **Docker 镜像(build)**:`csgclaw-cli` 改动 → 构建全部 embed 镜像;某模板 `Dockerfile` 改动 → 仅构建该模板;可多次构建,不改变 `version`。无改动时跳过 docker build。强制全建:`DOCKER_EMBED_FORCE_BUILD=1`。单独 target(如 `make build-picoclaw-manager-image`)始终构建指定镜像。

随后编译 `csgclaw`(`workspace` 改动通过 `go:embed` 进入二进制,无需 bump 镜像版本)。

**GitLab CI(main)**:仅读取已提交的 `version` / `image.ref` 构建并 push 镜像,**不会**修改 `agent.toml`;当本次 push 范围内(`CI_COMMIT_BEFORE_SHA..HEAD`)embed `agent.toml` 发生变化或 `version` 相对 compare base 改变时触发构建。

| 目标 | 说明 |
|------|------|
| `make sync-docker-embed-image-refs` | 按当前 `version` 同步 `image.ref`(不递增) |
| `make bump-docker-embed-version` | 递增全部 docker embed 模板的 `version` 并同步 `image.ref` |
| `make bump-docker-embed-version` | 按镜像输入规则递增(或跳过)各模板 `version` 并同步 `image.ref`(与 docker build 独立) |
| `make ensure-docker-embed-manifests` | `image.ref` 缺失或与 `version` 不一致时调用 `sync-docker-embed-image-refs`(`make build` / `make test` 使用) |

带 `Dockerfile` 的模板由 `scripts/list-docker-embed-templates.sh` 发现(当前为 `openclaw-manager`、`openclaw-worker`、`picoclaw-manager` 和 `picoclaw-worker`)。
Expand Down
2 changes: 1 addition & 1 deletion internal/templates/embed/picoclaw-manager/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# -t picoclaw-manager:local .

# Default upstream picoclaw base image (single source of truth for local make / CI builds).
ARG PICOCLAW_IMAGE=opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw:2026.6.8
ARG PICOCLAW_IMAGE=opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw:2026.6.10

FROM ${PICOCLAW_IMAGE}

Expand Down
4 changes: 2 additions & 2 deletions internal/templates/embed/picoclaw-manager/agent.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description = "Builtin PicoClaw manager template"
role = "manager"
runtime_kind = "picoclaw_sandbox"
updated_at = "2026-05-27T00:00:00Z"
version = "0.1.2"
version = "0.1.3"

[image]
ref = "opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw-manager:0.1.2"
ref = "opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw-manager:0.1.3"
2 changes: 1 addition & 1 deletion internal/templates/embed/picoclaw-worker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# -t picoclaw-worker:local .

# Default upstream picoclaw base image (keep in sync with picoclaw-manager/Dockerfile).
ARG PICOCLAW_IMAGE=opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw:2026.6.8
ARG PICOCLAW_IMAGE=opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw:2026.6.10

FROM ${PICOCLAW_IMAGE}

Expand Down
4 changes: 2 additions & 2 deletions internal/templates/embed/picoclaw-worker/agent.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description = "Builtin PicoClaw worker template"
role = "worker"
runtime_kind = "picoclaw_sandbox"
updated_at = "2026-05-27T00:00:00Z"
version = "0.1.1"
version = "0.1.2"

[image]
ref = "opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw-worker:0.1.1"
ref = "opencsg-registry.cn-beijing.cr.aliyuncs.com/opencsghq/picoclaw-worker:0.1.2"
17 changes: 15 additions & 2 deletions scripts/build-docker-embed-images.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
# Build container images for embed templates that include a Dockerfile.
# Image tag defaults to agent.toml version (after bump-docker-embed-version.sh).
# Image tag defaults to agent.toml version. Build selection is independent from bump:
# - cmd/csgclaw-cli/ changed vs HEAD -> build all templates
# - template Dockerfile changed vs HEAD -> build that template
# - PICOCLAW_BASE_IMAGE / OPENCLAW_BASE_IMAGE set -> build matching family
# Set DOCKER_EMBED_FORCE_BUILD=1 to build all templates regardless.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
Expand Down Expand Up @@ -87,11 +91,20 @@ build_one() {
}

chmod +x "${LIST_SCRIPT}"
built_any=false
if [ "$#" -eq 0 ]; then
while IFS= read -r name; do
[ -z "${name}" ] && continue
build_one "${name}"
if should_build_docker_embed_image "${name}"; then
build_one "${name}"
built_any=true
else
echo "skip docker build ${name}: image inputs unchanged (set DOCKER_EMBED_FORCE_BUILD=1 to force)"
fi
done < <("${LIST_SCRIPT}")
if [ "${built_any}" = false ]; then
echo "skip docker embed builds: no templates with changed image inputs"
fi
else
for name in "$@"; do
build_one "${name}"
Expand Down
2 changes: 2 additions & 0 deletions scripts/bump-docker-embed-version.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bash
# Bump agent.toml version (last segment) and sync [image].ref for docker embed templates.
# Bumps at most once vs git HEAD per image-input change (cmd/csgclaw-cli/ or template Dockerfile).
# Workspace changes do not bump. Independent from docker build. Set DOCKER_EMBED_FORCE_BUMP=1 to always increment.
#
# Usage:
# ./scripts/bump-docker-embed-version.sh # all docker templates
Expand Down
182 changes: 182 additions & 0 deletions scripts/docker-embed-agent-toml-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,179 @@ increment_version_last_segment() {
printf '%s.%s' "${prefix}" "$((last + 1))"
}

docker_embed_git_available() {
local root
root="$(docker_embed_root)"
git -C "${root}" rev-parse --is-inside-work-tree >/dev/null 2>&1
}

docker_embed_baseline_version() {
local template="$1"
local root manifest_rel tmp version

root="$(docker_embed_root)"
manifest_rel="internal/templates/embed/${template}/agent.toml"
if ! docker_embed_git_available; then
return 1
fi
if ! git -C "${root}" cat-file -e "HEAD:${manifest_rel}" 2>/dev/null; then
return 1
fi

tmp="$(mktemp)"
git -C "${root}" show "HEAD:${manifest_rel}" > "${tmp}"
version="$(read_agent_toml_version "${tmp}")"
rm -f "${tmp}"
if [ -z "${version}" ]; then
return 1
fi
printf '%s' "${version}"
}

# Prints -1 when left < right, 0 when equal, 1 when left > right.
embed_template_version_compare() {
local left="$1"
local right="$2"
local -a left_parts right_parts
local i max len left_val right_val

if [ "${left}" = "${right}" ]; then
printf '%s' "0"
return 0
fi

IFS=. read -r -a left_parts <<< "${left}"
IFS=. read -r -a right_parts <<< "${right}"
len="${#left_parts[@]}"
if [ "${#right_parts[@]}" -gt "${len}" ]; then
len="${#right_parts[@]}"
fi

for ((i = 0; i < len; i++)); do
left_val=$((10#${left_parts[i]:-0}))
right_val=$((10#${right_parts[i]:-0}))
if ((left_val > right_val)); then
printf '%s' "1"
return 0
fi
if ((left_val < right_val)); then
printf '%s' "-1"
return 0
fi
done

printf '%s' "0"
}

# Exit 0 when csgclaw-cli sources differ from HEAD (shared docker embed input).
docker_embed_cli_inputs_changed_vs_head() {
local root

root="$(docker_embed_root)"
if ! docker_embed_git_available; then
return 0
fi
if [ -n "$(git -C "${root}" status --porcelain -- cmd/csgclaw-cli/ 2>/dev/null || true)" ]; then
return 0
fi
return 1
}

# Exit 0 when the template Dockerfile differs from HEAD (includes untracked).
docker_embed_dockerfile_changed_vs_head() {
local template="$1"
local root dockerfile_rel

root="$(docker_embed_root)"
dockerfile_rel="internal/templates/embed/${template}/Dockerfile"
if ! docker_embed_git_available; then
return 0
fi
if [ -n "$(git -C "${root}" status --porcelain -- "${dockerfile_rel}" 2>/dev/null || true)" ]; then
return 0
fi
return 1
}

# Exit 0 when inputs that affect the sandbox image changed relative to git HEAD.
# Workspace and agent.toml metadata are excluded (they ship via go:embed, not the image).
docker_embed_image_inputs_changed_vs_head() {
local template="$1"

if docker_embed_cli_inputs_changed_vs_head; then
return 0
fi
if docker_embed_dockerfile_changed_vs_head "${template}"; then
return 0
fi
return 1
}

# Exit 0 when this template's image should be docker-built locally.
# Independent from version bump: repeated builds are allowed while version stays put.
should_build_docker_embed_image() {
local template="$1"

if [ "${DOCKER_EMBED_FORCE_BUILD:-}" = "1" ]; then
return 0
fi
if ! docker_embed_git_available; then
return 0
fi
if docker_embed_cli_inputs_changed_vs_head; then
return 0
fi
if docker_embed_dockerfile_changed_vs_head "${template}"; then
return 0
fi
case "${template}" in
picoclaw-*)
if [ -n "${PICOCLAW_BASE_IMAGE:-}" ]; then
return 0
fi
;;
openclaw-*)
if [ -n "${OPENCLAW_BASE_IMAGE:-}" ]; then
return 0
fi
;;
esac
return 1
}

# Exit 0 when version bump should be skipped (sync-only path).
should_skip_docker_embed_bump() {
local template="$1"
local manifest current baseline order

if [ "${DOCKER_EMBED_FORCE_BUMP:-}" = "1" ]; then
return 1
fi
if ! docker_embed_git_available; then
return 1
fi

manifest="$(docker_embed_manifest_path "${template}")"
current="$(read_agent_toml_version "${manifest}")"
baseline="$(docker_embed_baseline_version "${template}" || true)"
if [ -z "${baseline}" ]; then
return 1
fi

if ! docker_embed_image_inputs_changed_vs_head "${template}"; then
return 0
fi

order="$(embed_template_version_compare "${current}" "${baseline}")"
if [ "${order}" -gt 0 ]; then
return 0
fi
if [ "${order}" -eq 0 ]; then
return 1
fi
return 0
}

embed_image_ref_env_key() {
local template="$1"
local key="EMBED_IMAGE_REF_${template}"
Expand Down Expand Up @@ -283,6 +456,15 @@ bump_agent_toml_version_and_ref() {
fi

current="$(read_agent_toml_version "${manifest}")"
if should_skip_docker_embed_bump "${template}"; then
if ! docker_embed_manifest_is_current "${template}"; then
sync_agent_toml_version_and_ref "${template}"
return 0
fi
echo "skip bump ${manifest}: image inputs unchanged or already bumped (version=${current})"
return 0
fi

if [ -z "${current}" ]; then
next="0.1.0"
else
Expand Down
Loading
Loading