diff --git a/__init__.py b/__init__.py index f2e791b..6882052 100644 --- a/__init__.py +++ b/__init__.py @@ -1031,7 +1031,8 @@ def _open_guard(tool_name, args, session_id, turn_id): cmd = str((args or {}).get("command") or "") if not CARD_OPEN_RX.search(cmd) or PROBE_TITLE_RX.search(cmd): return None - if _is_crew_turn(session_id, turn_id): + # A role process may open a card here too, as it may through crew_plan above and kanban_create. + if os.environ.get("HERMES_KANBAN_TASK") or _is_crew_turn(session_id, turn_id): return None return {"action": "block", "message": "crew cards open only from the owner's /crew command. This session has no " diff --git a/tests/test_crew_tools_guard.py b/tests/test_crew_tools_guard.py index c1f0c9b..6015155 100644 --- a/tests/test_crew_tools_guard.py +++ b/tests/test_crew_tools_guard.py @@ -103,6 +103,13 @@ def test_a_terminal_card_open_is_still_refused_outside_a_crew_turn(self): self.assertEqual("block", res["action"]) self.assertIn("crew_card.py open is refused", res["message"]) + def test_a_terminal_card_open_with_a_kanban_task_is_allowed(self): + # The same rule as crew_plan and kanban_create: a role process may open a card. + os.environ["HERMES_KANBAN_TASK"] = "t_x" + cmd = 'sh "$HERMES_HOME/plugins/crew/scripts/crew-run.sh" crew_card.py open --title X' + with mock.patch.object(P, "_card_budget", lambda c: 0): + self.assertIsNone(self.guard("terminal", {"command": cmd}, role="worker")) + def test_verifier_cannot_plan_but_can_verdict(self): os.environ["HERMES_KANBAN_TASK"] = "t_x" with mock.patch.object(P, "_card_budget", lambda c: 0), mock.patch.object(P, "_verdict_fails", lambda c: 0):