diff --git a/changelog/current.md b/changelog/current.md index f3ae6b4eb..cff4c3c30 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -27,6 +27,7 @@ Record image-affecting changes to `manager/`, `worker/`, `copaw/`, `openclaw-bas **Bug Fixes** +- **OpenHuman AgentTeams runtime contract**: OpenHuman workers now consume terminal `AGENTTEAMS_*` identity, storage, Matrix, gateway, and controller settings; the controller again preserves their dedicated workspace and `openhuman` runtime label after backend normalization, and Helm selects the renamed worker image. - **Legacy Team channel policy**: Legacy Team reconciliation now writes final Matrix channel allow-lists to member runtime config and re-adds the Team Leader to the Manager allow-list so controller integration tests observe durable policy state. - **Sandbox worker-deps hardening**: Sandbox-backed Workers now prepare controller-owned worker-deps env/token/data material before claim creation, recycle stale SandboxClaims and bound Sandboxes on runtime-affecting changes, and use bounded ServiceAccount token projection for built-in SandboxClaim mounts. - **CLI AgentTeams auth env**: The `hiclaw` CLI now discovers `AGENTTEAMS_CONTROLLER_URL`, `AGENTTEAMS_AUTH_TOKEN`, `AGENTTEAMS_AUTH_TOKEN_FILE`, and `AGENTTEAMS_CLUSTER_ID` while preserving legacy `HICLAW_*` fallbacks, so Manager and Worker containers can use the terminal env names for controller calls. diff --git a/helm/hiclaw/values.yaml b/helm/hiclaw/values.yaml index 436241ace..b4272c4b3 100644 --- a/helm/hiclaw/values.yaml +++ b/helm/hiclaw/values.yaml @@ -298,7 +298,7 @@ worker: repository: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/agentteams-hermes-worker tag: "" # defaults to global.imageTag openhuman: - repository: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/hiclaw-openhuman-worker + repository: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/agentteams-openhuman-worker tag: "" # defaults to global.imageTag defaultRuntime: "openclaw" resources: diff --git a/hiclaw-controller/internal/backend/docker.go b/hiclaw-controller/internal/backend/docker.go index 056960256..416c20be0 100644 --- a/hiclaw-controller/internal/backend/docker.go +++ b/hiclaw-controller/internal/backend/docker.go @@ -140,7 +140,9 @@ func (d *DockerBackend) Create(ctx context.Context, req CreateRequest) (*WorkerR // Infer WorkingDir from HOME env if not set if req.WorkingDir == "" { - if home, ok := req.Env["HOME"]; ok { + if req.Runtime == RuntimeOpenHuman { + req.WorkingDir = "/home/openhuman/.openhuman" + } else if home, ok := req.Env["HOME"]; ok { req.WorkingDir = home } } diff --git a/hiclaw-controller/internal/backend/docker_test.go b/hiclaw-controller/internal/backend/docker_test.go index 971d6eb1e..446263d3e 100644 --- a/hiclaw-controller/internal/backend/docker_test.go +++ b/hiclaw-controller/internal/backend/docker_test.go @@ -243,11 +243,13 @@ func TestDockerCreatePullsImage(t *testing.T) { } // captureCreateImagesServer is a minimal Docker mock that records the Image -// field of every POST /containers/create request. Other endpoints return the -// minimum responses required to make DockerBackend.Create succeed. +// and WorkingDir fields of every POST /containers/create request. Other +// endpoints return the minimum responses required to make DockerBackend.Create +// succeed. type capturedCreateBodies struct { - srv *httptest.Server - images []string + srv *httptest.Server + images []string + workingDirs []string } func (c *capturedCreateBodies) lastImage() string { @@ -257,6 +259,13 @@ func (c *capturedCreateBodies) lastImage() string { return c.images[len(c.images)-1] } +func (c *capturedCreateBodies) lastWorkingDir() string { + if len(c.workingDirs) == 0 { + return "" + } + return c.workingDirs[len(c.workingDirs)-1] +} + func captureCreateImagesServer(t *testing.T) *capturedCreateBodies { t.Helper() captured := &capturedCreateBodies{} @@ -271,6 +280,11 @@ func captureCreateImagesServer(t *testing.T) *capturedCreateBodies { if img, ok := body["Image"].(string); ok { captured.images = append(captured.images, img) } + if workingDir, ok := body["WorkingDir"].(string); ok { + captured.workingDirs = append(captured.workingDirs, workingDir) + } else { + captured.workingDirs = append(captured.workingDirs, "") + } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"Id": "sha256-test"}) }) @@ -442,11 +456,13 @@ func TestDockerCreateResolvesImageFromRuntime(t *testing.T) { }{ {"explicit_copaw_uses_copaw_image", RuntimeCopaw, "", "agentteams/copaw-worker:latest"}, {"explicit_hermes_uses_hermes_image", RuntimeHermes, "", "agentteams/hermes-worker:latest"}, + {"explicit_openhuman_uses_openhuman_image", RuntimeOpenHuman, "", "agentteams/openhuman-worker:latest"}, {"explicit_qwenpaw_uses_qwenpaw_image", RuntimeQwenPaw, "", "agentteams/qwenpaw-worker:latest"}, {"explicit_openclaw_uses_worker_image", RuntimeOpenClaw, "", "agentteams/worker-agent:latest"}, {"empty_runtime_with_no_fallback_uses_worker_image", "", "", "agentteams/worker-agent:latest"}, {"empty_runtime_with_copaw_fallback_uses_copaw_image", "", RuntimeCopaw, "agentteams/copaw-worker:latest"}, {"empty_runtime_with_hermes_fallback_uses_hermes_image", "", RuntimeHermes, "agentteams/hermes-worker:latest"}, + {"empty_runtime_with_openhuman_fallback_uses_openhuman_image", "", RuntimeOpenHuman, "agentteams/openhuman-worker:latest"}, {"empty_runtime_with_qwenpaw_fallback_uses_qwenpaw_image", "", RuntimeQwenPaw, "agentteams/qwenpaw-worker:latest"}, {"explicit_runtime_overrides_fallback", RuntimeOpenClaw, RuntimeHermes, "agentteams/worker-agent:latest"}, } @@ -457,11 +473,12 @@ func TestDockerCreateResolvesImageFromRuntime(t *testing.T) { b := &DockerBackend{ config: DockerConfig{ - WorkerImage: "agentteams/worker-agent:latest", - CopawWorkerImage: "agentteams/copaw-worker:latest", - HermesWorkerImage: "agentteams/hermes-worker:latest", - QwenPawWorkerImage: "agentteams/qwenpaw-worker:latest", - DefaultNetwork: "hiclaw-net", + WorkerImage: "agentteams/worker-agent:latest", + CopawWorkerImage: "agentteams/copaw-worker:latest", + HermesWorkerImage: "agentteams/hermes-worker:latest", + OpenHumanWorkerImage: "agentteams/openhuman-worker:latest", + QwenPawWorkerImage: "agentteams/qwenpaw-worker:latest", + DefaultNetwork: "hiclaw-net", }, containerPrefix: "agentteams-worker-", client: &http.Client{ @@ -483,3 +500,52 @@ func TestDockerCreateResolvesImageFromRuntime(t *testing.T) { }) } } + +func TestDockerCreateRuntimeWorkingDir(t *testing.T) { + const home = "/root/hiclaw-fs/agents/x" + cases := []struct { + name string + runtime string + explicit string + wantWorkingDir string + }{ + {"openclaw_uses_home", RuntimeOpenClaw, "", home}, + {"copaw_uses_home", RuntimeCopaw, "", home}, + {"hermes_uses_home", RuntimeHermes, "", home}, + {"openhuman_uses_dedicated_workspace", RuntimeOpenHuman, "", "/home/openhuman/.openhuman"}, + {"empty_runtime_uses_home", "", "", home}, + {"explicit_working_dir_wins", RuntimeOpenHuman, "/custom/workspace", "/custom/workspace"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + captured := captureCreateImagesServer(t) + defer captured.srv.Close() + b := &DockerBackend{ + config: DockerConfig{ + WorkerImage: "agentteams/worker-agent:latest", + CopawWorkerImage: "agentteams/copaw-worker:latest", + HermesWorkerImage: "agentteams/hermes-worker:latest", + OpenHumanWorkerImage: "agentteams/openhuman-worker:latest", + DefaultNetwork: "hiclaw-net", + }, + containerPrefix: "agentteams-worker-", + client: &http.Client{ + Transport: &testTransport{serverURL: captured.srv.URL}, + }, + } + + _, err := b.Create(context.Background(), CreateRequest{ + Name: "x", + Runtime: tc.runtime, + WorkingDir: tc.explicit, + Env: map[string]string{"HOME": home}, + }) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + if got := captured.lastWorkingDir(); got != tc.wantWorkingDir { + t.Fatalf("create body WorkingDir = %q, want %q", got, tc.wantWorkingDir) + } + }) + } +} diff --git a/hiclaw-controller/internal/backend/interface_test.go b/hiclaw-controller/internal/backend/interface_test.go index d0d3cf251..17e2c97a3 100644 --- a/hiclaw-controller/internal/backend/interface_test.go +++ b/hiclaw-controller/internal/backend/interface_test.go @@ -13,10 +13,12 @@ func TestResolveRuntime(t *testing.T) { {"explicit_over_empty_fallback", RuntimeOpenClaw, "", RuntimeOpenClaw}, {"empty_uses_fallback_hermes", "", RuntimeHermes, RuntimeHermes}, {"empty_uses_fallback_copaw", "", RuntimeCopaw, RuntimeCopaw}, + {"empty_uses_fallback_openhuman", "", RuntimeOpenHuman, RuntimeOpenHuman}, {"empty_uses_fallback_qwenpaw", "", RuntimeQwenPaw, RuntimeQwenPaw}, {"empty_and_no_fallback_uses_openclaw", "", "", RuntimeOpenClaw}, {"explicit_openclaw_preserved", RuntimeOpenClaw, RuntimeHermes, RuntimeOpenClaw}, {"explicit_hermes_preserved", RuntimeHermes, RuntimeCopaw, RuntimeHermes}, + {"explicit_openhuman_preserved", RuntimeOpenHuman, RuntimeCopaw, RuntimeOpenHuman}, {"explicit_qwenpaw_preserved", RuntimeQwenPaw, RuntimeCopaw, RuntimeQwenPaw}, } for _, tc := range cases { @@ -38,6 +40,7 @@ func TestValidRuntime(t *testing.T) { {RuntimeOpenClaw, true}, {RuntimeCopaw, true}, {RuntimeHermes, true}, + {RuntimeOpenHuman, true}, {RuntimeQwenPaw, true}, {"unknown", false}, } diff --git a/hiclaw-controller/internal/backend/kubernetes.go b/hiclaw-controller/internal/backend/kubernetes.go index 5fda04d7e..595d024ca 100644 --- a/hiclaw-controller/internal/backend/kubernetes.go +++ b/hiclaw-controller/internal/backend/kubernetes.go @@ -280,6 +280,8 @@ func (k *K8sBackend) Create(ctx context.Context, req CreateRequest) (*WorkerResu req.Env = map[string]string{} } req.Env["HOME"] = req.WorkingDir + case req.Runtime == RuntimeOpenHuman: + req.WorkingDir = "/home/openhuman/.openhuman" default: // Both openclaw and hermes use the same workspace layout: // HOME == WorkingDir == /root/hiclaw-fs/agents/ (== MinIO @@ -744,6 +746,8 @@ func defaultRuntime(runtime string) string { return RuntimeCopaw case RuntimeHermes: return RuntimeHermes + case RuntimeOpenHuman: + return RuntimeOpenHuman case RuntimeQwenPaw: return RuntimeQwenPaw default: diff --git a/hiclaw-controller/internal/backend/kubernetes_test.go b/hiclaw-controller/internal/backend/kubernetes_test.go index 6cb000e20..1dfe43956 100644 --- a/hiclaw-controller/internal/backend/kubernetes_test.go +++ b/hiclaw-controller/internal/backend/kubernetes_test.go @@ -618,8 +618,8 @@ func TestK8sWithPrefixStop(t *testing.T) { // TestK8sCreateRuntimeWorkingDir verifies WorkingDir / HOME defaulting per // runtime. The hermes runtime now shares the openclaw layout: WorkingDir == -// HOME == /root/hiclaw-fs/agents/ (== MinIO mirror root). Only copaw -// now shares the same layout. +// HOME == /root/hiclaw-fs/agents/ (== MinIO mirror root). OpenHuman +// keeps the dedicated workspace baked into its image. func TestK8sCreateRuntimeWorkingDir(t *testing.T) { cases := []struct { name string @@ -630,18 +630,20 @@ func TestK8sCreateRuntimeWorkingDir(t *testing.T) { {"openclaw", RuntimeOpenClaw, "/root/hiclaw-fs/agents/x", "/root/hiclaw-fs/agents/x"}, {"hermes", RuntimeHermes, "/root/hiclaw-fs/agents/x", "/root/hiclaw-fs/agents/x"}, {"copaw", RuntimeCopaw, "/root/hiclaw-fs/agents/x", "/root/hiclaw-fs/agents/x"}, + {"openhuman", RuntimeOpenHuman, "/home/openhuman/.openhuman", ""}, {"empty_default", "", "/root/hiclaw-fs/agents/x", "/root/hiclaw-fs/agents/x"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { client := newFakeK8sCoreClient() b := NewK8sBackendWithClient(client, K8sConfig{ - Namespace: "hiclaw", - WorkerImage: "agentteams/worker-agent:latest", - CopawWorkerImage: "agentteams/copaw-worker:latest", - HermesWorkerImage: "agentteams/hermes-worker:latest", - WorkerCPU: "1000m", - WorkerMemory: "2Gi", + Namespace: "hiclaw", + WorkerImage: "agentteams/worker-agent:latest", + CopawWorkerImage: "agentteams/copaw-worker:latest", + HermesWorkerImage: "agentteams/hermes-worker:latest", + OpenHumanWorkerImage: "agentteams/openhuman-worker:latest", + WorkerCPU: "1000m", + WorkerMemory: "2Gi", }, "agentteams-worker-", nil) if _, err := b.Create(context.Background(), CreateRequest{ @@ -685,11 +687,13 @@ func TestK8sCreateResolvesImageFromRuntime(t *testing.T) { }{ {"explicit_copaw", RuntimeCopaw, "", "agentteams/copaw-worker:latest", RuntimeCopaw}, {"explicit_hermes", RuntimeHermes, "", "agentteams/hermes-worker:latest", RuntimeHermes}, + {"explicit_openhuman", RuntimeOpenHuman, "", "agentteams/openhuman-worker:latest", RuntimeOpenHuman}, {"explicit_qwenpaw", RuntimeQwenPaw, "", "agentteams/qwenpaw-worker:latest", RuntimeQwenPaw}, {"explicit_openclaw", RuntimeOpenClaw, "", "agentteams/worker-agent:latest", RuntimeOpenClaw}, {"empty_no_fallback", "", "", "agentteams/worker-agent:latest", RuntimeOpenClaw}, {"empty_with_copaw_fallback", "", RuntimeCopaw, "agentteams/copaw-worker:latest", RuntimeCopaw}, {"empty_with_hermes_fallback", "", RuntimeHermes, "agentteams/hermes-worker:latest", RuntimeHermes}, + {"empty_with_openhuman_fallback", "", RuntimeOpenHuman, "agentteams/openhuman-worker:latest", RuntimeOpenHuman}, {"empty_with_qwenpaw_fallback", "", RuntimeQwenPaw, "agentteams/qwenpaw-worker:latest", RuntimeQwenPaw}, {"explicit_overrides_fallback", RuntimeOpenClaw, RuntimeHermes, "agentteams/worker-agent:latest", RuntimeOpenClaw}, } @@ -697,13 +701,14 @@ func TestK8sCreateResolvesImageFromRuntime(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := newFakeK8sCoreClient() b := NewK8sBackendWithClient(client, K8sConfig{ - Namespace: "hiclaw", - WorkerImage: "agentteams/worker-agent:latest", - CopawWorkerImage: "agentteams/copaw-worker:latest", - HermesWorkerImage: "agentteams/hermes-worker:latest", - QwenPawWorkerImage: "agentteams/qwenpaw-worker:latest", - WorkerCPU: "1000m", - WorkerMemory: "2Gi", + Namespace: "hiclaw", + WorkerImage: "agentteams/worker-agent:latest", + CopawWorkerImage: "agentteams/copaw-worker:latest", + HermesWorkerImage: "agentteams/hermes-worker:latest", + OpenHumanWorkerImage: "agentteams/openhuman-worker:latest", + QwenPawWorkerImage: "agentteams/qwenpaw-worker:latest", + WorkerCPU: "1000m", + WorkerMemory: "2Gi", }, "agentteams-worker-", nil) if _, err := b.Create(context.Background(), CreateRequest{ diff --git a/openhuman/Dockerfile b/openhuman/Dockerfile index f8dcc2229..85e9d4469 100644 --- a/openhuman/Dockerfile +++ b/openhuman/Dockerfile @@ -1,11 +1,11 @@ # --------------------------------------------------------------------------- -# OpenHuman Worker — HiClaw-integrated multi-stage Docker build +# OpenHuman Worker - AgentTeams-integrated multi-stage Docker build # # Based on the upstream OpenHuman Dockerfile with the following changes: # - Enables `channel-matrix` feature flag for native Matrix support -# - Installs mc (MinIO Client) for file sync with HiClaw centralized storage +# - Installs mc (MinIO Client) for file sync with AgentTeams centralized storage # - Installs jq for JSON processing in entrypoint scripts -# - Copies HiClaw shared scripts and OpenHuman worker entrypoint +# - Copies AgentTeams shared scripts and OpenHuman worker entrypoint # - Copies OpenHuman worker agent template (AGENTS.md, skills) # # License notice: @@ -14,7 +14,7 @@ # The GPL-3.0 license text is preserved at /usr/share/licenses/openhuman/LICENSE # inside the built image. # -# Build context: HiClaw repo root (to access shared/lib/ and manager/agent/). +# Build context: AgentTeams repo root (to access shared/lib/ and manager/agent/). # OpenHuman upstream source is fetched via git inside the builder stage — # no `openhuman-src/` directory is required in the build context. # @@ -100,7 +100,7 @@ WORKDIR /build/openhuman-src RUN cargo build --release --bin openhuman-core --features channel-matrix # ========================================================================== -# Stage 3: Minimal runtime image with HiClaw integration +# Stage 3: Minimal runtime image with AgentTeams integration # ========================================================================== FROM debian:bookworm-slim AS runtime @@ -149,7 +149,7 @@ RUN mkdir -p /home/openhuman/.openhuman/skills \ /home/openhuman/.openhuman/agent-config \ && chown -R openhuman:openhuman /home/openhuman -# Copy HiClaw shared scripts +# Copy AgentTeams shared scripts COPY shared/lib/ /opt/hiclaw/scripts/lib/ RUN chmod +x /opt/hiclaw/scripts/lib/*.sh 2>/dev/null || true diff --git a/openhuman/scripts/openhuman-worker-entrypoint.sh b/openhuman/scripts/openhuman-worker-entrypoint.sh index 8eb638d62..413849bd9 100755 --- a/openhuman/scripts/openhuman-worker-entrypoint.sh +++ b/openhuman/scripts/openhuman-worker-entrypoint.sh @@ -4,26 +4,27 @@ # sets up MinIO file sync, launches openhuman-core with native Matrix support. # # Config bridging (in priority order): -# 1. openclaw.json - channels.matrix.* + models.providers.hiclaw-gateway +# 1. openclaw.json - channels.matrix.* + models.providers.agentteams-gateway # (pulled from MinIO, same source as hermes/copaw) -# 2. MATRIX_* / HICLAW_AI_GATEWAY_URL env vars - controller-injected fallback +# 2. AGENTTEAMS_* / MATRIX_* env vars - controller-injected fallback # # Generated config.toml sections: # - [channels_config.matrix] # Native Matrix channel for direct human/manager interaction. # - LLM inference settings (via openhuman-core CLI): -# Routes LLM traffic to the HiClaw AI gateway (Higress); startup is +# Routes LLM traffic to the AgentTeams AI gateway (Higress); startup is # aborted (fail-closed) if the gateway config is missing. # # Environment variables (set by controller during worker creation): -# HICLAW_WORKER_NAME - Worker name (required) -# HICLAW_FS_ENDPOINT - MinIO endpoint (required in local mode) -# HICLAW_FS_ACCESS_KEY - MinIO access key (required in local mode) -# HICLAW_FS_SECRET_KEY - MinIO secret key (required in local mode) -# HICLAW_RUNTIME - "aliyun" for cloud mode (uses RRSA/STS) -# HICLAW_AI_GATEWAY_URL - HiClaw AI gateway base URL (required) -# HICLAW_WORKER_GATEWAY_KEY - Higress consumer key (required) -# HICLAW_DEFAULT_MODEL - Default model id (default qwen-plus) +# AGENTTEAMS_WORKER_NAME - Worker name (required) +# AGENTTEAMS_FS_ENDPOINT - MinIO endpoint (required in local mode) +# AGENTTEAMS_FS_ACCESS_KEY - MinIO access key (required in local mode) +# AGENTTEAMS_FS_SECRET_KEY - MinIO secret key (required in local mode) +# AGENTTEAMS_STORAGE_PREFIX - mc alias and bucket prefix +# AGENTTEAMS_RUNTIME - "aliyun" for cloud mode (uses RRSA/STS) +# AGENTTEAMS_AI_GATEWAY_URL - AgentTeams AI gateway base URL (required) +# AGENTTEAMS_WORKER_GATEWAY_KEY - Higress consumer key (required) +# AGENTTEAMS_DEFAULT_MODEL - Default model id (default qwen-plus) # MATRIX_HOMESERVER_URL - Matrix homeserver URL (fallback) # MATRIX_ACCESS_TOKEN - Matrix access token (fallback) # MATRIX_HOME_ROOM_ID - Matrix room ID @@ -37,12 +38,12 @@ set -e # Source shared environment bootstrap (provides ensure_mc_credentials in cloud mode) source /opt/hiclaw/scripts/lib/hiclaw-env.sh 2>/dev/null || true -WORKER_NAME="${HICLAW_WORKER_NAME:?HICLAW_WORKER_NAME is required}" -WORKER_CR_NAME="${HICLAW_WORKER_CR_NAME:-${WORKER_NAME}}" +WORKER_NAME="${AGENTTEAMS_WORKER_NAME:?AGENTTEAMS_WORKER_NAME is required}" +WORKER_CR_NAME="${AGENTTEAMS_WORKER_CR_NAME:-${WORKER_NAME}}" WORKSPACE="${OPENHUMAN_WORKSPACE:-/home/openhuman/.openhuman}" log() { - echo "[hiclaw-openhuman-worker $(date '+%Y-%m-%d %H:%M:%S')] $1" + echo "[agentteams-openhuman-worker $(date '+%Y-%m-%d %H:%M:%S')] $1" } # ============================================================ @@ -57,17 +58,17 @@ fi # ============================================================ # Step 1: Configure mc alias for centralized file system # ============================================================ -if [ "${HICLAW_RUNTIME:-}" = "aliyun" ]; then +if [ "${AGENTTEAMS_RUNTIME:-}" = "aliyun" ]; then log "Configuring mc alias for cloud (RRSA OIDC)..." ensure_mc_credentials || { log "ERROR: Failed to obtain OSS credentials"; exit 1; } - FS_BUCKET="${HICLAW_FS_BUCKET:-hiclaw-cloud-storage}" + FS_BUCKET="${AGENTTEAMS_FS_BUCKET:-agentteams-storage}" else - FS_ENDPOINT="${HICLAW_FS_ENDPOINT:?HICLAW_FS_ENDPOINT is required}" - FS_ACCESS_KEY="${HICLAW_FS_ACCESS_KEY:?HICLAW_FS_ACCESS_KEY is required}" - FS_SECRET_KEY="${HICLAW_FS_SECRET_KEY:?HICLAW_FS_SECRET_KEY is required}" - FS_BUCKET="${HICLAW_FS_BUCKET:-hiclaw-storage}" + FS_ENDPOINT="${AGENTTEAMS_FS_ENDPOINT:?AGENTTEAMS_FS_ENDPOINT is required}" + FS_ACCESS_KEY="${AGENTTEAMS_FS_ACCESS_KEY:?AGENTTEAMS_FS_ACCESS_KEY is required}" + FS_SECRET_KEY="${AGENTTEAMS_FS_SECRET_KEY:?AGENTTEAMS_FS_SECRET_KEY is required}" + FS_BUCKET="${AGENTTEAMS_FS_BUCKET:-agentteams-storage}" log "Configuring mc alias for local MinIO..." - mc alias set hiclaw "${FS_ENDPOINT}" "${FS_ACCESS_KEY}" "${FS_SECRET_KEY}" + mc alias set "${AGENTTEAMS_STORAGE_ALIAS:-agentteams}" "${FS_ENDPOINT}" "${FS_ACCESS_KEY}" "${FS_SECRET_KEY}" fi log " FS bucket: ${FS_BUCKET}" @@ -79,9 +80,9 @@ mkdir -p "${WORKSPACE}" "${WORKSPACE}/shared" "${WORKSPACE}/memory" \ log "Pulling Worker config from centralized storage..." ensure_mc_credentials 2>/dev/null || true -mc mirror "${HICLAW_STORAGE_PREFIX}/agents/${WORKER_NAME}/" "${WORKSPACE}/agent-config/" \ +mc mirror "${AGENTTEAMS_STORAGE_PREFIX}/agents/${WORKER_NAME}/" "${WORKSPACE}/agent-config/" \ --overwrite 2>/dev/null || true -mc mirror "${HICLAW_STORAGE_PREFIX}/shared/" "${WORKSPACE}/shared/" \ +mc mirror "${AGENTTEAMS_STORAGE_PREFIX}/shared/" "${WORKSPACE}/shared/" \ --overwrite 2>/dev/null || true # Copy essential files from agent-config to workspace root @@ -116,7 +117,7 @@ while [ ! -f "${WORKSPACE}/SOUL.md" ] || [ ! -f "${WORKSPACE}/AGENTS.md" ]; do fi log "Waiting for config files to appear in MinIO (attempt ${RETRY}/6)..." sleep 5 - mc mirror "${HICLAW_STORAGE_PREFIX}/agents/${WORKER_NAME}/" "${WORKSPACE}/agent-config/" \ + mc mirror "${AGENTTEAMS_STORAGE_PREFIX}/agents/${WORKER_NAME}/" "${WORKSPACE}/agent-config/" \ --overwrite 2>/dev/null || true for _f in SOUL.md AGENTS.md; do [ -f "${WORKSPACE}/agent-config/${_f}" ] && cp -f "${WORKSPACE}/agent-config/${_f}" "${WORKSPACE}/${_f}" @@ -152,10 +153,10 @@ if [ -f "${OPENCLAW_JSON}" ] && command -v jq >/dev/null 2>&1; then _AT=$(jq -r '.channels.matrix.accessToken // empty' "${OPENCLAW_JSON}") _UID=$(jq -r '.channels.matrix.userId // empty' "${OPENCLAW_JSON}") - BRIDGE_HOMESERVER="${_HS:-${MATRIX_HOMESERVER_URL:-}}" - BRIDGE_ACCESS_TOKEN="${_AT:-${MATRIX_ACCESS_TOKEN:-}}" - BRIDGE_USER_ID="${_UID:-${MATRIX_USER_ID:-}}" - BRIDGE_ROOM_ID="${MATRIX_HOME_ROOM_ID:-}" # room_id is not in openclaw.json; always from env + BRIDGE_HOMESERVER="${_HS:-${AGENTTEAMS_MATRIX_URL:-${MATRIX_HOMESERVER_URL:-}}}" + BRIDGE_ACCESS_TOKEN="${_AT:-${AGENTTEAMS_WORKER_MATRIX_TOKEN:-${MATRIX_ACCESS_TOKEN:-}}}" + BRIDGE_USER_ID="${_UID:-${AGENTTEAMS_MATRIX_USER_ID:-${MATRIX_USER_ID:-}}}" + BRIDGE_ROOM_ID="${AGENTTEAMS_WORKER_ROOM_ID:-${MATRIX_HOME_ROOM_ID:-}}" # Allowed users — merge dm.allowFrom + groupAllowFrom (deduplicated) BRIDGE_ALLOWED_USERS=$( @@ -166,34 +167,39 @@ if [ -f "${OPENCLAW_JSON}" ] && command -v jq >/dev/null 2>&1; then ) fi - # --- LLM provider config (HiClaw AI gateway via Higress) --- - # Maps openclaw.json's models.providers["hiclaw-gateway"] + + # --- LLM provider config (AgentTeams AI gateway via Higress) --- + # Maps openclaw.json's models.providers["agentteams-gateway"] + # agents.defaults.model.primary into OpenHuman's [[cloud_providers]] # and [model_routes] sections so that the worker routes LLM traffic # through Higress instead of falling back to api.openhuman.ai. - BRIDGE_LLM_BASE_URL=$(jq -r '.models.providers["hiclaw-gateway"].baseUrl // empty' "${OPENCLAW_JSON}") - BRIDGE_LLM_API_KEY=$(jq -r '.models.providers["hiclaw-gateway"].apiKey // empty' "${OPENCLAW_JSON}") - # primary is "hiclaw-gateway/" — strip the provider prefix. - BRIDGE_LLM_PRIMARY=$(jq -r '.agents.defaults.model.primary // empty | sub("^hiclaw-gateway/"; "")' "${OPENCLAW_JSON}") + BRIDGE_LLM_BASE_URL=$(jq -r '.models.providers["agentteams-gateway"].baseUrl // empty' "${OPENCLAW_JSON}") + BRIDGE_LLM_API_KEY=$(jq -r '.models.providers["agentteams-gateway"].apiKey // empty' "${OPENCLAW_JSON}") + # primary is "agentteams-gateway/" - strip the provider prefix. + BRIDGE_LLM_PRIMARY=$(jq -r '.agents.defaults.model.primary // empty | sub("^agentteams-gateway/"; "")' "${OPENCLAW_JSON}") fi # Apply fallback from env vars when openclaw.json was absent or incomplete. -BRIDGE_HOMESERVER="${BRIDGE_HOMESERVER:-${MATRIX_HOMESERVER_URL:-}}" -BRIDGE_ACCESS_TOKEN="${BRIDGE_ACCESS_TOKEN:-${MATRIX_ACCESS_TOKEN:-}}" -BRIDGE_ROOM_ID="${BRIDGE_ROOM_ID:-${MATRIX_HOME_ROOM_ID:-}}" -BRIDGE_USER_ID="${BRIDGE_USER_ID:-${MATRIX_USER_ID:-}}" - -# LLM fallback: HICLAW_AI_GATEWAY_URL is the base host (no /v1 suffix); -# HICLAW_WORKER_GATEWAY_KEY is the Higress consumer key for this worker. -if [ -z "${BRIDGE_LLM_BASE_URL:-}" ] && [ -n "${HICLAW_AI_GATEWAY_URL:-}" ]; then - BRIDGE_LLM_BASE_URL="${HICLAW_AI_GATEWAY_URL%/}/v1" +BRIDGE_HOMESERVER="${BRIDGE_HOMESERVER:-${AGENTTEAMS_MATRIX_URL:-${MATRIX_HOMESERVER_URL:-}}}" +BRIDGE_ACCESS_TOKEN="${BRIDGE_ACCESS_TOKEN:-${AGENTTEAMS_WORKER_MATRIX_TOKEN:-${MATRIX_ACCESS_TOKEN:-}}}" +BRIDGE_ROOM_ID="${BRIDGE_ROOM_ID:-${AGENTTEAMS_WORKER_ROOM_ID:-${MATRIX_HOME_ROOM_ID:-}}}" +BRIDGE_USER_ID="${BRIDGE_USER_ID:-${AGENTTEAMS_MATRIX_USER_ID:-${MATRIX_USER_ID:-}}}" +if [ -z "${BRIDGE_USER_ID}" ] && [ -n "${AGENTTEAMS_MATRIX_DOMAIN:-}" ]; then + BRIDGE_USER_ID="@${WORKER_NAME}:${AGENTTEAMS_MATRIX_DOMAIN}" fi -BRIDGE_LLM_API_KEY="${BRIDGE_LLM_API_KEY:-${HICLAW_WORKER_GATEWAY_KEY:-}}" -BRIDGE_LLM_PRIMARY="${BRIDGE_LLM_PRIMARY:-${HICLAW_DEFAULT_MODEL:-qwen-plus}}" + +# LLM fallback: AGENTTEAMS_AI_GATEWAY_URL is the base host (no /v1 suffix); +# AGENTTEAMS_WORKER_GATEWAY_KEY is the Higress consumer key for this worker. +if [ -z "${BRIDGE_LLM_BASE_URL:-}" ] && [ -n "${AGENTTEAMS_AI_GATEWAY_URL:-}" ]; then + BRIDGE_LLM_BASE_URL="${AGENTTEAMS_AI_GATEWAY_URL%/}/v1" +fi +BRIDGE_LLM_API_KEY="${BRIDGE_LLM_API_KEY:-${AGENTTEAMS_WORKER_GATEWAY_KEY:-}}" +BRIDGE_LLM_PRIMARY="${BRIDGE_LLM_PRIMARY:-${AGENTTEAMS_DEFAULT_MODEL:-qwen-plus}}" # If bridge didn't yield allowed users, fall back to MATRIX_ALLOWED_USERS env var. if [ -z "${BRIDGE_ALLOWED_USERS:-}" ] && [ -n "${MATRIX_ALLOWED_USERS:-}" ]; then BRIDGE_ALLOWED_USERS=$(echo "${MATRIX_ALLOWED_USERS}" | tr ',' '\n') +elif [ -z "${BRIDGE_ALLOWED_USERS:-}" ] && [ -n "${AGENTTEAMS_MATRIX_DOMAIN:-}" ]; then + BRIDGE_ALLOWED_USERS="@manager:${AGENTTEAMS_MATRIX_DOMAIN}" fi # Convert newline-separated user list to TOML array entries. @@ -223,9 +229,9 @@ EOF log "config.toml generated at ${WORKSPACE}/config.toml" -# --- LLM routing through HiClaw AI gateway (Higress) --- -# Use openhuman-core's CLI to register the HiClaw gateway as an -# OpenAI-compatible inference endpoint. This is REQUIRED for HiClaw-managed +# --- LLM routing through AgentTeams AI gateway (Higress) --- +# Use openhuman-core's CLI to register the AgentTeams gateway as an +# OpenAI-compatible inference endpoint. This is REQUIRED for AgentTeams-managed # workers; if not configured, the entrypoint aborts (fail-closed) to # prevent silent routing of workloads to external services. export OPENHUMAN_CONFIG="${WORKSPACE}/config.toml" @@ -237,7 +243,7 @@ if [ -n "${BRIDGE_LLM_BASE_URL}" ] && [ -n "${BRIDGE_LLM_API_KEY}" ]; then --default_model "${BRIDGE_LLM_PRIMARY}" \ >/dev/null 2>&1 || log "WARNING: openhuman-core config update_model_settings failed" else - log "FATAL: LLM gateway not configured (HICLAW_AI_GATEWAY_URL or HICLAW_WORKER_GATEWAY_KEY missing). HiClaw-managed workers must route through the platform AI gateway; refusing to start to prevent silent fallback to external services." + log "FATAL: LLM gateway not configured (AGENTTEAMS_AI_GATEWAY_URL or AGENTTEAMS_WORKER_GATEWAY_KEY missing). AgentTeams-managed workers must route through the platform AI gateway; refusing to start to prevent silent fallback to external services." exit 1 fi @@ -260,16 +266,16 @@ export OPENHUMAN_CORE_TOKEN="${OPENHUMAN_CORE_TOKEN:-$(openssl rand -hex 32 2>/d if [ -n "${CHANGED}" ]; then ensure_mc_credentials 2>/dev/null || true mc mirror "${WORKSPACE}/memory/" \ - "${HICLAW_STORAGE_PREFIX}/agents/${WORKER_NAME}/memory/" \ + "${AGENTTEAMS_STORAGE_PREFIX}/agents/${WORKER_NAME}/memory/" \ --overwrite 2>/dev/null || true mc mirror "${WORKSPACE}/shared/" \ - "${HICLAW_STORAGE_PREFIX}/shared/" \ + "${AGENTTEAMS_STORAGE_PREFIX}/shared/" \ --overwrite --exclude "spec.md" --exclude "base/" 2>/dev/null || true # Push SOUL.md/AGENTS.md only if agent modified them for _mf in SOUL.md AGENTS.md MEMORY.md; do if [ -f "${WORKSPACE}/${_mf}" ] && [ "${WORKSPACE}/${_mf}" -nt "${PULL_MARKER}" ]; then mc cp "${WORKSPACE}/${_mf}" \ - "${HICLAW_STORAGE_PREFIX}/agents/${WORKER_NAME}/${_mf}" 2>/dev/null || true + "${AGENTTEAMS_STORAGE_PREFIX}/agents/${WORKER_NAME}/${_mf}" 2>/dev/null || true fi done fi @@ -283,10 +289,10 @@ log "Local->Remote sync started (PID: ${SYNC_LOCAL_PID})" while true; do sleep 300 ensure_mc_credentials 2>/dev/null || true - mc mirror "${HICLAW_STORAGE_PREFIX}/agents/${WORKER_NAME}/skills/" \ + mc mirror "${AGENTTEAMS_STORAGE_PREFIX}/agents/${WORKER_NAME}/skills/" \ "${WORKSPACE}/skills/" --overwrite 2>/dev/null || true find "${WORKSPACE}/skills" -name '*.sh' -exec chmod +x {} + 2>/dev/null || true - mc mirror "${HICLAW_STORAGE_PREFIX}/shared/" "${WORKSPACE}/shared/" \ + mc mirror "${AGENTTEAMS_STORAGE_PREFIX}/shared/" "${WORKSPACE}/shared/" \ --overwrite --newer-than "5m" 2>/dev/null || true touch "${PULL_MARKER}" done @@ -337,11 +343,15 @@ CORE_PID=$! log "OpenHuman Core is healthy" # Report ready to controller - if [ -n "${HICLAW_CONTROLLER_URL:-}" ]; then + if [ -n "${AGENTTEAMS_CONTROLLER_URL:-}" ]; then + _AUTH_TOKEN="${AGENTTEAMS_AUTH_TOKEN:-}" + if [ -z "${_AUTH_TOKEN}" ] && [ -n "${AGENTTEAMS_AUTH_TOKEN_FILE:-}" ]; then + _AUTH_TOKEN=$(cat "${AGENTTEAMS_AUTH_TOKEN_FILE}" 2>/dev/null || true) + fi hiclaw worker report-ready --name "${WORKER_CR_NAME}" 2>/dev/null || \ - curl -sf -X POST "${HICLAW_CONTROLLER_URL}/api/v1/workers/${WORKER_CR_NAME}/ready" \ + curl -sf -X POST "${AGENTTEAMS_CONTROLLER_URL}/api/v1/workers/${WORKER_CR_NAME}/ready" \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer $(cat ${HICLAW_AUTH_TOKEN_FILE:-/var/run/secrets/hiclaw/token} 2>/dev/null)" 2>/dev/null || \ + -H "Authorization: Bearer ${_AUTH_TOKEN}" 2>/dev/null || \ log "WARNING: Failed to report ready to controller" fi ) & diff --git a/openhuman/tests/test-entrypoint-agentteams-env.sh b/openhuman/tests/test-entrypoint-agentteams-env.sh new file mode 100755 index 000000000..197e90fb9 --- /dev/null +++ b/openhuman/tests/test-entrypoint-agentteams-env.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ENTRYPOINT="${REPO_ROOT}/openhuman/scripts/openhuman-worker-entrypoint.sh" +TMP_ROOT=$(mktemp -d) +trap 'rm -rf "${TMP_ROOT}"' EXIT + +MOCK_BIN="${TMP_ROOT}/bin" +LOG_DIR="${TMP_ROOT}/logs" +WORKSPACE="${TMP_ROOT}/workspace" +mkdir -p "${MOCK_BIN}" "${LOG_DIR}" "${WORKSPACE}" +printf '# Agent instructions\n' > "${WORKSPACE}/AGENTS.md" +printf '# Worker identity\n' > "${WORKSPACE}/SOUL.md" + +cat > "${MOCK_BIN}/mc" <<'EOF' +#!/bin/sh +printf '%s\n' "$*" >> "${TEST_LOG_DIR}/mc.log" +EOF + +cat > "${MOCK_BIN}/openhuman-core" <<'EOF' +#!/bin/sh +printf '%s\n' "$*" >> "${TEST_LOG_DIR}/openhuman-core.log" +EOF + +for command_name in curl hiclaw sleep; do + cat > "${MOCK_BIN}/${command_name}" <<'EOF' +#!/bin/sh +exit 1 +EOF +done +chmod +x "${MOCK_BIN}"/* + +env -i \ + PATH="${MOCK_BIN}:${PATH}" \ + HOME="${TMP_ROOT}/home" \ + TEST_LOG_DIR="${LOG_DIR}" \ + OPENHUMAN_WORKSPACE="${WORKSPACE}" \ + AGENTTEAMS_WORKER_NAME="worker-a" \ + AGENTTEAMS_WORKER_CR_NAME="worker-cr-a" \ + AGENTTEAMS_RUNTIME="docker" \ + AGENTTEAMS_FS_ENDPOINT="http://minio:9000" \ + AGENTTEAMS_FS_ACCESS_KEY="worker-a" \ + AGENTTEAMS_FS_SECRET_KEY="minio-secret" \ + AGENTTEAMS_FS_BUCKET="agentteams-storage" \ + AGENTTEAMS_STORAGE_ALIAS="agentteams" \ + AGENTTEAMS_STORAGE_PREFIX="agentteams/agentteams-storage" \ + AGENTTEAMS_MATRIX_URL="http://matrix:8080" \ + AGENTTEAMS_MATRIX_DOMAIN="matrix.example" \ + AGENTTEAMS_WORKER_MATRIX_TOKEN="matrix-token" \ + AGENTTEAMS_WORKER_ROOM_ID="!worker-room:matrix.example" \ + AGENTTEAMS_AI_GATEWAY_URL="http://gateway:8080" \ + AGENTTEAMS_WORKER_GATEWAY_KEY="gateway-key" \ + AGENTTEAMS_DEFAULT_MODEL="qwen-test" \ + bash "${ENTRYPOINT}" > "${LOG_DIR}/entrypoint.log" 2>&1 + +grep -Fq 'alias set agentteams http://minio:9000 worker-a minio-secret' "${LOG_DIR}/mc.log" +grep -Fq 'mirror agentteams/agentteams-storage/agents/worker-a/' "${LOG_DIR}/mc.log" +grep -Fq 'config update_model_settings --inference_url http://gateway:8080/v1 --api_key gateway-key --default_model qwen-test' \ + "${LOG_DIR}/openhuman-core.log" +grep -Fq 'homeserver = "http://matrix:8080"' "${WORKSPACE}/config.toml" +grep -Fq 'access_token = "matrix-token"' "${WORKSPACE}/config.toml" +grep -Fq 'room_id = "!worker-room:matrix.example"' "${WORKSPACE}/config.toml" +grep -Fq 'user_id = "@worker-a:matrix.example"' "${WORKSPACE}/config.toml" +grep -Fq 'models.providers["agentteams-gateway"]' "${ENTRYPOINT}" + +legacy_env_prefix="HICLAW""_" +if grep -q "${legacy_env_prefix}" "${ENTRYPOINT}"; then + echo "entrypoint still references retired environment variables" >&2 + exit 1 +fi + +echo "OpenHuman AgentTeams entrypoint contract: PASS"