mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-16 07:44:06 +00:00
fix: show websocket cron jobs in automations
This commit is contained in:
parent
3725b42e0e
commit
8dac6b2889
@ -4,29 +4,26 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, Protocol
|
from typing import Any, Protocol
|
||||||
|
|
||||||
|
from nanobot.cron.automation import is_bound_agent_job
|
||||||
from nanobot.cron.types import CronJob
|
from nanobot.cron.types import CronJob
|
||||||
|
|
||||||
|
|
||||||
class _CronServiceLike(Protocol):
|
class _CronServiceLike(Protocol):
|
||||||
def list_bound_agent_jobs_for_session(
|
def list_jobs(self, *, include_disabled: bool = False) -> list[CronJob]: ...
|
||||||
self,
|
|
||||||
session_key: str,
|
|
||||||
*,
|
|
||||||
include_disabled: bool = True,
|
|
||||||
) -> list[CronJob]: ...
|
|
||||||
|
|
||||||
|
|
||||||
def bound_session_automation_jobs(
|
def session_automation_jobs(
|
||||||
cron_service: _CronServiceLike | None,
|
cron_service: _CronServiceLike | None,
|
||||||
session_key: str,
|
session_key: str,
|
||||||
) -> list[CronJob]:
|
) -> list[CronJob]:
|
||||||
"""Return agent-turn automation jobs explicitly bound to *session_key*."""
|
"""Return user automations attached to the WebUI session."""
|
||||||
if cron_service is None:
|
if cron_service is None:
|
||||||
return []
|
return []
|
||||||
return cron_service.list_bound_agent_jobs_for_session(
|
return [
|
||||||
session_key,
|
job
|
||||||
include_disabled=True,
|
for job in cron_service.list_jobs(include_disabled=True)
|
||||||
)
|
if _matches_webui_session(job, session_key)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def session_automations_payload(
|
def session_automations_payload(
|
||||||
@ -34,7 +31,22 @@ def session_automations_payload(
|
|||||||
session_key: str,
|
session_key: str,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return user-created automation jobs attached to a WebUI session."""
|
"""Return user-created automation jobs attached to a WebUI session."""
|
||||||
return {"jobs": serialize_automation_jobs(bound_session_automation_jobs(cron_service, session_key))}
|
return {
|
||||||
|
"jobs": serialize_automation_jobs(session_automation_jobs(cron_service, session_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _matches_webui_session(job: CronJob, session_key: str) -> bool:
|
||||||
|
payload = job.payload
|
||||||
|
if payload.kind != "agent_turn":
|
||||||
|
return False
|
||||||
|
if is_bound_agent_job(job):
|
||||||
|
return payload.session_key == session_key
|
||||||
|
return bool(
|
||||||
|
payload.channel == "websocket"
|
||||||
|
and payload.to
|
||||||
|
and session_key == f"websocket:{payload.to}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def serialize_automation_jobs(jobs: list[CronJob]) -> list[dict[str, Any]]:
|
def serialize_automation_jobs(jobs: list[CronJob]) -> list[dict[str, Any]]:
|
||||||
|
|||||||
@ -62,8 +62,8 @@ from nanobot.webui.http_utils import (
|
|||||||
)
|
)
|
||||||
from nanobot.webui.media_gateway import WebUIMediaGateway
|
from nanobot.webui.media_gateway import WebUIMediaGateway
|
||||||
from nanobot.webui.session_automations import (
|
from nanobot.webui.session_automations import (
|
||||||
bound_session_automation_jobs,
|
|
||||||
serialize_automation_jobs,
|
serialize_automation_jobs,
|
||||||
|
session_automation_jobs,
|
||||||
session_automations_payload,
|
session_automations_payload,
|
||||||
)
|
)
|
||||||
from nanobot.webui.session_list_index import list_webui_sessions
|
from nanobot.webui.session_list_index import list_webui_sessions
|
||||||
@ -452,17 +452,17 @@ class GatewayHTTPHandler:
|
|||||||
return _http_error(404, "session not found")
|
return _http_error(404, "session not found")
|
||||||
query = _parse_query(request.path)
|
query = _parse_query(request.path)
|
||||||
delete_automations = (_query_first(query, "delete_automations") or "").lower()
|
delete_automations = (_query_first(query, "delete_automations") or "").lower()
|
||||||
bound_jobs = bound_session_automation_jobs(self.cron_service, decoded_key)
|
automation_jobs = session_automation_jobs(self.cron_service, decoded_key)
|
||||||
if bound_jobs and delete_automations not in {"1", "true", "yes"}:
|
if automation_jobs and delete_automations not in {"1", "true", "yes"}:
|
||||||
return _http_json_response(
|
return _http_json_response(
|
||||||
{
|
{
|
||||||
"deleted": False,
|
"deleted": False,
|
||||||
"blocked_by_automations": True,
|
"blocked_by_automations": True,
|
||||||
"automations": serialize_automation_jobs(bound_jobs),
|
"automations": serialize_automation_jobs(automation_jobs),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if bound_jobs and self.cron_service is not None:
|
if automation_jobs and self.cron_service is not None:
|
||||||
for job in bound_jobs:
|
for job in automation_jobs:
|
||||||
self.cron_service.remove_job(job.id)
|
self.cron_service.remove_job(job.id)
|
||||||
deleted = self.session_manager.delete_session(decoded_key)
|
deleted = self.session_manager.delete_session(decoded_key)
|
||||||
delete_webui_thread(decoded_key)
|
delete_webui_thread(decoded_key)
|
||||||
|
|||||||
@ -189,7 +189,7 @@ async def test_session_automations_route_filters_by_webui_session(
|
|||||||
cron.add_job(
|
cron.add_job(
|
||||||
name="Legacy same target",
|
name="Legacy same target",
|
||||||
schedule=hourly,
|
schedule=hourly,
|
||||||
message="Legacy job should not be treated as bound",
|
message="Legacy job should still show in WebUI",
|
||||||
deliver=True,
|
deliver=True,
|
||||||
channel="websocket",
|
channel="websocket",
|
||||||
to="abc",
|
to="abc",
|
||||||
@ -227,11 +227,15 @@ async def test_session_automations_route_filters_by_webui_session(
|
|||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
body = resp.json()
|
body = resp.json()
|
||||||
assert [job["name"] for job in body["jobs"]] == ["Morning check"]
|
assert [job["name"] for job in body["jobs"]] == [
|
||||||
|
"Morning check",
|
||||||
|
"Legacy same target",
|
||||||
|
]
|
||||||
job = body["jobs"][0]
|
job = body["jobs"][0]
|
||||||
assert job["schedule"]["kind"] == "every"
|
assert job["schedule"]["kind"] == "every"
|
||||||
assert job["schedule"]["every_ms"] == 3_600_000
|
assert job["schedule"]["every_ms"] == 3_600_000
|
||||||
assert job["payload"]["message"] == "Check the project status"
|
assert job["payload"]["message"] == "Check the project status"
|
||||||
|
assert body["jobs"][1]["payload"]["message"] == "Legacy job should still show in WebUI"
|
||||||
finally:
|
finally:
|
||||||
await channel.stop()
|
await channel.stop()
|
||||||
await server_task
|
await server_task
|
||||||
@ -743,9 +747,7 @@ async def test_session_delete_can_cascade_bound_automations(
|
|||||||
assert resp.json()["deleted"] is True
|
assert resp.json()["deleted"] is True
|
||||||
assert not path.exists()
|
assert not path.exists()
|
||||||
assert cron.list_bound_agent_jobs_for_session("websocket:doomed") == []
|
assert cron.list_bound_agent_jobs_for_session("websocket:doomed") == []
|
||||||
assert [job.name for job in cron.list_jobs(include_disabled=True)] == [
|
assert cron.list_jobs(include_disabled=True) == []
|
||||||
"Legacy same target"
|
|
||||||
]
|
|
||||||
finally:
|
finally:
|
||||||
await channel.stop()
|
await channel.stop()
|
||||||
await server_task
|
await server_task
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user