diff --git a/nanobot/webui/session_automations.py b/nanobot/webui/session_automations.py index 308ec3aa..19340856 100644 --- a/nanobot/webui/session_automations.py +++ b/nanobot/webui/session_automations.py @@ -152,11 +152,11 @@ def _origin_payload( job: CronJob, session_manager: _SessionManagerLike | None, ) -> dict[str, Any] | None: - session_key = job.payload.session_key - if not session_key and job.payload.origin_channel and job.payload.origin_chat_id: - session_key = f"{job.payload.origin_channel}:{job.payload.origin_chat_id}" - if not session_key: + channel = job.payload.origin_channel + chat_id = job.payload.origin_chat_id + if not channel or not chat_id: return None + session_key = f"{channel}:{chat_id}" title = "" preview = "" @@ -166,7 +166,6 @@ def _origin_payload( title = str(data.get("title") or "") preview = _session_preview(data.get("messages")) - channel, _, chat_id = session_key.partition(":") return { "session_key": session_key, "channel": channel, diff --git a/tests/channels/test_websocket_http_routes.py b/tests/channels/test_websocket_http_routes.py index 4767f9dd..4ee76da9 100644 --- a/tests/channels/test_websocket_http_routes.py +++ b/tests/channels/test_websocket_http_routes.py @@ -826,6 +826,12 @@ async def test_webui_automations_route_lists_all_jobs_and_allows_user_actions( origin_channel="websocket", origin_chat_id="abc", ) + legacy_job = cron.add_job( + name="english-quiz", + schedule=CronSchedule(kind="every", every_ms=3_600_000), + message="Practice English", + session_key="unified:default", + ) cron.register_system_job( CronJob( id="heartbeat", @@ -862,6 +868,8 @@ async def test_webui_automations_route_lists_all_jobs_and_allows_user_actions( assert by_id[user_job.id]["state"]["run_history"] == [] assert by_id[user_job.id]["origin"]["session_key"] == "websocket:abc" assert by_id[user_job.id]["origin"]["preview"] == "hi" + assert by_id[legacy_job.id]["payload"]["session_key"] == "unified:default" + assert by_id[legacy_job.id]["origin"] is None assert by_id["heartbeat"]["protected"] is True disabled = await _http_get( diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx index c8c4fd93..c9b3ef9d 100644 --- a/webui/src/components/settings/SettingsView.tsx +++ b/webui/src/components/settings/SettingsView.tsx @@ -3521,6 +3521,9 @@ function AutomationRow({ t(key, { defaultValue: fallback, ...(values ?? {}) }); const status = automationStatus(job, tx); const origin = automationOriginLabel(job, tx); + const originHref = job.origin?.channel === "websocket" + ? `#/chat/${encodeURIComponent(job.origin.session_key)}` + : null; const history = job.state.run_history ?? []; const canManage = !job.protected; const canRun = canManage && job.enabled && !job.state.pending; @@ -3554,11 +3557,11 @@ function AutomationRow({ {formatAutomationLast(job, locale, tx)} - - {job.origin?.session_key ? ( + + {originHref ? ( {origin} @@ -3751,7 +3754,10 @@ function automationOriginLabel( ): string { if (job.protected) return tx("settings.automations.origin.system", "System"); const origin = job.origin; - if (!origin) return tx("settings.automations.origin.unknown", "Unknown session"); + if (!origin && job.payload.kind === "agent_turn") { + return tx("settings.automations.origin.legacy", "Recreate in target chat"); + } + if (!origin) return tx("settings.automations.origin.unknown", "No linked chat"); return origin.title || origin.preview || origin.session_key; } diff --git a/webui/src/i18n/locales/en/common.json b/webui/src/i18n/locales/en/common.json index 04838947..89b3ec99 100644 --- a/webui/src/i18n/locales/en/common.json +++ b/webui/src/i18n/locales/en/common.json @@ -493,7 +493,7 @@ "schedule": "Schedule", "next": "Next", "last": "Last", - "origin": "Origin" + "origin": "Linked chat" }, "runNow": "Run now", "pause": "Pause", @@ -512,7 +512,8 @@ }, "origin": { "system": "System", - "unknown": "Unknown session" + "unknown": "No linked chat", + "legacy": "Recreate in target chat" }, "schedule": { "at": "At {{time}}", diff --git a/webui/src/i18n/locales/es/common.json b/webui/src/i18n/locales/es/common.json index c04429ee..c52a531c 100644 --- a/webui/src/i18n/locales/es/common.json +++ b/webui/src/i18n/locales/es/common.json @@ -493,7 +493,7 @@ "schedule": "Programación", "next": "Siguiente", "last": "Última", - "origin": "Origen" + "origin": "Chat vinculado" }, "runNow": "Ejecutar ahora", "pause": "Pausar", @@ -512,7 +512,8 @@ }, "origin": { "system": "Sistema", - "unknown": "Sesión desconocida" + "unknown": "Sin chat vinculado", + "legacy": "Recréala en el chat de destino" }, "schedule": { "at": "A las {{time}}", diff --git a/webui/src/i18n/locales/fr/common.json b/webui/src/i18n/locales/fr/common.json index 36fabcfe..6a6bb70e 100644 --- a/webui/src/i18n/locales/fr/common.json +++ b/webui/src/i18n/locales/fr/common.json @@ -493,7 +493,7 @@ "schedule": "Planning", "next": "Prochaine", "last": "Dernière", - "origin": "Origine" + "origin": "Discussion liée" }, "runNow": "Exécuter maintenant", "pause": "Mettre en pause", @@ -512,7 +512,8 @@ }, "origin": { "system": "Système", - "unknown": "Session inconnue" + "unknown": "Aucune discussion liée", + "legacy": "Recréez-la dans la discussion cible" }, "schedule": { "at": "À {{time}}", diff --git a/webui/src/i18n/locales/id/common.json b/webui/src/i18n/locales/id/common.json index 522735d6..9c49045b 100644 --- a/webui/src/i18n/locales/id/common.json +++ b/webui/src/i18n/locales/id/common.json @@ -493,7 +493,7 @@ "schedule": "Jadwal", "next": "Berikutnya", "last": "Terakhir", - "origin": "Asal" + "origin": "Chat tertaut" }, "runNow": "Jalankan sekarang", "pause": "Jeda", @@ -512,7 +512,8 @@ }, "origin": { "system": "Sistem", - "unknown": "Sesi tidak dikenal" + "unknown": "Tidak ada chat tertaut", + "legacy": "Buat ulang di chat tujuan" }, "schedule": { "at": "Pada {{time}}", diff --git a/webui/src/i18n/locales/ja/common.json b/webui/src/i18n/locales/ja/common.json index 57a3d0b1..34bc8262 100644 --- a/webui/src/i18n/locales/ja/common.json +++ b/webui/src/i18n/locales/ja/common.json @@ -493,7 +493,7 @@ "schedule": "スケジュール", "next": "次回", "last": "前回", - "origin": "発生元" + "origin": "関連チャット" }, "runNow": "今すぐ実行", "pause": "一時停止", @@ -512,7 +512,8 @@ }, "origin": { "system": "システム", - "unknown": "不明なセッション" + "unknown": "関連チャットなし", + "legacy": "対象チャットで作り直してください" }, "schedule": { "at": "{{time}}", diff --git a/webui/src/i18n/locales/ko/common.json b/webui/src/i18n/locales/ko/common.json index c1acc078..1580a0ef 100644 --- a/webui/src/i18n/locales/ko/common.json +++ b/webui/src/i18n/locales/ko/common.json @@ -493,7 +493,7 @@ "schedule": "일정", "next": "다음", "last": "마지막", - "origin": "출처" + "origin": "연결된 채팅" }, "runNow": "지금 실행", "pause": "일시 중지", @@ -512,7 +512,8 @@ }, "origin": { "system": "시스템", - "unknown": "알 수 없는 세션" + "unknown": "연결된 채팅 없음", + "legacy": "대상 채팅에서 다시 만드세요" }, "schedule": { "at": "{{time}}", diff --git a/webui/src/i18n/locales/vi/common.json b/webui/src/i18n/locales/vi/common.json index 1deddbac..1d41a3f8 100644 --- a/webui/src/i18n/locales/vi/common.json +++ b/webui/src/i18n/locales/vi/common.json @@ -493,7 +493,7 @@ "schedule": "Lịch", "next": "Tiếp theo", "last": "Lần trước", - "origin": "Nguồn" + "origin": "Cuộc trò chuyện liên kết" }, "runNow": "Chạy ngay", "pause": "Tạm dừng", @@ -512,7 +512,8 @@ }, "origin": { "system": "Hệ thống", - "unknown": "Phiên không xác định" + "unknown": "Chưa liên kết cuộc trò chuyện", + "legacy": "Tạo lại trong cuộc trò chuyện đích" }, "schedule": { "at": "Vào {{time}}", diff --git a/webui/src/i18n/locales/zh-CN/common.json b/webui/src/i18n/locales/zh-CN/common.json index d6300380..6d410477 100644 --- a/webui/src/i18n/locales/zh-CN/common.json +++ b/webui/src/i18n/locales/zh-CN/common.json @@ -493,7 +493,7 @@ "schedule": "计划", "next": "下次", "last": "上次", - "origin": "来源" + "origin": "关联会话" }, "runNow": "立即运行", "pause": "暂停", @@ -512,7 +512,8 @@ }, "origin": { "system": "系统", - "unknown": "未知会话" + "unknown": "未关联会话", + "legacy": "请在目标会话中重新创建" }, "schedule": { "at": "在 {{time}}", diff --git a/webui/src/i18n/locales/zh-TW/common.json b/webui/src/i18n/locales/zh-TW/common.json index 4d1bab31..8e59eeb0 100644 --- a/webui/src/i18n/locales/zh-TW/common.json +++ b/webui/src/i18n/locales/zh-TW/common.json @@ -493,7 +493,7 @@ "schedule": "排程", "next": "下次", "last": "上次", - "origin": "來源" + "origin": "關聯會話" }, "runNow": "立即執行", "pause": "暫停", @@ -512,7 +512,8 @@ }, "origin": { "system": "系統", - "unknown": "未知會話" + "unknown": "未關聯會話", + "legacy": "請在目標會話中重新建立" }, "schedule": { "at": "於 {{time}}", diff --git a/webui/src/tests/app-layout.test.tsx b/webui/src/tests/app-layout.test.tsx index a52099de..1c9f04fb 100644 --- a/webui/src/tests/app-layout.test.tsx +++ b/webui/src/tests/app-layout.test.tsx @@ -365,6 +365,26 @@ describe("App layout", () => { preview: "Check release blockers", }, }, + { + id: "legacy-quiz", + name: "english-quiz", + enabled: true, + protected: false, + delete_after_run: false, + schedule: { kind: "cron", expr: "30 9-23 * * *", tz: "Asia/Shanghai" }, + payload: { + message: "Practice English", + kind: "agent_turn", + session_key: "unified:default", + }, + state: { + next_run_at_ms: Date.UTC(2026, 3, 17, 11, 0, 0), + last_status: "ok", + pending: false, + run_history: [], + }, + origin: null, + }, { id: "heartbeat", name: "heartbeat", @@ -393,6 +413,9 @@ describe("App layout", () => { expect(screen.getByText("Daily repo check")).toBeInTheDocument(); expect(screen.getByText("Check the repo status")).toBeInTheDocument(); expect(screen.getByText("Release prep")).toBeInTheDocument(); + expect(screen.getByText("english-quiz")).toBeInTheDocument(); + expect(screen.getByText("Recreate in target chat")).toBeInTheDocument(); + expect(screen.queryByText("unified:default")).not.toBeInTheDocument(); expect(screen.getByText("heartbeat")).toBeInTheDocument(); expect(within(sidebar).getByRole("button", { name: "Automations" })).toHaveAttribute( "aria-current",