Skip to content
Open
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
1 change: 1 addition & 0 deletions changelog/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 12 additions & 1 deletion copaw/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions copaw/scripts/copaw-worker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
13 changes: 13 additions & 0 deletions copaw/src/copaw_worker/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion copaw/src/copaw_worker/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 28 additions & 9 deletions copaw/src/copaw_worker/matrix_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"
Expand Down
19 changes: 11 additions & 8 deletions copaw/src/copaw_worker/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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",
Expand All @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions manager/Dockerfile.copaw
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
9 changes: 7 additions & 2 deletions manager/scripts/init/start-copaw-manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion tests/lib/test-helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/test-01-manager-boot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading