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
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/components/AgentSelect/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { AgentSelect } from "./AgentSelect";
export { AgentSelect, type AgentSelectAgent } from "./AgentSelect";
1 change: 1 addition & 0 deletions apps/desktop/src/renderer/hooks/useV2AgentChoices/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useV2AgentChoices } from "./useV2AgentChoices";
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMemo } from "react";
import type { AgentSelectAgent } from "renderer/components/AgentSelect";
import { useV2AgentConfigs } from "renderer/hooks/useV2AgentConfigs";

interface UseV2AgentChoicesResult {
agents: AgentSelectAgent[];
isFetched: boolean;
}

const SUPERSET_AGENT: AgentSelectAgent = {
id: "superset",
label: "Superset",
iconId: "superset",
};

// Superset chat isn't in the host's `host_agent_configs` table — it's
// routed by id inside `runAgentInWorkspace`. Append after the host's
// terminal rows so the user's preferred terminal agents stay on top.
export function useV2AgentChoices(
hostUrl: string | null,
): UseV2AgentChoicesResult {
const query = useV2AgentConfigs(hostUrl);
const agents = useMemo<AgentSelectAgent[]>(() => {
const terminalAgents: AgentSelectAgent[] = (query.data ?? []).map(
(config) => ({
id: config.id,
label: config.label,
iconId: config.presetId,
}),
);
return [...terminalAgents, SUPERSET_AGENT];
}, [query.data]);

return { agents, isFetched: query.isFetched };
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function launchChatAdapter(
paneId = created.paneId;
}

tabs.setTabAutoTitle(tabId, "Superset Chat");
tabs.setTabAutoTitle(tabId, "Superset");

const pane = tabs.getPane(paneId);
let sessionId = request.chat.sessionId ?? pane?.chat?.sessionId ?? null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { HiArrowRight, HiChevronDown } from "react-icons/hi2";
import { AgentSelect } from "renderer/components/AgentSelect";
import { env } from "renderer/env.renderer";
import { useHostUrl } from "renderer/hooks/host-service/useHostTargetUrl";
import { useV2AgentConfigs } from "renderer/hooks/useV2AgentConfigs";
import { useV2AgentChoices } from "renderer/hooks/useV2AgentChoices";
import { authClient } from "renderer/lib/auth-client";
import { DevicePicker } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker";
import { useWorkspaceHostOptions } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions";
Expand Down Expand Up @@ -118,16 +118,8 @@ export function OpenInWorkspaceV2({ task }: OpenInWorkspaceV2Props) {
}, [v2Projects, githubRepositories, setUpProjectIds]);

const launchHostUrl = useHostUrl(hostId);
const v2AgentConfigsQuery = useV2AgentConfigs(launchHostUrl);
const v2Agents = useMemo(
() =>
(v2AgentConfigsQuery.data ?? []).map((config) => ({
id: config.id,
label: config.label,
iconId: config.presetId,
})),
[v2AgentConfigsQuery.data],
);
const { agents: v2Agents, isFetched: v2AgentsFetched } =
useV2AgentChoices(launchHostUrl);
const validAgentIds = useMemo(
() => new Set(v2Agents.map((agent) => agent.id)),
[v2Agents],
Expand All @@ -154,15 +146,15 @@ export function OpenInWorkspaceV2({ task }: OpenInWorkspaceV2Props) {
const [selectedAgent, setSelectedAgentState] =
useState<SelectedAgent>(readStoredAgent);
useEffect(() => {
if (!v2AgentConfigsQuery.isFetched) return;
if (!v2AgentsFetched) return;
if (selectedAgent !== NONE && validAgentIds.has(selectedAgent)) return;
const stored = readStoredAgent();
if (stored !== NONE && validAgentIds.has(stored)) {
setSelectedAgentState(stored);
} else if (selectedAgent !== NONE) {
setSelectedAgentState(NONE);
}
}, [v2AgentConfigsQuery.isFetched, validAgentIds, selectedAgent]);
}, [v2AgentsFetched, validAgentIds, selectedAgent]);
const setSelectedAgent = (next: SelectedAgent) => {
setSelectedAgentState(next);
if (typeof window !== "undefined") {
Expand Down Expand Up @@ -200,7 +192,7 @@ export function OpenInWorkspaceV2({ task }: OpenInWorkspaceV2Props) {
// query resolves and the corrective effect runs — block submission so
// we don't send an id this host doesn't recognize.
if (selectedAgent !== NONE) {
if (!v2AgentConfigsQuery.isFetched) return "Checking agents…";
if (!v2AgentsFetched) return "Checking agents…";
if (!validAgentIds.has(selectedAgent)) {
return "Selected agent is not available on this host";
}
Expand All @@ -211,7 +203,7 @@ export function OpenInWorkspaceV2({ task }: OpenInWorkspaceV2Props) {
selectedProject?.needsSetup,
setUpProjectIds,
selectedAgent,
v2AgentConfigsQuery.isFetched,
v2AgentsFetched,
validAgentIds,
hostId,
machineId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { HiCheck, HiMiniPlay } from "react-icons/hi2";
import { AgentSelect } from "renderer/components/AgentSelect";
import { env } from "renderer/env.renderer";
import { useHostUrl } from "renderer/hooks/host-service/useHostTargetUrl";
import { useV2AgentConfigs } from "renderer/hooks/useV2AgentConfigs";
import { useV2AgentChoices } from "renderer/hooks/useV2AgentChoices";
import { authClient } from "renderer/lib/auth-client";
import { DevicePicker } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker";
import { useWorkspaceHostOptions } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions";
Expand Down Expand Up @@ -146,16 +146,8 @@ export function RunInWorkspacePopoverV2({
(project) => project.id === selectedProjectId,
);

const v2AgentConfigsQuery = useV2AgentConfigs(launchHostUrl);
const v2Agents = useMemo(
() =>
(v2AgentConfigsQuery.data ?? []).map((config) => ({
id: config.id,
label: config.label,
iconId: config.presetId,
})),
[v2AgentConfigsQuery.data],
);
const { agents: v2Agents, isFetched: v2AgentsFetched } =
useV2AgentChoices(launchHostUrl);
const validAgentIds = useMemo(
() => new Set(v2Agents.map((agent) => agent.id)),
[v2Agents],
Expand All @@ -164,15 +156,15 @@ export function RunInWorkspacePopoverV2({
const [selectedAgent, setSelectedAgentState] =
useState<SelectedAgent>(readStoredAgent);
useEffect(() => {
if (!v2AgentConfigsQuery.isFetched) return;
if (!v2AgentsFetched) return;
if (selectedAgent !== NONE && validAgentIds.has(selectedAgent)) return;
const stored = readStoredAgent();
if (stored !== NONE && validAgentIds.has(stored)) {
setSelectedAgentState(stored);
} else if (selectedAgent !== NONE) {
setSelectedAgentState(NONE);
}
}, [v2AgentConfigsQuery.isFetched, validAgentIds, selectedAgent]);
}, [v2AgentsFetched, validAgentIds, selectedAgent]);
const setSelectedAgent = (next: SelectedAgent) => {
setSelectedAgentState(next);
if (typeof window !== "undefined") {
Expand Down Expand Up @@ -201,7 +193,7 @@ export function RunInWorkspacePopoverV2({
// Agent UUIDs are host-scoped; block until the host-specific config
// query resolves and the selection is verified to exist there.
if (selectedAgent !== NONE) {
if (!v2AgentConfigsQuery.isFetched) return "Checking agents…";
if (!v2AgentsFetched) return "Checking agents…";
if (!validAgentIds.has(selectedAgent)) {
return "Selected agent is not available on this host";
}
Expand All @@ -212,7 +204,7 @@ export function RunInWorkspacePopoverV2({
selectedProject?.needsSetup,
setUpProjectIds,
selectedAgent,
v2AgentConfigsQuery.isFetched,
v2AgentsFetched,
validAgentIds,
hostId,
machineId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { LinkedIssuePill } from "renderer/components/Chat/ChatInterface/componen
import { IssueLinkCommand } from "renderer/components/Chat/ChatInterface/components/IssueLinkCommand";
import { resolveHostUrl } from "renderer/hooks/host-service/useHostTargetUrl";
import { useAgentLaunchPreferences } from "renderer/hooks/useAgentLaunchPreferences";
import { useV2AgentConfigs } from "renderer/hooks/useV2AgentConfigs";
import { useV2AgentChoices } from "renderer/hooks/useV2AgentChoices";
import { PLATFORM } from "renderer/hotkeys";
import { authClient } from "renderer/lib/auth-client";
import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider";
Expand Down Expand Up @@ -129,16 +129,8 @@ export function PromptGroup({
}) ?? null
);
}, [draft.hostId, machineId, activeHostUrl, activeOrganizationId]);
const v2AgentConfigsQuery = useV2AgentConfigs(launchHostUrl);
const v2Agents = useMemo(
() =>
(v2AgentConfigsQuery.data ?? []).map((config) => ({
id: config.id,
label: config.label,
iconId: config.presetId,
})),
[v2AgentConfigsQuery.data],
);
const { agents: v2Agents, isFetched: v2AgentsFetched } =
useV2AgentChoices(launchHostUrl);
const selectableAgentIds = useMemo(
() => v2Agents.map((agent) => agent.id),
[v2Agents],
Expand All @@ -149,7 +141,7 @@ export function PromptGroup({
defaultAgent: "none",
fallbackAgent: "none",
validAgents: ["none", ...selectableAgentIds],
agentsReady: v2AgentConfigsQuery.isFetched,
agentsReady: v2AgentsFetched,
});

// Promote the placeholder "none" → first configured agent whenever the
Expand All @@ -160,7 +152,7 @@ export function PromptGroup({
// useAgentLaunchPreferences resets to "none"). The corrective effect
// can't rescue these on its own because "none" is always in validAgents.
useEffect(() => {
if (!v2AgentConfigsQuery.isFetched) return;
if (!v2AgentsFetched) return;
if (selectedAgent !== "none") return;
const stored =
typeof window !== "undefined"
Expand All @@ -169,12 +161,7 @@ export function PromptGroup({
if (stored === "none") return;
const first = selectableAgentIds[0];
if (first) setSelectedAgent(first);
}, [
v2AgentConfigsQuery.isFetched,
selectableAgentIds,
selectedAgent,
setSelectedAgent,
]);
}, [v2AgentsFetched, selectableAgentIds, selectedAgent, setSelectedAgent]);

const branchPreview = branchNameEdited
? sanitizeUserBranchName(branchName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function getPreviewPrompt(preset: ResolvedAgentConfig): string {

export function getPreviewNoPromptCommand(preset: ResolvedAgentConfig): string {
if (preset.kind !== "terminal") {
return "Superset Chat opens a chat pane without a shell command.";
return "Superset opens a chat pane without a shell command.";
}

return preset.command.trim() || "No command configured.";
Expand All @@ -32,8 +32,8 @@ export function getPreviewNoPromptCommand(preset: ResolvedAgentConfig): string {
export function getPreviewTaskCommand(preset: ResolvedAgentConfig): string {
if (preset.kind !== "terminal") {
return preset.model
? `Superset Chat opens with model ${preset.model}.`
: "Superset Chat opens with the rendered task prompt.";
? `Superset opens with model ${preset.model}.`
: "Superset opens with the rendered task prompt.";
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
import { createWorkspaceStore, type WorkspaceState } from "@superset/panes";
import type { PaneViewerData } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types";
import type {
ChatPaneData,
PaneViewerData,
TerminalPaneData,
} from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types";

const EMPTY_STATE: WorkspaceState<PaneViewerData> = {
version: 1,
tabs: [],
activeTabId: null,
};

type AgentLaunchResult =
| { ok: true; kind: "terminal"; sessionId: string; label: string }
| { ok: true; kind: "chat"; sessionId: string; label: string }
| { ok: false; error: string };

interface AppendArgs {
existing: WorkspaceState<PaneViewerData> | undefined;
terminals: Array<{ terminalId: string; label?: string }>;
agents: Array<
| { ok: true; sessionId: string; label: string }
| { ok: false; error: string }
>;
agents: AgentLaunchResult[];
}

interface PaneLaunch {
kind: "terminal" | "chat";
sessionId: string;
label?: string;
}

export function appendLaunchesToPaneLayout({
existing,
terminals,
agents,
}: AppendArgs): WorkspaceState<PaneViewerData> {
const launches = [
...terminals,
...agents
.filter((entry): entry is Extract<typeof entry, { ok: true }> => entry.ok)
.map((entry) => ({ terminalId: entry.sessionId, label: entry.label })),
];
const terminalLaunches: PaneLaunch[] = terminals.map((entry) => ({
kind: "terminal",
sessionId: entry.terminalId,
label: entry.label,
}));
const agentLaunches: PaneLaunch[] = agents
.filter((entry): entry is Extract<typeof entry, { ok: true }> => entry.ok)
.map((entry) => ({
kind: entry.kind,
sessionId: entry.sessionId,
label: entry.label,
}));
const launches = [...terminalLaunches, ...agentLaunches];

if (launches.length === 0) {
return existing ?? EMPTY_STATE;
Expand All @@ -40,10 +59,17 @@ export function appendLaunchesToPaneLayout({
store.getState().addTab({
titleOverride: launch.label,
panes: [
{
kind: "terminal",
data: { terminalId: launch.terminalId },
},
launch.kind === "chat"
? {
kind: "chat",
data: { sessionId: launch.sessionId } satisfies ChatPaneData,
}
: {
kind: "terminal",
data: {
terminalId: launch.sessionId,
} satisfies TerminalPaneData,
},
],
});
}
Expand Down
2 changes: 2 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
"@trpc/client": "^11.7.1",
"date-fns": "^4.1.0",
"ink": "^7.0.1",
"mime-types": "^3.0.2",
"react": "19.2.0",
"superjson": "^2.2.5"
},
"devDependencies": {
"@superset/typescript": "workspace:*",
"@types/mime-types": "^3.0.1",
"@types/react": "~19.2.2",
"bun-types": "^1.3.1",
"typescript": "^5.9.3"
Expand Down
12 changes: 11 additions & 1 deletion packages/cli/src/commands/agents/list/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ export default command({
userJwt: ctx.bearer,
});

return target.client.settings.agentConfigs.list.query();
const terminalConfigs =
await target.client.settings.agentConfigs.list.query();
return [
...terminalConfigs,
{
id: "superset",
presetId: "superset",
label: "Superset",
command: "(superset runtime)",
},
];
},
});
Loading
Loading