fix(webui): show pending local triggers
This commit is contained in:
@@ -71,6 +71,7 @@ class ChannelManager:
|
||||
local_trigger_store: Any | None = None,
|
||||
webui_runtime_model_name: Callable[[], str | None] | None = None,
|
||||
webui_cron_pending_job_ids: Callable[[str], set[str]] | None = None,
|
||||
webui_local_trigger_pending_ids: Callable[[str], set[str]] | None = None,
|
||||
webui_static_dist: bool = True,
|
||||
webui_runtime_surface: str = "browser",
|
||||
webui_runtime_capabilities: dict[str, Any] | None = None,
|
||||
@@ -82,6 +83,7 @@ class ChannelManager:
|
||||
self._local_trigger_store = local_trigger_store
|
||||
self._webui_runtime_model_name = webui_runtime_model_name
|
||||
self._webui_cron_pending_job_ids = webui_cron_pending_job_ids
|
||||
self._webui_local_trigger_pending_ids = webui_local_trigger_pending_ids
|
||||
self._webui_static_dist = webui_static_dist
|
||||
self._webui_runtime_surface = webui_runtime_surface
|
||||
self._webui_runtime_capabilities = dict(webui_runtime_capabilities or {})
|
||||
@@ -143,6 +145,7 @@ class ChannelManager:
|
||||
cron_service=self._cron_service,
|
||||
local_trigger_store=self._local_trigger_store,
|
||||
cron_pending_job_ids=self._webui_cron_pending_job_ids,
|
||||
local_trigger_pending_ids=self._webui_local_trigger_pending_ids,
|
||||
logger=logger,
|
||||
)
|
||||
kwargs["gateway"] = gateway
|
||||
|
||||
@@ -1154,6 +1154,11 @@ def _run_gateway(
|
||||
local_trigger_store=trigger_store,
|
||||
webui_runtime_model_name=_webui_runtime_model_name,
|
||||
webui_cron_pending_job_ids=getattr(agent, "pending_cron_job_ids_for_session", None),
|
||||
webui_local_trigger_pending_ids=getattr(
|
||||
agent,
|
||||
"pending_local_trigger_ids_for_session",
|
||||
None,
|
||||
),
|
||||
webui_static_dist=webui_static_dist,
|
||||
webui_runtime_surface=webui_runtime_surface,
|
||||
webui_runtime_capabilities=webui_runtime_capabilities,
|
||||
|
||||
@@ -28,6 +28,7 @@ class GatewayServices:
|
||||
cron_service: Any | None
|
||||
local_trigger_store: Any | None
|
||||
cron_pending_job_ids: Callable[[str], set[str]] | None
|
||||
local_trigger_pending_ids: Callable[[str], set[str]] | None
|
||||
|
||||
|
||||
def build_gateway_services(
|
||||
@@ -45,6 +46,7 @@ def build_gateway_services(
|
||||
cron_service: Any | None = None,
|
||||
local_trigger_store: Any | None = None,
|
||||
cron_pending_job_ids: Callable[[str], set[str]] | None = None,
|
||||
local_trigger_pending_ids: Callable[[str], set[str]] | None = None,
|
||||
logger: Any = default_logger,
|
||||
) -> GatewayServices:
|
||||
tokens = GatewayTokenStore()
|
||||
@@ -74,6 +76,7 @@ def build_gateway_services(
|
||||
cron_service=cron_service,
|
||||
local_trigger_store=local_trigger_store,
|
||||
cron_pending_job_ids=cron_pending_job_ids,
|
||||
local_trigger_pending_ids=local_trigger_pending_ids,
|
||||
log=logger,
|
||||
)
|
||||
return GatewayServices(
|
||||
@@ -86,4 +89,5 @@ def build_gateway_services(
|
||||
cron_service=cron_service,
|
||||
local_trigger_store=local_trigger_store,
|
||||
cron_pending_job_ids=cron_pending_job_ids,
|
||||
local_trigger_pending_ids=local_trigger_pending_ids,
|
||||
)
|
||||
|
||||
@@ -135,6 +135,7 @@ def _serialize_job(
|
||||
if isinstance(job, LocalTrigger):
|
||||
return _serialize_trigger(
|
||||
job,
|
||||
pending=pending,
|
||||
include_details=include_details,
|
||||
session_manager=session_manager,
|
||||
)
|
||||
@@ -189,6 +190,7 @@ def _serialize_job(
|
||||
def _serialize_trigger(
|
||||
trigger: LocalTrigger,
|
||||
*,
|
||||
pending: bool = False,
|
||||
include_details: bool = False,
|
||||
session_manager: _SessionManagerLike | None = None,
|
||||
) -> dict[str, Any]:
|
||||
@@ -213,7 +215,7 @@ def _serialize_trigger(
|
||||
"state": {
|
||||
"next_run_at_ms": None,
|
||||
"last_status": trigger.last_status,
|
||||
"pending": False,
|
||||
"pending": pending,
|
||||
},
|
||||
}
|
||||
if not include_details:
|
||||
|
||||
@@ -157,6 +157,7 @@ class GatewayHTTPHandler:
|
||||
cron_service: CronService | None = None,
|
||||
local_trigger_store: LocalTriggerStore | None = None,
|
||||
cron_pending_job_ids: Callable[[str], set[str]] | None = None,
|
||||
local_trigger_pending_ids: Callable[[str], set[str]] | None = None,
|
||||
log: Any = logger,
|
||||
) -> None:
|
||||
self.config = config
|
||||
@@ -172,6 +173,7 @@ class GatewayHTTPHandler:
|
||||
self.cron_service = cron_service
|
||||
self.local_trigger_store = local_trigger_store
|
||||
self.cron_pending_job_ids = cron_pending_job_ids
|
||||
self.local_trigger_pending_ids = local_trigger_pending_ids
|
||||
self._log = log
|
||||
self._runtime_surface = runtime_surface
|
||||
|
||||
@@ -487,9 +489,7 @@ class GatewayHTTPHandler:
|
||||
return _http_error(400, "invalid session key")
|
||||
if not _is_websocket_channel_session_key(decoded_key):
|
||||
return _http_error(404, "session not found")
|
||||
pending_job_ids: set[str] = set()
|
||||
if self.cron_pending_job_ids is not None:
|
||||
pending_job_ids = self.cron_pending_job_ids(decoded_key)
|
||||
pending_job_ids = self._pending_automation_ids_for_session(decoded_key)
|
||||
return _http_json_response(
|
||||
session_automations_payload(
|
||||
self.cron_service,
|
||||
@@ -561,15 +561,37 @@ class GatewayHTTPHandler:
|
||||
pending.update(self.cron_pending_job_ids(session_key))
|
||||
return pending
|
||||
|
||||
def _pending_local_trigger_ids_for_all(self) -> set[str]:
|
||||
if self.local_trigger_store is None or self.local_trigger_pending_ids is None:
|
||||
return set()
|
||||
pending: set[str] = set()
|
||||
for trigger in self.local_trigger_store.list_triggers(include_disabled=True):
|
||||
session_key = trigger.session_key
|
||||
if not session_key and trigger.channel and trigger.chat_id:
|
||||
session_key = f"{trigger.channel}:{trigger.chat_id}"
|
||||
if session_key:
|
||||
pending.update(self.local_trigger_pending_ids(session_key))
|
||||
return pending
|
||||
|
||||
def _pending_automation_ids_for_session(self, session_key: str) -> set[str]:
|
||||
pending: set[str] = set()
|
||||
if self.cron_pending_job_ids is not None:
|
||||
pending.update(self.cron_pending_job_ids(session_key))
|
||||
if self.local_trigger_pending_ids is not None:
|
||||
pending.update(self.local_trigger_pending_ids(session_key))
|
||||
return pending
|
||||
|
||||
def _handle_webui_automations(self, request: WsRequest) -> Response:
|
||||
if not self.check_api_token(request):
|
||||
return _http_error(401, "Unauthorized")
|
||||
pending_job_ids = self._pending_cron_job_ids_for_all()
|
||||
pending_job_ids.update(self._pending_local_trigger_ids_for_all())
|
||||
return _http_json_response(
|
||||
all_automations_payload(
|
||||
self.cron_service,
|
||||
local_trigger_store=self.local_trigger_store,
|
||||
session_manager=self.session_manager,
|
||||
pending_job_ids=self._pending_cron_job_ids_for_all(),
|
||||
pending_job_ids=pending_job_ids,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ def _make_handler(
|
||||
cron_service: CronService | None = None,
|
||||
local_trigger_store: LocalTriggerStore | None = None,
|
||||
cron_pending_job_ids: Any | None = None,
|
||||
local_trigger_pending_ids: Any | None = None,
|
||||
) -> GatewayServices:
|
||||
config = WebSocketConfig.model_validate(cfg) if isinstance(cfg, dict) else cfg
|
||||
workspace = workspace_path or Path.cwd()
|
||||
@@ -65,6 +66,7 @@ def _make_handler(
|
||||
cron_service=cron_service,
|
||||
local_trigger_store=local_trigger_store,
|
||||
cron_pending_job_ids=cron_pending_job_ids,
|
||||
local_trigger_pending_ids=local_trigger_pending_ids,
|
||||
)
|
||||
|
||||
|
||||
@@ -79,6 +81,7 @@ def _ch(
|
||||
cron_service: CronService | None = None,
|
||||
local_trigger_store: LocalTriggerStore | None = None,
|
||||
cron_pending_job_ids: Any | None = None,
|
||||
local_trigger_pending_ids: Any | None = None,
|
||||
**extra: Any,
|
||||
) -> WebSocketChannel:
|
||||
cfg: dict[str, Any] = {
|
||||
@@ -99,6 +102,7 @@ def _ch(
|
||||
cron_service=cron_service,
|
||||
local_trigger_store=local_trigger_store,
|
||||
cron_pending_job_ids=cron_pending_job_ids,
|
||||
local_trigger_pending_ids=local_trigger_pending_ids,
|
||||
)
|
||||
return WebSocketChannel(cfg, bus, gateway=gateway)
|
||||
|
||||
@@ -341,6 +345,9 @@ async def test_session_automations_route_lists_local_triggers(
|
||||
bus,
|
||||
session_manager=_seed_session(tmp_path, key="websocket:abc"),
|
||||
local_trigger_store=trigger_store,
|
||||
local_trigger_pending_ids=lambda key: (
|
||||
{trigger.id} if key == "websocket:abc" else set()
|
||||
),
|
||||
port=port,
|
||||
)
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
@@ -363,6 +370,7 @@ async def test_session_automations_route_lists_local_triggers(
|
||||
assert job["schedule"]["kind"] == "local"
|
||||
assert job["payload"]["kind"] == "local_trigger"
|
||||
assert job["payload"]["command"] == f'nanobot trigger {trigger.id} "message"'
|
||||
assert job["state"]["pending"] is True
|
||||
finally:
|
||||
await channel.stop()
|
||||
await server_task
|
||||
@@ -1146,6 +1154,9 @@ async def test_webui_automations_route_manages_local_triggers(
|
||||
bus,
|
||||
session_manager=_seed_session(tmp_path, key="websocket:abc"),
|
||||
local_trigger_store=trigger_store,
|
||||
local_trigger_pending_ids=lambda key: (
|
||||
{trigger.id} if key == "websocket:abc" else set()
|
||||
),
|
||||
port=port,
|
||||
)
|
||||
server_task = asyncio.create_task(channel.start())
|
||||
@@ -1159,6 +1170,7 @@ async def test_webui_automations_route_manages_local_triggers(
|
||||
assert listed.status_code == 200
|
||||
by_id = {job["id"]: job for job in listed.json()["jobs"]}
|
||||
assert by_id[trigger.id]["kind"] == "local_trigger"
|
||||
assert by_id[trigger.id]["state"]["pending"] is True
|
||||
assert by_id[trigger.id]["trigger"]["command"] == f'nanobot trigger {trigger.id} "message"'
|
||||
|
||||
disabled = await _http_get(
|
||||
|
||||
Reference in New Issue
Block a user