Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const AgentsPage = lazy(() => import("@/pages/agents/agents-page"));
const AgentNewPage = lazy(() => import("@/pages/agents/agent-new"));
const AgentDetailPage = lazy(() => import("@/pages/agents/agent-detail"));
const FilesPage = lazy(() => import("@/pages/files/files-page"));
const ArtifactsPage = lazy(() => import("@/pages/artifacts/artifacts-page"));
const ArtifactPage = lazy(() => import("@/pages/artifacts/artifact-page"));
const AutomationsPage = lazy(() => import("@/pages/automations/automations-page"));
const PluginsPage = lazy(() => import("@/pages/plugins/plugins-page"));
Expand All @@ -36,13 +35,10 @@ const LoginsPage = lazy(() => import("@/pages/vault/logins-page"));
const TotpPage = lazy(() => import("@/pages/vault/totp-page"));
const CardsPage = lazy(() => import("@/pages/vault/cards-page"));
const PasskeysPage = lazy(() => import("@/pages/vault/passkeys-page"));
const IntegrationsPage = lazy(() => import("@/pages/integrations/integrations-page"));
const MessagingPage = lazy(() => import("@/pages/messaging/messaging-page"));
const BrowserPage = lazy(() => import("@/pages/browser/browser-page"));
const ComputerPage = lazy(() => import("@/pages/computer/computer-page"));
const VmsPage = lazy(() => import("@/pages/vms/vms-page"));
const SshPage = lazy(() => import("@/pages/ssh/ssh-page"));
const AndroidPage = lazy(() => import("@/pages/android/android-page"));
const AndroidDevicePage = lazy(() => import("@/pages/android/android-device-page"));
const CallsPage = lazy(() => import("@/pages/calls/calls-page"));
const RunnersPage = lazy(() => import("@/pages/runners/runners-page"));
Expand Down Expand Up @@ -204,8 +200,8 @@ export function App() {
<Route path="/agents" element={<AgentsPage />} />
<Route path="/agents/new" element={<AgentNewPage />} />
<Route path="/agents/:agentId/*" element={<AgentDetailPage />} />
<Route path="/artifacts" element={<ArtifactsPage />} />
<Route path="/artifacts/:artifactId" element={<ArtifactPage />} />
<Route path="/artifacts" element={<ToSettings section="artifacts" />} />
<Route path="/artifacts/:artifactId" element={b.settings.artifacts.enabled ? <ArtifactPage /> : <ToSettings section="artifacts" />} />
<Route path="/files" element={<Navigate to="/settings/files" replace />} />
<Route path="/files/:agentId" element={<FilesPage />} />
<Route path="/tasks" element={<TasksPage />} />
Expand All @@ -221,14 +217,14 @@ export function App() {
<Route path="/vault/2fa" element={<TotpPage />} />
<Route path="/vault/cards" element={<CardsPage />} />
<Route path="/vault/passkeys" element={<PasskeysPage />} />
<Route path="/integrations" element={<IntegrationsPage />} />
<Route path="/messaging" element={<MessagingPage />} />
<Route path="/integrations" element={<ToSettings section="integrations" />} />
<Route path="/messaging" element={<ToSettings section="messaging" />} />
<Route path="/browser" element={<BrowserPage />} />
<Route path="/computer" element={<ComputerPage />} />
<Route path="/vms" element={<VmsPage />} />
<Route path="/ssh" element={<SshPage />} />
<Route path="/android" element={<AndroidPage />} />
<Route path="/android/:id" element={<AndroidDevicePage />} />
<Route path="/android" element={<ToSettings section="android" />} />
<Route path="/android/:id" element={b.settings.android.enabled ? <AndroidDevicePage /> : <ToSettings section="android" />} />
<Route path="/calls" element={<CallsPage />} />
<Route path="/runners" element={<RunnersPage />} />
<Route path="/inbox" element={<InboxPage />} />
Expand Down Expand Up @@ -275,6 +271,13 @@ function NotFoundRedirect() {

/** Links from before Mods were Plugins (notifications, chat notes): /mods?mod=<id> → /plugins?plugin=<id>. */
/** My tasks became the Inbox's board: old links (notifications, tickets) open the same task there. */
/** A page that moved into Settings: its old address, with its query (?tab=, ?connection=), opens the section. */
function ToSettings({ section }: { section: string }) {
const { search } = useLocation();
if (section === "integrations" && new URLSearchParams(search).get("tab") === "api-keys") return <Navigate to="/settings/api-keys" replace />;
return <Navigate to={`/settings/${section}${search}`} replace />;
}

function MyTasksRedirect() {
const params = new URLSearchParams(useLocation().search);
if (!params.has("task")) params.set("view", "board");
Expand Down
37 changes: 21 additions & 16 deletions apps/desktop/src/components/agents/agent-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,10 @@ export function AgentForm({
const inherited = useInheritedInstructions(values.workspaceId, undefined, values.projectId);
const vmChoices = useVmChoices();
const androidOn = !!boot?.settings.android.enabled;
const sections = SECTIONS.filter((s) => (s.id !== "vm" || vmChoices.available) && (s.id !== "android" || androidOn));
const integrationsOn = !!boot?.settings.integrations.enabled;
const sections = SECTIONS.filter(
(s) => (s.id !== "vm" || vmChoices.available) && (s.id !== "android" || androidOn) && (s.id !== "tools" || integrationsOn),
);

const { hash } = useLocation();
// Again once the VM or Android section shows up: they wait for the VM status and the settings (links from the VMs page go to #vm).
Expand Down Expand Up @@ -761,20 +764,22 @@ export function AgentForm({
</FormSection>
)}

<FormSection id="tools" title="Tools & integrations" description="APIs, MCP servers and connected apps this agent can use.">
<div className="space-y-5">
<ToggleRow
id="agent-inherit-mcp"
icon={<Plug className="size-4" />}
title="Inherit shared integrations"
description="Also use the global and workspace tools, MCP servers and Composio apps."
checked={values.inheritMcp}
onChange={(v) => set("inheritMcp", v)}
/>
<ApiToolsField agentId={agentId} workspaceId={values.workspaceId} savedWorkspaceId={initial?.workspaceId ?? null} inherit={values.inheritMcp} />
<McpField agentId={agentId} value={values.mcpServerIds} onChange={(v) => set("mcpServerIds", v)} />
</div>
</FormSection>
{integrationsOn && (
<FormSection id="tools" title="Tools & integrations" description="APIs, MCP servers and connected apps this agent can use.">
<div className="space-y-5">
<ToggleRow
id="agent-inherit-mcp"
icon={<Plug className="size-4" />}
title="Inherit shared integrations"
description="Also use the global and workspace tools, MCP servers and Composio apps."
checked={values.inheritMcp}
onChange={(v) => set("inheritMcp", v)}
/>
<ApiToolsField agentId={agentId} workspaceId={values.workspaceId} savedWorkspaceId={initial?.workspaceId ?? null} inherit={values.inheritMcp} />
<McpField agentId={agentId} value={values.mcpServerIds} onChange={(v) => set("mcpServerIds", v)} />
</div>
</FormSection>
)}

<FormSection
id="subagents"
Expand Down Expand Up @@ -1535,7 +1540,7 @@ function ApiToolsField({
<div className="space-y-2">
<div className="flex items-center justify-between gap-3">
<Label>API tools</Label>
<Link to="/integrations?tab=tools" className="text-xs font-medium text-muted-foreground underline-offset-[3px] hover:text-foreground hover:underline">
<Link to="/settings/integrations?tab=tools" className="text-xs font-medium text-muted-foreground underline-offset-[3px] hover:text-foreground hover:underline">
Manage tools
</Link>
</div>
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/components/android/android-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function AndroidChip({
{!isLoading && devices.length === 0 ? (
<div className="space-y-3 px-3 py-4 text-center">
<p className="text-xs text-muted-foreground">No phones yet.</p>
<Button size="sm" onClick={() => go("/android")}>
<Button size="sm" onClick={() => go("/settings/android")}>
<Plus /> Connect a phone
</Button>
</div>
Expand Down Expand Up @@ -160,7 +160,7 @@ export function AndroidChip({
</CommandList>
</Command>
<div className="border-t p-1">
<Button type="button" variant="ghost" size="sm" className="w-full justify-start font-normal text-muted-foreground" onClick={() => go("/android")}>
<Button type="button" variant="ghost" size="sm" className="w-full justify-start font-normal text-muted-foreground" onClick={() => go("/settings/android")}>
<Settings2 /> Manage phones
</Button>
</div>
Expand All @@ -180,7 +180,7 @@ export function AndroidField({ value, onChange }: { value: string[]; onChange: (
<Smartphone className="size-5 shrink-0 text-muted-foreground" />
<p className="min-w-0 flex-1 basis-48 text-sm text-muted-foreground">No phones yet — connect one and this agent can use it.</p>
<Button type="button" variant="outline" size="sm" asChild>
<Link to="/android">
<Link to="/settings/android">
<Plus /> Connect a phone
</Link>
</Button>
Expand All @@ -194,7 +194,7 @@ export function AndroidField({ value, onChange }: { value: string[]; onChange: (
<span id="agent-android-label" className="text-sm font-medium">
Can control
</span>
<Link to="/android" className="flex items-center gap-1 text-xs text-muted-foreground transition hover:text-foreground">
<Link to="/settings/android" className="flex items-center gap-1 text-xs text-muted-foreground transition hover:text-foreground">
Manage phones <ArrowRight className="size-3" />
</Link>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import type { AndroidDevice } from "@godmode/shared";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Spinner } from "@/components/ui/spinner";
import { EmptyState, PageBody, PageHeader } from "@/components/common";
import { EmptyState } from "@/components/common";
import { ConnectWifiDialog, EditPhoneDialog, ForgetPhoneDialog } from "@/components/android/android-dialogs";
import { CONNECTION_LABEL } from "@/components/android/android-parts";
import { DeviceCard } from "@/components/android/device-card";
import { toastApiError } from "@/components/vault/vault-utils";
import { api, errorMessage } from "@/lib/api";
import { useAndroidDevices, useAndroidStatus, useSettings } from "@/lib/hooks";
import { useAndroidDevices, useAndroidStatus } from "@/lib/hooks";
import { qk } from "@/lib/queryKeys";

const STEPS = [
Expand All @@ -38,7 +38,7 @@ function SetupSteps() {

function DeviceGrid({ devices, onEdit, onForget }: { devices: AndroidDevice[]; onEdit: (d: AndroidDevice) => void; onForget: (d: AndroidDevice) => void }) {
return (
<div className="grid grid-cols-1 items-start gap-4 @3xl:grid-cols-2 @6xl:grid-cols-3">
<div className="grid grid-cols-1 items-start gap-4 @2xl:grid-cols-2 @5xl:grid-cols-3">
<AnimatePresence initial={false}>
{devices.map((device, i) => (
<motion.div
Expand All @@ -59,11 +59,10 @@ function DeviceGrid({ devices, onEdit, onForget }: { devices: AndroidDevice[]; o
}

/** Android phones connected over adb: watch them, take control, give them to agents. */
export default function AndroidPage() {
export function AndroidOverview() {
const qc = useQueryClient();
const status = useAndroidStatus();
const list = useAndroidDevices();
const { data: settings } = useSettings();
const devices = list.data ?? [];
const [wifiOpen, setWifiOpen] = useState(false);
const [editing, setEditing] = useState<AndroidDevice | null>(null);
Expand All @@ -86,35 +85,24 @@ export default function AndroidPage() {

const adb = status.data?.adb;
const pending = status.data?.pending ?? [];
const off = settings?.android.enabled === false;
const connected = devices.filter((d) => d.state === "connected").length;

return (
<div className="relative">
<PageHeader
icon={<Smartphone />}
title="Android phones"
description="Phones connected to this computer over USB or Wi-Fi. Agents see the screen, tap, type, open apps and run commands — you watch live and can take over any time."
actions={
<>
<Button variant="outline" size="icon" aria-label="Look for phones again" onClick={() => refresh.mutate()} disabled={refresh.isPending || !adb?.installed}>
{refresh.isPending ? <Spinner className="size-4" /> : <RefreshCw />}
</Button>
<Button variant="outline" onClick={() => setWifiOpen(true)} disabled={!adb?.installed || off}>
<Wifi /> Connect over Wi-Fi
</Button>
</>
}
/>
<PageBody>
{off ? (
<EmptyState
icon={<Smartphone />}
title="Android phones are turned off"
description="Turn them on in Settings → Android phones to let Godmode look for phones and agents control them."
/>
) : status.isLoading || list.isLoading ? (
<div className="grid grid-cols-1 gap-4 @3xl:grid-cols-2 @6xl:grid-cols-3">
<div className="@container relative space-y-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="eyebrow">{devices.length ? `${connected} of ${devices.length} connected` : "Phones"}</h3>
<div className="flex items-center gap-2">
<Button variant="outline" size="icon-sm" aria-label="Look for phones again" onClick={() => refresh.mutate()} disabled={refresh.isPending || !adb?.installed}>
{refresh.isPending ? <Spinner className="size-4" /> : <RefreshCw />}
</Button>
<Button variant="outline" size="sm" onClick={() => setWifiOpen(true)} disabled={!adb?.installed}>
<Wifi /> Connect over Wi-Fi
</Button>
</div>
</div>
<div>
{status.isLoading || list.isLoading ? (
<div className="grid grid-cols-1 gap-4 @2xl:grid-cols-2 @5xl:grid-cols-3">
{[0, 1].map((i) => (
<Skeleton key={i} className="h-96 rounded-xl" />
))}
Expand All @@ -126,7 +114,7 @@ export default function AndroidPage() {
description={
status.data?.canInstall
? "adb is Android's free debugging bridge from Google. Godmode installs its own copy (about 16 MB) — nothing else on your computer changes."
: "Install Android's platform-tools (adb) and make sure it's on your PATH, or set its path in Settings → Android phones."
: "Install Android's platform-tools (adb) and make sure it's on your PATH, or set its path below."
}
action={
status.data?.canInstall && (
Expand Down Expand Up @@ -196,10 +184,6 @@ export default function AndroidPage() {
/>
) : (
<>
<p className="text-xs text-muted-foreground">
{connected} of {devices.length} connected · adb {adb?.version ?? ""}
{adb?.managed ? " (Godmode's copy)" : ""}
</p>
<DeviceGrid devices={devices.filter((d) => d.state !== "disconnected")} onEdit={setEditing} onForget={setForgetting} />
{devices.some((d) => d.state === "disconnected") && (
<section aria-label="Not connected" className="mt-4 grid gap-3">
Expand All @@ -211,7 +195,7 @@ export default function AndroidPage() {
)}
</div>
)}
</PageBody>
</div>

<ConnectWifiDialog open={wifiOpen} onOpenChange={setWifiOpen} />
<EditPhoneDialog device={editing} onClose={() => setEditing(null)} />
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/android/phone-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function PhonePanel({ phones, activity, onHide }: { phones: AndroidDevice
</div>

<Link
to="/android"
to="/settings/android"
className="group flex shrink-0 items-center gap-3 border-t px-4 py-3 transition hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none"
>
<span className="grid size-8 shrink-0 place-items-center rounded-md border bg-card text-muted-foreground shadow-card">
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/artifacts/artifact-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export function ArtifactView({
onOpenChange={setDeleting}
onDeleted={() => {
onClose?.();
if (variant === "page") actions.navigate("/artifacts");
if (variant === "page") actions.navigate("/settings/artifacts");
}}
/>
</div>
Expand Down
Loading
Loading