diff --git a/changelog/current.md b/changelog/current.md index f3ae6b4eb..658a2c894 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -5,6 +5,7 @@ Record image-affecting changes to `manager/`, `worker/`, `copaw/`, `openclaw-bas --- - feat(qwenpaw): add the QwenPaw worker runtime Python package baseline with runtime config sync, storage sync, heartbeat reporting, Matrix channel overlay, and focused unit tests. +- feat(copaw): migrate Worker and Manager runtime from copaw 1.0.2 to qwenpaw (pip install qwenpaw, agents.profiles injection, QWENPAW_WORKING_DIR alignment, CI pgrep dual-compatible with `copaw app || qwenpaw app`) - fix(controller): surface Kubernetes Pod container failures in Worker backend status and status API responses. - feat(controller): expose low-cardinality AgentTeams controller metrics and optional Helm ServiceMonitor. - feat(controller): add Matrix AppService Human SSO identity provisioning with hash-derived Matrix IDs, AppService login, deletion deactivation, and Team admin/member identity resolution from Human status. diff --git a/copaw/Dockerfile b/copaw/Dockerfile index baeb45fa5..3827cd7f7 100644 --- a/copaw/Dockerfile +++ b/copaw/Dockerfile @@ -90,6 +90,13 @@ RUN python -m venv /opt/venv/standard \ --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ /tmp/copaw-worker/ +# --- Install QwenPaw (migrate from copaw to qwenpaw) --- +RUN /opt/venv/standard/bin/pip install --no-cache-dir \ + --index-url "${PIP_INDEX_URL}" \ + --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ + qwenpaw \ + && ln -sf /opt/venv/standard/bin/qwenpaw /usr/local/bin/qwenpaw + # --- Patch CoPaw 1.0.2 Matrix channel indentation bug --- # _sync_loop method def is indented one level too deep inside # _refresh_matrix_token, causing IndentationError that prevents @@ -110,7 +117,11 @@ RUN python -m venv /opt/venv/lite \ && /opt/venv/lite/bin/pip install --no-cache-dir \ --index-url "${PIP_INDEX_URL}" \ --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ - /tmp/copaw-worker/ + /tmp/copaw-worker/ \ + && /opt/venv/lite/bin/pip install --no-cache-dir \ + --index-url "${PIP_INDEX_URL}" \ + --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ + qwenpaw # --------------------------------------------------------------------------- # Patch installed packages in the lite venv to defer heavyweight modules that diff --git a/copaw/scripts/copaw-worker-entrypoint.sh b/copaw/scripts/copaw-worker-entrypoint.sh index cdf333bed..fff600ff0 100755 --- a/copaw/scripts/copaw-worker-entrypoint.sh +++ b/copaw/scripts/copaw-worker-entrypoint.sh @@ -25,6 +25,10 @@ WORKER_NAME="${AGENTTEAMS_WORKER_NAME:-${HICLAW_WORKER_NAME:-}}" INSTALL_DIR="/root/.copaw-worker" CONSOLE_PORT="${AGENTTEAMS_CONSOLE_PORT:-${HICLAW_CONSOLE_PORT:-}}" +# QwenPaw uses QWENPAW_WORKING_DIR to locate workspace config files. +# Align it with bridge.py output path (config.json written to .copaw/). +export QWENPAW_WORKING_DIR="${INSTALL_DIR}/${WORKER_NAME}/.copaw" + log() { echo "[hiclaw-copaw-worker $(date '+%Y-%m-%d %H:%M:%S')] $1" } diff --git a/copaw/src/copaw_worker/bridge.py b/copaw/src/copaw_worker/bridge.py index fef88b17b..26cf00446 100644 --- a/copaw/src/copaw_worker/bridge.py +++ b/copaw/src/copaw_worker/bridge.py @@ -261,6 +261,19 @@ def _write_config_json( "max_input_length" ] = context_window + # Ensure agents.profiles section exists (required by qwenpaw for + # channel routing — maps agent ID to workspace directory). + # Use nested merge so partially-populated agents/profiles blocks + # are completed rather than skipped. + agents = existing.setdefault("agents", {}) + agents.setdefault("active_agent", "default") + profiles = agents.setdefault("profiles", {}) + default_profile = profiles.setdefault("default", {}) + default_profile.setdefault("id", "default") + default_profile.setdefault( + "workspace_dir", str(working_dir / "workspaces" / "default") + ) + with open(config_path, "w") as f: json.dump(existing, f, indent=2, ensure_ascii=False) diff --git a/copaw/src/copaw_worker/health.py b/copaw/src/copaw_worker/health.py index 3b296c59b..30b1f626a 100644 --- a/copaw/src/copaw_worker/health.py +++ b/copaw/src/copaw_worker/health.py @@ -12,7 +12,7 @@ - Component health detection strategy: * copaw: Startup health: - - check: start uvicorn.Server for "copaw.app._app:app". + - check: start uvicorn.Server for "qwenpaw.app._app:app". - check: after starting the server, the worker performs one bounded startup probe against the native CoPaw health endpoint. - healthy: the startup probe gets HTTP 200 from diff --git a/copaw/src/copaw_worker/matrix_channel.py b/copaw/src/copaw_worker/matrix_channel.py index fd4ac9262..cd9206751 100644 --- a/copaw/src/copaw_worker/matrix_channel.py +++ b/copaw/src/copaw_worker/matrix_channel.py @@ -44,12 +44,36 @@ TOKEN_REFRESH_BACKOFF_S = 5 # --------------------------------------------------------------------------- -# Lazy import of CoPaw base types so this file can be syntax-checked without -# copaw installed (it's only executed inside a copaw environment). +# Lazy import of runtime base types. Try qwenpaw first (migrated runtime), +# then copaw (legacy). This file is used as a custom channel in both runtimes. # --------------------------------------------------------------------------- try: - from copaw.app.channels.base import BaseChannel - from copaw.app.channels.schema import ChannelType + from qwenpaw.app.channels.base import BaseChannel + from qwenpaw.app.channels.schema import ChannelType + _QWENPAW_AVAILABLE = True + _COPAW_AVAILABLE = True +except ImportError: + _QWENPAW_AVAILABLE = False + try: + from copaw.app.channels.base import BaseChannel + from copaw.app.channels.schema import ChannelType + _COPAW_AVAILABLE = True + except ImportError: # pragma: no cover + _COPAW_AVAILABLE = False + BaseChannel = object # type: ignore[assignment,misc] + ChannelType = str # type: ignore[assignment] + +# Content types — try qwenpaw first, then agentscope_runtime (copaw fallback) +try: + from qwenpaw.app.channels.schema import ( + AudioContent, + ContentType, + FileContent, + ImageContent, + TextContent, + VideoContent, + ) +except ImportError: from agentscope_runtime.engine.schemas.agent_schemas import ( AudioContent, ContentType, @@ -58,11 +82,6 @@ TextContent, VideoContent, ) - _COPAW_AVAILABLE = True -except ImportError: # pragma: no cover - _COPAW_AVAILABLE = False - BaseChannel = object # type: ignore[assignment,misc] - ChannelType = str # type: ignore[assignment] CHANNEL_KEY = "matrix" diff --git a/copaw/src/copaw_worker/worker.py b/copaw/src/copaw_worker/worker.py index b1600307b..3866eedc9 100644 --- a/copaw/src/copaw_worker/worker.py +++ b/copaw/src/copaw_worker/worker.py @@ -195,7 +195,9 @@ async def _run_copaw(self) -> None: async def _run_copaw_with_console(self, port: int) -> None: """Run CoPaw's full FastAPI app (runner + channels + web console).""" import uvicorn - from copaw.app.channels.registry import clear_builtin_channel_cache + # Use qwenpaw runtime (copaw→qwenpaw migration). + # qwenpaw.app._app:app is the FastAPI ASGI application. + from qwenpaw.app.channels.registry import clear_builtin_channel_cache clear_builtin_channel_cache() @@ -257,7 +259,7 @@ async def _readiness(): await api_server.start() uv_config = uvicorn.Config( - "copaw.app._app:app", + "qwenpaw.app._app:app", host="0.0.0.0", port=port, log_level="info", @@ -275,12 +277,13 @@ async def _readiness(): await api_server.stop() async def _run_copaw_headless(self) -> None: - """Start CoPaw's AgentRunner + ChannelManager (no HTTP server).""" - from copaw.app.runner.runner import AgentRunner - from copaw.config.utils import load_config - from copaw.app.channels.manager import ChannelManager - from copaw.app.channels.utils import make_process_from_runner - from copaw.app.channels.registry import clear_builtin_channel_cache + """Start QwenPaw's AgentRunner + ChannelManager (no HTTP server).""" + # Use qwenpaw runtime (copaw→qwenpaw migration). + from qwenpaw.app.runner.runner import AgentRunner + from qwenpaw.config.utils import load_config + from qwenpaw.app.channels.manager import ChannelManager + from qwenpaw.app.channels.utils import make_process_from_runner + from qwenpaw.app.channels.registry import clear_builtin_channel_cache # Force registry reload so newly installed matrix_channel.py is picked up clear_builtin_channel_cache() diff --git a/manager/Dockerfile.copaw b/manager/Dockerfile.copaw index 546e763ff..1514e0118 100644 --- a/manager/Dockerfile.copaw +++ b/manager/Dockerfile.copaw @@ -115,6 +115,16 @@ RUN SITE=/opt/copaw-venv/lib/python3.11/site-packages \ && cp -a /tmp/copaw-worker/src/copaw_worker/* "$SITE/copaw_worker/" \ && rm -rf /tmp/copaw-worker/ +# ---- Install QwenPaw (migrate Manager from copaw to qwenpaw) ---- +# Must be installed AFTER copaw-worker source overlay to prevent +# copaw-worker's copaw>=1.0.2 dependency from downgrading agentscope. +# Installed last → qwenpaw's agentscope==1.0.20 wins. +RUN /opt/copaw-venv/bin/pip install --no-cache-dir \ + -i "${PIP_INDEX_URL}" \ + --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ + qwenpaw \ + && ln -sf /opt/copaw-venv/bin/qwenpaw /usr/local/bin/qwenpaw + # ---- Copy Manager agent definitions ---- COPY manager/agent/ /opt/hiclaw/agent/ COPY manager/configs/ /opt/hiclaw/configs/ diff --git a/manager/scripts/init/start-copaw-manager.sh b/manager/scripts/init/start-copaw-manager.sh index 40569b6ce..254540ff8 100644 --- a/manager/scripts/init/start-copaw-manager.sh +++ b/manager/scripts/init/start-copaw-manager.sh @@ -13,6 +13,9 @@ source /opt/hiclaw/scripts/lib/hiclaw-env.sh # ============================================================ OPENCLAW_WORKSPACE="${HOME}" COPAW_WORKING_DIR="${HOME}/.copaw" +# QwenPaw reads config from QWENPAW_WORKING_DIR. +# Align with bridge.py output path (same as COPAW_WORKING_DIR). +export QWENPAW_WORKING_DIR="${COPAW_WORKING_DIR}" # ============================================================ # 1. Create CoPaw directory structure @@ -239,5 +242,7 @@ export COPAW_LOG_LEVEL # Set PYTHONPATH to include copaw_worker module export PYTHONPATH="/opt/hiclaw/copaw/src:${PYTHONPATH:-}" -# Use uvicorn to run CoPaw FastAPI app (enables AgentConfigWatcher for hot-reload) -exec python3 -m copaw app --host 0.0.0.0 --port 18799 +# Launch QwenPaw Manager (copaw→qwenpaw migration). +# QwenPaw reads config from QWENPAW_WORKING_DIR (set above, same as .copaw/). +# The qwenpaw CLI is installed by pip (provides the same CLI as copaw). +exec python3 -m qwenpaw app --host 0.0.0.0 --port 18799 diff --git a/tests/lib/test-helpers.sh b/tests/lib/test-helpers.sh index b8c6a4192..5ad2de3a1 100755 --- a/tests/lib/test-helpers.sh +++ b/tests/lib/test-helpers.sh @@ -203,7 +203,8 @@ wait_for_manager_agent_ready() { while [ "${elapsed}" -lt "${timeout}" ]; do case "${manager_runtime}" in copaw) - if docker exec "${agent_container}" pgrep -f "copaw app" >/dev/null 2>&1 && \ + # Accept either "copaw app" (legacy) or "qwenpaw app" (migrated runtime). + if docker exec "${agent_container}" sh -c 'pgrep -f "copaw app" || pgrep -f "qwenpaw app"' >/dev/null 2>&1 && \ docker exec "${agent_container}" curl -sf http://127.0.0.1:18799/ >/dev/null 2>&1; then runtime_ready=true break diff --git a/tests/test-01-manager-boot.sh b/tests/test-01-manager-boot.sh index 91fbb402a..63295b80e 100755 --- a/tests/test-01-manager-boot.sh +++ b/tests/test-01-manager-boot.sh @@ -118,7 +118,7 @@ case "${MANAGER_RUNTIME}" in log_fail "CoPaw agent.json valid" fi - if docker exec "${_AGENT_CTR}" pgrep -f "copaw app" >/dev/null 2>&1; then + if docker exec "${_AGENT_CTR}" sh -c 'pgrep -f "copaw app" || pgrep -f "qwenpaw app"' >/dev/null 2>&1; then log_pass "CoPaw process running" else log_fail "CoPaw process running"