fix: clarify container-scoped runtime warnings for minimal images (#395)

* fix(ui): clarify container-scoped runtime warnings

* docs(runtime): clarify docker image variant expectations

* test(tools): align media path expectations with workspace policy

* docs(tests): narrow message media path contract wording
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-03-30 21:44:53 +07:00
1 parent 65d0d62acf
commit 4c60dd021e
13 files changed
+103 -53

No files matched your search

+3 -1
View File
@@ -222,7 +222,9 @@ make down WITH_BROWSER=1 WITH_OTEL=1
When `GOCLAW_*_API_KEY` environment variables are set, the gateway auto-onboards without interactive prompts — detects provider, runs migrations, and seeds default data.
> For detailed configuration and Docker image tags, see the [Deployment Guide](https://docs.goclaw.sh/#deploy-docker-compose).
> `ghcr.io/nextlevelbuilder/goclaw:latest` is the minimal runtime image and does **not** include Python or Node.js by default. If you need those runtimes preinstalled, use the `python`, `node`, or `full` image variant, run `./scripts/setup-docker.sh --variant <python|node|full>`, or rebuild locally with `ENABLE_PYTHON=true`, `ENABLE_NODE=true`, or `ENABLE_FULL_SKILLS=true`.
> For build variants (OTel, Tailscale, Redis), Docker image tags, and compose overlays, see the [Deployment Guide](https://docs.goclaw.sh/#deploy-docker-compose).
## Multi-Agent Orchestration
+25 -23
View File
@@ -1,6 +1,6 @@
# 14 - Skills Runtime Environment
How skills access Python, Node.js, and system tools inside the Docker container. Covers pre-installed packages, runtime installation, and security constraints.
How skills access Python, Node.js, and system tools inside the Docker container. Covers image variants, pre-installed packages, runtime installation, and security constraints.
---
@@ -14,10 +14,10 @@ How skills access Python, Node.js, and system tools inside the Docker container.
│ │ Pre-installed │ │ Writable Runtime Dir │ │
│ │ (image layer) │ │ /app/data/.runtime/ │ │
│ │ │ │ │ │
│ │ python3, node │ │ pip/ ← PIP_TARGET │ │
│ │ gh, pandoc │ │ pip-cache/ ← PIP_CACHE │ │
│ │ pypdf, openpyxl │ │ npm-global/ ← NPM_PREFIX │ │
│ │ pandas, etc. │ │ │ │
│ │ latest/alpine │ │ pip/ ← PIP_TARGET │ │
│ │ no py/node │ │ pip-cache/ ← PIP_CACHE │ │
│ │ python/node/full│ │ npm-global/ ← NPM_PREFIX │ │
│ │ add runtimes │ │ │ │
│ └─────────────────┘ └──────────────────────────────┘ │
│ │
│ Volumes (read-write): │
@@ -33,9 +33,20 @@ How skills access Python, Node.js, and system tools inside the Docker container.
## 2. Pre-installed Packages (Option A)
Installed at build time in the Dockerfile when `ENABLE_PYTHON=true`.
Pre-installed runtimes depend on the Docker image variant you deploy. The Packages page and `/v1/packages/runtimes` report what exists inside the active GoClaw container, not what exists on the host machine.
### Python Packages
### Runtime Variant Matrix
| Variant | Published tag | Build args | Pre-installed runtimes |
|---------|---------------|------------|------------------------|
| Minimal | `latest` | `ENABLE_PYTHON=false`, `ENABLE_NODE=false`, `ENABLE_FULL_SKILLS=false` | No Python or Node.js runtimes |
| Python | `python` | `ENABLE_PYTHON=true` | `python3`, `py3-pip`, `edge-tts` |
| Node | `node` | `ENABLE_NODE=true` | `nodejs`, `npm` |
| Full | `full` | `ENABLE_FULL_SKILLS=true` | `python3`, `py3-pip`, `nodejs`, `npm`, `pandoc`, `github-cli`, bundled skill deps |
### Full Variant Extras
#### Python Packages
| Package | Version | Used By |
|---------|---------|---------|
@@ -45,22 +56,13 @@ Installed at build time in the Dockerfile when `ENABLE_PYTHON=true`.
| `python-pptx` | latest | pptx skill |
| `markitdown` | latest | pptx skill (content extraction) |
### Node.js Packages (global)
#### Node.js Packages (global)
| Package | Used By |
|---------|---------|
| `docx` | docx skill (document creation) |
| `pptxgenjs` | pptx skill (presentation creation) |
### System Tools
| Tool | Purpose |
|------|---------|
| `python3` + `py3-pip` | Python runtime + package manager |
| `nodejs` + `npm` | Node.js runtime + package manager |
| `pandoc` | Document format conversion |
| `github-cli` (`gh`) | GitHub API operations |
---
## 3. Runtime Package Installation (Option B)
@@ -90,11 +92,11 @@ PATH=/app/data/.runtime/npm-global/bin:/app/data/.runtime/pip/bin:$PATH
### Agent Guidance
The system prompt includes this section so agents know what's available:
The system prompt and UI should treat runtime availability as variant-dependent:
```
Pre-installed: python3, node, gh, pypdf, openpyxl, pandas, python-pptx,
markitdown, docx (npm), pptxgenjs (npm), pandoc.
Minimal `latest`: Python/Node may be missing in the container.
`python`, `node`, and `full` variants pre-install different runtimes.
To install additional packages: pip3 install <pkg> or npm install -g <pkg>
```
@@ -179,10 +181,10 @@ When a user uploads a skill with the same name via the UI, the managed version t
To add a new package to the Docker image:
1. **Python**: Add to the `pip3 install` line in `Dockerfile`
2. **Node.js**: Add to the `npm install -g` line in `Dockerfile`
1. **Python**: Add to the `pip3 install` line in `Dockerfile` (usually `full`, sometimes `python`)
2. **Node.js**: Add to the `npm install -g` line in `Dockerfile` (usually `full`, sometimes `node`)
3. **System tool**: Add to the `apk add` line in `Dockerfile`
4. **System prompt**: Update the pre-installed list in `systemprompt.go` (`buildToolSection`)
4. **Docs/UI guidance**: Update runtime variant docs and any UI copy that describes pre-installed tools
5. **Rebuild**: `docker compose ... up -d --build`
For packages only needed by specific skills, prefer runtime installation (Option B) to keep the image lean.
+12 -4
View File
@@ -506,7 +506,7 @@ CLI authentication credentials for secure command execution. Requires **admin ro
## 17. Runtime & Packages Management
Manage system (apk), Python (pip), and Node (npm) package installation in the runtime container. Requires authentication. When `GOCLAW_GATEWAY_TOKEN` is empty (dev/single-user mode), all users get admin role and can manage packages.
Manage system (apk), Python (pip), and Node (npm) package installation in the GoClaw runtime container. These endpoints do not inspect host-level runtimes. Requires authentication. When `GOCLAW_GATEWAY_TOKEN` is empty (dev/single-user mode), all users get admin role and can manage packages.
### List Installed Packages
@@ -591,17 +591,25 @@ Same format as install. System packages are removed from persist file and contai
GET /v1/packages/runtimes
```
Check if Python and Node runtimes are available in the container.
Check which prerequisite runtimes are available inside the active GoClaw runtime container. Host-installed runtimes and shell-profile-managed binaries (for example `nvm`) are not included in this result.
**Response:**
```json
{
"python": true,
"node": true
"runtimes": [
{"name": "python3", "available": false},
{"name": "pip3", "available": false},
{"name": "node", "available": false},
{"name": "npm", "available": false},
{"name": "pkg-helper", "available": true, "version": "socket"}
],
"ready": false
}
```
The published `ghcr.io/nextlevelbuilder/goclaw:latest` image is the minimal variant, so missing Python or Node runtimes can be expected there.
---
## 18. Traces & Costs
+6 -3
View File
@@ -338,9 +338,12 @@ func isGroupContext(ctx context.Context) bool {
}
// resolveMediaPath extracts and validates a file path from a "MEDIA:path" string.
// Uses the same workspace-aware path resolution as other filesystem tools:
// - When restrict_to_workspace is true: allows workspace dir + /tmp/
// - When restrict_to_workspace is false: allows any valid path
// Uses the same workspace-aware path resolution as other filesystem tools.
// Multi-tenant isolation forces MEDIA: paths through restricted resolution
// first, with one explicit fallback for generated media artifacts under /tmp/.
// In practice MEDIA: paths may resolve to:
// - files inside the agent workspace
// - absolute paths under /tmp/ for generated media artifacts
//
// Relative paths are resolved against the agent's workspace.
func (t *MessageTool) resolveMediaPath(ctx context.Context, s string) (string, bool) {
+4 -4
View File
@@ -52,10 +52,10 @@ done
if [ -z "$VARIANT" ]; then
echo "Select GoClaw Docker variant:"
echo ""
echo " 1) alpine — Base image (~50 MB), no runtimes"
echo " 2) node — + Node.js (for JS/TS skills)"
echo " 3) python — + Python (for Python skills)"
echo " 4) full — + Node.js + Python + all skill deps"
echo " 1) alpine — Base image (~50 MB), no runtimes (published as :latest)"
echo " 2) node — + Node.js (published as :node)"
echo " 3) python — + Python (published as :python)"
echo " 4) full — + Node.js + Python + all skill deps (published as :full)"
echo ""
read -rp "Choice [1-4, default=1]: " choice
case "${choice:-1}" in
+6 -2
View File
@@ -1,10 +1,14 @@
{
"title": "Runtime & Packages",
"description": "Manage system runtimes and installed packages",
"description": "Manage runtimes and installed packages inside the active GoClaw runtime container",
"runtimes": {
"title": "Runtimes",
"scopeTitle": "Container-scoped runtime status",
"scopeDesc": "These checks run inside the active GoClaw runtime container. Host-installed runtimes, shell profile changes, and nvm-managed binaries are not used here.",
"minimalImageHint": "The published latest image is minimal, so missing Python or Node runtimes can be expected there. Use the python, node, or full image variant, rebuild with ENABLE_PYTHON=true / ENABLE_NODE=true / ENABLE_FULL_SKILLS=true, or install runtimes manually in this container.",
"available": "Available",
"missing": "Missing"
"missing": "Missing",
"missingInContainer": "Missing in container"
},
"system": {
"title": "System Packages",
+4 -3
View File
@@ -99,8 +99,9 @@
"statusActive": "Active",
"statusArchived": "Missing deps",
"runtimeMissing": "Runtime Prerequisites Missing",
"runtimeMissingDesc": "Core skills require Python and/or Node.js runtimes. Rebuild with ENABLE_PYTHON=true, ENABLE_NODE=true, or ENABLE_FULL_SKILLS=true, or install manually.",
"runtimeRequired": "Install runtimes first"
"runtimeMissingDesc": "Core skills require Python and/or Node.js runtimes inside the GoClaw runtime container. Host-installed runtimes are not used here. If you are using the published latest image, missing runtimes can be expected there.",
"runtimeMissingAction": "Open Packages to verify or install runtimes in this container.",
"runtimeRequired": "Install container runtimes first"
},
"detail": {
"title": "Skill Detail",
@@ -119,4 +120,4 @@
"rescanNoChanges": "All skills up to date",
"rescanFailed": "Failed to rescan dependencies"
}
}
}
+6 -2
View File
@@ -1,10 +1,14 @@
{
"title": "Runtime & Gói phần mềm",
"description": "Quản lý runtime hệ thống và các gói đã cài đặt",
"description": "Quản lý runtime và các gói đã cài đặt bên trong container GoClaw đang hoạt động",
"runtimes": {
"title": "Runtimes",
"scopeTitle": "Trạng thái runtime theo container",
"scopeDesc": "Các kiểm tra này chạy bên trong container runtime GoClaw hiện tại. Runtime cài trên máy host, thay đổi shell profile và binary do nvm quản lý sẽ không được dùng ở đây.",
"minimalImageHint": "Image latest được phát hành là bản tối giản, nên việc thiếu Python hoặc Node ở đó có thể là bình thường. Hãy dùng biến thể image python, node hoặc full, build lại với ENABLE_PYTHON=true / ENABLE_NODE=true / ENABLE_FULL_SKILLS=true, hoặc cài runtime trực tiếp trong container này.",
"available": "Sẵn sàng",
"missing": "Chưa cài"
"missing": "Chưa cài",
"missingInContainer": "Thiếu trong container"
},
"system": {
"title": "Gói hệ thống",
+4 -3
View File
@@ -99,8 +99,9 @@
"statusActive": "Active",
"statusArchived": "Missing deps",
"runtimeMissing": "Runtime Prerequisites Missing",
"runtimeMissingDesc": "Core skills require Python and/or Node.js runtimes. Rebuild with ENABLE_PYTHON=true, ENABLE_NODE=true, or ENABLE_FULL_SKILLS=true, or install manually.",
"runtimeRequired": "Install runtimes first"
"runtimeMissingDesc": "Các core skill cần runtime Python và/hoặc Node.js bên trong container runtime GoClaw. Runtime cài trên máy host sẽ không được dùng ở đây. Nếu bạn đang dùng image latest đã phát hành thì việc thiếu runtime có thể là bình thường.",
"runtimeMissingAction": "Mở trang Packages để kiểm tra hoặc cài runtime trong container này.",
"runtimeRequired": "Hãy cài runtime trong container trước"
},
"detail": {
"title": "Chi tiết skill",
@@ -119,4 +120,4 @@
"rescanNoChanges": "Tất cả skill đã cập nhật",
"rescanFailed": "Không thể quét lại dependencies"
}
}
}
+6 -2
View File
@@ -1,10 +1,14 @@
{
"title": "运行时与软件包",
"description": "管理系统运行时和已安装的软件包",
"description": "管理当前 GoClaw 运行时容器内的运行时和已安装软件包",
"runtimes": {
"title": "运行时",
"scopeTitle": "容器范围内的运行时状态",
"scopeDesc": "这些检查在当前活动的 GoClaw 运行时容器内执行。宿主机已安装的运行时、shell profile 变更以及由 nvm 管理的二进制文件都不会在这里被使用。",
"minimalImageHint": "已发布的 latest 镜像是最小化变体,因此缺少 Python 或 Node 运行时在该镜像中可能是正常现象。请改用 python、node 或 full 镜像变体,或使用 ENABLE_PYTHON=true / ENABLE_NODE=true / ENABLE_FULL_SKILLS=true 重新构建,或者直接在此容器中手动安装运行时。",
"available": "可用",
"missing": "缺失"
"missing": "缺失",
"missingInContainer": "容器内缺失"
},
"system": {
"title": "系统软件包",
+4 -3
View File
@@ -99,8 +99,9 @@
"statusActive": "Active",
"statusArchived": "Missing deps",
"runtimeMissing": "Runtime Prerequisites Missing",
"runtimeMissingDesc": "Core skills require Python and/or Node.js runtimes. Rebuild with ENABLE_PYTHON=true, ENABLE_NODE=true, or ENABLE_FULL_SKILLS=true, or install manually.",
"runtimeRequired": "Install runtimes first"
"runtimeMissingDesc": "核心技能需要在 GoClaw 运行时容器内提供 Python 和/或 Node.js 运行时。宿主机上的运行时不会在这里被使用。如果你使用的是已发布的 latest 镜像,那么缺少这些运行时可能是正常现象。",
"runtimeMissingAction": "打开 Packages 页面,在此容器中检查或安装运行时。",
"runtimeRequired": "请先安装容器内运行时"
},
"detail": {
"title": "Skill详情",
@@ -119,4 +120,4 @@
"rescanNoChanges": "所有技能已是最新",
"rescanFailed": "重新扫描依赖失败"
}
}
}
+15 -3
View File
@@ -1,8 +1,9 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { RefreshCw, Loader2, Trash2, Download, CheckCircle2, XCircle } from "lucide-react";
import { RefreshCw, Loader2, Trash2, Download, CheckCircle2, XCircle, AlertTriangle } from "lucide-react";
import { PageHeader } from "@/components/shared/page-header";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { usePackages, type PackageInfo } from "./hooks/use-packages";
import { usePackageRuntimes } from "./hooks/use-package-runtimes";
@@ -13,6 +14,7 @@ export function PackagesPage() {
const { t } = useTranslation("packages");
const { packages, loading, refresh, installPackage, uninstallPackage } = usePackages();
const { runtimes, loading: runtimesLoading, refresh: refreshRuntimes } = usePackageRuntimes();
const hasMissingRuntimes = (runtimes?.runtimes?.some((rt) => !rt.available)) ?? false;
return (
<div className="p-4 sm:p-6 space-y-6">
@@ -33,8 +35,18 @@ export function PackagesPage() {
/>
{/* Runtimes Section */}
<section>
<section className="space-y-3">
<h2 className="text-lg font-medium mb-3">{t("runtimes.title")}</h2>
<Alert className="border-sky-200/70 bg-sky-50/70 text-sky-950 dark:border-sky-900/50 dark:bg-sky-950/20 dark:text-sky-100">
<AlertTriangle className="h-4 w-4 text-sky-600 dark:text-sky-300" />
<AlertTitle className="text-sky-900 dark:text-sky-100">
{t("runtimes.scopeTitle")}
</AlertTitle>
<AlertDescription className="text-xs text-sky-800 dark:text-sky-200">
<p>{t("runtimes.scopeDesc")}</p>
{hasMissingRuntimes && <p>{t("runtimes.minimalImageHint")}</p>}
</AlertDescription>
</Alert>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
{runtimes?.runtimes?.map((rt) => (
<div
@@ -57,7 +69,7 @@ export function PackagesPage() {
<p className="text-xs text-muted-foreground mt-1 font-mono truncate">{rt.version}</p>
)}
{!rt.available && (
<p className="text-xs text-red-600 dark:text-red-400 mt-1">{t("runtimes.missing")}</p>
<p className="text-xs text-red-600 dark:text-red-400 mt-1">{t("runtimes.missingInContainer")}</p>
)}
</div>
))}
@@ -1,7 +1,9 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import { Button } from "@/components/ui/button";
import { Download, Loader2, AlertTriangle, CheckCircle2, XCircle } from "lucide-react";
import { ROUTES } from "@/lib/constants";
import type { RuntimeStatus } from "./hooks/use-runtimes";
interface MissingDepsPanelProps {
@@ -81,6 +83,12 @@ export function MissingDepsPanel({ missing, onInstallItem, runtimes }: MissingDe
<p className="text-xs text-red-700 dark:text-red-300">
{t("deps.runtimeMissingDesc")}
</p>
<Link
to={ROUTES.PACKAGES}
className="inline-flex text-xs font-medium text-red-700 underline underline-offset-4 hover:text-red-900 dark:text-red-300 dark:hover:text-red-100"
>
{t("deps.runtimeMissingAction")}
</Link>
<div className="flex flex-wrap gap-1.5 mt-2">
{missingRuntimes.map((r) => (
<span