fix: unbreak main CI — skills HIVE_HOME refactor + run_parallel_workers task text (#7149)

* fix(skills): restore module-level path constants for HIVE_HOME refactor

ae2aa30e replaced module-level USER_SKILLS_DIR / INSTALL_NOTICE_SENTINEL
in installer.py and _NOTICE_SENTINEL_PATH / _TRUSTED_REPOS_PATH in
trust.py with lazy helper functions, but left callers and tests still
referencing the original symbols. CI fails with ImportError /
AttributeError.

Restore them as module-level constants computed from HIVE_HOME so the
desktop-shell override still works, callers in cli.py keep importing
the same names, and existing test monkeypatches stay valid.

Refs #7148

* fix(colony): preserve task text in run_parallel_workers spawn data

run_parallel_workers stamps __template_task_id into spec['data'] before
calling spawn_batch. Once that mutation makes spec['data'] non-empty,
colony_runtime.spawn()'s ``input_data or {"task": task}`` fallback no
longer fires and the task description disappears from the worker's
first user message. Workers loop on empty responses and never emit
SUBAGENT_REPORT.

Hoist the ``setdefault("task")`` step out of the template-publish try
block so task text survives even if the template store fails
non-fatally. Inner loop only stamps __template_task_id.

Refs #7148
This commit is contained in:
Hundao
2026-04-30 18:43:54 +08:00
committed by GitHub
parent 2cfea915f4
commit 7881177f1f
3 changed files with 29 additions and 35 deletions
+12 -22
View File
@@ -15,26 +15,17 @@ import subprocess
import tempfile
from pathlib import Path
from framework.config import HIVE_HOME
from framework.skills.parser import ParsedSkill
from framework.skills.skill_errors import SkillError, SkillErrorCode
# Default install destination for user-scope skills.
# Anchored on HIVE_HOME so the desktop shell can override the install
# root via $HIVE_HOME without patching every call site.
USER_SKILLS_DIR = HIVE_HOME / "skills"
# Default install destination for user-scope skills + sentinel file for
# the one-time security notice on first install (NFR-5). Computed via
# helpers so HIVE_HOME (set by the desktop shell to a per-user dir)
# is honoured. ``framework.config.HIVE_HOME`` is module-global and
# resolved at first import — so a single call here is enough; we don't
# need to re-resolve on every access.
def _user_skills_dir() -> Path:
from framework.config import HIVE_HOME
return HIVE_HOME / "skills"
def _install_notice_sentinel() -> Path:
from framework.config import HIVE_HOME
return HIVE_HOME / ".install_notice_shown"
# Sentinel file for the one-time security notice on first install (NFR-5).
INSTALL_NOTICE_SENTINEL = HIVE_HOME / ".install_notice_shown"
_INSTALL_NOTICE = """\
@@ -60,13 +51,12 @@ def maybe_show_install_notice() -> None:
Touches a sentinel file in $HIVE_HOME after showing the notice so it is
only displayed once across all future installs.
"""
sentinel = _install_notice_sentinel()
if sentinel.exists():
if INSTALL_NOTICE_SENTINEL.exists():
return
print(_INSTALL_NOTICE, flush=True)
try:
sentinel.parent.mkdir(parents=True, exist_ok=True)
sentinel.touch()
INSTALL_NOTICE_SENTINEL.parent.mkdir(parents=True, exist_ok=True)
INSTALL_NOTICE_SENTINEL.touch()
except OSError:
pass # If we can't write the sentinel, just show the notice every time
@@ -107,7 +97,7 @@ def install_from_git(
fix="Install git (https://git-scm.com/) and retry.",
)
dest = (target_dir or _user_skills_dir()) / skill_name
dest = (target_dir or USER_SKILLS_DIR) / skill_name
if dest.exists():
raise SkillError(
code=SkillErrorCode.SKILL_ACTIVATION_FAILED,
@@ -208,7 +198,7 @@ def remove_skill(name: str, skills_dir: Path | None = None) -> bool:
Raises:
SkillError: If the directory exists but cannot be removed.
"""
target = (skills_dir or _user_skills_dir()) / name
target = (skills_dir or USER_SKILLS_DIR) / name
if not target.exists():
return False
try:
+7 -12
View File
@@ -20,6 +20,7 @@ from enum import StrEnum
from pathlib import Path
from urllib.parse import urlparse
from framework.config import HIVE_HOME
from framework.skills.parser import ParsedSkill
logger = logging.getLogger(__name__)
@@ -30,17 +31,11 @@ _ENV_TRUST_ALL = "HIVE_TRUST_PROJECT_SKILLS"
# Env var for comma-separated own-remote glob patterns (e.g. "github.com/myorg/*").
_ENV_OWN_REMOTES = "HIVE_OWN_REMOTES"
# Persisted store of trusted git remotes (one-shot consent per repo).
_TRUSTED_REPOS_PATH = HIVE_HOME / "trusted_repos.json"
def _trusted_repos_path() -> Path:
from framework.config import HIVE_HOME
return HIVE_HOME / "trusted_repos.json"
def _notice_sentinel_path() -> Path:
from framework.config import HIVE_HOME
return HIVE_HOME / ".skill_trust_notice_shown"
# Sentinel for the one-time security notice (NFR-5).
_NOTICE_SENTINEL_PATH = HIVE_HOME / ".skill_trust_notice_shown"
# ---------------------------------------------------------------------------
@@ -59,7 +54,7 @@ class TrustedRepoStore:
"""Persists permanently-trusted repo keys to ~/.hive/trusted_repos.json."""
def __init__(self, path: Path | None = None) -> None:
self._path = path or _trusted_repos_path()
self._path = path or _TRUSTED_REPOS_PATH
self._entries: dict[str, TrustedRepoEntry] = {}
self._loaded = False
@@ -426,7 +421,7 @@ class TrustGate:
def _maybe_show_security_notice(self, Colors) -> None: # noqa: N803
"""Show the one-time security notice if not already shown (NFR-5)."""
sentinel = _notice_sentinel_path()
sentinel = _NOTICE_SENTINEL_PATH
if sentinel.exists():
return
self._print("")
+10 -1
View File
@@ -1326,6 +1326,16 @@ def register_queen_lifecycle_tools(
# the entries' template ids can be threaded into the spawn data
# (workers' ctx.picked_up_from references them). This mirrors the
# plan §5d "auto-populated by run_parallel_workers" behavior.
# Preserve the task text in spec["data"] before any template-store
# mutation. Once spec["data"] is non-empty, spawn()'s
# ``input_data or {"task": task}`` fallback no longer fires, so the
# task description would otherwise vanish from the worker's first
# user message. Hoisted out of the try below so a non-fatal template
# failure cannot drop task text from the spawn payload.
for spec in normalised:
spec["data"] = dict(spec.get("data") or {})
spec["data"].setdefault("task", spec["task"])
_template_ids: list[int | None] = [None] * len(normalised)
try:
from framework.tasks import TaskListRole, get_task_store
@@ -1343,7 +1353,6 @@ def register_queen_lifecycle_tools(
_template_ids[i] = rec.id
# Thread the template id into the worker's spawn data so
# ColonyRuntime.spawn populates ctx.picked_up_from correctly.
spec["data"] = dict(spec.get("data") or {})
spec["data"]["__template_task_id"] = rec.id
except Exception:
logger.warning(