+
+ {liveList.length > 0 && statusFilter !== "succeeded" && statusFilter !== "failed" && statusFilter !== "cancelled" && statusFilter !== "paused" && (
+
+
+ {t("liveNow")}
+ {liveList.length}
+
+
+ {liveList.map((r) => (
+ setParam("run", r.runId)} />
+ ))}
+
+
+ )}
+
+
+
+
+
+ setSearch(e.target.value)} placeholder={t("searchPlaceholder")} aria-label={t("searchLabel")} className="pl-9" />
+
+
+
+
+
+
+
+ {STATUSES.map((s) => (
+
+ ))}
+ {runs.length > 0 && (
+
+ {successRate != null ? t("countWithSuccess", { count: runs.length, rate: successRate }) : t("count", { count: runs.length })}
+
+ )}
+
+
+ {runsQ.isLoading ? (
+
+ {Array.from({ length: 8 }, (_, i) => (
+
+ ))}
+
+ ) : runsQ.isError ? (
+
}
+ title={t("loadFailed")}
+ description={errorMessage(runsQ.error)}
+ action={
+
+ }
+ />
+ ) : runs.length === 0 ? (
+
}
+ title={filtered ? t("noMatch") : t("emptyTitle")}
+ description={filtered ? undefined : t("emptyDescription")}
+ action={
+ filtered ? (
+
+ ) : undefined
+ }
+ />
+ ) : (
+
+ {groups.map((g) => (
+
+ {g.label}
+
+ {g.runs.map((r, i) => (
+
+ setParam("run", run.id)} />
+
+ ))}
+
+
+ ))}
+ {(runsQ.data?.length ?? 0) >= 200 && (
+
{t("limitHint", { count: 200 })}
+ )}
+
+ )}
+
+
!o && setParam("run", null)} />
+
+ );
+}
+
+function LiveCard({ live, agent, onOpen }: { live: LiveRun; agent?: Agent; onOpen: () => void }) {
+ const reduce = useReduceAnimations();
+ const { t } = useTranslation(["activity", "common"]);
+ const now = useNow(true);
+ return (
+
{mod ? (
diff --git a/apps/desktop/src/components/layout/app-shell.tsx b/apps/desktop/src/components/layout/app-shell.tsx
index f0c169d7..bf93c636 100644
--- a/apps/desktop/src/components/layout/app-shell.tsx
+++ b/apps/desktop/src/components/layout/app-shell.tsx
@@ -3,7 +3,6 @@ import { useEffect, useState } from "react";
import { Link, NavLink, useLocation, useNavigate } from "react-router";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import {
- Activity,
ArrowLeft,
Bot,
Box,
@@ -18,7 +17,6 @@ import {
PanelLeftClose,
PanelLeftOpen,
Phone,
- Puzzle,
Search,
Server,
Settings,
@@ -27,7 +25,6 @@ import {
X,
} from "lucide-react";
import { useTranslation } from "react-i18next";
-import { modState, modUpdateAvailable } from "@godmode/shared";
import {
Sidebar,
SidebarContent,
@@ -65,7 +62,8 @@ import { PageScrollContext } from "@/components/layout/page-scroll";
import { Callout } from "@/components/settings/settings-kit";
import { FreeTrialChip, LicenseBanner } from "@/components/license/license-banner";
import { useMediaQuery } from "@/hooks/use-media-query";
-import { useBootstrap, useMods } from "@/lib/hooks";
+import { useBootstrap } from "@/lib/hooks";
+import { usePluginsWaiting } from "@/components/mods/use-plugins-waiting";
import { useEasyMode } from "@/lib/mode";
import { api } from "@/lib/api";
import { qk } from "@/lib/queryKeys";
@@ -132,9 +130,6 @@ export function AppShell({ children }: { children: ReactNode }) {
const waiting = attention?.total ?? (boot?.counts.openQuestions ?? 0) + (boot?.counts.openMissingLogins ?? 0);
const inboxCount = waiting || (boot?.counts.unreadNotifications ?? 0);
const hiddenNav = useUi((s) => s.hiddenNav);
- const { data: mods = [] } = useMods();
- // Plugins that wait for the human: an agent's draft or install to review, code the check refuses, an update.
- const modsWaiting = mods.filter((m) => ["review", "broken"].includes(modState(m)) || modUpdateAvailable(m)).length;
const easy = useEasyMode();
// Notices when the human comes back after a while (Home then sums up what happened).
@@ -197,9 +192,7 @@ export function AppShell({ children }: { children: ReactNode }) {
{ to: "/tasks", label: t("nav.tasks"), icon:
, badge: (attention?.review ?? 0) + (attention?.blocked ?? 0) || undefined },
{ to: "/agents", label: t("nav.agents"), icon:
, badge: runningCount || undefined },
{ to: "/automations", label: t("nav.automations"), icon:
, badge: attention?.automation || undefined },
- { to: "/activity", label: t("nav.activity"), icon:
},
{ to: "/calls", label: t("nav.calls"), icon:
},
- { to: "/plugins", label: t("nav.plugins"), icon:
, badge: modsWaiting || undefined },
{ to: "/vault/logins", label: t("nav.passwords"), icon:
, also: ["/vault/2fa", "/vault/passkeys"] },
{ to: "/vault/cards", label: t("nav.cards"), icon:
},
];
@@ -220,10 +213,8 @@ export function AppShell({ children }: { children: ReactNode }) {
easyItem("/vault/logins", t("nav.passwords")),
];
const easyMore: NavItem[] = [
- easyItem("/activity", t("system:easyPages.activity.title")),
easyItem("/calls", t("system:easyPages.calls.title")),
easyItem("/vault/cards", t("system:easyPages.cards.title")),
- easyItem("/plugins", t("system:easyPages.plugins.title")),
easyItem("/browser", t("nav.browser")),
easyItem("/computer", t("system:easyPages.computer.title")),
easyItem("/vms", t("nav.vms")),
@@ -687,6 +678,7 @@ function FooterBar() {
const { data: boot } = useBootstrap();
// People waiting to talk to a bot: Settings → Messaging shows who.
const requests = boot?.settings.messaging.enabled ? boot.counts.messagingRequests : 0;
+ const plugins = usePluginsWaiting();
const connected = useLive((s) => s.connected);
const qc = useQueryClient();
const lock = useMutation({
@@ -729,16 +721,28 @@ function FooterBar() {
variant={SETTINGS_PATHS.some((p) => location.pathname.startsWith(p)) ? "secondary" : "ghost"}
size="icon"
className="relative size-8"
- aria-label={requests ? t("footer.settingsWaitingLabel", { count: requests }) : t("common:words.settings")}
+ aria-label={
+ requests
+ ? t("footer.settingsWaitingLabel", { count: requests })
+ : plugins
+ ? t("footer.settingsPluginsLabel", { count: plugins })
+ : t("common:words.settings")
+ }
asChild
>
-
+
- {requests > 0 &&
}
+ {requests + plugins > 0 &&
}
-
{requests ? t("footer.settingsWaiting", { count: requests }) : t("footer.settingsShortcut", { shortcut: `${modKey},` })}
+
+ {requests
+ ? t("footer.settingsWaiting", { count: requests })
+ : plugins
+ ? t("footer.settingsPlugins", { count: plugins })
+ : t("footer.settingsShortcut", { shortcut: `${modKey},` })}
+
);
diff --git a/apps/desktop/src/components/layout/command-palette.tsx b/apps/desktop/src/components/layout/command-palette.tsx
index 1f41965f..0cb1cc33 100644
--- a/apps/desktop/src/components/layout/command-palette.tsx
+++ b/apps/desktop/src/components/layout/command-palette.tsx
@@ -93,7 +93,7 @@ const SETTINGS = [
{ id: "messaging", label: "messaging", icon: MessageCircle, words: "chat apps slack telegram microsoft teams bots feature turn on off" },
{ id: "android", label: "android", icon: Smartphone, words: "adb usb debugging emulator device feature turn on off" },
{ id: "activity", label: "activity", icon: Activity, words: "history runs log feature turn on off" },
- { id: "plugins", label: "plugins", icon: Puzzle, words: "claude code mods skills hooks add-ons feature turn on off" },
+ { id: "plugins", label: "plugins", icon: Puzzle, words: "claude code mods skills hooks guardrails github install add-ons feature turn on off" },
{ id: "calls", label: "calls", icon: Phone, words: "elevenlabs twilio number call voice agent inbound feature turn on off" },
{ id: "cards", label: "cards", icon: CreditCard, words: "payment credit cards purchases pay feature turn on off" },
{ id: "vms", label: "vms", icon: Box, words: "vm tart macos linux feature turn on off" },
@@ -412,16 +412,6 @@ export function CommandPalette() {