fix(webui): show pending local triggers

This commit is contained in:
chengyongru
2026-07-02 13:32:46 +08:00
committed by Xubin Ren
parent f32007c83f
commit afef27dd6c
6 changed files with 53 additions and 5 deletions
+3
View File
@@ -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
+5
View File
@@ -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,
+4
View File
@@ -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,
)
+3 -1
View File
@@ -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:
+26 -4
View File
@@ -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(