From 78defccb2908b03e968721a2942317c2f075232b Mon Sep 17 00:00:00 2001 From: Gagan Date: Tue, 7 Jul 2026 13:58:59 +0530 Subject: [PATCH] feat(apps): post-install prompt to enable bundled agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bundled agents install disabled (§8.3), but nothing told the installer they exist — the app opened empty with the refresher buried in bg-tasks. After an install that materialized agents, offer to turn them on: 'Turn on & run now' activates each task and fires a first run so the app opens with data; 'Not now' keeps them off. --- .../renderer/src/components/apps/catalog.tsx | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/apps/x/apps/renderer/src/components/apps/catalog.tsx b/apps/x/apps/renderer/src/components/apps/catalog.tsx index 3820c6ab..899d5224 100644 --- a/apps/x/apps/renderer/src/components/apps/catalog.tsx +++ b/apps/x/apps/renderer/src/components/apps/catalog.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { Download, Link2, RefreshCw, Search, ShieldAlert } from 'lucide-react' +import { Bot, Download, Link2, RefreshCw, Search, ShieldAlert } from 'lucide-react' import type { rowboatApp } from '@x/shared' // Catalog tab (spec §14): search the registry, install with the D18 capability @@ -74,6 +74,42 @@ function InstallConfirmDialog({ preview, busy, onConfirm, onCancel }: { ) } +/** Post-install opt-in (§8.3): bundled agents land disabled; without this + * prompt a fresh installer opens an empty app with no hint that the refresher + * exists, buried in bg-tasks. */ +function EnableAgentsDialog({ appName, names, busy, onEnable, onSkip }: { + appName: string + names: string[] + busy: boolean + onEnable: () => void + onSkip: () => void +}) { + return ( +
+
+
+ Turn on {names.length === 1 ? 'its background agent' : 'its background agents'}? +
+

+ {appName} ships {names.length === 1 ? 'an agent' : 'agents'} that keep{names.length === 1 ? 's' : ''} its + data fresh on a schedule, using your connected accounts and AI models. {names.length === 1 ? 'It is' : 'They are'} currently off. +

+
    + {names.map((n) =>
  • {n}
  • )} +
+
+ + +
+
+
+ ) +} + export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => void }) { const [records, setRecords] = useState([]) const [stale, setStale] = useState(false) @@ -83,6 +119,8 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v const [busy, setBusy] = useState(false) const [urlDialog, setUrlDialog] = useState(false) const [url, setUrl] = useState('') + const [agentPrompt, setAgentPrompt] = useState<{ folder: string; appName: string; slugs: string[]; names: string[] } | null>(null) + const [enabling, setEnabling] = useState(false) const load = async (force = false) => { setError(null) @@ -127,6 +165,23 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v } } + const enableAgents = async () => { + if (!agentPrompt) return + setEnabling(true) + try { + for (const slug of agentPrompt.slugs) { + await window.ipc.invoke('bg-task:patch', { slug, partial: { active: true } }) + // First run so the app opens with data; resolves when the run ends, so + // fire-and-forget. + void window.ipc.invoke('bg-task:run', { slug }).catch(() => { /* surfaced in bg-tasks */ }) + } + } catch { /* patch failures land the user in the same place as "Not now" */ } + const folder = agentPrompt.folder + setAgentPrompt(null) + setEnabling(false) + onInstalled(folder) + } + const confirmInstall = async () => { if (!preview) return setBusy(true) @@ -136,7 +191,22 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v ? await window.ipc.invoke('apps:installFromUrl', { url: preview.url, confirmed: true }) : await window.ipc.invoke('apps:install', { name: preview.name ?? '', confirmed: true }) setPreview(null) - if (r.status === 'installed' && r.app) onInstalled(r.app.folder) + if (r.status === 'installed' && r.app) { + if (r.app.agentSlugs.length > 0) { + // Offer to switch the bundled agents on (they install disabled). + const names = await Promise.all(r.app.agentSlugs.map(async (slug) => { + try { + const g = await window.ipc.invoke('bg-task:get', { slug }) + return g.task?.name ?? slug + } catch { + return slug + } + })) + setAgentPrompt({ folder: r.app.folder, appName: r.app.manifest?.name ?? r.app.folder, slugs: r.app.agentSlugs, names }) + } else { + onInstalled(r.app.folder) + } + } } catch (e) { setError(e instanceof Error ? e.message : String(e)) } finally { @@ -226,6 +296,20 @@ export function CatalogTab({ onInstalled }: { onInstalled: (folder: string) => v onCancel={() => setPreview(null)} /> )} + + {agentPrompt && ( + void enableAgents()} + onSkip={() => { + const folder = agentPrompt.folder + setAgentPrompt(null) + onInstalled(folder) + }} + /> + )} ) }