diff --git a/tests/tools/test_kanban_tools.py b/tests/tools/test_kanban_tools.py index 9031d81d8e..fdde48a2aa 100644 --- a/tests/tools/test_kanban_tools.py +++ b/tests/tools/test_kanban_tools.py @@ -610,3 +610,103 @@ def test_orchestrator_complete_any_task_allowed(monkeypatch, tmp_path): out = kt._handle_complete({"task_id": tid, "summary": "orchestrator close"}) d = json.loads(out) assert d.get("ok") is True and d.get("task_id") == tid + + +# --------------------------------------------------------------------------- +# kanban_create auto-subscribe to gateway notifications (#19479) +# --------------------------------------------------------------------------- +# +# When an orchestrator agent (running under the gateway) calls kanban_create, +# the originating (platform, chat, thread) should be auto-subscribed to the +# new task's terminal events — matching the /kanban create slash-command +# behavior in gateway/run.py. In CLI / cron contexts (no session vars set), +# no subscription row is written. + + +def test_create_no_subscribe_in_cli_context(worker_env): + """Classic CLI: no gateway session vars -> no notify subscription.""" + from tools import kanban_tools as kt + from hermes_cli import kanban_db as kb + out = kt._handle_create({"title": "cli task", "assignee": "peer"}) + d = json.loads(out) + assert d.get("ok") is True + assert d.get("subscribed") is False + conn = kb.connect() + try: + assert kb.list_notify_subs(conn, d["task_id"]) == [] + finally: + conn.close() + + +def test_create_auto_subscribes_in_gateway_context(worker_env): + """Gateway session vars set -> auto-subscribe the originating source.""" + from gateway.session_context import set_session_vars, clear_session_vars + from tools import kanban_tools as kt + from hermes_cli import kanban_db as kb + + tokens = set_session_vars( + platform="telegram", + chat_id="1234567", + thread_id="42", + user_id="u_alice", + ) + try: + out = kt._handle_create({"title": "gateway task", "assignee": "peer"}) + d = json.loads(out) + assert d.get("ok") is True + assert d.get("subscribed") is True + conn = kb.connect() + try: + subs = kb.list_notify_subs(conn, d["task_id"]) + finally: + conn.close() + assert len(subs) == 1 + assert subs[0]["platform"] == "telegram" + assert subs[0]["chat_id"] == "1234567" + assert subs[0]["thread_id"] == "42" + assert subs[0]["user_id"] == "u_alice" + finally: + clear_session_vars(tokens) + + +def test_create_subscribe_without_thread_id(worker_env): + """DM / no-thread platforms subscribe without a thread_id.""" + from gateway.session_context import set_session_vars, clear_session_vars + from tools import kanban_tools as kt + from hermes_cli import kanban_db as kb + + tokens = set_session_vars(platform="discord", chat_id="ch_dm_789") + try: + out = kt._handle_create({"title": "dm task", "assignee": "peer"}) + d = json.loads(out) + assert d.get("subscribed") is True + conn = kb.connect() + try: + subs = kb.list_notify_subs(conn, d["task_id"]) + finally: + conn.close() + assert len(subs) == 1 + assert subs[0]["thread_id"] == "" + assert subs[0]["user_id"] is None + finally: + clear_session_vars(tokens) + + +def test_create_no_subscribe_when_chat_id_missing(worker_env): + """Partial gateway context (platform but no chat_id) -> no subscription.""" + from gateway.session_context import set_session_vars, clear_session_vars + from tools import kanban_tools as kt + from hermes_cli import kanban_db as kb + + tokens = set_session_vars(platform="telegram", chat_id="") + try: + out = kt._handle_create({"title": "partial ctx", "assignee": "peer"}) + d = json.loads(out) + assert d.get("subscribed") is False + conn = kb.connect() + try: + assert kb.list_notify_subs(conn, d["task_id"]) == [] + finally: + conn.close() + finally: + clear_session_vars(tokens) diff --git a/tools/kanban_tools.py b/tools/kanban_tools.py index 1f99f6896c..baa5f2210a 100644 --- a/tools/kanban_tools.py +++ b/tools/kanban_tools.py @@ -380,10 +380,40 @@ def _handle_create(args: dict, **kw) -> str: skills=skills, created_by=os.environ.get("HERMES_PROFILE") or "worker", ) + # Auto-subscribe the originating gateway source (if any) to the + # new task's terminal events. Mirrors the behavior of the + # `/kanban create` slash command in gateway/run.py so that + # tool-driven creation (orchestrator agents calling kanban_create) + # gets the same blocked/completed/gave_up notifications as human- + # driven creation. No-op in CLI / cron contexts where no gateway + # session context is active. See issue #19479. + subscribed = False + try: + from gateway.session_context import get_session_env + platform = get_session_env("HERMES_SESSION_PLATFORM") + chat_id = get_session_env("HERMES_SESSION_CHAT_ID") + thread_id = get_session_env("HERMES_SESSION_THREAD_ID") or None + user_id = get_session_env("HERMES_SESSION_USER_ID") or None + if platform and chat_id: + kb.add_notify_sub( + conn, + task_id=new_tid, + platform=platform, + chat_id=chat_id, + thread_id=thread_id, + user_id=user_id, + ) + subscribed = True + except Exception: + # Subscription is best-effort; don't fail the whole create + # if the gateway context module isn't importable (e.g. in + # test rigs that stub out gateway.*). + logger.debug("kanban_create notify-sub skipped", exc_info=True) new_task = kb.get_task(conn, new_tid) return _ok( task_id=new_tid, status=new_task.status if new_task else None, + subscribed=subscribed, ) finally: conn.close()