Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 extensions/cli/src/__mocks__/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ export const SERVICE_NAMES = {
CHAT_HISTORY: "chatHistory",
UPDATE: "update",
STORAGE_SYNC: "storageSync",
WORKFLOW: "workflow",
AGENT_FILE: "agent-file",
} as const;
4 changes: 2 additions & 2 deletions extensions/cli/src/commands/BaseCommandOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export interface BaseCommandOptions {
ask?: string[];
/** Array of tools to exclude from use (--exclude) */
exclude?: string[];
/** Workflow slug from the hub (--workflow) */
workflow?: string;
/** Agent file slug from the hub (--agent) */
agent?: string;
}

/**
Expand Down
14 changes: 7 additions & 7 deletions extensions/cli/src/commands/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { sentryService } from "../sentry.js";
import { initializeServices, services } from "../services/index.js";
import { serviceContainer } from "../services/ServiceContainer.js";
import {
AgentFileServiceState,
ModelServiceState,
SERVICE_NAMES,
WorkflowServiceState,
} from "../services/types.js";
import {
loadSession,
Expand Down Expand Up @@ -478,11 +478,11 @@ async function runHeadlessMode(
const { processAndCombinePrompts } = await import(
"../util/promptProcessor.js"
);
const workflowState = await serviceContainer.get<WorkflowServiceState>(
SERVICE_NAMES.WORKFLOW,
const agentFileState = await serviceContainer.get<AgentFileServiceState>(
SERVICE_NAMES.AGENT_FILE,
);
const initialPrompt =
`${workflowState?.workflowFile?.prompt ?? ""}\n\n${prompt ?? ""}`.trim() ||
`${agentFileState?.agentFile?.prompt ?? ""}\n\n${prompt ?? ""}`.trim() ||
undefined;
const initialUserInput = await processAndCombinePrompts(
options.prompt,
Expand Down Expand Up @@ -549,12 +549,12 @@ export async function chat(prompt?: string, options: ChatOptions = {}) {
toolPermissionOverrides: permissionOverrides,
});

const workflowState = await serviceContainer.get<WorkflowServiceState>(
SERVICE_NAMES.WORKFLOW,
const agentFileState = await serviceContainer.get<AgentFileServiceState>(
SERVICE_NAMES.AGENT_FILE,
);

const initialPrompt =
`${workflowState?.workflowFile?.prompt ?? ""}\n\n${prompt ?? ""}`.trim() ||
`${agentFileState?.agentFile?.prompt ?? ""}\n\n${prompt ?? ""}`.trim() ||
undefined;

// Start TUI with skipOnboarding since we already handled it
Expand Down
36 changes: 18 additions & 18 deletions extensions/cli/src/configEnhancer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ vi.mock("./hubLoader.js", () => ({
modelProcessor: {},
}));

// Mock the service container to provide empty workflow state
// Mock the service container to provide empty agent file state
vi.mock("./services/ServiceContainer.js", () => ({
serviceContainer: {
get: vi.fn(() =>
Promise.resolve({
workflowService: null,
workflowModelName: null,
workflowFile: null,
workflow: null,
agentFileService: null,
agentFileModelName: null,
agentFile: null,
slug: null,
}),
),
},
}));

vi.mock("./services/types.js", () => ({
SERVICE_NAMES: {
WORKFLOW: "workflow",
AGENT_FILE: "agent-file",
},
}));

Expand Down Expand Up @@ -288,41 +288,41 @@ describe("ConfigEnhancer", () => {
});
});

it("should handle workflow integration gracefully when no workflow", async () => {
// The mocked service container returns null workflow state
it("should handle agent file integration gracefully when no agent file", async () => {
// The mocked service container returns null agent file state
const options: BaseCommandOptions = {
rule: ["test-rule"],
};

const config = await enhancer.enhanceConfig(mockConfig, options);

// Should work normally when no workflow is active
// Should work normally when no agent file is active
expect(config.rules).toEqual(["test-rule"]);
});

it("should handle workflow integration when workflow is active", async () => {
// Mock service container to return active workflow
it("should handle agent file integration when agent file is active", async () => {
// Mock service container to return active agent file
const options: BaseCommandOptions = {
rule: ["user-rule"],
prompt: ["user-prompt"],
};

const config = await enhancer.enhanceConfig(mockConfig, options, {
workflowFile: {
name: "Test Workflow",
agentFile: {
name: "Test Agent",
prompt: "You are a test assistant",
rules: "Always be helpful",
model: "gpt-4",
tools: "bash,read",
},
slug: "owner/test-workflow",
workflowModelName: null,
workflowService: null,
slug: "owner/test-agent",
agentFileModelName: null,
agentFileService: null,
});

// Should have both workflow and user rules
// Should have both agent file and user rules
expect(config.rules).toHaveLength(2);
expect(config.rules?.[0]).toBe("Always be helpful"); // Workflow rule first
expect(config.rules?.[0]).toBe("Always be helpful"); // Agent file rule first
expect(config.rules?.[1]).toBe("user-rule"); // User rule second
});
});
43 changes: 23 additions & 20 deletions extensions/cli/src/configEnhancer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
AssistantUnrolled,
parseWorkflowTools,
parseAgentFileTools,
Rule,
} from "@continuedev/config-yaml";

Expand All @@ -12,24 +12,24 @@ import {
modelProcessor,
processRule,
} from "./hubLoader.js";
import { WorkflowServiceState } from "./services/types.js";
import { AgentFileServiceState } from "./services/types.js";
import { logger } from "./util/logger.js";

/**
* Enhances a configuration by injecting additional components from CLI flags
*/
export class ConfigEnhancer {
// added this for lint complexity rule
private async enhanceConfigFromWorkflow(
private async enhanceConfigFromAgentFile(
config: AssistantUnrolled,
_options: BaseCommandOptions | undefined,
workflowState?: WorkflowServiceState,
agentFileState?: AgentFileServiceState,
) {
const enhancedConfig = { ...config };
const options = { ..._options };

if (workflowState?.workflowFile) {
const { rules, model, tools, prompt } = workflowState?.workflowFile;
if (agentFileState?.agentFile) {
const { rules, model, tools, prompt } = agentFileState?.agentFile;
if (rules) {
options.rule = [
...rules
Expand All @@ -42,38 +42,41 @@ export class ConfigEnhancer {

if (tools) {
try {
const parsedTools = parseWorkflowTools(tools);
const parsedTools = parseAgentFileTools(tools);
if (parsedTools.mcpServers.length > 0) {
options.mcp = [...parsedTools.mcpServers, ...(options.mcp || [])];
}
} catch (e) {
logger.error("Failed to parse workflow tools", e);
logger.error("Failed to parse agent file tools", e);
}
}

// --model takes precedence over workflow model
// --model takes precedence over agent file model
if (model) {
try {
const workflowModel = await loadPackageFromHub(model, modelProcessor);
const agentFileModel = await loadPackageFromHub(
model,
modelProcessor,
);
enhancedConfig.models = [
workflowModel,
agentFileModel,
...(enhancedConfig.models ?? []),
];
workflowState?.workflowService?.setWorkflowModelName(
workflowModel.name,
agentFileState?.agentFileService?.setagentFileModelName(
agentFileModel.name,
);
} catch (e) {
logger.error("Failed to load workflow model", e);
logger.error("Failed to load agent model", e);
}
}

// Workflow prompt is included as a slash command, initial kickoff is handled elsewhere
// Agent file prompt is included as a slash command, initial kickoff is handled elsewhere
if (prompt) {
enhancedConfig.prompts = [
{
name: `Workflow prompt (${workflowState.workflowFile.name})`,
name: `Agent prompt (${agentFileState.agentFile.name})`,
prompt,
description: workflowState.workflowFile.description,
description: agentFileState.agentFile.description,
},
...(enhancedConfig.prompts ?? []),
];
Expand All @@ -87,12 +90,12 @@ export class ConfigEnhancer {
async enhanceConfig(
config: AssistantUnrolled,
_options?: BaseCommandOptions,
workflowState?: WorkflowServiceState,
agentFileState?: AgentFileServiceState,
): Promise<AssistantUnrolled> {
const enhanced = await this.enhanceConfigFromWorkflow(
const enhanced = await this.enhanceConfigFromAgentFile(
config,
_options,
workflowState,
agentFileState,
);
let { enhancedConfig } = enhanced;
const { options } = enhanced;
Expand Down
14 changes: 7 additions & 7 deletions extensions/cli/src/hubLoader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseWorkflowFile, WorkflowFile } from "@continuedev/config-yaml";
import { AgentFile, parseAgentFile } from "@continuedev/config-yaml";
import JSZip from "jszip";

import { env } from "./env.js";
Expand All @@ -12,7 +12,7 @@ const HUB_SLUG_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
/**
* Hub package type definitions
*/
export type HubPackageType = "rule" | "mcp" | "model" | "prompt" | "workflow";
export type HubPackageType = "rule" | "mcp" | "model" | "prompt" | "agent-file";

/**
* Hub package processor interface
Expand Down Expand Up @@ -100,12 +100,12 @@ export const promptProcessor: HubPackageProcessor<string> = {
parseContent: (content: string) => content,
};

export const workflowProcessor: HubPackageProcessor<WorkflowFile> = {
type: "workflow",
export const agentFileProcessor: HubPackageProcessor<AgentFile> = {
type: "agent-file",
expectedFileExtensions: [".md"],
parseContent: (content: string) => parseWorkflowFile(content),
validateContent: (workflowFile: WorkflowFile) => {
return !!workflowFile.name;
parseContent: (content: string) => parseAgentFile(content),
validateContent: (agentFile: AgentFile) => {
return !!agentFile.name;
},
};

Expand Down
6 changes: 3 additions & 3 deletions extensions/cli/src/integration/rule-duplication.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ vi.mock("../hubLoader.js", () => ({
modelProcessor: {},
}));

// Mock the service container to provide empty workflow state
// Mock the service container to provide empty agent file state
vi.mock("../services/ServiceContainer.js", () => ({
serviceContainer: {
get: vi.fn(() =>
Promise.resolve({
workflowFile: null,
agentFile: null,
slug: null,
}),
),
Expand All @@ -33,7 +33,7 @@ vi.mock("../services/ServiceContainer.js", () => ({

vi.mock("../services/types.js", () => ({
SERVICE_NAMES: {
WORKFLOW: "workflow",
AGENT_FILE: "agent-file",
},
}));

Expand Down
Loading
Loading