diff --git a/README.md b/README.md index 3f980225..b849c20e 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ ## 📢 News +- **2026-06-16** ⚠️ Breaking change: agent-turn automation jobs now require complete target chat or channel metadata. Cron jobs created by older versions without that metadata should be recreated from the target chat or channel; existing cron records remain in the cron store. - **2026-06-01** 🚀 Released **v0.2.1** — **The Workbench Release** turns the packaged WebUI into a daily agent workbench: clearer Thought/response timelines, live file-edit activity, project workspaces, model and context controls, steadier sustained goals, CLI Apps + MCP extensions, and broader provider/channel support. Please see [release notes](https://github.com/HKUDS/nanobot/releases/tag/v0.2.1) for details. - **2026-05-30** 🔐 Safer Matrix verification, bounded media downloads, clearer WebUI model timeline. - **2026-05-29** 🧩 Extension registry, context-window tuning, document extraction controls. diff --git a/nanobot/webui/session_automations.py b/nanobot/webui/session_automations.py index 0780d781..0680dde7 100644 --- a/nanobot/webui/session_automations.py +++ b/nanobot/webui/session_automations.py @@ -122,16 +122,6 @@ def _serialize_job( payload["created_at_ms"] = job.created_at_ms payload["updated_at_ms"] = job.updated_at_ms payload["payload"].update({"kind": job.payload.kind}) - if _expose_origin_identifiers(job): - payload["payload"].update( - { - "session_key": job.payload.session_key, - "origin_channel": job.payload.origin_channel, - "origin_chat_id": job.payload.origin_chat_id, - } - ) - elif job.payload.origin_channel: - payload["payload"]["origin_channel"] = job.payload.origin_channel payload["state"].update( { "last_run_at_ms": job.state.last_run_at_ms, @@ -183,12 +173,6 @@ def _origin_payload( "preview": preview, } - -def _expose_origin_identifiers(job: CronJob) -> bool: - channel = job.payload.origin_channel - return not channel or channel == "websocket" - - def _session_preview(messages: Any) -> str: if not isinstance(messages, list): return "" diff --git a/tests/channels/test_websocket_http_routes.py b/tests/channels/test_websocket_http_routes.py index 885cc37a..4451f486 100644 --- a/tests/channels/test_websocket_http_routes.py +++ b/tests/channels/test_websocket_http_routes.py @@ -842,7 +842,7 @@ 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( + incomplete_job = cron.add_job( name="english-quiz", schedule=CronSchedule(kind="every", every_ms=3_600_000), message="Practice English", @@ -899,6 +899,7 @@ async def test_webui_automations_route_lists_all_jobs_and_allows_user_actions( ) assert resp.status_code == 200 assert "wx-chat" not in resp.text + assert "unified:default" not in resp.text body = resp.json() by_id = {job["id"]: job for job in body["jobs"]} assert by_id[user_job.id]["protected"] is False @@ -906,10 +907,12 @@ 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[external_job.id]["payload"]["origin_channel"] == "weixin" + assert "session_key" not in by_id[incomplete_job.id]["payload"] + assert "origin_channel" not in by_id[incomplete_job.id]["payload"] + assert "origin_chat_id" not in by_id[incomplete_job.id]["payload"] + assert by_id[incomplete_job.id]["origin"] is None assert "session_key" not in by_id[external_job.id]["payload"] + assert "origin_channel" not in by_id[external_job.id]["payload"] assert "origin_chat_id" not in by_id[external_job.id]["payload"] assert by_id[external_job.id]["origin"]["channel"] == "weixin" assert "session_key" not in by_id[external_job.id]["origin"] diff --git a/webui/src/components/settings/SettingsView.tsx b/webui/src/components/settings/SettingsView.tsx index c6efe0d0..71c5b58b 100644 --- a/webui/src/components/settings/SettingsView.tsx +++ b/webui/src/components/settings/SettingsView.tsx @@ -3698,7 +3698,6 @@ function AutomationDetailPanel({ const originHref = job.origin?.channel === "websocket" && job.origin.session_key ? `#/chat/${encodeURIComponent(job.origin.session_key)}` : null; - const needsRecreation = automationNeedsRecreation(job); const created = job.created_at_ms ? fmtDateTime(job.created_at_ms, locale) : null; const updated = job.updated_at_ms ? fmtDateTime(job.updated_at_ms, locale) : null; const message = job.payload.message || tx("settings.automations.systemTask", "System-managed automation"); @@ -3787,15 +3786,6 @@ function AutomationDetailPanel({ - {needsRecreation ? ( -
- {tx( - "settings.automations.legacyWarning", - "This older automation is missing its target chat. Recreate it from the chat or channel where it should run.", - )} -
- ) : null} - {job.state.last_error ? (
{job.state.last_error} @@ -4227,19 +4217,14 @@ function AutomationDeleteDialog({ ); } -function automationNeedsRecreation(job: SessionAutomationJob): boolean { - return !job.protected && !job.origin && job.payload.kind === "agent_turn"; -} - function automationNeedsAttention(job: SessionAutomationJob): boolean { - return automationNeedsRecreation(job) || job.state.last_status === "error"; + return job.state.last_status === "error"; } function automationStatusKey( job: SessionAutomationJob, -): "active" | "running" | "paused" | "failed" | "system" | "needs_setup" | "completed" | "idle" { +): "active" | "running" | "paused" | "failed" | "system" | "completed" | "idle" { if (job.protected) return "system"; - if (automationNeedsRecreation(job)) return "needs_setup"; if (job.state.pending) return "running"; if (!job.enabled) return "paused"; if (job.state.last_status === "error") return "failed"; @@ -4412,9 +4397,6 @@ function automationStatus( ): { label: string; tone: "neutral" | "success" | "warning" } { const status = automationStatusKey(job); if (status === "system") return { label: tx("settings.automations.status.system", "System"), tone: "neutral" }; - if (status === "needs_setup") { - return { label: tx("settings.automations.status.needsSetup", "Needs setup"), tone: "warning" }; - } if (status === "running") { return { label: tx("settings.automations.status.running", "Running now"), tone: "warning" }; } @@ -4437,9 +4419,6 @@ function automationOriginLabel( ): string { if (job.protected) return tx("settings.automations.origin.system", "System"); const origin = job.origin; - 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"); if (origin.channel !== "websocket") return automationChannelLabel(origin.channel, tx); return origin.title || origin.preview || origin.session_key || automationChannelLabel(origin.channel, tx); @@ -4574,7 +4553,7 @@ function formatAutomationNextTitle( function automationStatusDotClass(job: SessionAutomationJob): string { const status = automationStatusKey(job); if (status === "active" || status === "running") return "bg-emerald-500"; - if (status === "failed" || status === "needs_setup") return "bg-amber-500"; + if (status === "failed") return "bg-amber-500"; if (status === "system") return "bg-blue-500"; return "bg-muted-foreground/45"; } diff --git a/webui/src/i18n/locales/en/common.json b/webui/src/i18n/locales/en/common.json index aaa35a21..372173b6 100644 --- a/webui/src/i18n/locales/en/common.json +++ b/webui/src/i18n/locales/en/common.json @@ -525,18 +525,15 @@ "system": "System", "pending": "Pending", "running": "Running now", - "needsSetup": "Needs setup", "paused": "Paused", "failed": "Failed", "completed": "Completed", "noSchedule": "No schedule", "active": "Active" }, - "legacyWarning": "This older automation is missing its target chat. Recreate it from the chat or channel where it should run.", "origin": { "system": "System", - "unknown": "No linked chat", - "legacy": "Recreate in target chat" + "unknown": "No linked chat" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/es/common.json b/webui/src/i18n/locales/es/common.json index aba33564..cc27c365 100644 --- a/webui/src/i18n/locales/es/common.json +++ b/webui/src/i18n/locales/es/common.json @@ -525,18 +525,15 @@ "system": "Sistema", "pending": "Pendiente", "running": "Ejecutándose ahora", - "needsSetup": "Requiere configuración", "paused": "Pausada", "failed": "Fallida", "completed": "Completada", "noSchedule": "Sin programación", "active": "Activa" }, - "legacyWarning": "Esta automatización antigua no tiene chat de destino. Vuelve a crearla desde el chat o canal donde debe ejecutarse.", "origin": { "system": "Sistema", - "unknown": "Sin chat vinculado", - "legacy": "Recréala en el chat de destino" + "unknown": "Sin chat vinculado" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/fr/common.json b/webui/src/i18n/locales/fr/common.json index 5ce9b6dc..9e2c4268 100644 --- a/webui/src/i18n/locales/fr/common.json +++ b/webui/src/i18n/locales/fr/common.json @@ -525,18 +525,15 @@ "system": "Système", "pending": "En attente", "running": "En cours d’exécution", - "needsSetup": "Configuration requise", "paused": "En pause", "failed": "Échouée", "completed": "Terminée", "noSchedule": "Aucun planning", "active": "En cours" }, - "legacyWarning": "Cette ancienne automatisation n’a pas de discussion cible. Recréez-la depuis la discussion ou le canal où elle doit s’exécuter.", "origin": { "system": "Système", - "unknown": "Aucune discussion liée", - "legacy": "Recréez-la dans la discussion cible" + "unknown": "Aucune discussion liée" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/id/common.json b/webui/src/i18n/locales/id/common.json index adab95db..6885d3a1 100644 --- a/webui/src/i18n/locales/id/common.json +++ b/webui/src/i18n/locales/id/common.json @@ -525,18 +525,15 @@ "system": "Sistem", "pending": "Menunggu", "running": "Sedang berjalan", - "needsSetup": "Perlu disiapkan", "paused": "Dijeda", "failed": "Gagal", "completed": "Selesai", "noSchedule": "Tanpa jadwal", "active": "Aktif" }, - "legacyWarning": "Otomasi lama ini tidak memiliki chat tujuan. Buat ulang dari chat atau channel tempat otomasi akan berjalan.", "origin": { "system": "Sistem", - "unknown": "Tidak ada chat tertaut", - "legacy": "Buat ulang di chat tujuan" + "unknown": "Tidak ada chat tertaut" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/ja/common.json b/webui/src/i18n/locales/ja/common.json index 632b4f42..e5be1c4d 100644 --- a/webui/src/i18n/locales/ja/common.json +++ b/webui/src/i18n/locales/ja/common.json @@ -525,18 +525,15 @@ "system": "システム", "pending": "待機中", "running": "実行中", - "needsSetup": "再設定が必要", "paused": "一時停止", "failed": "失敗", "completed": "完了", "noSchedule": "スケジュールなし", "active": "実行中" }, - "legacyWarning": "この古い自動タスクには対象チャットがありません。実行先のチャットまたは外部 channel から作り直してください。", "origin": { "system": "システム", - "unknown": "関連チャットなし", - "legacy": "対象チャットで作り直してください" + "unknown": "関連チャットなし" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/ko/common.json b/webui/src/i18n/locales/ko/common.json index 15e1611b..e8741c83 100644 --- a/webui/src/i18n/locales/ko/common.json +++ b/webui/src/i18n/locales/ko/common.json @@ -525,18 +525,15 @@ "system": "시스템", "pending": "대기 중", "running": "실행 중", - "needsSetup": "설정 필요", "paused": "일시 중지", "failed": "실패", "completed": "완료", "noSchedule": "일정 없음", "active": "활성" }, - "legacyWarning": "이전 자동화에 대상 채팅이 없습니다. 실행되어야 하는 채팅 또는 외부 channel에서 다시 만드세요.", "origin": { "system": "시스템", - "unknown": "연결된 채팅 없음", - "legacy": "대상 채팅에서 다시 만드세요" + "unknown": "연결된 채팅 없음" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/vi/common.json b/webui/src/i18n/locales/vi/common.json index 2e549e1f..4132f093 100644 --- a/webui/src/i18n/locales/vi/common.json +++ b/webui/src/i18n/locales/vi/common.json @@ -525,18 +525,15 @@ "system": "Hệ thống", "pending": "Đang chờ", "running": "Đang chạy", - "needsSetup": "Cần thiết lập", "paused": "Đã tạm dừng", "failed": "Thất bại", "completed": "Hoàn tất", "noSchedule": "Không có lịch", "active": "Đang chạy" }, - "legacyWarning": "Tự động hóa cũ này thiếu cuộc trò chuyện đích. Hãy tạo lại từ cuộc trò chuyện hoặc channel nơi nó sẽ chạy.", "origin": { "system": "Hệ thống", - "unknown": "Chưa liên kết cuộc trò chuyện", - "legacy": "Tạo lại trong cuộc trò chuyện đích" + "unknown": "Chưa liên kết cuộc trò chuyện" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/zh-CN/common.json b/webui/src/i18n/locales/zh-CN/common.json index f5e62e90..df54b609 100644 --- a/webui/src/i18n/locales/zh-CN/common.json +++ b/webui/src/i18n/locales/zh-CN/common.json @@ -525,18 +525,15 @@ "system": "系统", "pending": "等待中", "running": "正在运行", - "needsSetup": "需重新设置", "paused": "已暂停", "failed": "失败", "completed": "已完成", "noSchedule": "无计划", "active": "运行中" }, - "legacyWarning": "这个旧版自动任务缺少目标会话。请从它应该运行的聊天或外部 channel 中重新创建。", "origin": { "system": "系统", - "unknown": "未关联会话", - "legacy": "请在目标会话中重新创建" + "unknown": "未关联会话" }, "channels": { "api": "API", diff --git a/webui/src/i18n/locales/zh-TW/common.json b/webui/src/i18n/locales/zh-TW/common.json index c787de5a..2c6f438f 100644 --- a/webui/src/i18n/locales/zh-TW/common.json +++ b/webui/src/i18n/locales/zh-TW/common.json @@ -525,18 +525,15 @@ "system": "系統", "pending": "等待中", "running": "正在執行", - "needsSetup": "需重新設定", "paused": "已暫停", "failed": "失敗", "completed": "已完成", "noSchedule": "無排程", "active": "執行中" }, - "legacyWarning": "這個舊版自動任務缺少目標會話。請從它應該執行的聊天或外部 channel 中重新建立。", "origin": { "system": "系統", - "unknown": "未關聯會話", - "legacy": "請在目標會話中重新建立" + "unknown": "未關聯會話" }, "channels": { "api": "API", diff --git a/webui/src/lib/types.ts b/webui/src/lib/types.ts index 436aa8d6..88de7e38 100644 --- a/webui/src/lib/types.ts +++ b/webui/src/lib/types.ts @@ -114,9 +114,6 @@ export interface SessionAutomationJob { payload: { message: string; kind?: "agent_turn" | "system_event" | string; - session_key?: string | null; - origin_channel?: string | null; - origin_chat_id?: string | null; }; state: { next_run_at_ms?: number | null; diff --git a/webui/src/tests/app-layout.test.tsx b/webui/src/tests/app-layout.test.tsx index ff3e95d1..46657c28 100644 --- a/webui/src/tests/app-layout.test.tsx +++ b/webui/src/tests/app-layout.test.tsx @@ -372,7 +372,6 @@ describe("App layout", () => { payload: { message: "Check the repo status", kind: "agent_turn", - session_key: "websocket:chat-a", }, state: { next_run_at_ms: Date.UTC(2026, 3, 17, 10, 0, 0), @@ -388,26 +387,6 @@ 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: "external-quiz", name: "WeChat quiz", @@ -418,7 +397,6 @@ describe("App layout", () => { payload: { message: "Send a quiz", kind: "agent_turn", - origin_channel: "weixin", }, state: { next_run_at_ms: Date.UTC(2026, 3, 17, 11, 30, 0), @@ -464,9 +442,6 @@ describe("App layout", () => { expect(screen.getAllByText("Daily repo check").length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText("Check the repo status").length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText("Release prep").length).toBeGreaterThanOrEqual(1); - expect(screen.getByText("english-quiz")).toBeInTheDocument(); - expect(screen.getByText("Recreate in target chat")).toBeInTheDocument(); - expect(screen.queryByText("unified:default")).not.toBeInTheDocument(); expect(screen.getByText("WeChat quiz")).toBeInTheDocument(); expect(screen.getByText("WeChat")).toBeInTheDocument(); expect(screen.queryByText("weixin:wx-chat")).not.toBeInTheDocument(); @@ -490,7 +465,6 @@ describe("App layout", () => { payload: { message: "Old one-shot message", kind: "agent_turn", - session_key: "websocket:chat-a", }, state: { next_run_at_ms: null, @@ -589,7 +563,6 @@ describe("App layout", () => { payload: { message: longMessage, kind: "agent_turn", - session_key: "websocket:chat-a", }, state: { next_run_at_ms: Date.UTC(2026, 3, 18, 10, 0, 0), @@ -650,7 +623,6 @@ describe("App layout", () => { payload: { message: "检查仓库状态", kind: "agent_turn", - session_key: "websocket:chat-a", }, state: { next_run_at_ms: Date.UTC(2026, 3, 17, 10, 0, 0),