Skip to content
Open
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
4 changes: 0 additions & 4 deletions extensions/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ addCommonOptions(program)
)
.option("--resume", "Resume from last session")
.option("--fork <sessionId>", "Fork from an existing session ID")
.option(
"--beta-subagent-tool",
"Enable beta Subagent tool for invoking subagents",
)
.action(async (prompt, options) => {
// Telemetry: record command invocation
await posthogService.capture("cliCommand", { command: "cn" });
Expand Down
8 changes: 1 addition & 7 deletions extensions/cli/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { loadAuthConfig } from "../auth/workos.js";
import { initializeWithOnboarding } from "../onboarding.js";
import {
setBetaSubagentToolEnabled,
setBetaUploadArtifactToolEnabled,
} from "../tools/toolsConfig.js";
import { setBetaUploadArtifactToolEnabled } from "../tools/toolsConfig.js";
import { logger } from "../util/logger.js";

import { AgentFileService } from "./AgentFileService.js";
Expand Down Expand Up @@ -66,9 +63,6 @@ export async function initializeServices(initOptions: ServiceInitOptions = {}) {
if (commandOptions.betaUploadArtifactTool) {
setBetaUploadArtifactToolEnabled(true);
}
if (commandOptions.betaSubagentTool) {
setBetaSubagentToolEnabled(true);
}
// Handle onboarding for TUI mode (headless: false) unless explicitly skipped
if (!initOptions.headless && !initOptions.skipOnboarding) {
const authConfig = loadAuthConfig();
Expand Down
32 changes: 30 additions & 2 deletions extensions/cli/src/stream/handleToolCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,41 @@ export async function handleToolCalls(

// Execute the valid preprocessed tool calls
// Note: executeStreamedToolCalls adds tool results to toolCallStates via
// services.chatHistory.addToolResult() internally
const { hasRejection } = await executeStreamedToolCalls(
// services.chatHistory.addToolResult() internally when service is available
const { hasRejection, chatHistoryEntries } = await executeStreamedToolCalls(
preprocessedCalls,
callbacks,
isHeadless,
);

// When ChatHistoryService is disabled in subagent execution,
// manually update the local chatHistory array with tool results
if (!useService && chatHistoryEntries.length > 0) {
const lastAssistantIndex = chatHistory.findLastIndex(
(item) => item.message.role === "assistant" && item.toolCallStates,
);
if (
lastAssistantIndex >= 0 &&
chatHistory[lastAssistantIndex].toolCallStates
) {
for (const entry of chatHistoryEntries) {
const toolState = chatHistory[lastAssistantIndex].toolCallStates!.find(
(ts) => ts.toolCallId === entry.tool_call_id,
);
if (toolState) {
toolState.status = entry.status;
toolState.output = [
{
content: String(entry.content) || "",
name: "Tool Result",
description: "Tool execution result",
},
];
}
}
}
}

if (isHeadless && hasRejection) {
logger.debug(
"Tool call rejected in headless mode - returning current content",
Expand Down
90 changes: 90 additions & 0 deletions extensions/cli/src/subagent/builtInSubagents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { ModelConfig } from "@continuedev/config-yaml";

import { logger } from "src/util/logger.js";

export interface BuiltInSubagent {
name: string;
systemPrompt: string;
model: string;
}

export const NAVIGATOR_SUBAGENT: BuiltInSubagent = {
name: "navigator",
model: "claude-haiku-4-5",
systemPrompt: `You are a Codebase Navigator subagent specialized in exploring, searching, and mapping large codebases.
When to use:
Use this subagent whenever you need to explore or find or understand a codebase or a folder.
When navigating a codebase, you will:
1. **Locate Relevant Code**: Use file and code search tools to find the most relevant files, modules, functions, and types. Prefer a small, high-signal set of locations over exhaustive listings.
2. **Trace Behavior and Dependencies**: Follow call chains, imports, and data flow to understand how the relevant pieces interact, including upstream/downstream dependencies and important side effects.
3. **Map the Codebase for Others**: Build a concise mental map: which components are core, which are helpers, where entry points live, and how configuration or environment affects behavior.
Your output should be concise and actionable, starting with a brief summary of what you found and listing the key files/paths, functions, symbols, and important relationships or flows between them in plain language. If you cannot find something, describe what you searched for, where you looked, and suggest next places or strategies to investigate.`,
};

export const CODE_REVIEWER_SUBAGENT: BuiltInSubagent = {
name: "code-reviewer",
model: "claude-sonnet-4-6",
systemPrompt: `You are a Senior Code Reviewer with expertise in software architecture, design patterns, and best practices. Your role is to review completed project steps against original plans and ensure code quality standards are met.
When to use:
Use this subagent whenever you are requested to review code, or after a feature or refactor is implemented and you want a structured review against the original plan and code quality standards.
When reviewing completed work, you will:
1. **Plan Alignment Analysis**: Compare implementation against original plans, identify justified vs problematic deviations, and verify all planned functionality is complete
2. **Code Quality Assessment**: Review adherence to patterns, error handling, type safety, naming conventions, test coverage, and potential security or performance issues
3. **Architecture and Design Review**: Ensure proper architectural patterns, separation of concerns, loose coupling, system integration, and scalability considerations
4. **Documentation and Standards**: Verify appropriate comments, function documentation, file headers, and adherence to project-specific coding standards
5. **Issue Identification and Recommendations**: Categorize issues as Critical/Important/Suggestions with specific examples, actionable recommendations, and code examples when helpful
Your output should be structured, actionable, and focused on helping maintain high code quality while ensuring project goals are met. Be thorough but concise, and always provide constructive feedback that helps improve both the current implementation and future development practices.`,
};

export const BUILT_IN_SUBAGENTS: BuiltInSubagent[] = [
NAVIGATOR_SUBAGENT,
CODE_REVIEWER_SUBAGENT,
];

export function createBuiltInSubagentModel(
subagent: BuiltInSubagent,
baseModel: ModelConfig,
): ModelConfig {
return {
...baseModel,
name: subagent.name,
model: subagent.model,
roles: ["subagent"],
chatOptions: {
...baseModel.chatOptions,
baseSystemMessage: subagent.systemPrompt,
},
};
}

export function isLocalAnthropicModel(model: ModelConfig | null): boolean {
if (!model) {
return false;
}

const isAnthropic = model.provider === "anthropic";
const hasDirectApiKey =
typeof model.apiKey === "string" && model.apiKey.length > 0;

logger.debug("subagent_enabled_for_anthropic", {
enabled: isAnthropic && hasDirectApiKey,
});

return isAnthropic && hasDirectApiKey;
}
15 changes: 14 additions & 1 deletion extensions/cli/src/subagent/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { streamChatResponse } from "../stream/streamChatResponse.js";
import { escapeEvents } from "../util/cli.js";
import { logger } from "../util/logger.js";

let isInsideSubagent = false;

/**
* Options for executing a subagent
*/
Expand Down Expand Up @@ -54,17 +56,26 @@ async function buildAgentSystemMessage(
/**
* Execute a subagent in a child session
*/
// eslint-disable-next-line complexity
export async function executeSubAgent(
options: SubAgentExecutionOptions,
): Promise<SubAgentResult> {
if (isInsideSubagent) {
return {
success: false,
response: "",
error: "Nested subagent invocation is not allowed",
};
}

const { agent: subAgent, prompt, abortController, onOutputUpdate } = options;

const mainAgentPermissionsState =
await serviceContainer.get<ToolPermissionServiceState>(
SERVICE_NAMES.TOOL_PERMISSIONS,
);

isInsideSubagent = true;

try {
logger.debug("Starting subagent execution", {
agent: subAgent.model?.name,
Expand Down Expand Up @@ -209,5 +220,7 @@ export async function executeSubAgent(
response: "",
error: error.message,
};
} finally {
isInsideSubagent = false;
}
}
51 changes: 38 additions & 13 deletions extensions/cli/src/subagent/get-agents.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,65 @@
import { createLlmApi } from "../config.js";
import { ModelService } from "../services/ModelService.js";
import type { ModelServiceState } from "../services/types.js";

/**
* Get an agent by name
*/
import {
BUILT_IN_SUBAGENTS,
createBuiltInSubagentModel,
isLocalAnthropicModel,
} from "./builtInSubagents.js";

function getAllSubagentModels(modelState: ModelServiceState) {
const configSubagents = ModelService.getSubagentModels(modelState);

if (!isLocalAnthropicModel(modelState.model)) {
return configSubagents;
}

const builtInSubagents = BUILT_IN_SUBAGENTS.map((subagent) => {
const subagentModel = createBuiltInSubagentModel(
subagent,
modelState.model!,
);
return {
llmApi: createLlmApi(subagentModel, modelState.authConfig),
model: subagentModel,
assistant: modelState.assistant,
authConfig: modelState.authConfig,
};
});

return [...configSubagents, ...builtInSubagents];
}

export function getSubagent(modelState: ModelServiceState, name: string) {
return (
ModelService.getSubagentModels(modelState).find(
getAllSubagentModels(modelState).find(
(model) => model.model.name === name,
) ?? null
);
}

/**
* Generate dynamic tool description listing available agents
*/
export function generateSubagentToolDescription(
modelState: ModelServiceState,
): string {
const agentList = ModelService.getSubagentModels(modelState)
const agentList = getAllSubagentModels(modelState)
.map(
(subagentModel) =>
` - ${subagentModel.model.name}: ${subagentModel.model.chatOptions?.baseSystemMessage}`,
)
.join("\n");

// todo: refine this prompt later
return `Launch a specialized subagent to handle a specific task.
return `Launch an autonomous specialized subagent to handle a specific task.

You have the independence to make decisions within you scope. You should focus on the specific task given by the main agent.

Remember: You are part of a larger system. Your specialized focus helps the main agent handle multiple concerns efficiently.

Here are the available subagents:
${agentList}
`;
}

export function getAgentNames(modelState: ModelServiceState): string[] {
return ModelService.getSubagentModels(modelState).map(
(model) => model.model.name,
);
return getAllSubagentModels(modelState).map((model) => model.model.name);
}
9 changes: 2 additions & 7 deletions extensions/cli/src/tools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import { runTerminalCommandTool } from "./runTerminalCommand.js";
import { checkIfRipgrepIsInstalled, searchCodeTool } from "./searchCode.js";
import { skillsTool } from "./skills.js";
import { subagentTool } from "./subagent.js";
import {
isBetaSubagentToolEnabled,
isBetaUploadArtifactToolEnabled,
} from "./toolsConfig.js";
import { isBetaUploadArtifactToolEnabled } from "./toolsConfig.js";
import {
type Tool,
type ToolCall,
Expand Down Expand Up @@ -127,9 +124,7 @@ export async function getAllAvailableTools(
tools.push(exitTool);
}

if (isBetaSubagentToolEnabled()) {
tools.push(await subagentTool());
}
tools.push(await subagentTool());

tools.push(await skillsTool());

Expand Down
9 changes: 0 additions & 9 deletions extensions/cli/src/tools/toolsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

let betaUploadArtifactToolEnabled = false;
let betaSubagentToolEnabled = false;

export function setBetaUploadArtifactToolEnabled(enabled: boolean): void {
betaUploadArtifactToolEnabled = enabled;
Expand All @@ -13,11 +12,3 @@ export function setBetaUploadArtifactToolEnabled(enabled: boolean): void {
export function isBetaUploadArtifactToolEnabled(): boolean {
return betaUploadArtifactToolEnabled;
}

export function setBetaSubagentToolEnabled(enabled: boolean): void {
betaSubagentToolEnabled = enabled;
}

export function isBetaSubagentToolEnabled(): boolean {
return betaSubagentToolEnabled;
}
Loading