feat: subagent runtime & CLI display - done

This commit is contained in:
tanzhenxin
2025-09-09 15:53:10 +08:00
parent 4985bfc000
commit 35e996d46c
23 changed files with 767 additions and 684 deletions

View File

@@ -47,7 +47,7 @@ export type {
ToolConfig,
SubagentTerminateMode,
OutputObject,
} from './subagent.js';
} from './types.js';
export { SubAgentScope } from './subagent.js';
@@ -55,11 +55,19 @@ export { SubAgentScope } from './subagent.js';
export type {
SubAgentEvent,
SubAgentStartEvent,
SubAgentFinishEvent,
SubAgentRoundEvent,
SubAgentStreamTextEvent,
SubAgentToolCallEvent,
SubAgentToolResultEvent,
SubAgentModelTextEvent,
SubAgentFinishEvent,
SubAgentErrorEvent,
} from './subagent-events.js';
export { SubAgentEventEmitter } from './subagent-events.js';
// Statistics and formatting
export type {
SubagentStatsSummary,
ToolUsageStats,
} from './subagent-statistics.js';
export { formatCompact, formatDetailed } from './subagent-result-format.js';

View File

@@ -10,17 +10,21 @@ export type SubAgentEvent =
| 'start'
| 'round_start'
| 'round_end'
| 'model_text'
| 'stream_text'
| 'tool_call'
| 'tool_result'
| 'finish'
| 'error';
export interface SubAgentModelTextEvent {
subagentId: string;
round: number;
text: string;
timestamp: number;
export enum SubAgentEventType {
START = 'start',
ROUND_START = 'round_start',
ROUND_END = 'round_end',
STREAM_TEXT = 'stream_text',
TOOL_CALL = 'tool_call',
TOOL_RESULT = 'tool_result',
FINISH = 'finish',
ERROR = 'error',
}
export interface SubAgentStartEvent {
@@ -38,12 +42,20 @@ export interface SubAgentRoundEvent {
timestamp: number;
}
export interface SubAgentStreamTextEvent {
subagentId: string;
round: number;
text: string;
timestamp: number;
}
export interface SubAgentToolCallEvent {
subagentId: string;
round: number;
callId: string;
name: string;
args: Record<string, unknown>;
description: string;
timestamp: number;
}
@@ -54,6 +66,7 @@ export interface SubAgentToolResultEvent {
name: string;
success: boolean;
error?: string;
resultDisplay?: string;
durationMs?: number;
timestamp: number;
}
@@ -72,6 +85,12 @@ export interface SubAgentFinishEvent {
totalTokens?: number;
}
export interface SubAgentErrorEvent {
subagentId: string;
error: string;
timestamp: number;
}
export class SubAgentEventEmitter {
private ee = new EventEmitter();

View File

@@ -21,15 +21,13 @@ import {
CreateSubagentOptions,
SubagentError,
SubagentErrorCode,
} from './types.js';
import { SubagentValidator } from './validation.js';
import {
SubAgentScope,
PromptConfig,
ModelConfig,
RunConfig,
ToolConfig,
} from './subagent.js';
} from './types.js';
import { SubagentValidator } from './validation.js';
import { SubAgentScope } from './subagent.js';
import { Config } from '../config/config.js';
const QWEN_CONFIG_DIR = '.qwen';
@@ -369,9 +367,7 @@ export class SubagentManager {
const runConfig = frontmatter['runConfig'] as
| Record<string, unknown>
| undefined;
const backgroundColor = frontmatter['backgroundColor'] as
| string
| undefined;
const color = frontmatter['color'] as string | undefined;
// Determine level from file path
// Project level paths contain the project root, user level paths are in home directory
@@ -387,11 +383,9 @@ export class SubagentManager {
systemPrompt: systemPrompt.trim(),
level,
filePath,
modelConfig: modelConfig as Partial<
import('./subagent.js').ModelConfig
>,
runConfig: runConfig as Partial<import('./subagent.js').RunConfig>,
backgroundColor,
modelConfig: modelConfig as Partial<ModelConfig>,
runConfig: runConfig as Partial<RunConfig>,
color,
};
// Validate the parsed configuration
@@ -436,8 +430,8 @@ export class SubagentManager {
frontmatter['runConfig'] = config.runConfig;
}
if (config.backgroundColor && config.backgroundColor !== 'auto') {
frontmatter['backgroundColor'] = config.backgroundColor;
if (config.color && config.color !== 'auto') {
frontmatter['color'] = config.color;
}
// Serialize to YAML

View File

@@ -4,17 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
export interface SubAgentBasicStats {
rounds: number;
totalDurationMs: number;
totalToolCalls: number;
successfulToolCalls: number;
failedToolCalls: number;
successRate?: number;
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
}
import { SubagentStatsSummary } from './subagent-statistics.js';
function fmtDuration(ms: number): string {
if (ms < 1000) return `${Math.round(ms)}ms`;
@@ -30,7 +20,7 @@ function fmtDuration(ms: number): string {
}
export function formatCompact(
stats: SubAgentBasicStats,
stats: SubagentStatsSummary,
taskDesc: string,
): string {
const sr =
@@ -52,16 +42,7 @@ export function formatCompact(
}
export function formatDetailed(
stats: SubAgentBasicStats & {
toolUsage?: Array<{
name: string;
count: number;
success: number;
failure: number;
lastError?: string;
averageDurationMs?: number;
}>;
},
stats: SubagentStatsSummary,
taskDesc: string,
): string {
const sr =
@@ -118,18 +99,7 @@ export function formatDetailed(
return lines.join('\n');
}
export function generatePerformanceTips(
stats: SubAgentBasicStats & {
toolUsage?: Array<{
name: string;
count: number;
success: number;
failure: number;
lastError?: string;
averageDurationMs?: number;
}>;
},
): string[] {
export function generatePerformanceTips(stats: SubagentStatsSummary): string[] {
const tips: string[] = [];
const totalCalls = stats.totalToolCalls;
const sr =

View File

@@ -14,7 +14,7 @@ export interface ToolUsageStats {
averageDurationMs: number;
}
export interface SubagentSummary {
export interface SubagentStatsSummary {
rounds: number;
totalDurationMs: number;
totalToolCalls: number;
@@ -79,7 +79,7 @@ export class SubagentStatistics {
this.outputTokens += Math.max(0, output || 0);
}
getSummary(now = Date.now()): SubagentSummary {
getSummary(now = Date.now()): SubagentStatsSummary {
const totalDurationMs = this.startTimeMs ? now - this.startTimeMs : 0;
const totalToolCalls = this.totalToolCalls;
const successRate =

View File

@@ -5,15 +5,14 @@
*/
import { vi, describe, it, expect, beforeEach, Mock, afterEach } from 'vitest';
import { ContextState, SubAgentScope } from './subagent.js';
import {
ContextState,
SubAgentScope,
SubagentTerminateMode,
PromptConfig,
ModelConfig,
RunConfig,
ToolConfig,
} from './subagent.js';
} from './types.js';
import { Config, ConfigParameters } from '../config/config.js';
import { GeminiChat } from '../core/geminiChat.js';
import { createContentGenerator } from '../core/contentGenerator.js';

View File

@@ -19,9 +19,30 @@ import {
GenerateContentResponseUsageMetadata,
} from '@google/genai';
import { GeminiChat } from '../core/geminiChat.js';
import { SubAgentEventEmitter } from './subagent-events.js';
import { formatCompact, formatDetailed } from './subagent-result-format.js';
import { SubagentStatistics } from './subagent-statistics.js';
import {
OutputObject,
SubagentTerminateMode,
PromptConfig,
ModelConfig,
RunConfig,
ToolConfig,
} from './types.js';
import {
SubAgentEventEmitter,
SubAgentEventType,
SubAgentFinishEvent,
SubAgentRoundEvent,
SubAgentStartEvent,
SubAgentToolCallEvent,
SubAgentToolResultEvent,
SubAgentStreamTextEvent,
SubAgentErrorEvent,
} from './subagent-events.js';
import { formatCompact } from './subagent-result-format.js';
import {
SubagentStatistics,
SubagentStatsSummary,
} from './subagent-statistics.js';
import { SubagentHooks } from './subagent-hooks.js';
import { logSubagentExecution } from '../telemetry/loggers.js';
import { SubagentExecutionEvent } from '../telemetry/types.js';
@@ -46,114 +67,6 @@ interface ExecutionStats {
estimatedCost?: number;
}
/**
* Describes the possible termination modes for a subagent.
* This enum provides a clear indication of why a subagent's execution might have ended.
*/
export enum SubagentTerminateMode {
/**
* Indicates that the subagent's execution terminated due to an unrecoverable error.
*/
ERROR = 'ERROR',
/**
* Indicates that the subagent's execution terminated because it exceeded the maximum allowed working time.
*/
TIMEOUT = 'TIMEOUT',
/**
* Indicates that the subagent's execution successfully completed all its defined goals.
*/
GOAL = 'GOAL',
/**
* Indicates that the subagent's execution terminated because it exceeded the maximum number of turns.
*/
MAX_TURNS = 'MAX_TURNS',
}
/**
* Represents the output structure of a subagent's execution.
* This interface defines the data that a subagent will return upon completion,
* including the final result and the reason for its termination.
*/
export interface OutputObject {
/**
* The final result text returned by the subagent upon completion.
* This contains the direct output from the model's final response.
*/
result: string;
/**
* The reason for the subagent's termination, indicating whether it completed
* successfully, timed out, or encountered an error.
*/
terminate_reason: SubagentTerminateMode;
}
/**
* Configures the initial prompt for the subagent.
*/
export interface PromptConfig {
/**
* A single system prompt string that defines the subagent's persona and instructions.
* Note: You should use either `systemPrompt` or `initialMessages`, but not both.
*/
systemPrompt?: string;
/**
* An array of user/model content pairs to seed the chat history for few-shot prompting.
* Note: You should use either `systemPrompt` or `initialMessages`, but not both.
*/
initialMessages?: Content[];
}
/**
* Configures the tools available to the subagent during its execution.
*/
export interface ToolConfig {
/**
* A list of tool names (from the tool registry) or full function declarations
* that the subagent is permitted to use.
*/
tools: Array<string | FunctionDeclaration>;
}
/**
* Configures the generative model parameters for the subagent.
* This interface specifies the model to be used and its associated generation settings,
* such as temperature and top-p values, which influence the creativity and diversity of the model's output.
*/
export interface ModelConfig {
/**
* The name or identifier of the model to be used (e.g., 'gemini-2.5-pro').
*
* TODO: In the future, this needs to support 'auto' or some other string to support routing use cases.
*/
model?: string;
/**
* The temperature for the model's sampling process.
*/
temp?: number;
/**
* The top-p value for nucleus sampling.
*/
top_p?: number;
}
/**
* Configures the execution environment and constraints for the subagent.
* This interface defines parameters that control the subagent's runtime behavior,
* such as maximum execution time, to prevent infinite loops or excessive resource consumption.
*
* TODO: Consider adding max_tokens as a form of budgeting.
*/
export interface RunConfig {
/** The maximum execution time for the subagent in minutes. */
max_time_minutes?: number;
/**
* The maximum number of conversational turns (a user message + model response)
* before the execution is terminated. Helps prevent infinite loops.
*/
max_turns?: number;
}
/**
* Manages the runtime context state for the subagent.
* This class provides a mechanism to store and retrieve key-value pairs
@@ -450,7 +363,7 @@ export class SubAgentScope {
let turnCounter = 0;
try {
// Emit start event
this.eventEmitter?.emit('start', {
this.eventEmitter?.emit(SubAgentEventType.START, {
subagentId: this.subagentId,
name: this.name,
model: this.modelConfig.model,
@@ -458,7 +371,7 @@ export class SubAgentScope {
typeof t === 'string' ? t : t.name,
),
timestamp: Date.now(),
});
} as SubAgentStartEvent);
// Log telemetry for subagent start
const startEvent = new SubagentExecutionEvent(this.name, 'started');
@@ -494,12 +407,12 @@ export class SubAgentScope {
messageParams,
promptId,
);
this.eventEmitter?.emit('round_start', {
this.eventEmitter?.emit(SubAgentEventType.ROUND_START, {
subagentId: this.subagentId,
round: turnCounter,
promptId,
timestamp: Date.now(),
});
} as SubAgentRoundEvent);
const functionCalls: FunctionCall[] = [];
let roundText = '';
@@ -514,12 +427,12 @@ export class SubAgentScope {
const txt = (p as Part & { text?: string }).text;
if (txt) roundText += txt;
if (txt)
this.eventEmitter?.emit('model_text', {
this.eventEmitter?.emit(SubAgentEventType.STREAM_TEXT, {
subagentId: this.subagentId,
round: turnCounter,
text: txt,
timestamp: Date.now(),
});
} as SubAgentStreamTextEvent);
}
if (resp.usageMetadata) lastUsage = resp.usageMetadata;
}
@@ -565,6 +478,7 @@ export class SubAgentScope {
functionCalls,
abortController,
promptId,
turnCounter,
);
} else {
// No tool calls — treat this as the model's final answer.
@@ -586,21 +500,21 @@ export class SubAgentScope {
},
];
}
this.eventEmitter?.emit('round_end', {
this.eventEmitter?.emit(SubAgentEventType.ROUND_END, {
subagentId: this.subagentId,
round: turnCounter,
promptId,
timestamp: Date.now(),
});
} as SubAgentRoundEvent);
}
} catch (error) {
console.error('Error during subagent execution:', error);
this.output.terminate_reason = SubagentTerminateMode.ERROR;
this.eventEmitter?.emit('error', {
this.eventEmitter?.emit(SubAgentEventType.ERROR, {
subagentId: this.subagentId,
error: error instanceof Error ? error.message : String(error),
timestamp: Date.now(),
});
} as SubAgentErrorEvent);
// Log telemetry for subagent error
const errorEvent = new SubagentExecutionEvent(this.name, 'failed', {
@@ -614,7 +528,7 @@ export class SubAgentScope {
if (externalSignal) externalSignal.removeEventListener('abort', onAbort);
this.executionStats.totalDurationMs = Date.now() - startTime;
const summary = this.stats.getSummary(Date.now());
this.eventEmitter?.emit('finish', {
this.eventEmitter?.emit(SubAgentEventType.FINISH, {
subagentId: this.subagentId,
terminate_reason: this.output.terminate_reason,
timestamp: Date.now(),
@@ -626,7 +540,7 @@ export class SubAgentScope {
inputTokens: summary.inputTokens,
outputTokens: summary.outputTokens,
totalTokens: summary.totalTokens,
});
} as SubAgentFinishEvent);
// Log telemetry for subagent completion
const completionEvent = new SubagentExecutionEvent(
@@ -637,7 +551,8 @@ export class SubAgentScope {
{
terminate_reason: this.output.terminate_reason,
result: this.finalText,
execution_summary: this.formatCompactResult(
execution_summary: formatCompact(
summary,
'Subagent execution completed',
),
},
@@ -669,6 +584,7 @@ export class SubAgentScope {
functionCalls: FunctionCall[],
abortController: AbortController,
promptId: string,
currentRound: number,
): Promise<Content[]> {
const toolResponseParts: Part[] = [];
@@ -683,6 +599,20 @@ export class SubAgentScope {
prompt_id: promptId,
};
// Get tool description before execution
const description = this.getToolDescription(toolName, requestInfo.args);
// Emit tool call event BEFORE execution
this.eventEmitter?.emit(SubAgentEventType.TOOL_CALL, {
subagentId: this.subagentId,
round: currentRound,
callId,
name: toolName,
args: requestInfo.args,
description,
timestamp: Date.now(),
} as SubAgentToolCallEvent);
// Execute tools with timing and hooks
const start = Date.now();
await this.hooks?.preToolUse?.({
@@ -717,13 +647,7 @@ export class SubAgentScope {
tu.count += 1;
if (toolResponse?.error) {
tu.failure += 1;
const disp =
typeof toolResponse.resultDisplay === 'string'
? toolResponse.resultDisplay
: toolResponse.resultDisplay
? JSON.stringify(toolResponse.resultDisplay)
: undefined;
tu.lastError = disp || toolResponse.error?.message || 'Unknown error';
tu.lastError = toolResponse.error?.message || 'Unknown error';
} else {
tu.success += 1;
}
@@ -737,31 +661,22 @@ export class SubAgentScope {
}
this.toolUsage.set(toolName, tu);
// Emit tool call/result events
this.eventEmitter?.emit('tool_call', {
// Emit tool result event
this.eventEmitter?.emit(SubAgentEventType.TOOL_RESULT, {
subagentId: this.subagentId,
round: this.executionStats.rounds,
callId,
name: toolName,
args: requestInfo.args,
timestamp: Date.now(),
});
this.eventEmitter?.emit('tool_result', {
subagentId: this.subagentId,
round: this.executionStats.rounds,
round: currentRound,
callId,
name: toolName,
success: !toolResponse?.error,
error: toolResponse?.error
error: toolResponse?.error?.message,
resultDisplay: toolResponse?.resultDisplay
? typeof toolResponse.resultDisplay === 'string'
? toolResponse.resultDisplay
: toolResponse.resultDisplay
? JSON.stringify(toolResponse.resultDisplay)
: toolResponse.error.message
: JSON.stringify(toolResponse.resultDisplay)
: undefined,
durationMs: duration,
timestamp: Date.now(),
});
} as SubAgentToolResultEvent);
// Update statistics service
this.stats.recordToolCall(
@@ -779,19 +694,13 @@ export class SubAgentScope {
args: requestInfo.args,
success: !toolResponse?.error,
durationMs: duration,
errorMessage: toolResponse?.error
? typeof toolResponse.resultDisplay === 'string'
? toolResponse.resultDisplay
: toolResponse.resultDisplay
? JSON.stringify(toolResponse.resultDisplay)
: toolResponse.error.message
: undefined,
errorMessage: toolResponse?.error?.message,
timestamp: Date.now(),
});
if (toolResponse.error) {
console.error(
`Error executing tool ${functionCall.name}: ${toolResponse.resultDisplay || toolResponse.error.message}`,
`Error executing tool ${functionCall.name}: ${toolResponse.error.message}`,
);
}
@@ -836,47 +745,14 @@ export class SubAgentScope {
};
}
formatCompactResult(taskDesc: string, _useColors = false) {
const stats = this.getStatistics();
return formatCompact(
{
rounds: stats.rounds,
totalDurationMs: stats.totalDurationMs,
totalToolCalls: stats.totalToolCalls,
successfulToolCalls: stats.successfulToolCalls,
failedToolCalls: stats.failedToolCalls,
successRate: stats.successRate,
inputTokens: this.executionStats.inputTokens,
outputTokens: this.executionStats.outputTokens,
totalTokens: this.executionStats.totalTokens,
},
taskDesc,
);
getExecutionSummary(): SubagentStatsSummary {
return this.stats.getSummary();
}
getFinalText(): string {
return this.finalText;
}
formatDetailedResult(taskDesc: string) {
const stats = this.getStatistics();
return formatDetailed(
{
rounds: stats.rounds,
totalDurationMs: stats.totalDurationMs,
totalToolCalls: stats.totalToolCalls,
successfulToolCalls: stats.successfulToolCalls,
failedToolCalls: stats.failedToolCalls,
successRate: stats.successRate,
inputTokens: this.executionStats.inputTokens,
outputTokens: this.executionStats.outputTokens,
totalTokens: this.executionStats.totalTokens,
toolUsage: stats.toolUsage,
},
taskDesc,
);
}
private async createChatObject(context: ContextState) {
if (!this.promptConfig.systemPrompt && !this.promptConfig.initialMessages) {
throw new Error(
@@ -944,6 +820,33 @@ export class SubAgentScope {
}
}
/**
* Safely retrieves the description of a tool by attempting to build it.
* Returns an empty string if any error occurs during the process.
*
* @param toolName The name of the tool to get description for.
* @param args The arguments that would be passed to the tool.
* @returns The tool description or empty string if error occurs.
*/
private getToolDescription(
toolName: string,
args: Record<string, unknown>,
): string {
try {
const toolRegistry = this.runtimeContext.getToolRegistry();
const tool = toolRegistry.getTool(toolName);
if (!tool) {
return '';
}
const toolInstance = tool.build(args);
return toolInstance.getDescription() || '';
} catch {
// Safely ignore all runtime errors and return empty string
return '';
}
}
private buildChatSystemPrompt(context: ContextState): string {
if (!this.promptConfig.systemPrompt) {
// This should ideally be caught in createChatObject, but serves as a safeguard.

View File

@@ -4,12 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {
PromptConfig,
ModelConfig,
RunConfig,
ToolConfig,
} from './subagent.js';
import { Content, FunctionDeclaration } from '@google/genai';
/**
* Represents the storage level for a subagent configuration.
@@ -61,10 +56,10 @@ export interface SubagentConfig {
runConfig?: Partial<RunConfig>;
/**
* Optional background color for runtime display.
* Optional color for runtime display.
* If 'auto' or omitted, uses automatic color assignment.
*/
backgroundColor?: string;
color?: string;
}
/**
@@ -159,3 +154,111 @@ export const SubagentErrorCode = {
export type SubagentErrorCode =
(typeof SubagentErrorCode)[keyof typeof SubagentErrorCode];
/**
* Describes the possible termination modes for a subagent.
* This enum provides a clear indication of why a subagent's execution might have ended.
*/
export enum SubagentTerminateMode {
/**
* Indicates that the subagent's execution terminated due to an unrecoverable error.
*/
ERROR = 'ERROR',
/**
* Indicates that the subagent's execution terminated because it exceeded the maximum allowed working time.
*/
TIMEOUT = 'TIMEOUT',
/**
* Indicates that the subagent's execution successfully completed all its defined goals.
*/
GOAL = 'GOAL',
/**
* Indicates that the subagent's execution terminated because it exceeded the maximum number of turns.
*/
MAX_TURNS = 'MAX_TURNS',
}
/**
* Represents the output structure of a subagent's execution.
* This interface defines the data that a subagent will return upon completion,
* including the final result and the reason for its termination.
*/
export interface OutputObject {
/**
* The final result text returned by the subagent upon completion.
* This contains the direct output from the model's final response.
*/
result: string;
/**
* The reason for the subagent's termination, indicating whether it completed
* successfully, timed out, or encountered an error.
*/
terminate_reason: SubagentTerminateMode;
}
/**
* Configures the initial prompt for the subagent.
*/
export interface PromptConfig {
/**
* A single system prompt string that defines the subagent's persona and instructions.
* Note: You should use either `systemPrompt` or `initialMessages`, but not both.
*/
systemPrompt?: string;
/**
* An array of user/model content pairs to seed the chat history for few-shot prompting.
* Note: You should use either `systemPrompt` or `initialMessages`, but not both.
*/
initialMessages?: Content[];
}
/**
* Configures the tools available to the subagent during its execution.
*/
export interface ToolConfig {
/**
* A list of tool names (from the tool registry) or full function declarations
* that the subagent is permitted to use.
*/
tools: Array<string | FunctionDeclaration>;
}
/**
* Configures the generative model parameters for the subagent.
* This interface specifies the model to be used and its associated generation settings,
* such as temperature and top-p values, which influence the creativity and diversity of the model's output.
*/
export interface ModelConfig {
/**
* The name or identifier of the model to be used (e.g., 'gemini-2.5-pro').
*
* TODO: In the future, this needs to support 'auto' or some other string to support routing use cases.
*/
model?: string;
/**
* The temperature for the model's sampling process.
*/
temp?: number;
/**
* The top-p value for nucleus sampling.
*/
top_p?: number;
}
/**
* Configures the execution environment and constraints for the subagent.
* This interface defines parameters that control the subagent's runtime behavior,
* such as maximum execution time, to prevent infinite loops or excessive resource consumption.
*
* TODO: Consider adding max_tokens as a form of budgeting.
*/
export interface RunConfig {
/** The maximum execution time for the subagent in minutes. */
max_time_minutes?: number;
/**
* The maximum number of conversational turns (a user message + model response)
* before the execution is terminated. Helps prevent infinite loops.
*/
max_turns?: number;
}

View File

@@ -9,6 +9,8 @@ import {
ValidationResult,
SubagentError,
SubagentErrorCode,
ModelConfig,
RunConfig,
} from './types.js';
/**
@@ -250,9 +252,7 @@ export class SubagentValidator {
* @param modelConfig - Partial model configuration to validate
* @returns ValidationResult
*/
validateModelConfig(
modelConfig: Partial<import('./subagent.js').ModelConfig>,
): ValidationResult {
validateModelConfig(modelConfig: ModelConfig): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
@@ -298,9 +298,7 @@ export class SubagentValidator {
* @param runConfig - Partial run configuration to validate
* @returns ValidationResult
*/
validateRunConfig(
runConfig: Partial<import('./subagent.js').RunConfig>,
): ValidationResult {
validateRunConfig(runConfig: RunConfig): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];