diff --git a/changelog/current.md b/changelog/current.md index d0e728ad4..4a0ebf241 100644 --- a/changelog/current.md +++ b/changelog/current.md @@ -23,6 +23,7 @@ Record image-affecting changes to `manager/`, `worker/`, `copaw/`, `hermes/`, `o **Branding and Compatibility** - **QwenPaw 2.0 runtime compatibility**: Adapt the QwenPaw Worker image, Matrix overlay, and native plugins to QwenPaw 2.0.0.post3 startup and schema contracts. +- **QwenPaw 2.0 Manager runtime**: Migrate the Manager container from copaw 1.0.2 to QwenPaw 2.0.1 with a separate venv to avoid agentscope version conflicts, register projectflow/taskflow/message/filesync tools through a QwenPaw plugin instead of monkey-patching CoPawAgent, replace the physical Matrix channel overlay with the QwenPaw plugin system, read Matrix credentials directly from agent.json so the manager tools work without importing copaw at runtime, align CMS observability packages and env vars with the Worker image, inject session-file privacy policy into prompt files, set approval_level=AUTO in the agent template, bridge YOLO mode to QwenPaw approval_level=OFF, disable the built-in QA Agent, rename start-copaw-manager.sh to start-qwenpaw-manager.sh with dual copaw|qwenpaw runtime value support, and remove dead PYTHONPATH entries. - **Complete AgentTeams runtime rename**: Rename installer and Helm entrypoints, the controller Go module and CLI, and container filesystem paths to AgentTeams while preserving thin compatibility aliases and upgrade migration for existing HiClaw installations. ([3121f5f](https://github.com/agentscope-ai/AgentTeams/commit/3121f5f)) - **Hard-cut AgentTeams naming**: Remove retired-brand installer wrappers, environment fallbacks, CLI aliases, Helm naming branches, runtime path migrations, and active source paths so fresh AgentTeams deployments use one canonical contract end to end. ([d20e606](https://github.com/agentscope-ai/AgentTeams/commit/d20e606617edefbbc42c28c1201c5629fa73fd88)) - **Hard-cut Team and Worker resources**: Make Worker CRs the sole owners of runtime configuration and lifecycle, make Team CRs reference existing Workers through `spec.workerMembers`, and remove inline-member, registry, migration, and dependent-script compatibility paths. ([b3cf360](https://github.com/agentscope-ai/AgentTeams/commit/b3cf360)) diff --git a/copaw/src/copaw_worker/hooks/tools/message.py b/copaw/src/copaw_worker/hooks/tools/message.py index 0af90a3d0..ddb74238c 100644 --- a/copaw/src/copaw_worker/hooks/tools/message.py +++ b/copaw/src/copaw_worker/hooks/tools/message.py @@ -230,11 +230,19 @@ def _sanitize_session_filename(name: str) -> str: def _resolve_copaw_working_dir() -> Path: - configured = os.environ.get("COPAW_WORKING_DIR") + configured = ( + os.environ.get("COPAW_WORKING_DIR") + or os.environ.get("QWENPAW_WORKING_DIR") + ) if configured: return Path(configured).expanduser().resolve() - from copaw.constant import WORKING_DIR + # copaw is the legacy name for qwenpaw; the package was renamed. + # In the qwenpaw 2.0 venv only the new name exists. + try: + from qwenpaw.constant import WORKING_DIR + except ImportError: + from copaw.constant import WORKING_DIR return Path(WORKING_DIR).expanduser().resolve() @@ -339,11 +347,24 @@ async def _record_matrix_outbound_to_session( def _matrix_config_for_agent(account_id: str) -> tuple[str, str, str]: - from copaw.config.config import load_agent_config + # Read agent.json directly to avoid a hard dependency on + # copaw.config.config, which is unavailable in the QwenPaw 2.0 venv. + # bridge.py writes homeserver/access_token/user_id into + # workspaces//agent.json regardless of runtime. + working_dir = _resolve_copaw_working_dir() + agent_json = ( + working_dir / "workspaces" / (account_id or "default") / "agent.json" + ) + try: + with open(agent_json, "r", encoding="utf-8") as f: + data = json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as exc: + raise MessageToolError( + f"cannot read agent config at {agent_json}: {exc}", + ) - agent_config = load_agent_config(account_id or "default") - channels = _read_config_value(agent_config, "channels") or {} - matrix_cfg = _read_config_value(channels, "matrix") or {} + channels = data.get("channels") or {} + matrix_cfg = channels.get("matrix") or {} homeserver = _read_config_value(matrix_cfg, "homeserver") or "" access_token = _read_config_value(matrix_cfg, "access_token", "accessToken") or "" diff --git a/copaw/src/copaw_worker/hooks/tools/taskflow.py b/copaw/src/copaw_worker/hooks/tools/taskflow.py index f479ce0a7..ee2950b06 100644 --- a/copaw/src/copaw_worker/hooks/tools/taskflow.py +++ b/copaw/src/copaw_worker/hooks/tools/taskflow.py @@ -151,15 +151,46 @@ def _current_actor() -> str | None: return configured.strip() try: - from copaw.config.config import load_agent_config - - agent_config = load_agent_config("default") - channels = _read_config_value(agent_config, "channels") or {} - matrix_cfg = _read_config_value(channels, "matrix") or {} - user_id = _read_config_value(matrix_cfg, "user_id", "userId") - return str(user_id).strip() if user_id else None + # Read agent.json directly (works in both copaw and qwenpaw venvs + # without importing either framework's config module). + working_dir = _resolve_working_dir() + if working_dir: + agent_json = working_dir / "workspaces" / "default" / "agent.json" + with open(agent_json, "r", encoding="utf-8") as f: + data = json.load(f) + matrix_cfg = (data.get("channels") or {}).get("matrix") or {} + user_id = matrix_cfg.get("user_id") or matrix_cfg.get("userId") + if user_id: + return str(user_id).strip() except Exception: - return None + pass + + return None + + +def _resolve_working_dir() -> Path | None: + """Resolve the QwenPaw/CoPaw working directory. + + Mirrors message.py's _resolve_copaw_working_dir() so that both tools + use the same path resolution logic, including the qwenpaw.constant + fallback when env vars are not set. + """ + configured = ( + os.getenv("COPAW_WORKING_DIR") + or os.getenv("QWENPAW_WORKING_DIR") + ) + if configured: + return Path(configured).expanduser().resolve() + + try: + from qwenpaw.constant import WORKING_DIR + except ImportError: + try: + from copaw.constant import WORKING_DIR + except ImportError: + return None + + return Path(WORKING_DIR).expanduser().resolve() def _read_config_value(obj: Any, *names: str) -> Any: diff --git a/copaw/src/copaw_worker/run_copaw_app.py b/copaw/src/copaw_worker/run_copaw_app.py index 8fd4eaee1..6b9cca509 100644 --- a/copaw/src/copaw_worker/run_copaw_app.py +++ b/copaw/src/copaw_worker/run_copaw_app.py @@ -1,15 +1,21 @@ -"""Run the upstream CoPaw app with AgentTeams runtime hooks installed.""" +"""Run the QwenPaw 2.0 app for AgentTeams Manager. + +Previously this module called ``install_tool_hooks()`` to monkey-patch +``CoPawAgent._create_toolkit``. On QwenPaw 2.0 that approach no longer +works — ``QwenPawAgent`` does not inherit from ``CoPawAgent`` and has no +``_create_toolkit`` method. The four manager tools (projectflow, taskflow, +message, filesync) are now registered via the ``agentteams-manager-tools`` +QwenPaw plugin (``api.register_tool()``), so this wrapper simply launches +QwenPaw. +""" from __future__ import annotations import runpy -from copaw_worker.hooks import install_tool_hooks - def main() -> None: - install_tool_hooks() - runpy.run_module("copaw", run_name="__main__", alter_sys=True) + runpy.run_module("qwenpaw", run_name="__main__", alter_sys=True) if __name__ == "__main__": diff --git a/copaw/src/copaw_worker/templates/agent.manager.json b/copaw/src/copaw_worker/templates/agent.manager.json index 19eb48d4a..fe31a4de9 100644 --- a/copaw/src/copaw_worker/templates/agent.manager.json +++ b/copaw/src/copaw_worker/templates/agent.manager.json @@ -2,12 +2,16 @@ "id": "default", "name": "Manager", "language": "zh", + "approval_level": "AUTO", "system_prompt_files": [ "AGENTS.md", "SOUL.md", "PROFILE.md", "TOOLS.md" ], + "running": { + "max_iters": 200 + }, "heartbeat": { "enabled": true, "every": "30m" @@ -25,8 +29,5 @@ "group_allow_from": [], "groups": {} } - }, - "running": { - "max_iters": 200 } } diff --git a/copaw/tests/test_message_tool.py b/copaw/tests/test_message_tool.py index 89838a63c..60a634d9e 100644 --- a/copaw/tests/test_message_tool.py +++ b/copaw/tests/test_message_tool.py @@ -510,13 +510,17 @@ async def query_handler(self, msgs, request=None, **kwargs): assert ("taskflow", "override") in names -def test_run_copaw_app_installs_hooks_before_start(monkeypatch): +def test_run_copaw_app_starts_qwenpaw(monkeypatch): + """run_copaw_app.main() should launch qwenpaw without tool hooks. + + The copaw hooks (install_tool_hooks) were removed because + QwenPaw 2.0's QwenPawAgent does not have _create_toolkit. + Tools are now registered via the agentteams-manager-tools plugin. + """ import copaw_worker.run_copaw_app as app calls = [] - monkeypatch.setattr(app, "install_tool_hooks", lambda: calls.append("hooks")) - def fake_run_module(name, *, run_name, alter_sys): calls.append((name, run_name, alter_sys)) @@ -524,4 +528,4 @@ def fake_run_module(name, *, run_name, alter_sys): app.main() - assert calls == ["hooks", ("copaw", "__main__", True)] + assert calls == [("qwenpaw", "__main__", True)] diff --git a/manager/Dockerfile.copaw b/manager/Dockerfile.copaw index e3ab1edb8..a6f67f71e 100644 --- a/manager/Dockerfile.copaw +++ b/manager/Dockerfile.copaw @@ -72,29 +72,6 @@ RUN pip install --no-cache-dir \ "copaw==${COPAW_VERSION}" \ "matrix-nio[e2e]>=0.24.0" -# ---- CoPaw CMS Plugin (observability) ---- -RUN pip install --no-cache-dir \ - -i "${PIP_INDEX_URL}" \ - --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ - "wrapt<2.0" \ - loongsuite-site-bootstrap \ - loongsuite-distro \ - opentelemetry-exporter-otlp \ - loongsuite-instrumentation-copaw \ - loongsuite-instrumentation-agentscope - -# ---- Overlay AgentTeams Matrix channel (not yet merged into CoPaw release) ---- -# Replace CoPaw's built-in Matrix channel with AgentTeams-enhanced version -COPY copaw/src/matrix/ /tmp/matrix-overlay/ -RUN SITE=$(python3 -c "import copaw; import os; print(os.path.dirname(copaw.__file__))") \ - && rm -rf "$SITE/app/channels/matrix" \ - && mkdir -p "$SITE/app/channels/matrix" \ - && cp /tmp/matrix-overlay/channel.py /tmp/matrix-overlay/__init__.py "$SITE/app/channels/matrix/" \ - && if [ -f /tmp/matrix-overlay/config.py ]; then \ - cp /tmp/matrix-overlay/config.py "$SITE/config/config.py"; \ - fi \ - && rm -rf /tmp/matrix-overlay - # ---- CoPaw worker bridge module (for config conversion) ---- # Stage the copaw-worker package metadata for dependency resolution COPY copaw/pyproject.toml copaw/README.md /tmp/copaw-worker/ @@ -115,6 +92,53 @@ 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/ +# ---- QwenPaw 2.0 Runtime (Manager) ---- +# Separate venv: copaw 1.0.2 requires agentscope==1.0.20, qwenpaw 2.0.1 +# requires agentscope>=2.0.0. Mixing them in one venv breaks copaw's +# ReActAgent import. The copaw venv above stays pristine for bridge.py; +# qwenpaw runs from this venv. +ARG QWENPAW_PIP_SPEC=qwenpaw==2.0.1 +RUN python3 -m venv /opt/venv/qwenpaw \ + && /opt/venv/qwenpaw/bin/pip install --no-cache-dir \ + -i "${PIP_INDEX_URL}" \ + --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ + "${QWENPAW_PIP_SPEC}" + +# Overlay copaw_worker source into qwenpaw venv. +# The 4 manager tools (projectflow/taskflow/message/filesync) depend only on +# agentscope 2.0.x and copaw_worker internal modules — not on copaw 1.0.2. +RUN cp -a /opt/copaw-venv/lib/python3.11/site-packages/copaw_worker \ + /opt/venv/qwenpaw/lib/python3.11/site-packages/ + +# ---- CMS Observability Plugin (in qwenpaw venv) ---- +# Aligned with qwenpaw Worker Dockerfile: installed into the runtime venv +# (qwenpaw) so that loongsuite-site-bootstrap is active when the Manager +# process starts. instrumentation-qwenpaw (not -copaw) + +# loongsuite-otel-util-genai for GenAI semantic conventions. +RUN /opt/venv/qwenpaw/bin/pip install --no-cache-dir \ + -i "${PIP_INDEX_URL}" \ + --trusted-host "$(echo ${PIP_INDEX_URL} | sed 's|https\?://||;s|/.*||')" \ + "wrapt<2.0" \ + loongsuite-site-bootstrap \ + loongsuite-distro \ + loongsuite-otel-util-genai \ + opentelemetry-exporter-otlp \ + loongsuite-instrumentation-qwenpaw \ + loongsuite-instrumentation-agentscope + +# qwenpaw CLI on PATH +RUN ln -sf /opt/venv/qwenpaw/bin/qwenpaw /usr/local/bin/qwenpaw + +# qwenpaw venv takes priority for Manager runtime +ENV PATH="/opt/venv/qwenpaw/bin:/opt/copaw-venv/bin:${PATH}" + +# AgentTeams plugins: Matrix channel + Manager tools +# Stored in image-local path (/opt/agentteams/plugins/). At runtime, +# start-qwenpaw-manager.sh copies them into the working dir's plugins/ +# directory because /root/manager-workspace is a host-mounted volume. +COPY plugins/agentteams-matrix-channel/ /opt/agentteams/plugins/agentteams-matrix-channel/ +COPY plugins/agentteams-manager-tools/ /opt/agentteams/plugins/agentteams-manager-tools/ + # ---- Copy Manager agent definitions ---- COPY manager/agent/ /opt/agentteams/agent/ COPY manager/configs/ /opt/agentteams/configs/ diff --git a/manager/scripts/init/start-manager-agent.sh b/manager/scripts/init/start-manager-agent.sh index 21d1ff5dc..3f208096c 100755 --- a/manager/scripts/init/start-manager-agent.sh +++ b/manager/scripts/init/start-manager-agent.sh @@ -6,8 +6,11 @@ # # Runtime selection: # AGENTTEAMS_MANAGER_RUNTIME=openclaw (default) - OpenClaw gateway mode -# AGENTTEAMS_MANAGER_RUNTIME=copaw - CoPaw workspace mode -# (hermes runtime is supported for Workers only; Managers run openclaw or copaw.) +# AGENTTEAMS_MANAGER_RUNTIME=copaw|qwenpaw - QwenPaw workspace mode +# "copaw" is the legacy runtime identifier still used by the controller, +# installer, and CI; "qwenpaw" is the forward-looking name. Both map to +# the same QwenPaw 2.0 startup script. +# (hermes runtime is supported for Workers only.) source /opt/agentteams/scripts/lib/agentteams-env.sh @@ -16,8 +19,8 @@ source /opt/agentteams/scripts/lib/agentteams-env.sh # ============================================================ MANAGER_RUNTIME="${AGENTTEAMS_MANAGER_RUNTIME:-openclaw}" case "${MANAGER_RUNTIME}" in - copaw) - log "Manager runtime: CoPaw (Python workspace)" + copaw|qwenpaw) + log "Manager runtime: QwenPaw (Python workspace, runtime=${MANAGER_RUNTIME})" ;; *) log "Manager runtime: OpenClaw (Node.js gateway)" @@ -1162,9 +1165,9 @@ fi # ============================================================ # Runtime-specific startup # ============================================================ -if [ "${MANAGER_RUNTIME}" = "copaw" ]; then - # Delegate to CoPaw startup script - exec /opt/agentteams/scripts/init/start-copaw-manager.sh +if [ "${MANAGER_RUNTIME}" = "copaw" ] || [ "${MANAGER_RUNTIME}" = "qwenpaw" ]; then + # Delegate to QwenPaw startup script + exec /opt/agentteams/scripts/init/start-qwenpaw-manager.sh else # ── OpenClaw Runtime ───────────────────────────────────────────────────── log "Starting OpenClaw Manager..." diff --git a/manager/scripts/init/start-copaw-manager.sh b/manager/scripts/init/start-qwenpaw-manager.sh similarity index 57% rename from manager/scripts/init/start-copaw-manager.sh rename to manager/scripts/init/start-qwenpaw-manager.sh index 57da7eb29..228c0dec1 100644 --- a/manager/scripts/init/start-copaw-manager.sh +++ b/manager/scripts/init/start-qwenpaw-manager.sh @@ -1,9 +1,9 @@ #!/bin/bash -# start-copaw-manager.sh - Start Manager Agent with CoPaw runtime -# Called by start-manager-agent.sh when AGENTTEAMS_MANAGER_RUNTIME=copaw +# start-qwenpaw-manager.sh - Start Manager Agent with QwenPaw 2.0 runtime +# Called by start-manager-agent.sh when AGENTTEAMS_MANAGER_RUNTIME=copaw|qwenpaw # -# This script converts an OpenClaw-style workspace to a CoPaw-style workspace -# and then launches the CoPaw application. +# This script converts an OpenClaw-style workspace to a QwenPaw-style workspace +# and then launches the QwenPaw application. source /opt/agentteams/scripts/lib/agentteams-env.sh @@ -46,8 +46,7 @@ if [ -f "${COPAW_WORKING_DIR}/config.json" ]; then fi log "Bridging openclaw.json -> CoPaw config (manager)..." -PYTHONPATH="/opt/agentteams/copaw/src:${PYTHONPATH:-}" \ - python3 -m copaw_worker.bridge \ +/opt/copaw-venv/bin/python3 -m copaw_worker.bridge \ --profile manager \ --openclaw-json "${OPENCLAW_JSON}" \ --working-dir "${COPAW_WORKING_DIR}" @@ -95,7 +94,26 @@ if [ -d "${OPENCLAW_WORKSPACE}/skills" ]; then fi # ============================================================ -# 5. DM room detection and auto-reply config (patches agent.json directly) +# 5. Inject session file privacy policy into prompt files +# ============================================================ +# Aligned with qwenpaw Worker (_ensure_session_file_prompt_policy): +# prevents the agent from reading/exposing files under sessions/. +SESSION_FILE_POLICY="Do not read, list, grep, glob, summarize, copy, or expose files under sessions/. +Session files are runtime-private state and may contain private conversation history. +This rule applies to all channels, users, and sessions, not only DingTalk." +SESSION_FILE_POLICY_MARKER="Session files are runtime-private state" +for _pf in AGENTS.md SOUL.md; do + _target="${WORKSPACE_DIR}/${_pf}" + if [ -f "${_target}" ]; then + if ! grep -q "${SESSION_FILE_POLICY_MARKER}" "${_target}" 2>/dev/null; then + printf '\n%s\n' "${SESSION_FILE_POLICY}" >> "${_target}" + log " Injected session file policy into ${_pf}" + fi + fi +done + +# ============================================================ +# 6. DM room detection and auto-reply config (patches agent.json directly) # ============================================================ # nio room.users is always 0 after token restore, so all rooms are treated as # "group" (requireMention=true by default). We detect actual DM rooms via @@ -106,7 +124,7 @@ fi log "Detecting DM rooms for auto-reply config..." AGENT_JSON="${WORKSPACE_DIR}/agent.json" if [ ! -f "${AGENT_JSON}" ]; then - log "ERROR: agent.json not found at ${AGENT_JSON} (bridge step must have failed)" + log "ERROR: agent.json not found at ${AGENT_JSON} (bridge steps must have failed)" exit 1 fi MANAGER_MATRIX_TOKEN_VAL=$(jq -r '.channels.matrix.access_token // ""' "${AGENT_JSON}") @@ -155,50 +173,71 @@ jq --slurpfile dm_rooms "${DM_ROOMS_FILE}" \ rm -f "${DM_ROOMS_FILE}" "${DM_ROOMS_FILE}.tmp" # ============================================================ -# 8. Configure CoPaw CMS plugin (LoongSuite observability) +# 7. Disable built-in QA Agent (QwenPaw_QA_Agent_0.2) +# ============================================================ +# Worker does this via api_client.disable_agent_if_present(). +# Manager runs QwenPaw in-process (no API client), so we set +# enabled=false in config.json's agents.profiles before startup. +# QwenPaw 2.0 start_all_configured_agents() skips enabled=false agents. +CONFIG_JSON="${COPAW_WORKING_DIR}/config.json" +if [ -f "${CONFIG_JSON}" ]; then + # AgentProfileRef requires id + workspace_dir (both mandatory). + # Writing only {"enabled": false} causes a Pydantic ValidationError + # that _remove_bad_field() strips, then ensure_qa_agent_exists() + # recreates it with enabled=True — defeating the disable. + # + # CRITICAL: must also inject the "default" profile alongside QA. + # If profiles contains ONLY the QA entry, QwenPaw's + # migrate_legacy_workspace_to_default_agent() sees len(profiles)==1 + # and "default" not in profiles → runs legacy migration → replaces + # config.agents entirely → QA profile lost → ensure_qa_agent_exists() + # re-creates it with enabled=True. Injecting "default" makes + # len(profiles)>=2 so migration is skipped. + _DEFAULT_WD="${COPAW_WORKING_DIR}/workspaces/default" + _QA_WD="${COPAW_WORKING_DIR}/workspaces/QwenPaw_QA_Agent_0.2" + jq --arg wd "${_QA_WD}" --arg dwd "${_DEFAULT_WD}" \ + '.agents.profiles = ((.agents.profiles // {}) + { + "default": { + "id": "default", + "workspace_dir": $dwd + }, + "QwenPaw_QA_Agent_0.2": { + "id": "QwenPaw_QA_Agent_0.2", + "workspace_dir": $wd, + "enabled": false + } + })' \ + "${CONFIG_JSON}" > "${CONFIG_JSON}.tmp" && mv "${CONFIG_JSON}.tmp" "${CONFIG_JSON}" + log "Disabled built-in QA Agent in config.json" +fi + +# ============================================================ +# 8. Configure CMS observability plugin (LoongSuite) # ============================================================ +# Aligned with qwenpaw Worker entrypoint: heredoc + env exports. CMS_TRACES_ENABLED="$(echo "${AGENTTEAMS_CMS_TRACES_ENABLED:-false}" | tr '[:upper:]' '[:lower:]')" if [ "${CMS_TRACES_ENABLED}" = "true" ]; then - log "Configuring CoPaw CMS plugin..." - - # Create bootstrap config directory - BOOTSTRAP_CONFIG_DIR="${HOME}/.loongsuite" - mkdir -p "${BOOTSTRAP_CONFIG_DIR}" - BOOTSTRAP_CONFIG="${BOOTSTRAP_CONFIG_DIR}/bootstrap-config.json" - - # Generate bootstrap-config.json - python3 - "${BOOTSTRAP_CONFIG}" <<'PYEOF' -import json -import sys -import os -from pathlib import Path - -cfg_path = Path(sys.argv[1]) -endpoint = os.getenv("AGENTTEAMS_CMS_ENDPOINT", "") -license_key = os.getenv("AGENTTEAMS_CMS_LICENSE_KEY", "") -arms_project = os.getenv("AGENTTEAMS_CMS_PROJECT", "") -cms_workspace = os.getenv("AGENTTEAMS_CMS_WORKSPACE", "") -service_name = os.getenv("AGENTTEAMS_CMS_SERVICE_NAME", "agentteams-manager") -protocol = "http/protobuf" # Default OTLP protocol - -config = { - "OTEL_EXPORTER_OTLP_ENDPOINT": endpoint, - "OTEL_EXPORTER_OTLP_PROTOCOL": protocol, - "OTEL_EXPORTER_OTLP_HEADERS": f"x-arms-license-key={license_key},x-arms-project={arms_project},x-cms-workspace={cms_workspace}", - "OTEL_SERVICE_NAME": service_name, - "OTEL_SEMCONV_STABILITY_OPT_IN": "http,gen_ai_latest_experimental", - "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "SPAN_AND_EVENT", - "LOONGSUITE_PYTHON_SITE_BOOTSTRAP": "true", -} + log "Configuring CMS observability plugin..." -cfg_path.parent.mkdir(parents=True, exist_ok=True) -with open(cfg_path, "w") as f: - json.dump(config, f, indent=2) + LOONGSUITE_DIR="${HOME}/.loongsuite" + mkdir -p "${LOONGSUITE_DIR}" -print(f"Bootstrap config written to: {cfg_path}") -PYEOF + cat > "${LOONGSUITE_DIR}/bootstrap-config.json" </dev/null | awk '{print $1}') if [ -n "${_curr_hash}" ] && [ "${_curr_hash}" != "${_prev_hash}" ]; then log "openclaw.json changed, re-bridging..." - _bridge_out=$(PYTHONPATH="/opt/agentteams/copaw/src:${PYTHONPATH:-}" \ - python3 -m copaw_worker.bridge \ + _bridge_out=$(/opt/copaw-venv/bin/python3 -m copaw_worker.bridge \ --profile manager \ --openclaw-json "${OPENCLAW_JSON}" \ --working-dir "${COPAW_WORKING_DIR}" 2>&1) @@ -228,17 +266,49 @@ fi log "openclaw.json watcher started (PID: $!)" # ============================================================ -# 10. Launch CoPaw Manager (app mode with hot-reload) +# 10. Copy AgentTeams plugins into working dir +# ============================================================ +# The Dockerfile copies plugins to /opt/agentteams/plugins/ (image-local). +# /root/manager-workspace is a host-mounted volume at runtime, so +# build-time files there are hidden. Copy plugins to the QwenPaw +# working dir's plugins/ directory before startup so PluginLoader +# discovers them. +PLUGINS_TARGET="${COPAW_WORKING_DIR}/plugins" +mkdir -p "${PLUGINS_TARGET}" +for _plugin_src in /opt/agentteams/plugins/*/; do + _plugin_name=$(basename "${_plugin_src}") + if [ -f "${_plugin_src}plugin.json" ]; then + rm -rf "${PLUGINS_TARGET}/${_plugin_name}" + cp -a "${_plugin_src}" "${PLUGINS_TARGET}/${_plugin_name}" + log "Installed plugin: ${_plugin_name}" + fi +done + +# ============================================================ +# 11. Launch QwenPaw 2.0 Manager (app mode) # ============================================================ +# copaw is the legacy name for qwenpaw; QwenPaw 2.0 treats ~/.copaw as +# a legacy installation and auto-uses it. We set QWENPAW_WORKING_DIR +# explicitly to be unambiguous. export COPAW_WORKING_DIR="${COPAW_WORKING_DIR}" +export QWENPAW_WORKING_DIR="${COPAW_WORKING_DIR}" +export QWENPAW_SECRET_DIR="${QWENPAW_SECRET_DIR:-${COPAW_WORKING_DIR}.secret}" +export QWENPAW_RUNNING_IN_CONTAINER=true +export QWENPAW_LOG_LEVEL="${COPAW_LOG_LEVEL:-info}" -log "Starting CoPaw Manager (app mode)..." +# YOLO mode: AGENTTEAMS_YOLO=1 → set approval_level=OFF in agent.json +# (QwenPaw 2.0 equivalent of OpenClaw's tools.exec.ask=off). +# start-manager-agent.sh promotes the yolo-mode marker file to +# AGENTTEAMS_YOLO=1 before calling this script. +if [ "${AGENTTEAMS_YOLO:-}" = "1" ] && [ -f "${AGENT_JSON}" ]; then + jq '.approval_level = "OFF"' "${AGENT_JSON}" > "${AGENT_JSON}.tmp" \ + && mv "${AGENT_JSON}.tmp" "${AGENT_JSON}" + log "YOLO mode: approval_level set to OFF" +fi + +log "Starting QwenPaw 2.0 Manager (app mode)..." COPAW_LOG_LEVEL="${COPAW_LOG_LEVEL:-info}" export COPAW_LOG_LEVEL -# Set PYTHONPATH to include copaw_worker module -export PYTHONPATH="/opt/agentteams/copaw/src:${PYTHONPATH:-}" - -# Use uvicorn to run CoPaw FastAPI app (enables AgentConfigWatcher for hot-reload) -# The wrapper installs AgentTeams-owned tools before CoPaw creates any agents. +# run_copaw_app.py starts qwenpaw app (tools registered via agentteams-manager-tools plugin) exec python3 -m copaw_worker.run_copaw_app app --host 0.0.0.0 --port 18799 diff --git a/plugins/agentteams-manager-tools/plugin.json b/plugins/agentteams-manager-tools/plugin.json new file mode 100644 index 000000000..084cd43c4 --- /dev/null +++ b/plugins/agentteams-manager-tools/plugin.json @@ -0,0 +1,17 @@ +{ + "id": "agentteams-manager-tools", + "name": "AgentTeams Manager Tools", + "version": "0.1.0", + "type": "tool", + "description": "projectflow, taskflow, message, filesync tools for Manager", + "author": "AgentTeams", + "entry": { + "backend": "plugin.py" + }, + "dependencies": [], + "min_version": "2.0.1", + "qwenpaw_version": { + "min": "2.0.1", + "max": "2.1.0" + } +} diff --git a/plugins/agentteams-manager-tools/plugin.py b/plugins/agentteams-manager-tools/plugin.py new file mode 100644 index 000000000..d2caba1b9 --- /dev/null +++ b/plugins/agentteams-manager-tools/plugin.py @@ -0,0 +1,57 @@ +"""Register AgentTeams Manager tools as a QwenPaw 2.0 plugin. + +The four tools (projectflow, taskflow, message, filesync) were previously +injected via ``install_tool_hooks()`` which monkey-patched +``CoPawAgent._create_toolkit``. QwenPaw 2.0's ``QwenPawAgent`` does not +have that method, so the tools are now registered through the plugin API's +``api.register_tool()``. + +Tool dependencies (verified against agentscope 2.0.4.post1): + - ``agentscope.message.TextBlock / Msg`` ✅ + - ``agentscope.tool.ToolResponse`` ✅ + - ``copaw_worker.task / sync / hooks.message_filter`` — pure stdlib + agentscope + +No dependency on ``copaw`` 1.0.2 at module-import time. The message and +taskflow tools read Matrix credentials directly from ``agent.json`` instead +of importing ``copaw.config.config``. +""" + + +class AgentTeamsManagerToolsPlugin: + def register(self, api): + from copaw_worker.hooks.tools.projectflow import projectflow + from copaw_worker.hooks.tools.taskflow import taskflow + from copaw_worker.hooks.tools.message import message + from copaw_worker.hooks.tools.filesync import filesync + + api.register_tool( + tool_name="projectflow", + tool_func=projectflow, + description="AgentTeams project/DAG lifecycle management", + tool_type="internal", + enabled=True, + ) + api.register_tool( + tool_name="taskflow", + tool_func=taskflow, + description="AgentTeams task state management", + tool_type="internal", + enabled=True, + ) + api.register_tool( + tool_name="message", + tool_func=message, + description="Send messages to workers via Matrix", + tool_type="internal", + enabled=True, + ) + api.register_tool( + tool_name="filesync", + tool_func=filesync, + description="Sync files between Manager and Workers via MinIO", + tool_type="internal", + enabled=True, + ) + + +plugin = AgentTeamsManagerToolsPlugin()