Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import { beforeEach, describe, expect, it, mock } from "bun:test";
import type { AgentLaunchRequest } from "@superset/shared/agent-launch";
import {
indexResolvedAgentConfigs,
resolveAgentConfigs,
} from "@superset/shared/agent-settings";
import type { ToolContext } from "./types";

let capturedRequest: AgentLaunchRequest | null = null;

const queueMock = mock((input: { request: AgentLaunchRequest }) => {
capturedRequest = input.request;
return {
workspaceId: input.request.workspaceId,
tabId: null,
paneId: null,
sessionId: null,
status: "queued" as const,
error: null,
};
});

const launchMock = mock(async (request: AgentLaunchRequest) => {
capturedRequest = request;
return {
workspaceId: request.workspaceId,
tabId: "tab-1",
paneId: "pane-1",
sessionId: null,
status: "running" as const,
error: null,
};
});

mock.module("renderer/lib/agent-session-orchestrator", () => ({
queueAgentSessionLaunch: queueMock,
launchAgentSession: launchMock,
}));

const { startAgentSession } = await import("./start-agent-session");

const OVERRIDE_COMMAND =
"claude --permission-mode plan --dangerously-skip-permissions";
const BAKED_PROMPT_COMMAND =
"claude --dangerously-skip-permissions BAKED-PROMPT";
const BAKED_FILE_COMMAND = "claude --dangerously-skip-permissions BAKED-FILE";

function configsById(
presets: {
id: string;
enabled?: boolean;
command?: string;
promptCommand?: string;
}[],
) {
return indexResolvedAgentConfigs(
resolveAgentConfigs({
overrideEnvelope: { version: 1, presets },
}),
);
}

function makeContext(
getResolvedAgentConfigsById?: ToolContext["getResolvedAgentConfigsById"],
): ToolContext {
return {
getWorkspaces: () => [
{ id: "workspace-1", projectId: "project-1", branch: "main" },
],
getResolvedAgentConfigsById,
} as unknown as ToolContext;
}

function promptParams() {
return {
workspaceId: "workspace-1",
command: BAKED_PROMPT_COMMAND,
name: "Claude",
agentType: "claude" as const,
request: {
kind: "terminal" as const,
workspaceId: "workspace-1",
agentType: "claude" as const,
terminal: {
command: BAKED_PROMPT_COMMAND,
name: "Claude",
prompt: "Fix the failing tests",
},
},
};
}

function taskParams() {
return {
workspaceId: "workspace-1",
command: BAKED_FILE_COMMAND,
name: "demo-task",
agentType: "claude" as const,
request: {
kind: "terminal" as const,
workspaceId: "workspace-1",
agentType: "claude" as const,
terminal: {
command: BAKED_FILE_COMMAND,
name: "demo-task",
taskPromptContent: "Do the task",
taskPromptFileName: "task-demo-task.md",
},
},
};
}

describe("start-agent-session device command resolution", () => {
beforeEach(() => {
capturedRequest = null;
queueMock.mockClear();
launchMock.mockClear();
});

it("re-resolves a prompt launch from a claude override", async () => {
const ctx = makeContext(() =>
configsById([{ id: "claude", promptCommand: OVERRIDE_COMMAND }]),
);

const result = await startAgentSession.execute(promptParams(), ctx);

expect(result.success).toBe(true);
const command =
capturedRequest?.kind === "terminal"
? capturedRequest.terminal.command
: undefined;
expect(command).toContain("--permission-mode plan");
expect(command).toContain("Fix the failing tests");
expect(command).not.toBe(BAKED_PROMPT_COMMAND);
});

it("re-resolves a task launch into the overridden file command", async () => {
const ctx = makeContext(() =>
configsById([{ id: "claude", promptCommand: OVERRIDE_COMMAND }]),
);

await startAgentSession.execute(taskParams(), ctx);

const command =
capturedRequest?.kind === "terminal"
? capturedRequest.terminal.command
: undefined;
expect(command).toContain("--permission-mode plan");
expect(command).toContain(".superset/task-demo-task.md");
expect(command).not.toBe(BAKED_FILE_COMMAND);
});

it("preserves server-authoritative fields when re-resolving", async () => {
const ctx = makeContext(() =>
configsById([{ id: "claude", promptCommand: OVERRIDE_COMMAND }]),
);

await startAgentSession.execute(taskParams(), ctx);

expect(capturedRequest?.kind).toBe("terminal");
if (capturedRequest?.kind === "terminal") {
expect(capturedRequest.terminal.name).toBe("demo-task");
expect(capturedRequest.terminal.taskPromptContent).toBe("Do the task");
expect(capturedRequest.terminal.taskPromptFileName).toBe(
"task-demo-task.md",
);
}
});

it("keeps the baked command when the config is disabled", async () => {
const ctx = makeContext(() =>
configsById([{ id: "claude", enabled: false }]),
);

await startAgentSession.execute(promptParams(), ctx);

const command =
capturedRequest?.kind === "terminal"
? capturedRequest.terminal.command
: undefined;
expect(command).toBe(BAKED_PROMPT_COMMAND);
});

it("keeps the baked command when no config matches the agent", async () => {
const ctx = makeContext(() => new Map());

await startAgentSession.execute(promptParams(), ctx);

const command =
capturedRequest?.kind === "terminal"
? capturedRequest.terminal.command
: undefined;
expect(command).toBe(BAKED_PROMPT_COMMAND);
});

it("keeps the baked command for an old-server request lacking prompt fields", async () => {
const ctx = makeContext(() =>
configsById([{ id: "claude", promptCommand: OVERRIDE_COMMAND }]),
);

await startAgentSession.execute(
{
workspaceId: "workspace-1",
command: BAKED_PROMPT_COMMAND,
name: "Claude",
request: {
kind: "terminal" as const,
workspaceId: "workspace-1",
terminal: { command: BAKED_PROMPT_COMMAND, name: "Claude" },
},
},
ctx,
);

const command =
capturedRequest?.kind === "terminal"
? capturedRequest.terminal.command
: undefined;
expect(command).toBe(BAKED_PROMPT_COMMAND);
});

it("keeps the baked command when the resolver getter is absent", async () => {
const ctx = makeContext(undefined);

await startAgentSession.execute(promptParams(), ctx);

const command =
capturedRequest?.kind === "terminal"
? capturedRequest.terminal.command
: undefined;
expect(command).toBe(BAKED_PROMPT_COMMAND);
});

it("never touches a chat/superset launch", async () => {
const ctx = makeContext(() =>
configsById([{ id: "claude", promptCommand: OVERRIDE_COMMAND }]),
);

await startAgentSession.execute(
{
workspaceId: "workspace-1",
openChatPane: true,
agentType: "superset" as const,
chatLaunchConfig: { initialPrompt: "Chat prompt" },
request: {
kind: "chat" as const,
workspaceId: "workspace-1",
agentType: "superset" as const,
chat: { initialPrompt: "Chat prompt" },
},
},
ctx,
);

expect(capturedRequest?.kind).toBe("chat");
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type { AgentDefinitionId } from "@superset/shared/agent-catalog";
import {
type AgentLaunchRequest,
chatLaunchConfigSchema,
normalizeAgentLaunchRequest,
STARTABLE_AGENT_TYPES,
} from "@superset/shared/agent-launch";
import {
buildFileCommandFromAgentConfig,
buildPromptCommandFromAgentConfig,
} from "@superset/shared/agent-settings";
import {
launchAgentSession,
queueAgentSessionLaunch,
Expand Down Expand Up @@ -55,7 +61,10 @@ async function execute(
params.request && typeof params.request === "object"
? { ...fallbackRequest, ...(params.request as Record<string, unknown>) }
: fallbackRequest;
const request = normalizeAgentLaunchRequest(mergedRequest);
const request = resolveDeviceLocalCommand(
normalizeAgentLaunchRequest(mergedRequest),
ctx,
);

if (request.workspaceId !== params.workspaceId) {
return {
Expand Down Expand Up @@ -110,6 +119,48 @@ async function execute(
}
}

/**
* Re-resolves a terminal launch command from the device-local agent presets,
* so MCP-launched sessions honor the user's UI-configured command/flags.
*
* The server sends a baked builtin command as a fallback plus the raw prompt
* (or task prompt file name) needed to re-resolve here. Falls back to the baked
* command whenever the presets, a matching enabled terminal config, or the
* prompt fields are unavailable — keeping old renderers and old servers working.
*/
function resolveDeviceLocalCommand(
request: AgentLaunchRequest,
ctx: ToolContext,
): AgentLaunchRequest {
if (request.kind !== "terminal" || !request.agentType) return request;

const config = ctx
.getResolvedAgentConfigsById?.()
?.get(request.agentType as AgentDefinitionId);
if (!config || !config.enabled || config.kind !== "terminal") return request;

const { taskPromptFileName, prompt } = request.terminal;
const command = taskPromptFileName
? buildFileCommandFromAgentConfig({
filePath: `.superset/${taskPromptFileName}`,
config,
})
: prompt
? buildPromptCommandFromAgentConfig({
prompt,
randomId: crypto.randomUUID(),
config,
})
: null;

if (!command) return request;

return {
...request,
terminal: { ...request.terminal, command },
};
}

export const startAgentSession: ToolDefinition<typeof schema> = {
name: "start_agent_session",
schema,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { SelectProject, SelectWorkspace } from "@superset/local-db";
import type { AgentDefinitionId } from "@superset/shared/agent-catalog";
import type { ResolvedAgentConfig } from "@superset/shared/agent-settings";
import type { electronTrpc } from "renderer/lib/electron-trpc";
import type { z } from "zod";

Expand Down Expand Up @@ -62,6 +64,11 @@ export interface ToolContext {
getProjects: () => SelectProject[] | undefined;
getActiveWorkspaceId: () => string | null;
getWorktreePathByWorkspaceId: (workspaceId: string) => string | undefined;
// Device-local agent presets, indexed by id. Absence (older renderer)
// signals callers to fall back to the server-baked command.
getResolvedAgentConfigsById?: () =>
| Map<AgentDefinitionId, ResolvedAgentConfig>
| undefined;
}

// Tool definition with schema and execute function
Expand Down
Loading