fix(webui): avoid fake automation origins

This commit is contained in:
chengyongru
2026-06-14 00:04:15 +08:00
parent 43830b7162
commit 5b10102629
13 changed files with 72 additions and 27 deletions
+4 -5
View File
@@ -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,
@@ -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(
+10 -4
View File
@@ -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({
<AutomationDetail label={tx("settings.automations.labels.last", "Last")}>
{formatAutomationLast(job, locale, tx)}
</AutomationDetail>
<AutomationDetail label={tx("settings.automations.labels.origin", "Origin")}>
{job.origin?.session_key ? (
<AutomationDetail label={tx("settings.automations.labels.origin", "Linked chat")}>
{originHref ? (
<a
className="inline-flex max-w-full items-center gap-1 text-foreground/80 underline-offset-2 hover:underline"
href={`#/chat/${encodeURIComponent(job.origin.session_key)}`}
href={originHref}
>
<span className="truncate">{origin}</span>
<ExternalLink className="h-3 w-3 shrink-0" aria-hidden />
@@ -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;
}
+3 -2
View File
@@ -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}}",
+3 -2
View File
@@ -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}}",
+3 -2
View File
@@ -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}}",
+3 -2
View File
@@ -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}}",
+3 -2
View File
@@ -493,7 +493,7 @@
"schedule": "スケジュール",
"next": "次回",
"last": "前回",
"origin": "発生元"
"origin": "関連チャット"
},
"runNow": "今すぐ実行",
"pause": "一時停止",
@@ -512,7 +512,8 @@
},
"origin": {
"system": "システム",
"unknown": "不明なセッション"
"unknown": "関連チャットなし",
"legacy": "対象チャットで作り直してください"
},
"schedule": {
"at": "{{time}}",
+3 -2
View File
@@ -493,7 +493,7 @@
"schedule": "일정",
"next": "다음",
"last": "마지막",
"origin": "출처"
"origin": "연결된 채팅"
},
"runNow": "지금 실행",
"pause": "일시 중지",
@@ -512,7 +512,8 @@
},
"origin": {
"system": "시스템",
"unknown": "알 수 없는 세션"
"unknown": "연결된 채팅 없음",
"legacy": "대상 채팅에서 다시 만드세요"
},
"schedule": {
"at": "{{time}}",
+3 -2
View File
@@ -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}}",
+3 -2
View File
@@ -493,7 +493,7 @@
"schedule": "计划",
"next": "下次",
"last": "上次",
"origin": "来源"
"origin": "关联会话"
},
"runNow": "立即运行",
"pause": "暂停",
@@ -512,7 +512,8 @@
},
"origin": {
"system": "系统",
"unknown": "未会话"
"unknown": "未关联会话",
"legacy": "请在目标会话中重新创建"
},
"schedule": {
"at": "在 {{time}}",
+3 -2
View File
@@ -493,7 +493,7 @@
"schedule": "排程",
"next": "下次",
"last": "上次",
"origin": "來源"
"origin": "關聯會話"
},
"runNow": "立即執行",
"pause": "暫停",
@@ -512,7 +512,8 @@
},
"origin": {
"system": "系統",
"unknown": "未會話"
"unknown": "未關聯會話",
"legacy": "請在目標會話中重新建立"
},
"schedule": {
"at": "於 {{time}}",
+23
View File
@@ -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",