fix(webui): drop legacy automation setup state
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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({
|
||||
</AutomationDetail>
|
||||
</div>
|
||||
|
||||
{needsRecreation ? (
|
||||
<div className="rounded-[16px] border border-amber-500/20 bg-amber-500/8 px-3 py-2 text-[12px] leading-5 text-amber-800 dark:text-amber-200">
|
||||
{tx(
|
||||
"settings.automations.legacyWarning",
|
||||
"This older automation is missing its target chat. Recreate it from the chat or channel where it should run.",
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{job.state.last_error ? (
|
||||
<div className="rounded-[16px] border border-destructive/20 bg-destructive/8 px-3 py-2 text-[12px] leading-5 text-destructive">
|
||||
{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";
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -525,18 +525,15 @@
|
||||
"system": "システム",
|
||||
"pending": "待機中",
|
||||
"running": "実行中",
|
||||
"needsSetup": "再設定が必要",
|
||||
"paused": "一時停止",
|
||||
"failed": "失敗",
|
||||
"completed": "完了",
|
||||
"noSchedule": "スケジュールなし",
|
||||
"active": "実行中"
|
||||
},
|
||||
"legacyWarning": "この古い自動タスクには対象チャットがありません。実行先のチャットまたは外部 channel から作り直してください。",
|
||||
"origin": {
|
||||
"system": "システム",
|
||||
"unknown": "関連チャットなし",
|
||||
"legacy": "対象チャットで作り直してください"
|
||||
"unknown": "関連チャットなし"
|
||||
},
|
||||
"channels": {
|
||||
"api": "API",
|
||||
|
||||
@@ -525,18 +525,15 @@
|
||||
"system": "시스템",
|
||||
"pending": "대기 중",
|
||||
"running": "실행 중",
|
||||
"needsSetup": "설정 필요",
|
||||
"paused": "일시 중지",
|
||||
"failed": "실패",
|
||||
"completed": "완료",
|
||||
"noSchedule": "일정 없음",
|
||||
"active": "활성"
|
||||
},
|
||||
"legacyWarning": "이전 자동화에 대상 채팅이 없습니다. 실행되어야 하는 채팅 또는 외부 channel에서 다시 만드세요.",
|
||||
"origin": {
|
||||
"system": "시스템",
|
||||
"unknown": "연결된 채팅 없음",
|
||||
"legacy": "대상 채팅에서 다시 만드세요"
|
||||
"unknown": "연결된 채팅 없음"
|
||||
},
|
||||
"channels": {
|
||||
"api": "API",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -525,18 +525,15 @@
|
||||
"system": "系统",
|
||||
"pending": "等待中",
|
||||
"running": "正在运行",
|
||||
"needsSetup": "需重新设置",
|
||||
"paused": "已暂停",
|
||||
"failed": "失败",
|
||||
"completed": "已完成",
|
||||
"noSchedule": "无计划",
|
||||
"active": "运行中"
|
||||
},
|
||||
"legacyWarning": "这个旧版自动任务缺少目标会话。请从它应该运行的聊天或外部 channel 中重新创建。",
|
||||
"origin": {
|
||||
"system": "系统",
|
||||
"unknown": "未关联会话",
|
||||
"legacy": "请在目标会话中重新创建"
|
||||
"unknown": "未关联会话"
|
||||
},
|
||||
"channels": {
|
||||
"api": "API",
|
||||
|
||||
@@ -525,18 +525,15 @@
|
||||
"system": "系統",
|
||||
"pending": "等待中",
|
||||
"running": "正在執行",
|
||||
"needsSetup": "需重新設定",
|
||||
"paused": "已暫停",
|
||||
"failed": "失敗",
|
||||
"completed": "已完成",
|
||||
"noSchedule": "無排程",
|
||||
"active": "執行中"
|
||||
},
|
||||
"legacyWarning": "這個舊版自動任務缺少目標會話。請從它應該執行的聊天或外部 channel 中重新建立。",
|
||||
"origin": {
|
||||
"system": "系統",
|
||||
"unknown": "未關聯會話",
|
||||
"legacy": "請在目標會話中重新建立"
|
||||
"unknown": "未關聯會話"
|
||||
},
|
||||
"channels": {
|
||||
"api": "API",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user